From 16a37e276922ce88e70483f0a8f44651f9e0241a Mon Sep 17 00:00:00 2001 From: AffectedArc07 Date: Sun, 12 Jan 2020 14:28:01 +0000 Subject: [PATCH] Standardises all files from CRLF to LF --- code/__DEFINES/admin.dm | 182 +- code/__DEFINES/atmospherics.dm | 596 +- code/__DEFINES/combat.dm | 398 +- code/__DEFINES/components.dm | 696 +- code/__DEFINES/configuration.dm | 18 +- code/__DEFINES/flags.dm | 186 +- code/__DEFINES/jobs.dm | 178 +- code/__DEFINES/machines.dm | 212 +- code/__DEFINES/medal.dm | 56 +- code/__DEFINES/networks.dm | 6 +- code/__DEFINES/preferences.dm | 168 +- code/__DEFINES/research.dm | 150 +- code/__DEFINES/sound.dm | 162 +- code/__DEFINES/stat.dm | 44 +- code/__DEFINES/tgs.config.dm | 20 +- code/__DEFINES/tgs.dm | 482 +- code/__DEFINES/tools.dm | 42 +- code/__DEFINES/wires.dm | 100 +- code/__HELPERS/_lists.dm | 1162 ++-- code/__HELPERS/files.dm | 146 +- code/__HELPERS/game.dm | 1140 ++-- code/__HELPERS/global_lists.dm | 256 +- code/__HELPERS/icons.dm | 2422 +++---- code/__HELPERS/mobs.dm | 1168 ++-- code/__HELPERS/names.dm | 496 +- code/__HELPERS/qdel.dm | 20 +- code/__HELPERS/radio.dm | 38 +- code/__HELPERS/text.dm | 1590 ++--- code/__HELPERS/time.dm | 166 +- code/__HELPERS/type2type.dm | 1262 ++-- code/_compile_options.dm | 114 +- code/_globalvars/bitfields.dm | 428 +- code/_globalvars/configuration.dm | 76 +- code/_globalvars/game_modes.dm | 22 +- code/_globalvars/genetics.dm | 54 +- code/_globalvars/lists/flavor_misc.dm | 446 +- code/_globalvars/lists/mapping.dm | 104 +- code/_globalvars/lists/mobs.dm | 206 +- code/_globalvars/lists/names.dm | 100 +- code/_globalvars/lists/objects.dm | 86 +- code/_globalvars/logging.dm | 128 +- code/_globalvars/misc.dm | 70 +- code/_js/byjax.dm | 96 +- code/_onclick/adjacent.dm | 210 +- code/_onclick/click.dm | 1040 +-- code/_onclick/cyborg.dm | 370 +- code/_onclick/drag_drop.dm | 294 +- code/_onclick/hud/_defines.dm | 330 +- code/_onclick/hud/credits.dm | 138 +- code/_onclick/hud/fullscreen.dm | 362 +- code/_onclick/hud/ghost.dm | 190 +- code/_onclick/hud/hud.dm | 594 +- code/_onclick/hud/human.dm | 1056 +-- code/_onclick/hud/monkey.dm | 330 +- code/_onclick/hud/robot.dm | 614 +- code/_onclick/hud/screen_objects.dm | 1430 ++--- code/_onclick/item_attack.dm | 334 +- code/_onclick/observer.dm | 166 +- code/_onclick/other_mobs.dm | 520 +- .../controllers/configuration/config_entry.dm | 480 +- .../configuration/configuration.dm | 768 +-- .../configuration/entries/comms.dm | 54 +- .../configuration/entries/dbconfig.dm | 102 +- .../configuration/entries/game_options.dm | 804 +-- .../configuration/entries/general.dm | 862 +-- code/controllers/failsafe.dm | 204 +- code/controllers/globals.dm | 110 +- code/controllers/subsystem/atoms.dm | 302 +- code/controllers/subsystem/blackbox.dm | 670 +- code/controllers/subsystem/dbcore.dm | 730 +-- code/controllers/subsystem/medals.dm | 174 +- 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 | 1060 +-- code/controllers/subsystem/spacedrift.dm | 118 +- code/controllers/subsystem/vote.dm | 1352 ++-- code/datums/actions/flightsuit.dm | 234 +- code/datums/actions/ninja.dm | 102 +- code/datums/ai_laws.dm | 928 +-- code/datums/browser.dm | 930 +-- code/datums/components/_component.dm | 628 +- code/datums/components/jousting.dm | 148 +- code/datums/components/lockon_aiming.dm | 478 +- code/datums/components/ntnet_interface.dm | 136 +- code/datums/components/riding.dm | 718 +-- code/datums/components/slippery.dm | 30 +- .../components/storage/concrete/_concrete.dm | 416 +- .../storage/concrete/bag_of_holding.dm | 72 +- .../components/storage/concrete/implant.dm | 36 +- .../components/storage/concrete/rped.dm | 72 +- code/datums/components/storage/storage.dm | 1600 ++--- code/datums/components/wet_floor.dm | 422 +- code/datums/datacore.dm | 592 +- code/datums/datum.dm | 320 +- code/datums/datumvars.dm | 2860 ++++----- code/datums/diseases/advance/advance.dm | 990 +-- code/datums/diseases/advance/presets.dm | 82 +- .../datums/diseases/advance/symptoms/beard.dm | 102 +- .../diseases/advance/symptoms/choking.dm | 296 +- .../diseases/advance/symptoms/confusion.dm | 122 +- .../datums/diseases/advance/symptoms/cough.dm | 150 +- .../diseases/advance/symptoms/deafness.dm | 118 +- .../datums/diseases/advance/symptoms/dizzy.dm | 104 +- .../datums/diseases/advance/symptoms/fever.dm | 120 +- .../diseases/advance/symptoms/flesh_eating.dm | 258 +- .../diseases/advance/symptoms/genetics.dm | 156 +- .../diseases/advance/symptoms/hallucigen.dm | 130 +- .../diseases/advance/symptoms/headache.dm | 118 +- code/datums/diseases/advance/symptoms/heal.dm | 970 +-- .../diseases/advance/symptoms/itching.dm | 106 +- .../diseases/advance/symptoms/oxygen.dm | 134 +- .../diseases/advance/symptoms/shedding.dm | 110 +- .../diseases/advance/symptoms/shivering.dm | 116 +- .../diseases/advance/symptoms/sneeze.dm | 102 +- .../diseases/advance/symptoms/symptoms.dm | 160 +- .../diseases/advance/symptoms/voice_change.dm | 162 +- .../datums/diseases/advance/symptoms/vomit.dm | 126 +- .../diseases/advance/symptoms/weight.dm | 100 +- .../datums/diseases/advance/symptoms/youth.dm | 114 +- code/datums/diseases/appendicitis.dm | 72 +- code/datums/diseases/beesease.dm | 76 +- code/datums/diseases/brainrot.dm | 118 +- code/datums/diseases/cold.dm | 104 +- code/datums/diseases/cold9.dm | 76 +- 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 | 134 +- code/datums/diseases/pierrot_throat.dm | 108 +- code/datums/diseases/retrovirus.dm | 166 +- code/datums/diseases/rhumba_beat.dm | 90 +- code/datums/diseases/transformation.dm | 654 +- code/datums/diseases/wizarditis.dm | 234 +- code/datums/dna.dm | 870 +-- code/datums/forced_movement.dm | 180 +- code/datums/helper_datums/getrev.dm | 242 +- code/datums/helper_datums/teleport.dm | 334 +- code/datums/helper_datums/topic_input.dm | 124 +- code/datums/holocall.dm | 704 +- code/datums/hud.dm | 260 +- code/datums/map_config.dm | 326 +- code/datums/mind.dm | 1560 ++--- code/datums/mood_events/drug_events.dm | 230 +- code/datums/mood_events/mood_event.dm | 40 +- code/datums/mood_events/needs_events.dm | 124 +- code/datums/position_point_vector.dm | 450 +- code/datums/spawners_menu.dm | 106 +- code/datums/verbs.dm | 204 +- code/datums/wires/airlock.dm | 372 +- code/datums/wires/mulebot.dm | 60 +- code/datums/wires/particle_accelerator.dm | 94 +- code/datums/wires/robot.dm | 172 +- code/datums/wires/syndicatebomb.dm | 184 +- code/game/area/Space_Station_13_areas.dm | 2704 ++++---- code/game/area/ai_monitored.dm | 60 +- code/game/atoms.dm | 1730 ++--- code/game/atoms_movable.dm | 1748 ++--- code/game/communications.dm | 398 +- code/game/gamemodes/brother/traitor_bro.dm | 144 +- code/game/gamemodes/changeling/changeling.dm | 254 +- .../game/gamemodes/changeling/traitor_chan.dm | 168 +- code/game/gamemodes/events.dm | 164 +- code/game/gamemodes/extended/extended.dm | 64 +- code/game/gamemodes/game_mode.dm | 1212 ++-- code/game/gamemodes/meteor/meteor.dm | 118 +- code/game/gamemodes/objective_items.dm | 530 +- code/game/gamemodes/sandbox/airlock_maker.dm | 282 +- code/game/gamemodes/sandbox/h_sandbox.dm | 606 +- code/game/gamemodes/sandbox/sandbox.dm | 42 +- code/game/gamemodes/traitor/double_agents.dm | 164 +- code/game/gamemodes/traitor/traitor.dm | 198 +- code/game/gamemodes/wizard/wizard.dm | 146 +- code/game/machinery/Beacon.dm | 96 +- code/game/machinery/PDApainter.dm | 286 +- code/game/machinery/Sleeper.dm | 824 +-- code/game/machinery/ai_slipper.dm | 96 +- code/game/machinery/airlock_control.dm | 326 +- code/game/machinery/autolathe.dm | 764 +-- code/game/machinery/buttons.dm | 536 +- code/game/machinery/camera/camera_assembly.dm | 296 +- code/game/machinery/camera/motion.dm | 154 +- code/game/machinery/camera/presets.dm | 178 +- code/game/machinery/camera/tracking.dm | 302 +- code/game/machinery/cell_charger.dm | 260 +- code/game/machinery/cloning.dm | 1112 ++-- code/game/machinery/computer/Operating.dm | 256 +- code/game/machinery/computer/aifixer.dm | 300 +- code/game/machinery/computer/atmos_alert.dm | 200 +- code/game/machinery/computer/atmos_control.dm | 610 +- .../game/machinery/computer/buildandrepair.dm | 290 +- code/game/machinery/computer/camera.dm | 508 +- code/game/machinery/computer/card.dm | 1260 ++-- code/game/machinery/computer/cloning.dm | 1028 +-- .../game/machinery/computer/communications.dm | 1518 ++--- code/game/machinery/computer/crew.dm | 418 +- code/game/machinery/computer/law.dm | 142 +- code/game/machinery/computer/medical.dm | 1170 ++-- code/game/machinery/computer/pod.dm | 266 +- code/game/machinery/computer/robot.dm | 330 +- code/game/machinery/computer/security.dm | 1624 ++--- code/game/machinery/computer/station_alert.dm | 196 +- .../machinery/computer/telecrystalconsoles.dm | 430 +- code/game/machinery/constructable_frame.dm | 558 +- code/game/machinery/doors/alarmlock.dm | 84 +- code/game/machinery/doors/brigdoors.dm | 512 +- code/game/machinery/doors/door.dm | 770 +-- code/game/machinery/doors/firedoor.dm | 952 +-- code/game/machinery/doors/poddoor.dm | 182 +- code/game/machinery/doors/shutters.dm | 26 +- code/game/machinery/doors/unpowered.dm | 42 +- code/game/machinery/doors/windowdoor.dm | 1070 ++-- code/game/machinery/doppler_array.dm | 374 +- .../embedded_controller/access_controller.dm | 630 +- .../embedded_controller/airlock_controller.dm | 628 +- .../embedded_controller_base.dm | 174 +- .../simple_vent_controller.dm | 144 +- code/game/machinery/flasher.dm | 410 +- code/game/machinery/hologram.dm | 1400 ++-- code/game/machinery/igniter.dm | 274 +- code/game/machinery/lightswitch.dm | 134 +- code/game/machinery/magnet.dm | 760 +-- code/game/machinery/mass_driver.dm | 76 +- code/game/machinery/navbeacon.dm | 428 +- code/game/machinery/pipe/construction.dm | 472 +- code/game/machinery/pipe/pipe_dispenser.dm | 424 +- .../machinery/porta_turret/portable_turret.dm | 2186 +++---- code/game/machinery/recharger.dm | 326 +- code/game/machinery/rechargestation.dm | 270 +- code/game/machinery/recycler.dm | 424 +- code/game/machinery/requests_console.dm | 1068 ++-- code/game/machinery/status_display.dm | 736 +-- code/game/machinery/suit_storage_unit.dm | 886 +-- code/game/machinery/syndicatebeacon.dm | 330 +- .../machinery/telecomms/computers/message.dm | 932 +-- .../telecomms/machines/message_server.dm | 360 +- .../machinery/telecomms/telecomunications.dm | 304 +- code/game/machinery/washing_machine.dm | 568 +- code/game/machinery/wishgranter.dm | 86 +- code/game/mecha/combat/combat.dm | 20 +- code/game/mecha/combat/durand.dm | 42 +- code/game/mecha/combat/gygax.dm | 136 +- code/game/mecha/combat/honker.dm | 310 +- code/game/mecha/combat/marauder.dm | 188 +- code/game/mecha/combat/phazon.dm | 58 +- code/game/mecha/combat/reticence.dm | 58 +- code/game/mecha/equipment/mecha_equipment.dm | 334 +- .../mecha/equipment/tools/medical_tools.dm | 1110 ++-- code/game/mecha/equipment/weapons/weapons.dm | 888 +-- code/game/mecha/mech_fabricator.dm | 848 +-- code/game/mecha/mecha.dm | 2134 +++--- code/game/mecha/mecha_construction_paths.dm | 3546 +++++----- code/game/mecha/mecha_control_console.dm | 284 +- code/game/mecha/mecha_parts.dm | 678 +- code/game/mecha/mecha_wreckage.dm | 506 +- code/game/mecha/medical/odysseus.dm | 62 +- code/game/mecha/working/ripley.dm | 396 +- code/game/objects/effects/bump_teleporter.dm | 74 +- code/game/objects/effects/decals/cleanable.dm | 192 +- .../effects/decals/cleanable/aliens.dm | 138 +- .../effects/decals/cleanable/humans.dm | 384 +- .../objects/effects/decals/cleanable/misc.dm | 470 +- .../effects/decals/cleanable/robots.dm | 130 +- code/game/objects/effects/decals/crayon.dm | 62 +- code/game/objects/effects/decals/decal.dm | 96 +- code/game/objects/effects/decals/misc.dm | 62 +- code/game/objects/effects/decals/remains.dm | 66 +- .../effects/effect_system/effects_foam.dm | 694 +- .../effects/effect_system/effects_smoke.dm | 656 +- code/game/objects/effects/forcefields.dm | 74 +- code/game/objects/effects/landmarks.dm | 966 +-- code/game/objects/effects/mines.dm | 352 +- code/game/objects/effects/misc.dm | 186 +- code/game/objects/effects/portals.dm | 370 +- code/game/objects/effects/proximity.dm | 224 +- .../objects/effects/spawners/bombspawner.dm | 130 +- .../objects/effects/spawners/gibspawner.dm | 480 +- .../game/objects/effects/spawners/lootdrop.dm | 760 +-- code/game/objects/effects/spiders.dm | 474 +- code/game/objects/effects/step_triggers.dm | 398 +- code/game/objects/empulse.dm | 62 +- code/game/objects/items.dm | 1702 ++--- code/game/objects/items/AI_modules.dm | 1230 ++-- code/game/objects/items/airlock_painter.dm | 254 +- code/game/objects/items/bodybag.dm | 166 +- code/game/objects/items/candle.dm | 160 +- code/game/objects/items/cards_ids.dm | 1086 ++-- code/game/objects/items/cigs_lighters.dm | 2132 +++--- code/game/objects/items/clown_items.dm | 364 +- code/game/objects/items/cosmetics.dm | 380 +- code/game/objects/items/devices/PDA/PDA.dm | 2348 +++---- code/game/objects/items/devices/PDA/cart.dm | 1554 ++--- code/game/objects/items/devices/PDA/radio.dm | 76 +- code/game/objects/items/devices/aicard.dm | 208 +- code/game/objects/items/devices/camera_bug.dm | 616 +- code/game/objects/items/devices/flashlight.dm | 1092 ++-- code/game/objects/items/devices/gps.dm | 456 +- .../game/objects/items/devices/instruments.dm | 554 +- .../objects/items/devices/laserpointer.dm | 378 +- .../objects/items/devices/lightreplacer.dm | 544 +- code/game/objects/items/devices/multitool.dm | 532 +- code/game/objects/items/devices/paicard.dm | 338 +- code/game/objects/items/devices/powersink.dm | 304 +- .../items/devices/radio/electropack.dm | 292 +- .../objects/items/devices/radio/headset.dm | 674 +- .../objects/items/devices/radio/intercom.dm | 300 +- .../game/objects/items/devices/radio/radio.dm | 858 +-- code/game/objects/items/devices/scanners.dm | 1558 ++--- .../objects/items/devices/taperecorder.dm | 582 +- .../objects/items/devices/transfer_valve.dm | 474 +- code/game/objects/items/dice.dm | 394 +- code/game/objects/items/dna_injector.dm | 738 +-- code/game/objects/items/extinguisher.dm | 514 +- .../objects/items/grenades/chem_grenade.dm | 1204 ++-- code/game/objects/items/grenades/flashbang.dm | 88 +- .../game/objects/items/grenades/ghettobomb.dm | 120 +- code/game/objects/items/grenades/grenade.dm | 248 +- code/game/objects/items/grenades/smokebomb.dm | 62 +- .../objects/items/grenades/syndieminibomb.dm | 100 +- code/game/objects/items/handcuffs.dm | 752 +-- code/game/objects/items/hot_potato.dm | 342 +- code/game/objects/items/implants/implant.dm | 228 +- .../objects/items/implants/implantcase.dm | 166 +- .../objects/items/implants/implantchair.dm | 384 +- code/game/objects/items/implants/implanter.dm | 152 +- code/game/objects/items/kitchen.dm | 366 +- code/game/objects/items/manuals.dm | 1012 +-- code/game/objects/items/melee/energy.dm | 772 +-- code/game/objects/items/paiwire.dm | 26 +- code/game/objects/items/robot/robot_items.dm | 1822 +++--- code/game/objects/items/robot/robot_parts.dm | 826 +-- .../objects/items/robot/robot_upgrades.dm | 1452 ++--- code/game/objects/items/scrolls.dm | 146 +- code/game/objects/items/shields.dm | 366 +- code/game/objects/items/shooting_range.dm | 192 +- code/game/objects/items/singularityhammer.dm | 230 +- code/game/objects/items/stacks/bscrystal.dm | 178 +- code/game/objects/items/stacks/medical.dm | 310 +- code/game/objects/items/stacks/rods.dm | 174 +- .../game/objects/items/stacks/sheets/glass.dm | 688 +- .../objects/items/stacks/sheets/leather.dm | 496 +- .../game/objects/items/stacks/sheets/light.dm | 70 +- .../objects/items/stacks/sheets/sheets.dm | 34 +- code/game/objects/items/storage/backpack.dm | 1240 ++-- code/game/objects/items/storage/bags.dm | 836 +-- code/game/objects/items/storage/belt.dm | 1586 ++--- code/game/objects/items/storage/boxes.dm | 2538 ++++---- code/game/objects/items/storage/briefcase.dm | 230 +- code/game/objects/items/storage/fancy.dm | 708 +- code/game/objects/items/storage/lockbox.dm | 400 +- code/game/objects/items/storage/secure.dm | 376 +- code/game/objects/items/storage/storage.dm | 56 +- code/game/objects/items/storage/toolbox.dm | 664 +- .../game/objects/items/storage/uplink_kits.dm | 798 +-- code/game/objects/items/stunbaton.dm | 506 +- code/game/objects/items/tanks/tank_types.dm | 350 +- code/game/objects/items/teleportation.dm | 480 +- code/game/objects/items/tools/crowbar.dm | 204 +- code/game/objects/items/tools/screwdriver.dm | 314 +- code/game/objects/items/tools/wirecutters.dm | 292 +- code/game/objects/items/tools/wrench.dm | 248 +- code/game/objects/items/trash.dm | 162 +- code/game/objects/items/vending_items.dm | 102 +- code/game/objects/items/weaponry.dm | 1376 ++-- code/game/objects/objs.dm | 498 +- code/game/objects/structures.dm | 222 +- code/game/objects/structures/ai_core.dm | 658 +- code/game/objects/structures/bedsheet_bin.dm | 796 +-- .../structures/crates_lockers/closets.dm | 1232 ++-- .../crates_lockers/closets/gimmick.dm | 218 +- .../crates_lockers/closets/job_closets.dm | 732 +-- .../crates_lockers/closets/secure/bar.dm | 26 +- .../crates_lockers/closets/secure/cargo.dm | 52 +- .../closets/secure/engineering.dm | 258 +- .../crates_lockers/closets/secure/freezer.dm | 212 +- .../closets/secure/hydroponics.dm | 24 +- .../crates_lockers/closets/secure/medical.dm | 214 +- .../crates_lockers/closets/secure/personal.dm | 150 +- .../closets/secure/scientist.dm | 68 +- .../closets/secure/secure_closets.dm | 24 +- .../crates_lockers/closets/secure/security.dm | 586 +- .../crates_lockers/closets/syndicate.dm | 240 +- .../crates_lockers/closets/utility_closets.dm | 398 +- .../structures/crates_lockers/crates.dm | 446 +- .../structures/crates_lockers/crates/bins.dm | 88 +- .../crates_lockers/crates/critter.dm | 74 +- .../structures/crates_lockers/crates/large.dm | 84 +- .../crates_lockers/crates/secure.dm | 144 +- code/game/objects/structures/displaycase.dm | 698 +- code/game/objects/structures/dresser.dm | 164 +- code/game/objects/structures/electricchair.dm | 92 +- code/game/objects/structures/extinguisher.dm | 312 +- code/game/objects/structures/flora.dm | 854 +-- code/game/objects/structures/hivebot.dm | 72 +- code/game/objects/structures/kitchen_spike.dm | 312 +- code/game/objects/structures/ladders.dm | 462 +- code/game/objects/structures/loom.dm | 86 +- code/game/objects/structures/manned_turret.dm | 432 +- code/game/objects/structures/mineral_doors.dm | 516 +- code/game/objects/structures/mirror.dm | 478 +- code/game/objects/structures/mop_bucket.dm | 56 +- code/game/objects/structures/morgue.dm | 772 +-- code/game/objects/structures/musician.dm | 762 +-- code/game/objects/structures/noticeboard.dm | 264 +- code/game/objects/structures/safe.dm | 422 +- code/game/objects/structures/tables_racks.dm | 1400 ++-- .../game/objects/structures/tank_dispenser.dm | 222 +- code/game/objects/structures/target_stake.dm | 152 +- .../transit_tube_construction.dm | 266 +- .../objects/structures/windoor_assembly.dm | 714 +-- code/game/shuttle_engines.dm | 312 +- code/game/world.dm | 638 +- code/modules/NTNet/services/_service.dm | 76 +- code/modules/admin/DB_ban/functions.dm | 1122 ++-- code/modules/admin/IsBanned.dm | 416 +- code/modules/admin/NewBan.dm | 476 +- code/modules/admin/admin.dm | 2022 +++--- code/modules/admin/admin_investigate.dm | 44 +- code/modules/admin/admin_ranks.dm | 612 +- code/modules/admin/banjob.dm | 80 +- code/modules/admin/chat_commands.dm | 286 +- code/modules/admin/create_mob.dm | 92 +- code/modules/admin/create_object.dm | 64 +- code/modules/admin/create_turf.dm | 20 +- code/modules/admin/holder2.dm | 420 +- code/modules/admin/player_panel.dm | 626 +- code/modules/admin/topic.dm | 5696 ++++++++--------- code/modules/admin/verbs/adminhelp.dm | 1398 ++-- code/modules/admin/verbs/adminjump.dm | 314 +- code/modules/admin/verbs/adminpm.dm | 650 +- code/modules/admin/verbs/adminsay.dm | 44 +- code/modules/admin/verbs/atmosdebug.dm | 82 +- code/modules/admin/verbs/cinematic.dm | 20 +- code/modules/admin/verbs/deadsay.dm | 72 +- code/modules/admin/verbs/fps.dm | 52 +- code/modules/admin/verbs/getlogs.dm | 68 +- code/modules/admin/verbs/mapping.dm | 754 +-- code/modules/admin/verbs/massmodvar.dm | 530 +- code/modules/admin/verbs/modifyvariables.dm | 1286 ++-- code/modules/admin/verbs/onlyone.dm | 60 +- code/modules/admin/verbs/playsound.dm | 406 +- code/modules/admin/verbs/possess.dm | 106 +- code/modules/admin/verbs/pray.dm | 150 +- code/modules/admin/verbs/randomverbs.dm | 2786 ++++---- code/modules/admin/whitelist.dm | 46 +- .../antagonists/_common/antag_spawner.dm | 564 +- .../antagonists/blob/blob/blobs/shield.dm | 126 +- .../changeling/changeling_power.dm | 168 +- .../antagonists/changeling/powers/absorb.dm | 234 +- .../changeling/powers/fakedeath.dm | 86 +- .../antagonists/changeling/powers/revive.dm | 88 +- .../antagonists/changeling/powers/spiders.dm | 32 +- .../changeling/powers/tiny_prick.dm | 532 +- code/modules/antagonists/revenant/revenant.dm | 2 +- .../antagonists/wizard/equipment/artefact.dm | 890 +-- .../antagonists/wizard/equipment/spellbook.dm | 1570 ++--- code/modules/assembly/assembly.dm | 254 +- code/modules/assembly/bomb.dm | 404 +- code/modules/assembly/flash.dm | 730 +-- code/modules/assembly/helpers.dm | 30 +- code/modules/assembly/holder.dm | 278 +- code/modules/assembly/igniter.dm | 82 +- code/modules/assembly/infrared.dm | 478 +- code/modules/assembly/mousetrap.dm | 284 +- code/modules/assembly/proximity.dm | 320 +- code/modules/assembly/shock_kit.dm | 78 +- code/modules/assembly/signaler.dm | 416 +- code/modules/assembly/timer.dm | 270 +- code/modules/assembly/voice.dm | 208 +- .../atmospherics/gasmixtures/gas_mixture.dm | 808 +-- .../gasmixtures/immutable_mixtures.dm | 148 +- .../atmospherics/machinery/atmosmachinery.dm | 714 +-- .../binary_devices/binary_devices.dm | 62 +- .../components/binary_devices/circulator.dm | 380 +- .../components/binary_devices/dp_vent_pump.dm | 506 +- .../trinary_devices/trinary_devices.dm | 96 +- .../components/unary_devices/cryo.dm | 892 +-- .../components/unary_devices/vent_scrubber.dm | 656 +- .../atmospherics/machinery/other/meter.dm | 296 +- .../machinery/pipes/layermanifold.dm | 254 +- .../portable/portable_atmospherics.dm | 312 +- .../atmospherics/machinery/portable/pump.dm | 312 +- .../machinery/portable/scrubber.dm | 288 +- .../awaymissions/bluespaceartillery.dm | 110 +- code/modules/awaymissions/exile.dm | 24 +- code/modules/awaymissions/gateway.dm | 510 +- .../awaymissions/mission_code/Academy.dm | 696 +- .../awaymissions/mission_code/centcomAway.dm | 124 +- .../awaymissions/mission_code/wildwest.dm | 340 +- code/modules/awaymissions/pamphlet.dm | 78 +- code/modules/awaymissions/zlevel.dm | 122 +- code/modules/cargo/console.dm | 456 +- code/modules/cargo/expressconsole.dm | 412 +- code/modules/cargo/order.dm | 220 +- code/modules/client/asset_cache.dm | 1362 ++-- code/modules/client/client_defines.dm | 164 +- code/modules/client/preferences_toggles.dm | 834 +-- code/modules/client/verbs/etips.dm | 40 +- code/modules/clothing/clothing.dm | 912 +-- code/modules/clothing/glasses/_glasses.dm | 922 +-- code/modules/clothing/glasses/hud.dm | 454 +- code/modules/clothing/gloves/_gloves.dm | 84 +- code/modules/clothing/gloves/boxing.dm | 38 +- code/modules/clothing/gloves/color.dm | 440 +- code/modules/clothing/gloves/miscellaneous.dm | 210 +- code/modules/clothing/head/_head.dm | 124 +- code/modules/clothing/head/collectable.dm | 308 +- code/modules/clothing/head/hardhat.dm | 320 +- code/modules/clothing/head/helmet.dm | 728 +-- code/modules/clothing/head/jobs.dm | 746 +-- code/modules/clothing/head/misc.dm | 846 +-- code/modules/clothing/head/misc_special.dm | 624 +- code/modules/clothing/head/soft_caps.dm | 284 +- code/modules/clothing/masks/_masks.dm | 144 +- code/modules/clothing/masks/breath.dm | 84 +- code/modules/clothing/masks/gasmask.dm | 406 +- code/modules/clothing/masks/miscellaneous.dm | 642 +- code/modules/clothing/shoes/_shoes.dm | 202 +- code/modules/clothing/shoes/magboots.dm | 118 +- .../clothing/spacesuits/_spacesuits.dm | 98 +- .../clothing/spacesuits/miscellaneous.dm | 888 +-- code/modules/clothing/spacesuits/syndi.dm | 298 +- code/modules/clothing/suits/_suits.dm | 68 +- code/modules/clothing/suits/bio.dm | 180 +- code/modules/clothing/suits/jobs.dm | 438 +- code/modules/clothing/suits/labcoat.dm | 140 +- code/modules/clothing/suits/miscellaneous.dm | 1816 +++--- code/modules/clothing/suits/utility.dm | 314 +- code/modules/clothing/suits/wiz_robe.dm | 456 +- code/modules/clothing/under/_under.dm | 294 +- code/modules/clothing/under/color.dm | 534 +- code/modules/clothing/under/jobs/civilian.dm | 848 +-- .../clothing/under/jobs/engineering.dm | 162 +- code/modules/clothing/under/jobs/medsci.dm | 414 +- code/modules/clothing/under/jobs/security.dm | 400 +- code/modules/clothing/under/pants.dm | 216 +- code/modules/clothing/under/shorts.dm | 72 +- code/modules/clothing/under/syndicate.dm | 178 +- code/modules/detectivework/detective_work.dm | 212 +- code/modules/detectivework/evidence.dm | 190 +- code/modules/detectivework/scanner.dm | 436 +- code/modules/events/anomaly.dm | 96 +- code/modules/events/anomaly_bluespace.dm | 44 +- code/modules/events/anomaly_flux.dm | 46 +- code/modules/events/anomaly_grav.dm | 44 +- code/modules/events/anomaly_vortex.dm | 46 +- code/modules/events/blob.dm | 68 +- code/modules/events/brand_intelligence.dm | 178 +- code/modules/events/carp_migration.dm | 62 +- .../modules/events/communications_blackout.dm | 54 +- code/modules/events/disease_outbreak.dm | 154 +- code/modules/events/dust.dm | 62 +- code/modules/events/electrical_storm.dm | 72 +- code/modules/events/false_alarm.dm | 124 +- code/modules/events/holiday/halloween.dm | 124 +- code/modules/events/immovable_rod.dm | 328 +- code/modules/events/ion_storm.dm | 1186 ++-- code/modules/events/mass_hallucination.dm | 78 +- code/modules/events/meateor_wave.dm | 22 +- code/modules/events/meteor_wave.dm | 182 +- code/modules/events/prison_break.dm | 126 +- code/modules/events/processor_overload.dm | 78 +- code/modules/events/radiation_storm.dm | 40 +- code/modules/events/spacevine.dm | 1096 ++-- code/modules/events/spider_infestation.dm | 80 +- .../events/spontaneous_appendicitis.dm | 64 +- code/modules/events/vent_clog.dm | 424 +- code/modules/fields/timestop.dm | 324 +- code/modules/flufftext/Dreaming.dm | 138 +- code/modules/food_and_drinks/pizzabox.dm | 692 +- code/modules/holodeck/items.dm | 456 +- code/modules/hydroponics/biogenerator.dm | 654 +- code/modules/hydroponics/grown.dm | 338 +- code/modules/hydroponics/growninedible.dm | 124 +- code/modules/hydroponics/hydroitemdefines.dm | 388 +- code/modules/hydroponics/hydroponics.dm | 1960 +++--- code/modules/hydroponics/seed_extractor.dm | 384 +- code/modules/hydroponics/seeds.dm | 936 +-- .../integrated_electronics/core/debugger.dm | 168 +- .../integrated_electronics/core/wirer.dm | 202 +- .../subtypes/atmospherics.dm | 1520 ++--- code/modules/jobs/job_types/assistant.dm | 86 +- code/modules/jobs/jobs.dm | 252 +- code/modules/jobs/map_changes/map_changes.dm | 6 +- code/modules/language/common.dm | 144 +- code/modules/language/language.dm | 240 +- code/modules/language/machine.dm | 38 +- code/modules/language/monkey.dm | 24 +- code/modules/language/xenocommon.dm | 22 +- code/modules/library/lib_items.dm | 704 +- code/modules/library/lib_machines.dm | 1214 ++-- code/modules/library/random_books.dm | 174 +- code/modules/lighting/lighting_source.dm | 622 +- code/modules/mapping/ruins.dm | 342 +- .../space_management/space_reservation.dm | 170 +- code/modules/mining/equipment/mining_tools.dm | 308 +- .../mining/equipment/regenerative_core.dm | 294 +- code/modules/mining/laborcamp/laborshuttle.dm | 52 +- code/modules/mining/laborcamp/laborstacker.dm | 314 +- code/modules/mining/machine_processing.dm | 422 +- code/modules/mining/machine_stacking.dm | 242 +- code/modules/mining/machine_unloading.dm | 60 +- code/modules/mining/mint.dm | 210 +- code/modules/mining/ores_coins.dm | 974 +-- code/modules/mining/satchel_ore_boxdm.dm | 178 +- code/modules/mob/camera/camera.dm | 74 +- code/modules/mob/dead/dead.dm | 258 +- code/modules/mob/dead/new_player/login.dm | 68 +- .../modules/mob/dead/new_player/new_player.dm | 1260 ++-- code/modules/mob/dead/new_player/poll.dm | 1238 ++-- .../mob/dead/new_player/preferences_setup.dm | 124 +- code/modules/mob/dead/observer/say.dm | 80 +- code/modules/mob/inventory.dm | 946 +-- code/modules/mob/living/carbon/alien/alien.dm | 290 +- .../mob/living/carbon/alien/alien_defense.dm | 256 +- code/modules/mob/living/carbon/alien/death.dm | 30 +- .../carbon/alien/humanoid/caste/hunter.dm | 180 +- .../mob/living/carbon/alien/humanoid/death.dm | 44 +- .../living/carbon/alien/humanoid/humanoid.dm | 238 +- .../mob/living/carbon/alien/humanoid/queen.dm | 278 +- .../carbon/alien/humanoid/update_icons.dm | 194 +- .../mob/living/carbon/alien/larva/death.dm | 46 +- .../mob/living/carbon/alien/larva/powers.dm | 126 +- code/modules/mob/living/carbon/alien/life.dm | 104 +- code/modules/mob/living/carbon/alien/say.dm | 46 +- .../modules/mob/living/carbon/alien/screen.dm | 64 +- .../carbon/alien/special/alien_embryo.dm | 278 +- .../living/carbon/alien/special/facehugger.dm | 542 +- code/modules/mob/living/carbon/carbon.dm | 1980 +++--- .../mob/living/carbon/carbon_defense.dm | 912 +-- .../mob/living/carbon/carbon_defines.dm | 132 +- code/modules/mob/living/carbon/death.dm | 144 +- code/modules/mob/living/carbon/emote.dm | 200 +- code/modules/mob/living/carbon/human/death.dm | 136 +- code/modules/mob/living/carbon/human/dummy.dm | 96 +- code/modules/mob/living/carbon/human/emote.dm | 378 +- .../mob/living/carbon/human/examine.dm | 830 +-- code/modules/mob/living/carbon/human/human.dm | 2280 +++---- .../mob/living/carbon/human/human_defense.dm | 1638 ++--- .../mob/living/carbon/human/human_defines.dm | 140 +- .../mob/living/carbon/human/human_helpers.dm | 284 +- .../mob/living/carbon/human/human_movement.dm | 156 +- .../mob/living/carbon/human/inventory.dm | 546 +- code/modules/mob/living/carbon/human/say.dm | 232 +- .../carbon/human/species_types/abductors.dm | 36 +- .../carbon/human/species_types/android.dm | 48 +- .../carbon/human/species_types/angel.dm | 288 +- .../carbon/human/species_types/flypeople.dm | 72 +- .../carbon/human/species_types/golems.dm | 2050 +++--- .../carbon/human/species_types/jellypeople.dm | 1968 +++--- .../human/species_types/lizardpeople.dm | 186 +- .../carbon/human/species_types/plasmamen.dm | 310 +- .../human/species_types/shadowpeople.dm | 444 +- .../carbon/human/species_types/skeletons.dm | 58 +- .../carbon/human/species_types/synths.dm | 252 +- .../carbon/human/species_types/zombies.dm | 196 +- code/modules/mob/living/carbon/inventory.dm | 284 +- code/modules/mob/living/carbon/life.dm | 1518 ++--- .../modules/mob/living/carbon/monkey/death.dm | 18 +- code/modules/mob/living/carbon/monkey/life.dm | 344 +- .../mob/living/carbon/monkey/monkey.dm | 348 +- .../mob/living/carbon/monkey/update_icons.dm | 158 +- .../modules/mob/living/carbon/update_icons.dm | 562 +- code/modules/mob/living/damage_procs.dm | 560 +- code/modules/mob/living/death.dm | 214 +- code/modules/mob/living/emote.dm | 1048 +-- code/modules/mob/living/inhand_holder.dm | 260 +- code/modules/mob/living/living_defense.dm | 1046 +-- code/modules/mob/living/living_defines.dm | 230 +- code/modules/mob/living/login.dm | 54 +- code/modules/mob/living/logout.dm | 8 +- code/modules/mob/living/say.dm | 838 +-- code/modules/mob/living/silicon/ai/ai.dm | 1992 +++--- code/modules/mob/living/silicon/ai/death.dm | 112 +- code/modules/mob/living/silicon/ai/examine.dm | 42 +- .../living/silicon/ai/freelook/cameranet.dm | 408 +- .../mob/living/silicon/ai/freelook/chunk.dm | 284 +- .../mob/living/silicon/ai/freelook/eye.dm | 412 +- code/modules/mob/living/silicon/ai/login.dm | 24 +- code/modules/mob/living/silicon/ai/logout.dm | 16 +- code/modules/mob/living/silicon/ai/say.dm | 358 +- .../mob/living/silicon/ai/vox_sounds.dm | 3216 +++++----- code/modules/mob/living/silicon/death.dm | 24 +- code/modules/mob/living/silicon/laws.dm | 186 +- code/modules/mob/living/silicon/login.dm | 20 +- code/modules/mob/living/silicon/pai/death.dm | 24 +- code/modules/mob/living/silicon/pai/pai.dm | 858 +-- .../mob/living/silicon/pai/personality.dm | 122 +- .../mob/living/silicon/pai/software.dm | 1264 ++-- .../modules/mob/living/silicon/robot/death.dm | 70 +- .../mob/living/silicon/robot/examine.dm | 106 +- .../mob/living/silicon/robot/inventory.dm | 470 +- code/modules/mob/living/silicon/robot/laws.dm | 164 +- code/modules/mob/living/silicon/robot/life.dm | 204 +- .../modules/mob/living/silicon/robot/login.dm | 10 +- .../mob/living/silicon/robot/robot_modules.dm | 2050 +++--- code/modules/mob/living/silicon/say.dm | 108 +- code/modules/mob/living/silicon/silicon.dm | 840 +-- .../mob/living/simple_animal/bot/bot.dm | 2060 +++--- .../mob/living/simple_animal/bot/ed209bot.dm | 1146 ++-- .../mob/living/simple_animal/bot/medbot.dm | 1134 ++-- .../mob/living/simple_animal/bot/secbot.dm | 888 +-- .../mob/living/simple_animal/constructs.dm | 924 +-- .../mob/living/simple_animal/corpse.dm | 442 +- .../mob/living/simple_animal/friendly/cat.dm | 626 +- .../mob/living/simple_animal/friendly/crab.dm | 158 +- .../simple_animal/friendly/farm_animals.dm | 948 +-- .../living/simple_animal/friendly/lizard.dm | 92 +- .../mob/living/simple_animal/hostile/carp.dm | 214 +- .../living/simple_animal/hostile/faithless.dm | 92 +- .../simple_animal/hostile/giant_spider.dm | 1088 ++-- .../living/simple_animal/hostile/hivebot.dm | 160 +- .../mob/living/simple_animal/hostile/mimic.dm | 548 +- .../living/simple_animal/hostile/mushroom.dm | 384 +- .../simple_animal/hostile/retaliate/bat.dm | 102 +- .../simple_animal/hostile/retaliate/clown.dm | 92 +- .../hostile/retaliate/retaliate.dm | 94 +- .../living/simple_animal/hostile/syndicate.dm | 626 +- .../mob/living/simple_animal/hostile/tree.dm | 142 +- .../mob/living/simple_animal/parrot.dm | 2048 +++--- .../modules/mob/living/simple_animal/shade.dm | 130 +- .../mob/living/simple_animal/simple_animal.dm | 1178 ++-- code/modules/mob/login.dm | 110 +- code/modules/mob/logout.dm | 34 +- code/modules/mob/mob_helpers.dm | 1076 ++-- code/modules/mob/mob_movement.dm | 814 +-- code/modules/mob/say.dm | 208 +- code/modules/mob/transform_procs.dm | 1172 ++-- code/modules/paperwork/clipboard.dm | 252 +- code/modules/paperwork/filingcabinet.dm | 450 +- code/modules/paperwork/folders.dm | 234 +- code/modules/paperwork/paperbin.dm | 348 +- code/modules/paperwork/pen.dm | 440 +- code/modules/paperwork/photocopier.dm | 680 +- code/modules/paperwork/stamps.dm | 142 +- code/modules/photography/_pictures.dm | 342 +- code/modules/photography/camera/camera.dm | 428 +- .../camera/camera_image_capturing.dm | 178 +- code/modules/photography/camera/film.dm | 28 +- code/modules/photography/camera/other.dm | 42 +- .../photography/camera/silicon_camera.dm | 198 +- code/modules/photography/photos/album.dm | 150 +- code/modules/photography/photos/frame.dm | 326 +- code/modules/photography/photos/photo.dm | 184 +- .../power/antimatter/containment_jar.dm | 78 +- code/modules/power/antimatter/control.dm | 714 +-- code/modules/power/antimatter/shielding.dm | 506 +- code/modules/power/cable.dm | 1700 ++--- code/modules/power/cell.dm | 732 +-- code/modules/power/generator.dm | 468 +- code/modules/power/gravitygenerator.dm | 834 +-- code/modules/power/monitor.dm | 242 +- code/modules/power/port_gen.dm | 574 +- code/modules/power/singularity/collector.dm | 464 +- .../power/singularity/containment_field.dm | 268 +- code/modules/power/singularity/emitter.dm | 1008 +-- .../power/singularity/field_generator.dm | 706 +- code/modules/power/singularity/generator.dm | 70 +- code/modules/power/singularity/investigate.dm | 6 +- .../particle_accelerator/particle.dm | 136 +- .../particle_accelerator.dm | 344 +- .../particle_accelerator/particle_control.dm | 660 +- code/modules/power/tracker.dm | 186 +- .../mapGenerators/lavaland.dm | 66 +- .../mapGenerators/repair.dm | 222 +- .../mapGenerators/syndicate.dm | 114 +- .../projectiles/ammunition/_ammunition.dm | 170 +- .../modules/projectiles/ammunition/_firing.dm | 126 +- .../projectiles/ammunition/ballistic/lmg.dm | 46 +- .../ammunition/ballistic/pistol.dm | 100 +- .../ammunition/ballistic/revolver.dm | 98 +- .../projectiles/ammunition/ballistic/rifle.dm | 56 +- .../ammunition/ballistic/shotgun.dm | 282 +- .../projectiles/ammunition/ballistic/smg.dm | 64 +- .../ammunition/ballistic/sniper.dm | 40 +- .../ammunition/caseless/_caseless.dm | 32 +- .../projectiles/ammunition/caseless/foam.dm | 130 +- .../projectiles/ammunition/caseless/misc.dm | 44 +- .../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 | 146 +- .../projectiles/ammunition/energy/lmg.dm | 12 +- .../projectiles/ammunition/energy/plasma.dm | 28 +- .../projectiles/ammunition/energy/portal.dm | 40 +- .../projectiles/ammunition/energy/special.dm | 146 +- .../projectiles/ammunition/energy/stun.dm | 58 +- .../projectiles/ammunition/special/magic.dm | 90 +- .../projectiles/ammunition/special/syringe.dm | 156 +- .../boxes_magazines/_box_magazine.dm | 276 +- .../boxes_magazines/external/grenade.dm | 16 +- .../boxes_magazines/external/lmg.dm | 44 +- .../boxes_magazines/external/pistol.dm | 122 +- .../boxes_magazines/external/rechargable.dm | 28 +- .../boxes_magazines/external/rifle.dm | 42 +- .../boxes_magazines/external/shotgun.dm | 82 +- .../boxes_magazines/external/smg.dm | 152 +- .../boxes_magazines/external/sniper.dm | 52 +- .../boxes_magazines/external/toy.dm | 118 +- .../boxes_magazines/internal/_cylinder.dm | 94 +- .../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 | 34 +- .../boxes_magazines/internal/shotgun.dm | 96 +- .../boxes_magazines/internal/toy.dm | 14 +- code/modules/projectiles/guns/energy.dm | 472 +- code/modules/projectiles/guns/energy/laser.dm | 450 +- .../projectiles/guns/energy/mounted.dm | 40 +- code/modules/projectiles/guns/energy/pulse.dm | 158 +- .../projectiles/guns/energy/special.dm | 650 +- code/modules/projectiles/guns/energy/stun.dm | 128 +- code/modules/projectiles/guns/magic.dm | 182 +- code/modules/projectiles/guns/magic/wand.dm | 354 +- .../projectiles/guns/misc/beam_rifle.dm | 1144 ++-- .../modules/projectiles/guns/misc/chem_gun.dm | 90 +- .../projectiles/guns/misc/grenade_launcher.dm | 92 +- code/modules/projectiles/guns/misc/medbeam.dm | 270 +- .../projectiles/guns/misc/syringe_gun.dm | 334 +- code/modules/projectiles/projectile.dm | 1296 ++-- code/modules/projectiles/projectile/beams.dm | 418 +- .../modules/projectiles/projectile/bullets.dm | 20 +- .../projectile/bullets/_incendiary.dm | 34 +- .../projectile/bullets/dart_syringe.dm | 160 +- .../projectile/bullets/dnainjector.dm | 48 +- .../projectiles/projectile/bullets/grenade.dm | 24 +- .../projectiles/projectile/bullets/lmg.dm | 88 +- .../projectiles/projectile/bullets/pistol.dm | 72 +- .../projectile/bullets/revolver.dm | 140 +- .../projectiles/projectile/bullets/rifle.dm | 32 +- .../projectiles/projectile/bullets/shotgun.dm | 200 +- .../projectiles/projectile/bullets/sniper.dm | 92 +- .../projectiles/projectile/bullets/special.dm | 54 +- .../projectiles/projectile/energy/_energy.dm | 16 +- .../projectiles/projectile/energy/ebow.dm | 32 +- .../projectiles/projectile/energy/misc.dm | 30 +- .../projectiles/projectile/energy/stun.dm | 70 +- .../projectiles/projectile/energy/tesla.dm | 58 +- .../projectile/reusable/_reusable.dm | 40 +- .../projectile/reusable/foam_dart.dm | 82 +- .../projectile/reusable/magspear.dm | 12 +- .../projectiles/projectile/special/curse.dm | 104 +- .../projectiles/projectile/special/floral.dm | 50 +- .../projectiles/projectile/special/gravity.dm | 180 +- .../projectile/special/hallucination.dm | 460 +- .../projectiles/projectile/special/ion.dm | 40 +- .../projectiles/projectile/special/meteor.dm | 38 +- .../projectile/special/mindflayer.dm | 18 +- .../projectile/special/neurotoxin.dm | 28 +- .../projectiles/projectile/special/plasma.dm | 108 +- .../projectiles/projectile/special/rocket.dm | 90 +- .../projectile/special/temperature.dm | 36 +- .../projectile/special/wormhole.dm | 58 +- code/modules/reagents/reagent_containers.dm | 494 +- .../reagents/reagent_containers/borghydro.dm | 528 +- .../reagents/reagent_containers/dropper.dm | 204 +- .../reagents/reagent_containers/glass.dm | 736 +-- .../reagents/reagent_containers/hypospray.dm | 1024 +-- .../reagents/reagent_containers/spray.dm | 648 +- .../reagents/reagent_containers/syringes.dm | 702 +- code/modules/reagents/reagent_dispenser.dm | 504 +- code/modules/recycling/sortingmachinery.dm | 392 +- code/modules/research/designs.dm | 176 +- .../research/designs/bluespace_designs.dm | 172 +- ..._cargo .dm => comp_board_designs_cargo.dm} | 2 +- .../research/designs/electronics_designs.dm | 320 +- .../research/designs/equipment_designs.dm | 108 +- .../research/designs/mining_designs.dm | 266 +- code/modules/research/destructive_analyzer.dm | 308 +- code/modules/research/experimentor.dm | 1334 ++-- .../modules/research/machinery/_production.dm | 730 +-- .../research/machinery/circuit_imprinter.dm | 64 +- .../departmental_circuit_imprinter.dm | 24 +- .../machinery/departmental_protolathe.dm | 84 +- .../machinery/departmental_techfab.dm | 80 +- code/modules/research/machinery/protolathe.dm | 46 +- code/modules/research/machinery/techfab.dm | 66 +- code/modules/research/rdconsole.dm | 2270 +++---- code/modules/research/rdmachines.dm | 212 +- code/modules/research/research_disk.dm | 80 +- .../research/techweb/__techweb_helpers.dm | 68 +- code/modules/research/techweb/_techweb.dm | 776 +-- .../modules/research/techweb/_techweb_node.dm | 194 +- .../security_levels/keycard_authentication.dm | 286 +- .../security_levels/security_levels.dm | 312 +- code/modules/shuttle/arrivals.dm | 412 +- code/modules/shuttle/computer.dm | 150 +- code/modules/shuttle/ferry.dm | 80 +- code/modules/shuttle/syndicate.dm | 132 +- code/modules/shuttle/white_ship.dm | 112 +- .../spells/spell_types/area_teleport.dm | 184 +- code/modules/spells/spell_types/conjure.dm | 200 +- code/modules/spells/spell_types/dumbfire.dm | 228 +- code/modules/spells/spell_types/emplosion.dm | 34 +- .../spells/spell_types/ethereal_jaunt.dm | 204 +- code/modules/spells/spell_types/explosion.dm | 30 +- code/modules/spells/spell_types/genetic.dm | 86 +- .../spells/spell_types/inflict_handler.dm | 112 +- code/modules/spells/spell_types/knock.dm | 62 +- code/modules/spells/spell_types/mime.dm | 310 +- .../spells/spell_types/mind_transfer.dm | 176 +- code/modules/spells/spell_types/projectile.dm | 192 +- .../spells/spell_types/rightandwrong.dm | 352 +- code/modules/spells/spell_types/trigger.dm | 58 +- code/modules/spells/spell_types/wizard.dm | 756 +-- code/modules/surgery/core_removal.dm | 82 +- code/modules/surgery/eye_surgery.dm | 92 +- code/modules/surgery/helpers.dm | 350 +- code/modules/surgery/implant_removal.dm | 120 +- code/modules/surgery/limb_augmentation.dm | 100 +- code/modules/surgery/lipoplasty.dm | 112 +- code/modules/surgery/organs/helpers.dm | 58 +- code/modules/surgery/organs/liver.dm | 242 +- code/modules/surgery/organs/stomach.dm | 196 +- code/modules/surgery/plastic_surgery.dm | 102 +- code/modules/surgery/surgery_step.dm | 260 +- code/modules/surgery/tools.dm | 720 +-- code/modules/tgs/core/_definitions.dm | 4 +- code/modules/tgs/core/core.dm | 288 +- code/modules/tgs/core/datum.dm | 148 +- .../modules/tgs/core/default_event_handler.dm | 60 +- code/modules/tgs/includes.dm | 12 +- code/modules/tgs/v3210/api.dm | 496 +- code/modules/tgs/v3210/commands.dm | 156 +- code/modules/tgui/tgui.dm | 774 +-- code/modules/unit_tests/_unit_tests.dm | 24 +- code/modules/uplink/uplink_devices.dm | 118 +- code/modules/vehicles/lavaboat.dm | 138 +- code/modules/vehicles/ridden.dm | 158 +- code/modules/vehicles/vehicle_actions.dm | 332 +- interface/interface.dm | 454 +- .../living/silicon/robot/dogborg_equipment.dm | 898 +-- tools/Redirector/Configurations.dm | 106 +- tools/Redirector/textprocs.dm | 304 +- 942 files changed, 189318 insertions(+), 189318 deletions(-) rename code/modules/research/designs/comp_board_designs/{comp_board_designs_cargo .dm => comp_board_designs_cargo.dm} (97%) diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index bded562a3c..3acd1443af 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -1,91 +1,91 @@ -//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 -#define BANTYPE_ANY_FULLBAN 5 //used to locate stuff to unban. - -#define BANTYPE_ADMIN_PERMA 7 -#define BANTYPE_ADMIN_TEMP 8 -#define BANTYPE_ANY_JOB 9 //used to remove jobbans - -//Admin Permissions -#define R_BUILDMODE (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_SOUNDS (1<<11) -#define R_SPAWN (1<<12) -#define R_AUTOLOGIN (1<<13) -#define R_DBRANKS (1<<14) - -#define R_DEFAULT R_AUTOLOGIN - -#define R_EVERYTHING ALL //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_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.x],[src.y],[src.z])" : "nonexistent location"]" -#define AREACOORD(src) "[src ? "[get_area_name(src, TRUE)] ([src.x], [src.y], [src.z])" : "nonexistent location"]" -#define ADMIN_COORDJMP(src) "[src ? "[COORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" -#define ADMIN_VERBOSEJMP(src) "[src ? "[AREACOORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" -#define ADMIN_INDIVIDUALLOG(user) "(LOGS)" - -#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_PIE "Cream Pie" - -#define AHELP_ACTIVE 1 -#define AHELP_CLOSED 2 -#define AHELP_RESOLVED 3 - -#define ROUNDSTART_LOGOUT_REPORT_TIME 6000 //Amount of time (in deciseconds) after the rounds starts, that the player disconnect report is issued. - -#define SPAM_TRIGGER_WARNING 5 //Number of identical messages required before the spam-prevention will warn you to stfu -#define SPAM_TRIGGER_AUTOMUTE 10 //Number of identical messages required before the spam-prevention will automute you - -///Max length of a keypress command before it's considered to be a forged packet/bogus command -#define MAX_KEYPRESS_COMMANDLENGTH 16 -///Max amount of keypress messages per second over two seconds before client is autokicked -#define MAX_KEYPRESS_AUTOKICK 100 -///Length of held key rolling buffer -#define HELD_KEY_BUFFER_LENGTH 15 +//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 +#define BANTYPE_ANY_FULLBAN 5 //used to locate stuff to unban. + +#define BANTYPE_ADMIN_PERMA 7 +#define BANTYPE_ADMIN_TEMP 8 +#define BANTYPE_ANY_JOB 9 //used to remove jobbans + +//Admin Permissions +#define R_BUILDMODE (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_SOUNDS (1<<11) +#define R_SPAWN (1<<12) +#define R_AUTOLOGIN (1<<13) +#define R_DBRANKS (1<<14) + +#define R_DEFAULT R_AUTOLOGIN + +#define R_EVERYTHING ALL //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_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.x],[src.y],[src.z])" : "nonexistent location"]" +#define AREACOORD(src) "[src ? "[get_area_name(src, TRUE)] ([src.x], [src.y], [src.z])" : "nonexistent location"]" +#define ADMIN_COORDJMP(src) "[src ? "[COORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" +#define ADMIN_VERBOSEJMP(src) "[src ? "[AREACOORD(src)] [ADMIN_JMP(src)]" : "nonexistent location"]" +#define ADMIN_INDIVIDUALLOG(user) "(LOGS)" + +#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_PIE "Cream Pie" + +#define AHELP_ACTIVE 1 +#define AHELP_CLOSED 2 +#define AHELP_RESOLVED 3 + +#define ROUNDSTART_LOGOUT_REPORT_TIME 6000 //Amount of time (in deciseconds) after the rounds starts, that the player disconnect report is issued. + +#define SPAM_TRIGGER_WARNING 5 //Number of identical messages required before the spam-prevention will warn you to stfu +#define SPAM_TRIGGER_AUTOMUTE 10 //Number of identical messages required before the spam-prevention will automute you + +///Max length of a keypress command before it's considered to be a forged packet/bogus command +#define MAX_KEYPRESS_COMMANDLENGTH 16 +///Max amount of keypress messages per second over two seconds before client is autokicked +#define MAX_KEYPRESS_AUTOKICK 100 +///Length of held key rolling buffer +#define HELD_KEY_BUFFER_LENGTH 15 diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index 41b5deb302..456582c6a5 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -1,298 +1,298 @@ -//LISTMOS -//indices of values in gas lists. -#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! -#define R_IDEAL_GAS_EQUATION 8.31 //kPa*L/(K*mol) -#define ONE_ATMOSPHERE 101.325 //kPa -#define TCMB 2.7 // -270.3degC -#define TCRYO 225 // -48.15degC -#define T0C 273.15 // 0degC -#define T20C 293.15 // 20degC - -#define MOLES_CELLSTANDARD (ONE_ATMOSPHERE*CELL_VOLUME/(T20C*R_IDEAL_GAS_EQUATION)) //moles in a 2.5 m^3 cell at 101.325 Pa and 20 degC -#define M_CELL_WITH_RATIO (MOLES_CELLSTANDARD * 0.005) //compared against for superconductivity -#define O2STANDARD 0.21 //percentage of oxygen in a normal mixture of air -#define N2STANDARD 0.79 //same but for nitrogen -#define MOLES_O2STANDARD (MOLES_CELLSTANDARD*O2STANDARD) // O2 standard value (21%) -#define MOLES_N2STANDARD (MOLES_CELLSTANDARD*N2STANDARD) // N2 standard value (79%) -#define CELL_VOLUME 2500 //liters in a cell -#define BREATH_VOLUME 0.5 //liters in a normal breath -#define BREATH_PERCENTAGE (BREATH_VOLUME/CELL_VOLUME) //Amount of air to take a from a tile - -//EXCITED GROUPS -#define EXCITED_GROUP_BREAKDOWN_CYCLES 4 //number of FULL air controller ticks before an excited group breaks down (averages gas contents across turfs) -#define EXCITED_GROUP_DISMANTLE_CYCLES 16 //number of FULL air controller ticks before an excited group dismantles and removes its turfs from active -#define MINIMUM_AIR_RATIO_TO_SUSPEND 0.1 //Ratio of air that must move to/from a tile to reset group processing -#define MINIMUM_AIR_RATIO_TO_MOVE 0.001 //Minimum ratio of air that must move to/from a tile -#define MINIMUM_AIR_TO_SUSPEND (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_SUSPEND) //Minimum amount of air that has to move before a group processing can be suspended -#define MINIMUM_MOLES_DELTA_TO_MOVE (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_MOVE) //Either this must be active -#define MINIMUM_TEMPERATURE_TO_MOVE (T20C+100) //or this (or both, obviously) -#define MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND 4 //Minimum temperature difference before group processing is suspended -#define MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER 0.5 //Minimum temperature difference before the gas temperatures are just set to be equal -#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 -#define WINDOW_HEAT_TRANSFER_COEFFICIENT 0.1 //a hack for now -#define HEAT_CAPACITY_VACUUM 7000 //a hack to help make vacuums "cold", sacrificing realism for gameplay - -//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 - -//GASES -#define MIN_TOXIC_GAS_DAMAGE 1 -#define MAX_TOXIC_GAS_DAMAGE 10 -#define MOLES_GAS_VISIBLE 0.25 //Moles in a standard cell after which gases are visible -#define FACTOR_GAS_VISIBLE_MAX 20 //moles_visible * FACTOR_GAS_VISIBLE_MAX = Moles after which gas is at maximum visibility -#define MOLES_GAS_VISIBLE_STEP 0.25 //Mole step for alpha updates. This means alpha can update at 0.25, 0.5, 0.75 and so on - -//REACTIONS -//return values for reactions (bitflags) -#define NO_REACTION 0 -#define REACTING 1 -#define STOP_REACTIONS 2 - -// Pressure limits. -#define HAZARD_HIGH_PRESSURE 550 //This determins at what pressure the ultra-high pressure red icon is displayed. (This one is set as a constant) -#define WARNING_HIGH_PRESSURE 325 //This determins when the orange pressure icon is displayed (it is 0.7 * HAZARD_HIGH_PRESSURE) -#define WARNING_LOW_PRESSURE 50 //This is when the gray low pressure icon is displayed. (it is 2.5 * HAZARD_LOW_PRESSURE) -#define HAZARD_LOW_PRESSURE 20 //This is when the black ultra-low pressure icon is displayed. (This one is set as a constant) - -#define TEMPERATURE_DAMAGE_COEFFICIENT 1.5 //This is used in handle_temperature_damage() for humans, and in reagents that affect body temperature. Temperature damage is multiplied by this amount. - -#define BODYTEMP_NORMAL 310.15 //The natural temperature for a body -#define BODYTEMP_AUTORECOVERY_DIVISOR 11 //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_MINIMUM 12 //Minimum amount of kelvin moved toward 310K per tick. So long as abs(310.15 - bodytemp) is more than 50. -#define BODYTEMP_COLD_DIVISOR 6 //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_HEAT_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_COOLING_MAX -100 //The maximum number of degrees that your body can cool in 1 tick, due to the environment, when in a cold area. -#define BODYTEMP_HEATING_MAX 30 //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_HEAT_DAMAGE_LIMIT (BODYTEMP_NORMAL + 20) // The limit the human body can take before it starts taking damage from heat. //CITADEL EDIT to 20 -#define BODYTEMP_COLD_DAMAGE_LIMIT (BODYTEMP_NORMAL - 50) // The limit the human body can take before it starts taking damage from coldness. - - -#define SPACE_HELM_MIN_TEMP_PROTECT 2.0 //what min_cold_protection_temperature is set to for space-helmet quality headwear. MUST NOT BE 0. -#define SPACE_HELM_MAX_TEMP_PROTECT 1500 //Thermal insulation works both ways /Malkevin -#define SPACE_SUIT_MIN_TEMP_PROTECT 2.0 //what min_cold_protection_temperature is set to for space-suit quality jumpsuits or suits. MUST NOT BE 0. -#define SPACE_SUIT_MAX_TEMP_PROTECT 1500 - -#define FIRE_SUIT_MIN_TEMP_PROTECT 60 //Cold protection for firesuits -#define FIRE_SUIT_MAX_TEMP_PROTECT 30000 //what max_heat_protection_temperature is set to for firesuit quality suits. MUST NOT BE 0. -#define FIRE_HELM_MIN_TEMP_PROTECT 60 //Cold protection for fire helmets -#define FIRE_HELM_MAX_TEMP_PROTECT 30000 //for fire helmet quality items (red and white hardhats) - -#define FIRE_IMMUNITY_MAX_TEMP_PROTECT 35000 //what max_heat_protection_temperature is set to for firesuit quality suits and helmets. MUST NOT BE 0. - -#define HELMET_MIN_TEMP_PROTECT 160 //For normal helmets -#define HELMET_MAX_TEMP_PROTECT 600 //For normal helmets -#define ARMOR_MIN_TEMP_PROTECT 160 //For armor -#define ARMOR_MAX_TEMP_PROTECT 600 //For armor - -#define GLOVES_MIN_TEMP_PROTECT 2.0 //For some gloves (black and) -#define GLOVES_MAX_TEMP_PROTECT 1500 //For some gloves -#define SHOES_MIN_TEMP_PROTECT 2.0 //For gloves -#define SHOES_MAX_TEMP_PROTECT 1500 //For gloves - -#define PRESSURE_DAMAGE_COEFFICIENT 4 //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 MAX_HIGH_PRESSURE_DAMAGE 16 // CITADEL CHANGES Max to 16, low to 8. -#define LOW_PRESSURE_DAMAGE 8 //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 COLD_SLOWDOWN_FACTOR 20 //Humans are slowed by the difference between bodytemp and BODYTEMP_COLD_DAMAGE_LIMIT divided by this - -//PIPES -//Atmos pipe limits -#define MAX_OUTPUT_PRESSURE 4500 // (kPa) What pressure pumps and powered equipment max out at. -#define MAX_TRANSFER_RATE 200 // (L/s) Maximum speed powered equipment can work at. - -//used for device_type vars -#define UNARY 1 -#define BINARY 2 -#define TRINARY 3 -#define QUATERNARY 4 - -//TANKS -#define TANK_MELT_TEMPERATURE 1000000 //temperature in kelvins at which a tank will start to melt -#define TANK_LEAK_PRESSURE (30.*ONE_ATMOSPHERE) //Tank starts leaking -#define TANK_RUPTURE_PRESSURE (35.*ONE_ATMOSPHERE) //Tank spills all contents into atmosphere -#define TANK_FRAGMENT_PRESSURE (40.*ONE_ATMOSPHERE) //Boom 3x3 base explosion -#define TANK_FRAGMENT_SCALE (6.*ONE_ATMOSPHERE) //+1 for each SCALE kPa aboe threshold -#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 -#define ATMOS_PASS_PROC -1 //ask CanAtmosPass() -#define ATMOS_PASS_DENSITY -2 //just check density - -#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 -#define OPENTURF_DEFAULT_ATMOS "o2=22;n2=82;TEMP=293.15" //the default air mix that open turfs spawn -#define TCOMMS_ATMOS "n2=100;TEMP=80" //-193,15°C telecommunications. also used for xenobiology slime killrooms -#define AIRLESS_ATMOS "TEMP=2.7" //space -#define FROZEN_ATMOS "o2=22;n2=82;TEMP=180" //-93.15°C snow and ice turfs -#define BURNMIX_ATMOS "o2=2500;plasma=5000;TEMP=370" //used in the holodeck burn test program - -//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 -#define LAVALAND_EQUIPMENT_EFFECT_PRESSURE 50 //what pressure you have to be under to increase the effect of equipment meant for lavaland -#define LAVALAND_DEFAULT_ATMOS "o2=14;n2=23;TEMP=300" - -//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 - -#define PIPING_ALL_LAYER (1<<0) //intended to connect with all layers, check for all instead of just one. -#define PIPING_ONE_PER_TURF (1<<1) //can only be built if nothing else with this flag is on the tile already. -#define PIPING_DEFAULT_LAYER_ONLY (1<<2) //can only exist at PIPING_LAYER_DEFAULT -#define PIPING_CARDINAL_AUTONORMALIZE (1<<3) //north/south east/west doesn't matter, auto normalize on build. - -//HELPERS -#define THERMAL_ENERGY(gas) (gas.temperature * gas.heat_capacity()) -#define QUANTIZE(variable) (round(variable,0.0000001))/*I feel the need to document what happens here. Basically this is used to catch most rounding errors, however it's previous value made it so that - once gases got hot enough, most procedures wouldnt occur due to the fact that the mole counts would get rounded away. Thus, we lowered it a few orders of magnititude */ - -//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];\ - } - -#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 - -//Unomos - So for whatever reason, garbage collection actually drastically decreases the cost of atmos later in the round. Turning this into a define yields massively improved performance. -#define GAS_GARBAGE_COLLECT(GASGASGAS)\ - var/list/CACHE_GAS = GASGASGAS;\ - for(var/id in CACHE_GAS){\ - if(QUANTIZE(CACHE_GAS[id]) <= 0)\ - CACHE_GAS -= id;\ - } - -#define ARCHIVE_TEMPERATURE(gas) gas.temperature_archived = gas.temperature - -GLOBAL_LIST_INIT(pipe_paint_colors, 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) -)) +//LISTMOS +//indices of values in gas lists. +#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! +#define R_IDEAL_GAS_EQUATION 8.31 //kPa*L/(K*mol) +#define ONE_ATMOSPHERE 101.325 //kPa +#define TCMB 2.7 // -270.3degC +#define TCRYO 225 // -48.15degC +#define T0C 273.15 // 0degC +#define T20C 293.15 // 20degC + +#define MOLES_CELLSTANDARD (ONE_ATMOSPHERE*CELL_VOLUME/(T20C*R_IDEAL_GAS_EQUATION)) //moles in a 2.5 m^3 cell at 101.325 Pa and 20 degC +#define M_CELL_WITH_RATIO (MOLES_CELLSTANDARD * 0.005) //compared against for superconductivity +#define O2STANDARD 0.21 //percentage of oxygen in a normal mixture of air +#define N2STANDARD 0.79 //same but for nitrogen +#define MOLES_O2STANDARD (MOLES_CELLSTANDARD*O2STANDARD) // O2 standard value (21%) +#define MOLES_N2STANDARD (MOLES_CELLSTANDARD*N2STANDARD) // N2 standard value (79%) +#define CELL_VOLUME 2500 //liters in a cell +#define BREATH_VOLUME 0.5 //liters in a normal breath +#define BREATH_PERCENTAGE (BREATH_VOLUME/CELL_VOLUME) //Amount of air to take a from a tile + +//EXCITED GROUPS +#define EXCITED_GROUP_BREAKDOWN_CYCLES 4 //number of FULL air controller ticks before an excited group breaks down (averages gas contents across turfs) +#define EXCITED_GROUP_DISMANTLE_CYCLES 16 //number of FULL air controller ticks before an excited group dismantles and removes its turfs from active +#define MINIMUM_AIR_RATIO_TO_SUSPEND 0.1 //Ratio of air that must move to/from a tile to reset group processing +#define MINIMUM_AIR_RATIO_TO_MOVE 0.001 //Minimum ratio of air that must move to/from a tile +#define MINIMUM_AIR_TO_SUSPEND (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_SUSPEND) //Minimum amount of air that has to move before a group processing can be suspended +#define MINIMUM_MOLES_DELTA_TO_MOVE (MOLES_CELLSTANDARD*MINIMUM_AIR_RATIO_TO_MOVE) //Either this must be active +#define MINIMUM_TEMPERATURE_TO_MOVE (T20C+100) //or this (or both, obviously) +#define MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND 4 //Minimum temperature difference before group processing is suspended +#define MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER 0.5 //Minimum temperature difference before the gas temperatures are just set to be equal +#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 +#define WINDOW_HEAT_TRANSFER_COEFFICIENT 0.1 //a hack for now +#define HEAT_CAPACITY_VACUUM 7000 //a hack to help make vacuums "cold", sacrificing realism for gameplay + +//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 + +//GASES +#define MIN_TOXIC_GAS_DAMAGE 1 +#define MAX_TOXIC_GAS_DAMAGE 10 +#define MOLES_GAS_VISIBLE 0.25 //Moles in a standard cell after which gases are visible +#define FACTOR_GAS_VISIBLE_MAX 20 //moles_visible * FACTOR_GAS_VISIBLE_MAX = Moles after which gas is at maximum visibility +#define MOLES_GAS_VISIBLE_STEP 0.25 //Mole step for alpha updates. This means alpha can update at 0.25, 0.5, 0.75 and so on + +//REACTIONS +//return values for reactions (bitflags) +#define NO_REACTION 0 +#define REACTING 1 +#define STOP_REACTIONS 2 + +// Pressure limits. +#define HAZARD_HIGH_PRESSURE 550 //This determins at what pressure the ultra-high pressure red icon is displayed. (This one is set as a constant) +#define WARNING_HIGH_PRESSURE 325 //This determins when the orange pressure icon is displayed (it is 0.7 * HAZARD_HIGH_PRESSURE) +#define WARNING_LOW_PRESSURE 50 //This is when the gray low pressure icon is displayed. (it is 2.5 * HAZARD_LOW_PRESSURE) +#define HAZARD_LOW_PRESSURE 20 //This is when the black ultra-low pressure icon is displayed. (This one is set as a constant) + +#define TEMPERATURE_DAMAGE_COEFFICIENT 1.5 //This is used in handle_temperature_damage() for humans, and in reagents that affect body temperature. Temperature damage is multiplied by this amount. + +#define BODYTEMP_NORMAL 310.15 //The natural temperature for a body +#define BODYTEMP_AUTORECOVERY_DIVISOR 11 //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_MINIMUM 12 //Minimum amount of kelvin moved toward 310K per tick. So long as abs(310.15 - bodytemp) is more than 50. +#define BODYTEMP_COLD_DIVISOR 6 //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_HEAT_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_COOLING_MAX -100 //The maximum number of degrees that your body can cool in 1 tick, due to the environment, when in a cold area. +#define BODYTEMP_HEATING_MAX 30 //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_HEAT_DAMAGE_LIMIT (BODYTEMP_NORMAL + 20) // The limit the human body can take before it starts taking damage from heat. //CITADEL EDIT to 20 +#define BODYTEMP_COLD_DAMAGE_LIMIT (BODYTEMP_NORMAL - 50) // The limit the human body can take before it starts taking damage from coldness. + + +#define SPACE_HELM_MIN_TEMP_PROTECT 2.0 //what min_cold_protection_temperature is set to for space-helmet quality headwear. MUST NOT BE 0. +#define SPACE_HELM_MAX_TEMP_PROTECT 1500 //Thermal insulation works both ways /Malkevin +#define SPACE_SUIT_MIN_TEMP_PROTECT 2.0 //what min_cold_protection_temperature is set to for space-suit quality jumpsuits or suits. MUST NOT BE 0. +#define SPACE_SUIT_MAX_TEMP_PROTECT 1500 + +#define FIRE_SUIT_MIN_TEMP_PROTECT 60 //Cold protection for firesuits +#define FIRE_SUIT_MAX_TEMP_PROTECT 30000 //what max_heat_protection_temperature is set to for firesuit quality suits. MUST NOT BE 0. +#define FIRE_HELM_MIN_TEMP_PROTECT 60 //Cold protection for fire helmets +#define FIRE_HELM_MAX_TEMP_PROTECT 30000 //for fire helmet quality items (red and white hardhats) + +#define FIRE_IMMUNITY_MAX_TEMP_PROTECT 35000 //what max_heat_protection_temperature is set to for firesuit quality suits and helmets. MUST NOT BE 0. + +#define HELMET_MIN_TEMP_PROTECT 160 //For normal helmets +#define HELMET_MAX_TEMP_PROTECT 600 //For normal helmets +#define ARMOR_MIN_TEMP_PROTECT 160 //For armor +#define ARMOR_MAX_TEMP_PROTECT 600 //For armor + +#define GLOVES_MIN_TEMP_PROTECT 2.0 //For some gloves (black and) +#define GLOVES_MAX_TEMP_PROTECT 1500 //For some gloves +#define SHOES_MIN_TEMP_PROTECT 2.0 //For gloves +#define SHOES_MAX_TEMP_PROTECT 1500 //For gloves + +#define PRESSURE_DAMAGE_COEFFICIENT 4 //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 MAX_HIGH_PRESSURE_DAMAGE 16 // CITADEL CHANGES Max to 16, low to 8. +#define LOW_PRESSURE_DAMAGE 8 //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 COLD_SLOWDOWN_FACTOR 20 //Humans are slowed by the difference between bodytemp and BODYTEMP_COLD_DAMAGE_LIMIT divided by this + +//PIPES +//Atmos pipe limits +#define MAX_OUTPUT_PRESSURE 4500 // (kPa) What pressure pumps and powered equipment max out at. +#define MAX_TRANSFER_RATE 200 // (L/s) Maximum speed powered equipment can work at. + +//used for device_type vars +#define UNARY 1 +#define BINARY 2 +#define TRINARY 3 +#define QUATERNARY 4 + +//TANKS +#define TANK_MELT_TEMPERATURE 1000000 //temperature in kelvins at which a tank will start to melt +#define TANK_LEAK_PRESSURE (30.*ONE_ATMOSPHERE) //Tank starts leaking +#define TANK_RUPTURE_PRESSURE (35.*ONE_ATMOSPHERE) //Tank spills all contents into atmosphere +#define TANK_FRAGMENT_PRESSURE (40.*ONE_ATMOSPHERE) //Boom 3x3 base explosion +#define TANK_FRAGMENT_SCALE (6.*ONE_ATMOSPHERE) //+1 for each SCALE kPa aboe threshold +#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 +#define ATMOS_PASS_PROC -1 //ask CanAtmosPass() +#define ATMOS_PASS_DENSITY -2 //just check density + +#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 +#define OPENTURF_DEFAULT_ATMOS "o2=22;n2=82;TEMP=293.15" //the default air mix that open turfs spawn +#define TCOMMS_ATMOS "n2=100;TEMP=80" //-193,15°C telecommunications. also used for xenobiology slime killrooms +#define AIRLESS_ATMOS "TEMP=2.7" //space +#define FROZEN_ATMOS "o2=22;n2=82;TEMP=180" //-93.15°C snow and ice turfs +#define BURNMIX_ATMOS "o2=2500;plasma=5000;TEMP=370" //used in the holodeck burn test program + +//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 +#define LAVALAND_EQUIPMENT_EFFECT_PRESSURE 50 //what pressure you have to be under to increase the effect of equipment meant for lavaland +#define LAVALAND_DEFAULT_ATMOS "o2=14;n2=23;TEMP=300" + +//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 + +#define PIPING_ALL_LAYER (1<<0) //intended to connect with all layers, check for all instead of just one. +#define PIPING_ONE_PER_TURF (1<<1) //can only be built if nothing else with this flag is on the tile already. +#define PIPING_DEFAULT_LAYER_ONLY (1<<2) //can only exist at PIPING_LAYER_DEFAULT +#define PIPING_CARDINAL_AUTONORMALIZE (1<<3) //north/south east/west doesn't matter, auto normalize on build. + +//HELPERS +#define THERMAL_ENERGY(gas) (gas.temperature * gas.heat_capacity()) +#define QUANTIZE(variable) (round(variable,0.0000001))/*I feel the need to document what happens here. Basically this is used to catch most rounding errors, however it's previous value made it so that + once gases got hot enough, most procedures wouldnt occur due to the fact that the mole counts would get rounded away. Thus, we lowered it a few orders of magnititude */ + +//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];\ + } + +#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 + +//Unomos - So for whatever reason, garbage collection actually drastically decreases the cost of atmos later in the round. Turning this into a define yields massively improved performance. +#define GAS_GARBAGE_COLLECT(GASGASGAS)\ + var/list/CACHE_GAS = GASGASGAS;\ + for(var/id in CACHE_GAS){\ + if(QUANTIZE(CACHE_GAS[id]) <= 0)\ + CACHE_GAS -= id;\ + } + +#define ARCHIVE_TEMPERATURE(gas) gas.temperature_archived = gas.temperature + +GLOBAL_LIST_INIT(pipe_paint_colors, 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) +)) diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 7b8c5ff6ae..bf2646d03e 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -1,199 +1,199 @@ -/*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 "tox" -#define OXY "oxy" -#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 EFFECT_STUN "stun" -#define EFFECT_KNOCKDOWN "knockdown" -#define EFFECT_UNCONSCIOUS "unconscious" -#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 - -//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 - -//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 - -//slowdown when in softcrit -#define SOFTCRIT_ADD_SLOWDOWN 6 - -//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_DISARM "disarm" -#define ATTACK_EFFECT_ASS_SLAP "ass_slap" -#define ATTACK_EFFECT_FACE_SLAP "face_slap" -#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 -//for the shove slowdown, see __DEFINES/movespeed_modification.dm -#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 150 //A Time in ticks, total removal time = (this/item.w_class) - -//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 -//E-gun self-recharge values -#define EGUN_NO_SELFCHARGE 0 -#define EGUN_SELFCHARGE 1 -#define EGUN_SELFCHARGE_BORG 2 - -//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 - -#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 - -//items total mass, used to calculate their attacks' stamina costs. If not defined, the cost will be (w_class * 1.25) -#define TOTAL_MASS_TINY_ITEM 1.25 -#define TOTAL_MASS_SMALL_ITEM 2.5 -#define TOTAL_MASS_NORMAL_ITEM 3.75 -#define TOTAL_MASS_BULKY_ITEM 5 -#define TOTAL_MASS_HUGE_ITEM 6.25 -#define TOTAL_MASS_GIGANTIC_ITEM 7.5 - -#define TOTAL_MASS_HAND_REPLACEMENT 5 //standard punching stamina cost. most hand replacements are huge items anyway. -#define TOTAL_MASS_MEDIEVAL_WEAPON 3.6 //very, very generic average sword/warpick/etc. weight in pounds. -#define TOTAL_MASS_TOY_SWORD 1.5 +/*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 "tox" +#define OXY "oxy" +#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 EFFECT_STUN "stun" +#define EFFECT_KNOCKDOWN "knockdown" +#define EFFECT_UNCONSCIOUS "unconscious" +#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 + +//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 + +//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 + +//slowdown when in softcrit +#define SOFTCRIT_ADD_SLOWDOWN 6 + +//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_DISARM "disarm" +#define ATTACK_EFFECT_ASS_SLAP "ass_slap" +#define ATTACK_EFFECT_FACE_SLAP "face_slap" +#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 +//for the shove slowdown, see __DEFINES/movespeed_modification.dm +#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 150 //A Time in ticks, total removal time = (this/item.w_class) + +//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 +//E-gun self-recharge values +#define EGUN_NO_SELFCHARGE 0 +#define EGUN_SELFCHARGE 1 +#define EGUN_SELFCHARGE_BORG 2 + +//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 + +#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 + +//items total mass, used to calculate their attacks' stamina costs. If not defined, the cost will be (w_class * 1.25) +#define TOTAL_MASS_TINY_ITEM 1.25 +#define TOTAL_MASS_SMALL_ITEM 2.5 +#define TOTAL_MASS_NORMAL_ITEM 3.75 +#define TOTAL_MASS_BULKY_ITEM 5 +#define TOTAL_MASS_HUGE_ITEM 6.25 +#define TOTAL_MASS_GIGANTIC_ITEM 7.5 + +#define TOTAL_MASS_HAND_REPLACEMENT 5 //standard punching stamina cost. most hand replacements are huge items anyway. +#define TOTAL_MASS_MEDIEVAL_WEAPON 3.6 //very, very generic average sword/warpick/etc. weight in pounds. +#define TOTAL_MASS_TOY_SWORD 1.5 diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index b0cc9030cb..3c72454af6 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -1,348 +1,348 @@ -#define SEND_SIGNAL(target, sigtype, arguments...) ( !target.comp_lookup || !target.comp_lookup[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) ) - -#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) ) - -#define COMPONENT_INCOMPATIBLE 1 -#define COMPONENT_NOTRANSFER 2 - -#define ELEMENT_INCOMPATIBLE 1 // Return value to cancel attaching - -// /datum/element flags -/// Causes the detach proc to be called when the host object is being deleted -#define ELEMENT_DETACH (1 << 0) -/** - * Only elements created with the same arguments given after `id_arg_index` share an element instance - * The arguments are the same when the text and number values are the same and all other values have the same ref - */ -#define ELEMENT_BESPOKE (1 << 1) - -// How multiple components of the exact same type are handled in the same datum - -#define COMPONENT_DUPE_HIGHLANDER 0 //old component is deleted (default) -#define COMPONENT_DUPE_ALLOWED 1 //duplicates allowed -#define COMPONENT_DUPE_UNIQUE 2 //new component is deleted -#define COMPONENT_DUPE_UNIQUE_PASSARGS 4 //old component is given the initialization args of the new - -// All signals. Format: -// When the signal is called: (signal arguments) -// All signals send the source datum of the signal as the first argument - -// global signals -// These are signals which can be listened to by any component on any parent -// start global signals with "!", this used to be necessary but now it's just a formatting choice -#define COMSIG_GLOB_NEW_Z "!new_z" //from base of datum/controller/subsystem/mapping/proc/add_new_zlevel(): (list/args) -#define COMSIG_GLOB_VAR_EDIT "!var_edit" //called after a successful var edit somewhere in the world: (list/args) -#define COMSIG_GLOB_LIVING_SAY_SPECIAL "!say_special" //global living say plug - use sparingly: (mob/speaker , message) -////////////////////////////////////////////////////////////////// - -// /datum signals -#define COMSIG_COMPONENT_ADDED "component_added" //sent to the new datum parent when a component is added to them: (/datum/component) -#define COMSIG_COMPONENT_REMOVING "component_removing" //sent to the datum parent before a component is removed from them because of RemoveComponent: (/datum/component) -#define COMSIG_COMPONENT_UNREGISTER_PARENT "component_unregister_parent" //sent to the component itself when unregistered from a parent -#define COMSIG_COMPONENT_REGISTER_PARENT "component_register_parent" //sent to the component itself when registered to a parent -#define COMSIG_PARENT_PREQDELETED "parent_preqdeleted" //before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation -#define COMSIG_PARENT_QDELETING "parent_qdeleting" //just before a datum's Destroy() is called: (force), at this point none of the other components chose to interrupt qdel and Destroy will be called - -// /atom signals -#define COMSIG_PARENT_ATTACKBY "atom_attackby" //from base of atom/attackby(): (/obj/item, /mob/living, params) - #define COMPONENT_NO_AFTERATTACK 1 //Return this in response if you don't want afterattack to be called -#define COMSIG_ATOM_HULK_ATTACK "hulk_attack" //from base of atom/attack_hulk(): (/mob/living/carbon/human) -#define COMSIG_PARENT_EXAMINE "atom_examine" //from base of atom/examine(): (/mob) -#define COMSIG_ATOM_GET_EXAMINE_NAME "atom_examine_name" //from base of atom/get_examine_name(): (/mob, list/overrides) - //Positions for overrides list - #define EXAMINE_POSITION_ARTICLE 1 - #define EXAMINE_POSITION_BEFORE 2 - //End positions - #define COMPONENT_EXNAME_CHANGED 1 -#define COMSIG_ATOM_UPDATE_ICON "atom_update_icon" //from base of atom/update_icon(): () - #define COMSIG_ATOM_NO_UPDATE_ICON_STATE 1 - #define COMSIG_ATOM_NO_UPDATE_OVERLAYS 2 -#define COMSIG_ATOM_UPDATE_OVERLAYS "atom_update_overlays" //from base of atom/update_overlays(): (list/new_overlays) -#define COMSIG_ATOM_ENTERED "atom_entered" //from base of atom/Entered(): (atom/movable/entering, /atom) -#define COMSIG_ATOM_EXIT "atom_exit" //from base of atom/Exit(): (/atom/movable/exiting, /atom/newloc) - #define COMPONENT_ATOM_BLOCK_EXIT 1 -#define COMSIG_ATOM_EXITED "atom_exited" //from base of atom/Exited(): (atom/movable/exiting, atom/newloc) -#define COMSIG_ATOM_EX_ACT "atom_ex_act" //from base of atom/ex_act(): (severity, target) -#define COMSIG_ATOM_EMP_ACT "atom_emp_act" //from base of atom/emp_act(): (severity) -#define COMSIG_ATOM_FIRE_ACT "atom_fire_act" //from base of atom/fire_act(): (exposed_temperature, exposed_volume) -#define COMSIG_ATOM_BULLET_ACT "atom_bullet_act" //from base of atom/bullet_act(): (/obj/item/projectile, def_zone) -#define COMSIG_ATOM_BLOB_ACT "atom_blob_act" //from base of atom/blob_act(): (/obj/structure/blob) -#define COMSIG_ATOM_ACID_ACT "atom_acid_act" //from base of atom/acid_act(): (acidpwr, acid_volume) -#define COMSIG_ATOM_EMAG_ACT "atom_emag_act" //from base of atom/emag_act(): () -#define COMSIG_ATOM_RAD_ACT "atom_rad_act" //from base of atom/rad_act(intensity) -#define COMSIG_ATOM_NARSIE_ACT "atom_narsie_act" //from base of atom/narsie_act(): () -#define COMSIG_ATOM_RATVAR_ACT "atom_ratvar_act" //from base of atom/ratvar_act(): () -#define COMSIG_ATOM_RCD_ACT "atom_rcd_act" //from base of atom/rcd_act(): (/mob, /obj/item/construction/rcd, passed_mode) -#define COMSIG_ATOM_SING_PULL "atom_sing_pull" //from base of atom/singularity_pull(): (S, current_size) -#define COMSIG_ATOM_SET_LIGHT "atom_set_light" //from base of atom/set_light(): (l_range, l_power, l_color) -#define COMSIG_ATOM_DIR_CHANGE "atom_dir_change" //from base of atom/setDir(): (old_dir, new_dir) -#define COMSIG_ATOM_CONTENTS_DEL "atom_contents_del" //from base of atom/handle_atom_del(): (atom/deleted) -#define COMSIG_ATOM_HAS_GRAVITY "atom_has_gravity" //from base of atom/has_gravity(): (turf/location, list/forced_gravities) -#define COMSIG_ATOM_RAD_PROBE "atom_rad_probe" //from proc/get_rad_contents(): () - #define COMPONENT_BLOCK_RADIATION 1 -#define COMSIG_ATOM_RAD_CONTAMINATING "atom_rad_contam" //from base of datum/radiation_wave/radiate(): (strength) - #define COMPONENT_BLOCK_CONTAMINATION 1 -#define COMSIG_ATOM_RAD_WAVE_PASSING "atom_rad_wave_pass" //from base of datum/radiation_wave/check_obstructions(): (datum/radiation_wave, width) - #define COMPONENT_RAD_WAVE_HANDLED 1 -#define COMSIG_ATOM_CANREACH "atom_can_reach" //from internal loop in atom/movable/proc/CanReach(): (list/next) - #define COMPONENT_BLOCK_REACH 1 -#define COMSIG_ATOM_SCREWDRIVER_ACT "atom_screwdriver_act" //from base of atom/screwdriver_act(): (mob/living/user, obj/item/I) -#define COMSIG_ATOM_INTERCEPT_TELEPORT "intercept_teleport" //called when teleporting into a protected turf: (channel, turf/origin, turf/destination) - #define COMPONENT_BLOCK_TELEPORT 1 -#define COMSIG_ATOM_HEARER_IN_VIEW "atom_hearer_in_view" //called when an atom with HEAR_1 is added to the hearers on /proc/get_hearers_in_view(): (list/processing_list, list/hearers) -///////////////// -#define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost" //from base of atom/attack_ghost(): (mob/dead/observer/ghost) -#define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand" //from base of atom/attack_hand(): (mob/user) -#define COMSIG_ATOM_ATTACK_PAW "atom_attack_paw" //from base of atom/attack_paw(): (mob/user) - #define COMPONENT_NO_ATTACK_HAND 1 //works on all 3. -//This signal return value bitflags can be found in __DEFINES/misc.dm -#define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact" //called for each movable in a turf contents on /turf/zImpact(): (atom/movable/A, levels) - - -///////////////// - -#define COMSIG_ENTER_AREA "enter_area" //from base of area/Entered(): (/area) -#define COMSIG_EXIT_AREA "exit_area" //from base of area/Exited(): (/area) - -#define COMSIG_CLICK "atom_click" //from base of atom/Click(): (location, control, params, mob/user) -#define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob) -#define COMSIG_CLICK_CTRL "ctrl_click" //from base of atom/CtrlClickOn(): (/mob) -#define COMSIG_CLICK_ALT "alt_click" //from base of atom/AltClick(): (/mob) -#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click" //from base of atom/CtrlShiftClick(/mob) -#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto" //from base of atom/MouseDrop(): (/atom/over, /mob/user) - #define COMPONENT_NO_MOUSEDROP 1 -#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto" //from base of atom/MouseDrop_T: (/atom/from, /mob/user) - -// /area signals -#define COMSIG_AREA_ENTERED "area_entered" //from base of area/Entered(): (atom/movable/M) -#define COMSIG_AREA_EXITED "area_exited" //from base of area/Exited(): (atom/movable/M) - -// /turf signals -#define COMSIG_TURF_CHANGE "turf_change" //from base of turf/ChangeTurf(): (path, list/new_baseturfs, flags, list/transferring_comps) -#define COMSIG_TURF_HAS_GRAVITY "turf_has_gravity" //from base of atom/has_gravity(): (atom/asker, list/forced_gravities) -#define COMSIG_TURF_MULTIZ_NEW "turf_multiz_new" //from base of turf/New(): (turf/source, direction) - -// /atom/movable signals -#define COMSIG_MOVABLE_MOVED "movable_moved" //from base of atom/movable/Moved(): (/atom, dir) -#define COMSIG_MOVABLE_CROSS "movable_cross" //from base of atom/movable/Cross(): (/atom/movable) -#define COMSIG_MOVABLE_CROSSED "movable_crossed" //from base of atom/movable/Crossed(): (/atom/movable) -#define COMSIG_MOVABLE_UNCROSS "movable_uncross" //from base of atom/movable/Uncross(): (/atom/movable) - #define COMPONENT_MOVABLE_BLOCK_UNCROSS 1 -#define COMSIG_MOVABLE_UNCROSSED "movable_uncrossed" //from base of atom/movable/Uncrossed(): (/atom/movable) -#define COMSIG_MOVABLE_BUMP "movable_bump" //from base of atom/movable/Bump(): (/atom) -#define COMSIG_MOVABLE_IMPACT "movable_impact" //from base of atom/movable/throw_impact(): (/atom/hit_atom, /datum/thrownthing/throwingdatum) -#define COMSIG_MOVABLE_IMPACT_ZONE "item_impact_zone" //from base of mob/living/hitby(): (mob/living/target, hit_zone) -#define COMSIG_MOVABLE_BUCKLE "buckle" //from base of atom/movable/buckle_mob(): (mob, force) -#define COMSIG_MOVABLE_UNBUCKLE "unbuckle" //from base of atom/movable/unbuckle_mob(): (mob, force) -#define COMSIG_MOVABLE_PRE_THROW "movable_pre_throw" //from base of atom/movable/throw_at(): (list/args) - #define COMPONENT_CANCEL_THROW 1 -#define COMSIG_MOVABLE_POST_THROW "movable_post_throw" //from base of atom/movable/throw_at(): (datum/thrownthing, spin) -#define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" //from base of atom/movable/onTransitZ(): (old_z, new_z) -#define COMSIG_MOVABLE_SECLUDED_LOCATION "movable_secluded" //called when the movable is placed in an unaccessible area, used for stationloving: () -#define COMSIG_MOVABLE_HEAR "movable_hear" //from base of atom/movable/Hear(): (message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) - #define HEARING_MESSAGE 1 - #define HEARING_SPEAKER 2 -// #define HEARING_LANGUAGE 3 - #define HEARING_RAW_MESSAGE 4 - /* #define HEARING_RADIO_FREQ 5 - #define HEARING_SPANS 6 - #define HEARING_MESSAGE_MODE 7 - #define HEARING_SOURCE 8*/ -#define COMSIG_MOVABLE_DISPOSING "movable_disposing" //called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source) -#define COMSIG_MOVABLE_TELEPORTED "movable_teleported" //from base of do_teleport(): (channel, turf/origin, turf/destination) - -// /mind signals -#define COMSIG_PRE_MIND_TRANSFER "pre_mind_transfer" //from base of mind/transfer_to() before it's done: (new_character, old_character) - #define COMPONENT_STOP_MIND_TRANSFER 1 //stops the mind transfer from happening. -#define COMSIG_MIND_TRANSFER "mind_transfer" //from base of mind/transfer_to() when it's done: (new_character, old_character) - -// /mob signals -#define COMSIG_MOB_EXAMINATE "mob_examinate" //from base of /mob/verb/examinate(): (atom/A) - #define COMPONENT_ALLOW_EXAMINE 1 -#define COMSIG_MOB_DEATH "mob_death" //from base of mob/death(): (gibbed) - #define COMPONENT_BLOCK_DEATH_BROADCAST 1 //stops the death from being broadcasted in deadchat. -#define COMSIG_MOB_GHOSTIZE "mob_ghostize" //from base of mob/Ghostize(): (can_reenter_corpse, special, penalize) - #define COMPONENT_BLOCK_GHOSTING 1 -#define COMSIG_MOB_ALLOWED "mob_allowed" //from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj -#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" //from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources) - #define COMPONENT_BLOCK_MAGIC 1 -#define COMSIG_MOB_HUD_CREATED "mob_hud_created" //from base of mob/create_mob_hud(): () -#define COMSIG_MOB_ATTACK_HAND "mob_attack_hand" //from base of -#define COMSIG_MOB_ITEM_ATTACK "mob_item_attack" //from base of /obj/item/attack(): (mob/M, mob/user) - #define COMPONENT_ITEM_NO_ATTACK 1 -#define COMSIG_MOB_ITEM_AFTERATTACK "mob_item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters) -#define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged" //from base of mob/RangedAttack(): (atom/A, params) -#define COMSIG_MOB_THROW "mob_throw" //from base of /mob/throw_item(): (atom/target) -#define COMSIG_MOB_KEY_CHANGE "mob_key_change" //from base of /mob/transfer_ckey(): (new_character, old_character) -#define COMSIG_MOB_PRE_PLAYER_CHANGE "mob_pre_player_change" //sent to the target mob from base of /mob/transfer_ckey() and /mind/transfer_to(): (our_character, their_character) -// #define COMPONENT_STOP_MIND_TRANSFER 1 -#define COMSIG_MOB_UPDATE_SIGHT "mob_update_sight" //from base of /mob/update_sight(): () -#define COMSIG_MOB_ON_NEW_MIND "mob_on_new_mind" //called when a new mind is assigned to a mob: () -#define COMSIG_MOB_SAY "mob_say" // from /mob/living/say(): (proc args list) - #define COMPONENT_UPPERCASE_SPEECH 1 - // used to access COMSIG_MOB_SAY argslist - #define SPEECH_MESSAGE 1 - // #define SPEECH_BUBBLE_TYPE 2 - #define SPEECH_SPANS 3 - /* #define SPEECH_SANITIZE 4 - #define SPEECH_LANGUAGE 5 - #define SPEECH_IGNORE_SPAM 6 - #define SPEECH_FORCED 7 */ - -// /mob/living signals -#define COMSIG_LIVING_FULLY_HEAL "living_fully_healed" //from base of /mob/living/fully_heal(): (admin_revive) -#define COMSIG_LIVING_REGENERATE_LIMBS "living_regenerate_limbs" //from base of /mob/living/regenerate_limbs(): (noheal, excluded_limbs) -#define COMSIG_LIVING_RESIST "living_resist" //from base of mob/living/resist() (/mob/living) -#define COMSIG_LIVING_IGNITED "living_ignite" //from base of mob/living/IgniteMob() (/mob/living) -#define COMSIG_LIVING_EXTINGUISHED "living_extinguished" //from base of mob/living/ExtinguishMob() (/mob/living) -#define COMSIG_LIVING_ELECTROCUTE_ACT "living_electrocute_act" //from base of mob/living/electrocute_act(): (shock_damage) -#define COMSIG_LIVING_MINOR_SHOCK "living_minor_shock" //sent by stuff like stunbatons and tasers: () -#define COMSIG_LIVING_GUN_PROCESS_FIRE "living_gun_process_fire" //from base of /obj/item/gun/proc/process_fire(): (atom/target, params, zone_override) - -// /mob/living/carbon signals -#define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" //from base of mob/living/carbon/soundbang_act(): (list(intensity)) - -// /mob/living/simple_animal/hostile signals -#define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget" - #define COMPONENT_HOSTILE_NO_ATTACK 1 - -// /obj signals -#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" //from base of obj/deconstruct(): (disassembled) -#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag) -#define COMSIG_OBJ_SETANCHORED "obj_setanchored" //called in /obj/structure/setAnchored(): (value) - -// /machinery signals -#define COMSIG_MACHINE_EJECT_OCCUPANT "eject_occupant" //from base of obj/machinery/dropContents() (occupant) - -// /obj/item signals -#define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user) -#define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (/mob) - #define COMPONENT_NO_INTERACT 1 -#define COMSIG_ITEM_ATTACK_OBJ "item_attack_obj" //from base of obj/item/attack_obj(): (/obj, /mob) - #define COMPONENT_NO_ATTACK_OBJ 1 -#define COMSIG_ITEM_PRE_ATTACK "item_pre_attack" //from base of obj/item/pre_attack(): (atom/target, mob/user, params) - #define COMPONENT_NO_ATTACK 1 -#define COMSIG_ITEM_AFTERATTACK "item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, params) -#define COMSIG_ITEM_EQUIPPED "item_equip" //from base of obj/item/equipped(): (/mob/equipper, slot) -#define COMSIG_ITEM_DROPPED "item_drop" //from base of obj/item/dropped(): (mob/user) -#define COMSIG_ITEM_PICKUP "item_pickup" //from base of obj/item/pickup(): (/mob/taker) -#define COMSIG_ITEM_ATTACK_ZONE "item_attack_zone" //from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone) -#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" //return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/targeted/lichdom/cast(): (mob/user) -#define COMSIG_ITEM_HIT_REACT "item_hit_react" //from base of obj/item/hit_reaction(): (list/args) - -// /obj/item/clothing signals -#define COMSIG_SHOES_STEP_ACTION "shoes_step_action" //from base of obj/item/clothing/shoes/proc/step_action(): () - -// /obj/item/implant signals -#define COMSIG_IMPLANT_ACTIVATED "implant_activated" //from base of /obj/item/implant/proc/activate(): () -#define COMSIG_IMPLANT_IMPLANTING "implant_implanting" //from base of /obj/item/implant/proc/implant(): (list/args) - #define COMPONENT_STOP_IMPLANTING 1 -#define COMSIG_IMPLANT_OTHER "implant_other" //called on already installed implants when a new one is being added in /obj/item/implant/proc/implant(): (list/args, obj/item/implant/new_implant) - //#define COMPONENT_STOP_IMPLANTING 1 //The name makes sense for both - #define COMPONENT_DELETE_NEW_IMPLANT 2 - #define COMPONENT_DELETE_OLD_IMPLANT 4 -#define COMSIG_IMPLANT_EXISTING_UPLINK "implant_uplink_exists" //called on implants being implanted into someone with an uplink implant: (datum/component/uplink) - //This uses all return values of COMSIG_IMPLANT_OTHER -#define COMSIG_IMPLANT_REMOVING "implant_removing" //from base of /obj/item/implant/proc/removed() (list/args) - -// /obj/item/pda signals -#define COMSIG_PDA_CHANGE_RINGTONE "pda_change_ringtone" //called on pda when the user changes the ringtone: (mob/living/user, new_ringtone) - #define COMPONENT_STOP_RINGTONE_CHANGE 1 - -// /obj/item/radio signals -#define COMSIG_RADIO_NEW_FREQUENCY "radio_new_frequency" //called from base of /obj/item/radio/proc/set_frequency(): (list/args) - -// /obj/item/pen signals -#define COMSIG_PEN_ROTATED "pen_rotated" //called after rotation in /obj/item/pen/attack_self(): (rotation, mob/living/carbon/user) - -// /obj/item/projectile signals (sent to the firer) -#define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" // from base of /obj/item/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle) -#define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire" // from base of /obj/item/projectile/proc/fire(): (obj/item/projectile, atom/original_target) - -// /mob/living/carbon/human signals -#define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target) -#define COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY "human_melee_unarmed_attackby" //from mob/living/carbon/human/UnarmedAttack(): (mob/living/carbon/human/attacker) -#define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" //Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted) - -// /datum/species signals -#define COMSIG_SPECIES_GAIN "species_gain" //from datum/species/on_species_gain(): (datum/species/new_species, datum/species/old_species) -#define COMSIG_SPECIES_LOSS "species_loss" //from datum/species/on_species_loss(): (datum/species/lost_species) - -/*******Component Specific Signals*******/ -//Janitor -#define COMSIG_TURF_IS_WET "check_turf_wet" //(): Returns bitflags of wet values. -#define COMSIG_TURF_MAKE_DRY "make_turf_try" //(max_strength, immediate, duration_decrease = INFINITY): Returns bool. -#define COMSIG_COMPONENT_CLEAN_ACT "clean_act" //called on an object to clean it of cleanables. Usualy with soap: (num/strength) - -//Blood color -#define COMSIG_BLOOD_COLOR "blood_DNA_to_color" //RGB blood stuff -//Food -#define COMSIG_FOOD_EATEN "food_eaten" //from base of obj/item/reagent_containers/food/snacks/attack(): (mob/living/eater, mob/feeder) - -//Gibs -#define COMSIG_GIBS_STREAK "gibs_streak" // from base of /obj/effect/decal/cleanable/blood/gibs/streak(): (list/directions, list/diseases) - -//Mood -#define COMSIG_ADD_MOOD_EVENT "add_mood" //Called when you send a mood event from anywhere in the code. -#define COMSIG_CLEAR_MOOD_EVENT "clear_mood" //Called when you clear a mood event from anywhere in the code. -#define COMSIG_MODIFY_SANITY "modify_sanity" //Called when you want to increase or decrease sanity from anywhere in the code. - -//NTnet -#define COMSIG_COMPONENT_NTNET_RECEIVE "ntnet_receive" //called on an object by its NTNET connection component on receive. (sending_id(number), sending_netname(text), data(datum/netdata)) - -//Nanites -#define COMSIG_HAS_NANITES "has_nanites" //() returns TRUE if nanites are found -#define COMSIG_NANITE_GET_PROGRAMS "nanite_get_programs" //(list/nanite_programs) - makes the input list a copy the nanites' program list -#define COMSIG_NANITE_SET_VOLUME "nanite_set_volume" //(amount) Sets current nanite volume to the given amount -#define COMSIG_NANITE_ADJUST_VOLUME "nanite_adjust" //(amount) Adjusts nanite volume by the given amount -#define COMSIG_NANITE_SET_MAX_VOLUME "nanite_set_max_volume" //(amount) Sets maximum nanite volume to the given amount -#define COMSIG_NANITE_SET_CLOUD "nanite_set_cloud" //(amount(0-100)) Sets cloud ID to the given amount -#define COMSIG_NANITE_SET_SAFETY "nanite_set_safety" //(amount) Sets safety threshold to the given amount -#define COMSIG_NANITE_SET_REGEN "nanite_set_regen" //(amount) Sets regeneration rate to the given amount -#define COMSIG_NANITE_SIGNAL "nanite_signal" //(code(1-9999)) Called when sending a nanite signal to a mob. -#define COMSIG_NANITE_SCAN "nanite_scan" //(mob/user, full_scan) - sends to chat a scan of the nanites to the user, returns TRUE if nanites are detected -#define COMSIG_NANITE_UI_DATA "nanite_ui_data" //(list/data, scan_level) - adds nanite data to the given data list - made for ui_data procs -#define COMSIG_NANITE_ADD_PROGRAM "nanite_add_program" //(datum/nanite_program/new_program, datum/nanite_program/source_program) Called when adding a program to a nanite component - #define COMPONENT_PROGRAM_INSTALLED 1 //Installation successful - #define COMPONENT_PROGRAM_NOT_INSTALLED 2 //Installation failed, but there are still nanites -#define COMSIG_NANITE_SYNC "nanite_sync" //(datum/component/nanites, full_overwrite, copy_activation) Called to sync the target's nanites to a given nanite component - -// /datum/component/storage signals -#define COMSIG_CONTAINS_STORAGE "is_storage" //() - returns bool. -#define COMSIG_TRY_STORAGE_INSERT "storage_try_insert" //(obj/item/inserting, mob/user, silent, force) - returns bool -#define COMSIG_TRY_STORAGE_SHOW "storage_show_to" //(mob/show_to, force) - returns bool. -#define COMSIG_TRY_STORAGE_HIDE_FROM "storage_hide_from" //(mob/hide_from) - returns bool -#define COMSIG_TRY_STORAGE_HIDE_ALL "storage_hide_all" //returns bool -#define COMSIG_TRY_STORAGE_SET_LOCKSTATE "storage_lock_set_state" //(newstate) -#define COMSIG_IS_STORAGE_LOCKED "storage_get_lockstate" //() - returns bool. MUST CHECK IF STORAGE IS THERE FIRST! -#define COMSIG_TRY_STORAGE_TAKE_TYPE "storage_take_type" //(type, atom/destination, amount = INFINITY, check_adjacent, force, mob/user, list/inserted) - returns bool - type can be a list of types. -#define COMSIG_TRY_STORAGE_FILL_TYPE "storage_fill_type" //(type, amount = INFINITY, force = FALSE) //don't fuck this up. Force will ignore max_items, and amount is normally clamped to max_items. -#define COMSIG_TRY_STORAGE_TAKE "storage_take_obj" //(obj, new_loc, force = FALSE) - returns bool -#define COMSIG_TRY_STORAGE_QUICK_EMPTY "storage_quick_empty" //(loc) - returns bool - if loc is null it will dump at parent location. -#define COMSIG_TRY_STORAGE_RETURN_INVENTORY "storage_return_inventory" //(list/list_to_inject_results_into, recursively_search_inside_storages = TRUE) -#define COMSIG_TRY_STORAGE_CAN_INSERT "storage_can_equip" //(obj/item/insertion_candidate, mob/user, silent) - returns bool - -// /datum/action signals -#define COMSIG_ACTION_TRIGGER "action_trigger" //from base of datum/action/proc/Trigger(): (datum/action) - #define COMPONENT_ACTION_BLOCK_TRIGGER 1 - -/*******Non-Signal Component Related Defines*******/ - -//Redirection component init flags -#define REDIRECT_TRANSFER_WITH_TURF 1 - -//Arch -#define ARCH_PROB "probability" //Probability for each item -#define ARCH_MAXDROP "max_drop_amount" //each item's max drop amount - -//Ouch my toes! -#define CALTROP_BYPASS_SHOES 1 -#define CALTROP_IGNORE_WALKERS 2 - -//Xenobio hotkeys -#define COMSIG_XENO_SLIME_CLICK_CTRL "xeno_slime_click_ctrl" //from slime CtrlClickOn(): (/mob) -#define COMSIG_XENO_SLIME_CLICK_ALT "xeno_slime_click_alt" //from slime AltClickOn(): (/mob) -#define COMSIG_XENO_SLIME_CLICK_SHIFT "xeno_slime_click_shift" //from slime ShiftClickOn(): (/mob) -#define COMSIG_XENO_TURF_CLICK_SHIFT "xeno_turf_click_shift" //from turf ShiftClickOn(): (/mob) -#define COMSIG_XENO_TURF_CLICK_CTRL "xeno_turf_click_alt" //from turf AltClickOn(): (/mob) -#define COMSIG_XENO_MONKEY_CLICK_CTRL "xeno_monkey_click_ctrl" //from monkey CtrlClickOn(): (/mob) +#define SEND_SIGNAL(target, sigtype, arguments...) ( !target.comp_lookup || !target.comp_lookup[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) ) + +#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) ) + +#define COMPONENT_INCOMPATIBLE 1 +#define COMPONENT_NOTRANSFER 2 + +#define ELEMENT_INCOMPATIBLE 1 // Return value to cancel attaching + +// /datum/element flags +/// Causes the detach proc to be called when the host object is being deleted +#define ELEMENT_DETACH (1 << 0) +/** + * Only elements created with the same arguments given after `id_arg_index` share an element instance + * The arguments are the same when the text and number values are the same and all other values have the same ref + */ +#define ELEMENT_BESPOKE (1 << 1) + +// How multiple components of the exact same type are handled in the same datum + +#define COMPONENT_DUPE_HIGHLANDER 0 //old component is deleted (default) +#define COMPONENT_DUPE_ALLOWED 1 //duplicates allowed +#define COMPONENT_DUPE_UNIQUE 2 //new component is deleted +#define COMPONENT_DUPE_UNIQUE_PASSARGS 4 //old component is given the initialization args of the new + +// All signals. Format: +// When the signal is called: (signal arguments) +// All signals send the source datum of the signal as the first argument + +// global signals +// These are signals which can be listened to by any component on any parent +// start global signals with "!", this used to be necessary but now it's just a formatting choice +#define COMSIG_GLOB_NEW_Z "!new_z" //from base of datum/controller/subsystem/mapping/proc/add_new_zlevel(): (list/args) +#define COMSIG_GLOB_VAR_EDIT "!var_edit" //called after a successful var edit somewhere in the world: (list/args) +#define COMSIG_GLOB_LIVING_SAY_SPECIAL "!say_special" //global living say plug - use sparingly: (mob/speaker , message) +////////////////////////////////////////////////////////////////// + +// /datum signals +#define COMSIG_COMPONENT_ADDED "component_added" //sent to the new datum parent when a component is added to them: (/datum/component) +#define COMSIG_COMPONENT_REMOVING "component_removing" //sent to the datum parent before a component is removed from them because of RemoveComponent: (/datum/component) +#define COMSIG_COMPONENT_UNREGISTER_PARENT "component_unregister_parent" //sent to the component itself when unregistered from a parent +#define COMSIG_COMPONENT_REGISTER_PARENT "component_register_parent" //sent to the component itself when registered to a parent +#define COMSIG_PARENT_PREQDELETED "parent_preqdeleted" //before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation +#define COMSIG_PARENT_QDELETING "parent_qdeleting" //just before a datum's Destroy() is called: (force), at this point none of the other components chose to interrupt qdel and Destroy will be called + +// /atom signals +#define COMSIG_PARENT_ATTACKBY "atom_attackby" //from base of atom/attackby(): (/obj/item, /mob/living, params) + #define COMPONENT_NO_AFTERATTACK 1 //Return this in response if you don't want afterattack to be called +#define COMSIG_ATOM_HULK_ATTACK "hulk_attack" //from base of atom/attack_hulk(): (/mob/living/carbon/human) +#define COMSIG_PARENT_EXAMINE "atom_examine" //from base of atom/examine(): (/mob) +#define COMSIG_ATOM_GET_EXAMINE_NAME "atom_examine_name" //from base of atom/get_examine_name(): (/mob, list/overrides) + //Positions for overrides list + #define EXAMINE_POSITION_ARTICLE 1 + #define EXAMINE_POSITION_BEFORE 2 + //End positions + #define COMPONENT_EXNAME_CHANGED 1 +#define COMSIG_ATOM_UPDATE_ICON "atom_update_icon" //from base of atom/update_icon(): () + #define COMSIG_ATOM_NO_UPDATE_ICON_STATE 1 + #define COMSIG_ATOM_NO_UPDATE_OVERLAYS 2 +#define COMSIG_ATOM_UPDATE_OVERLAYS "atom_update_overlays" //from base of atom/update_overlays(): (list/new_overlays) +#define COMSIG_ATOM_ENTERED "atom_entered" //from base of atom/Entered(): (atom/movable/entering, /atom) +#define COMSIG_ATOM_EXIT "atom_exit" //from base of atom/Exit(): (/atom/movable/exiting, /atom/newloc) + #define COMPONENT_ATOM_BLOCK_EXIT 1 +#define COMSIG_ATOM_EXITED "atom_exited" //from base of atom/Exited(): (atom/movable/exiting, atom/newloc) +#define COMSIG_ATOM_EX_ACT "atom_ex_act" //from base of atom/ex_act(): (severity, target) +#define COMSIG_ATOM_EMP_ACT "atom_emp_act" //from base of atom/emp_act(): (severity) +#define COMSIG_ATOM_FIRE_ACT "atom_fire_act" //from base of atom/fire_act(): (exposed_temperature, exposed_volume) +#define COMSIG_ATOM_BULLET_ACT "atom_bullet_act" //from base of atom/bullet_act(): (/obj/item/projectile, def_zone) +#define COMSIG_ATOM_BLOB_ACT "atom_blob_act" //from base of atom/blob_act(): (/obj/structure/blob) +#define COMSIG_ATOM_ACID_ACT "atom_acid_act" //from base of atom/acid_act(): (acidpwr, acid_volume) +#define COMSIG_ATOM_EMAG_ACT "atom_emag_act" //from base of atom/emag_act(): () +#define COMSIG_ATOM_RAD_ACT "atom_rad_act" //from base of atom/rad_act(intensity) +#define COMSIG_ATOM_NARSIE_ACT "atom_narsie_act" //from base of atom/narsie_act(): () +#define COMSIG_ATOM_RATVAR_ACT "atom_ratvar_act" //from base of atom/ratvar_act(): () +#define COMSIG_ATOM_RCD_ACT "atom_rcd_act" //from base of atom/rcd_act(): (/mob, /obj/item/construction/rcd, passed_mode) +#define COMSIG_ATOM_SING_PULL "atom_sing_pull" //from base of atom/singularity_pull(): (S, current_size) +#define COMSIG_ATOM_SET_LIGHT "atom_set_light" //from base of atom/set_light(): (l_range, l_power, l_color) +#define COMSIG_ATOM_DIR_CHANGE "atom_dir_change" //from base of atom/setDir(): (old_dir, new_dir) +#define COMSIG_ATOM_CONTENTS_DEL "atom_contents_del" //from base of atom/handle_atom_del(): (atom/deleted) +#define COMSIG_ATOM_HAS_GRAVITY "atom_has_gravity" //from base of atom/has_gravity(): (turf/location, list/forced_gravities) +#define COMSIG_ATOM_RAD_PROBE "atom_rad_probe" //from proc/get_rad_contents(): () + #define COMPONENT_BLOCK_RADIATION 1 +#define COMSIG_ATOM_RAD_CONTAMINATING "atom_rad_contam" //from base of datum/radiation_wave/radiate(): (strength) + #define COMPONENT_BLOCK_CONTAMINATION 1 +#define COMSIG_ATOM_RAD_WAVE_PASSING "atom_rad_wave_pass" //from base of datum/radiation_wave/check_obstructions(): (datum/radiation_wave, width) + #define COMPONENT_RAD_WAVE_HANDLED 1 +#define COMSIG_ATOM_CANREACH "atom_can_reach" //from internal loop in atom/movable/proc/CanReach(): (list/next) + #define COMPONENT_BLOCK_REACH 1 +#define COMSIG_ATOM_SCREWDRIVER_ACT "atom_screwdriver_act" //from base of atom/screwdriver_act(): (mob/living/user, obj/item/I) +#define COMSIG_ATOM_INTERCEPT_TELEPORT "intercept_teleport" //called when teleporting into a protected turf: (channel, turf/origin, turf/destination) + #define COMPONENT_BLOCK_TELEPORT 1 +#define COMSIG_ATOM_HEARER_IN_VIEW "atom_hearer_in_view" //called when an atom with HEAR_1 is added to the hearers on /proc/get_hearers_in_view(): (list/processing_list, list/hearers) +///////////////// +#define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost" //from base of atom/attack_ghost(): (mob/dead/observer/ghost) +#define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand" //from base of atom/attack_hand(): (mob/user) +#define COMSIG_ATOM_ATTACK_PAW "atom_attack_paw" //from base of atom/attack_paw(): (mob/user) + #define COMPONENT_NO_ATTACK_HAND 1 //works on all 3. +//This signal return value bitflags can be found in __DEFINES/misc.dm +#define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact" //called for each movable in a turf contents on /turf/zImpact(): (atom/movable/A, levels) + + +///////////////// + +#define COMSIG_ENTER_AREA "enter_area" //from base of area/Entered(): (/area) +#define COMSIG_EXIT_AREA "exit_area" //from base of area/Exited(): (/area) + +#define COMSIG_CLICK "atom_click" //from base of atom/Click(): (location, control, params, mob/user) +#define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob) +#define COMSIG_CLICK_CTRL "ctrl_click" //from base of atom/CtrlClickOn(): (/mob) +#define COMSIG_CLICK_ALT "alt_click" //from base of atom/AltClick(): (/mob) +#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click" //from base of atom/CtrlShiftClick(/mob) +#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto" //from base of atom/MouseDrop(): (/atom/over, /mob/user) + #define COMPONENT_NO_MOUSEDROP 1 +#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto" //from base of atom/MouseDrop_T: (/atom/from, /mob/user) + +// /area signals +#define COMSIG_AREA_ENTERED "area_entered" //from base of area/Entered(): (atom/movable/M) +#define COMSIG_AREA_EXITED "area_exited" //from base of area/Exited(): (atom/movable/M) + +// /turf signals +#define COMSIG_TURF_CHANGE "turf_change" //from base of turf/ChangeTurf(): (path, list/new_baseturfs, flags, list/transferring_comps) +#define COMSIG_TURF_HAS_GRAVITY "turf_has_gravity" //from base of atom/has_gravity(): (atom/asker, list/forced_gravities) +#define COMSIG_TURF_MULTIZ_NEW "turf_multiz_new" //from base of turf/New(): (turf/source, direction) + +// /atom/movable signals +#define COMSIG_MOVABLE_MOVED "movable_moved" //from base of atom/movable/Moved(): (/atom, dir) +#define COMSIG_MOVABLE_CROSS "movable_cross" //from base of atom/movable/Cross(): (/atom/movable) +#define COMSIG_MOVABLE_CROSSED "movable_crossed" //from base of atom/movable/Crossed(): (/atom/movable) +#define COMSIG_MOVABLE_UNCROSS "movable_uncross" //from base of atom/movable/Uncross(): (/atom/movable) + #define COMPONENT_MOVABLE_BLOCK_UNCROSS 1 +#define COMSIG_MOVABLE_UNCROSSED "movable_uncrossed" //from base of atom/movable/Uncrossed(): (/atom/movable) +#define COMSIG_MOVABLE_BUMP "movable_bump" //from base of atom/movable/Bump(): (/atom) +#define COMSIG_MOVABLE_IMPACT "movable_impact" //from base of atom/movable/throw_impact(): (/atom/hit_atom, /datum/thrownthing/throwingdatum) +#define COMSIG_MOVABLE_IMPACT_ZONE "item_impact_zone" //from base of mob/living/hitby(): (mob/living/target, hit_zone) +#define COMSIG_MOVABLE_BUCKLE "buckle" //from base of atom/movable/buckle_mob(): (mob, force) +#define COMSIG_MOVABLE_UNBUCKLE "unbuckle" //from base of atom/movable/unbuckle_mob(): (mob, force) +#define COMSIG_MOVABLE_PRE_THROW "movable_pre_throw" //from base of atom/movable/throw_at(): (list/args) + #define COMPONENT_CANCEL_THROW 1 +#define COMSIG_MOVABLE_POST_THROW "movable_post_throw" //from base of atom/movable/throw_at(): (datum/thrownthing, spin) +#define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" //from base of atom/movable/onTransitZ(): (old_z, new_z) +#define COMSIG_MOVABLE_SECLUDED_LOCATION "movable_secluded" //called when the movable is placed in an unaccessible area, used for stationloving: () +#define COMSIG_MOVABLE_HEAR "movable_hear" //from base of atom/movable/Hear(): (message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + #define HEARING_MESSAGE 1 + #define HEARING_SPEAKER 2 +// #define HEARING_LANGUAGE 3 + #define HEARING_RAW_MESSAGE 4 + /* #define HEARING_RADIO_FREQ 5 + #define HEARING_SPANS 6 + #define HEARING_MESSAGE_MODE 7 + #define HEARING_SOURCE 8*/ +#define COMSIG_MOVABLE_DISPOSING "movable_disposing" //called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source) +#define COMSIG_MOVABLE_TELEPORTED "movable_teleported" //from base of do_teleport(): (channel, turf/origin, turf/destination) + +// /mind signals +#define COMSIG_PRE_MIND_TRANSFER "pre_mind_transfer" //from base of mind/transfer_to() before it's done: (new_character, old_character) + #define COMPONENT_STOP_MIND_TRANSFER 1 //stops the mind transfer from happening. +#define COMSIG_MIND_TRANSFER "mind_transfer" //from base of mind/transfer_to() when it's done: (new_character, old_character) + +// /mob signals +#define COMSIG_MOB_EXAMINATE "mob_examinate" //from base of /mob/verb/examinate(): (atom/A) + #define COMPONENT_ALLOW_EXAMINE 1 +#define COMSIG_MOB_DEATH "mob_death" //from base of mob/death(): (gibbed) + #define COMPONENT_BLOCK_DEATH_BROADCAST 1 //stops the death from being broadcasted in deadchat. +#define COMSIG_MOB_GHOSTIZE "mob_ghostize" //from base of mob/Ghostize(): (can_reenter_corpse, special, penalize) + #define COMPONENT_BLOCK_GHOSTING 1 +#define COMSIG_MOB_ALLOWED "mob_allowed" //from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj +#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" //from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources) + #define COMPONENT_BLOCK_MAGIC 1 +#define COMSIG_MOB_HUD_CREATED "mob_hud_created" //from base of mob/create_mob_hud(): () +#define COMSIG_MOB_ATTACK_HAND "mob_attack_hand" //from base of +#define COMSIG_MOB_ITEM_ATTACK "mob_item_attack" //from base of /obj/item/attack(): (mob/M, mob/user) + #define COMPONENT_ITEM_NO_ATTACK 1 +#define COMSIG_MOB_ITEM_AFTERATTACK "mob_item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters) +#define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged" //from base of mob/RangedAttack(): (atom/A, params) +#define COMSIG_MOB_THROW "mob_throw" //from base of /mob/throw_item(): (atom/target) +#define COMSIG_MOB_KEY_CHANGE "mob_key_change" //from base of /mob/transfer_ckey(): (new_character, old_character) +#define COMSIG_MOB_PRE_PLAYER_CHANGE "mob_pre_player_change" //sent to the target mob from base of /mob/transfer_ckey() and /mind/transfer_to(): (our_character, their_character) +// #define COMPONENT_STOP_MIND_TRANSFER 1 +#define COMSIG_MOB_UPDATE_SIGHT "mob_update_sight" //from base of /mob/update_sight(): () +#define COMSIG_MOB_ON_NEW_MIND "mob_on_new_mind" //called when a new mind is assigned to a mob: () +#define COMSIG_MOB_SAY "mob_say" // from /mob/living/say(): (proc args list) + #define COMPONENT_UPPERCASE_SPEECH 1 + // used to access COMSIG_MOB_SAY argslist + #define SPEECH_MESSAGE 1 + // #define SPEECH_BUBBLE_TYPE 2 + #define SPEECH_SPANS 3 + /* #define SPEECH_SANITIZE 4 + #define SPEECH_LANGUAGE 5 + #define SPEECH_IGNORE_SPAM 6 + #define SPEECH_FORCED 7 */ + +// /mob/living signals +#define COMSIG_LIVING_FULLY_HEAL "living_fully_healed" //from base of /mob/living/fully_heal(): (admin_revive) +#define COMSIG_LIVING_REGENERATE_LIMBS "living_regenerate_limbs" //from base of /mob/living/regenerate_limbs(): (noheal, excluded_limbs) +#define COMSIG_LIVING_RESIST "living_resist" //from base of mob/living/resist() (/mob/living) +#define COMSIG_LIVING_IGNITED "living_ignite" //from base of mob/living/IgniteMob() (/mob/living) +#define COMSIG_LIVING_EXTINGUISHED "living_extinguished" //from base of mob/living/ExtinguishMob() (/mob/living) +#define COMSIG_LIVING_ELECTROCUTE_ACT "living_electrocute_act" //from base of mob/living/electrocute_act(): (shock_damage) +#define COMSIG_LIVING_MINOR_SHOCK "living_minor_shock" //sent by stuff like stunbatons and tasers: () +#define COMSIG_LIVING_GUN_PROCESS_FIRE "living_gun_process_fire" //from base of /obj/item/gun/proc/process_fire(): (atom/target, params, zone_override) + +// /mob/living/carbon signals +#define COMSIG_CARBON_SOUNDBANG "carbon_soundbang" //from base of mob/living/carbon/soundbang_act(): (list(intensity)) + +// /mob/living/simple_animal/hostile signals +#define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget" + #define COMPONENT_HOSTILE_NO_ATTACK 1 + +// /obj signals +#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" //from base of obj/deconstruct(): (disassembled) +#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag) +#define COMSIG_OBJ_SETANCHORED "obj_setanchored" //called in /obj/structure/setAnchored(): (value) + +// /machinery signals +#define COMSIG_MACHINE_EJECT_OCCUPANT "eject_occupant" //from base of obj/machinery/dropContents() (occupant) + +// /obj/item signals +#define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user) +#define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (/mob) + #define COMPONENT_NO_INTERACT 1 +#define COMSIG_ITEM_ATTACK_OBJ "item_attack_obj" //from base of obj/item/attack_obj(): (/obj, /mob) + #define COMPONENT_NO_ATTACK_OBJ 1 +#define COMSIG_ITEM_PRE_ATTACK "item_pre_attack" //from base of obj/item/pre_attack(): (atom/target, mob/user, params) + #define COMPONENT_NO_ATTACK 1 +#define COMSIG_ITEM_AFTERATTACK "item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, params) +#define COMSIG_ITEM_EQUIPPED "item_equip" //from base of obj/item/equipped(): (/mob/equipper, slot) +#define COMSIG_ITEM_DROPPED "item_drop" //from base of obj/item/dropped(): (mob/user) +#define COMSIG_ITEM_PICKUP "item_pickup" //from base of obj/item/pickup(): (/mob/taker) +#define COMSIG_ITEM_ATTACK_ZONE "item_attack_zone" //from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone) +#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" //return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/targeted/lichdom/cast(): (mob/user) +#define COMSIG_ITEM_HIT_REACT "item_hit_react" //from base of obj/item/hit_reaction(): (list/args) + +// /obj/item/clothing signals +#define COMSIG_SHOES_STEP_ACTION "shoes_step_action" //from base of obj/item/clothing/shoes/proc/step_action(): () + +// /obj/item/implant signals +#define COMSIG_IMPLANT_ACTIVATED "implant_activated" //from base of /obj/item/implant/proc/activate(): () +#define COMSIG_IMPLANT_IMPLANTING "implant_implanting" //from base of /obj/item/implant/proc/implant(): (list/args) + #define COMPONENT_STOP_IMPLANTING 1 +#define COMSIG_IMPLANT_OTHER "implant_other" //called on already installed implants when a new one is being added in /obj/item/implant/proc/implant(): (list/args, obj/item/implant/new_implant) + //#define COMPONENT_STOP_IMPLANTING 1 //The name makes sense for both + #define COMPONENT_DELETE_NEW_IMPLANT 2 + #define COMPONENT_DELETE_OLD_IMPLANT 4 +#define COMSIG_IMPLANT_EXISTING_UPLINK "implant_uplink_exists" //called on implants being implanted into someone with an uplink implant: (datum/component/uplink) + //This uses all return values of COMSIG_IMPLANT_OTHER +#define COMSIG_IMPLANT_REMOVING "implant_removing" //from base of /obj/item/implant/proc/removed() (list/args) + +// /obj/item/pda signals +#define COMSIG_PDA_CHANGE_RINGTONE "pda_change_ringtone" //called on pda when the user changes the ringtone: (mob/living/user, new_ringtone) + #define COMPONENT_STOP_RINGTONE_CHANGE 1 + +// /obj/item/radio signals +#define COMSIG_RADIO_NEW_FREQUENCY "radio_new_frequency" //called from base of /obj/item/radio/proc/set_frequency(): (list/args) + +// /obj/item/pen signals +#define COMSIG_PEN_ROTATED "pen_rotated" //called after rotation in /obj/item/pen/attack_self(): (rotation, mob/living/carbon/user) + +// /obj/item/projectile signals (sent to the firer) +#define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" // from base of /obj/item/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle) +#define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire" // from base of /obj/item/projectile/proc/fire(): (obj/item/projectile, atom/original_target) + +// /mob/living/carbon/human signals +#define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target) +#define COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY "human_melee_unarmed_attackby" //from mob/living/carbon/human/UnarmedAttack(): (mob/living/carbon/human/attacker) +#define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" //Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted) + +// /datum/species signals +#define COMSIG_SPECIES_GAIN "species_gain" //from datum/species/on_species_gain(): (datum/species/new_species, datum/species/old_species) +#define COMSIG_SPECIES_LOSS "species_loss" //from datum/species/on_species_loss(): (datum/species/lost_species) + +/*******Component Specific Signals*******/ +//Janitor +#define COMSIG_TURF_IS_WET "check_turf_wet" //(): Returns bitflags of wet values. +#define COMSIG_TURF_MAKE_DRY "make_turf_try" //(max_strength, immediate, duration_decrease = INFINITY): Returns bool. +#define COMSIG_COMPONENT_CLEAN_ACT "clean_act" //called on an object to clean it of cleanables. Usualy with soap: (num/strength) + +//Blood color +#define COMSIG_BLOOD_COLOR "blood_DNA_to_color" //RGB blood stuff +//Food +#define COMSIG_FOOD_EATEN "food_eaten" //from base of obj/item/reagent_containers/food/snacks/attack(): (mob/living/eater, mob/feeder) + +//Gibs +#define COMSIG_GIBS_STREAK "gibs_streak" // from base of /obj/effect/decal/cleanable/blood/gibs/streak(): (list/directions, list/diseases) + +//Mood +#define COMSIG_ADD_MOOD_EVENT "add_mood" //Called when you send a mood event from anywhere in the code. +#define COMSIG_CLEAR_MOOD_EVENT "clear_mood" //Called when you clear a mood event from anywhere in the code. +#define COMSIG_MODIFY_SANITY "modify_sanity" //Called when you want to increase or decrease sanity from anywhere in the code. + +//NTnet +#define COMSIG_COMPONENT_NTNET_RECEIVE "ntnet_receive" //called on an object by its NTNET connection component on receive. (sending_id(number), sending_netname(text), data(datum/netdata)) + +//Nanites +#define COMSIG_HAS_NANITES "has_nanites" //() returns TRUE if nanites are found +#define COMSIG_NANITE_GET_PROGRAMS "nanite_get_programs" //(list/nanite_programs) - makes the input list a copy the nanites' program list +#define COMSIG_NANITE_SET_VOLUME "nanite_set_volume" //(amount) Sets current nanite volume to the given amount +#define COMSIG_NANITE_ADJUST_VOLUME "nanite_adjust" //(amount) Adjusts nanite volume by the given amount +#define COMSIG_NANITE_SET_MAX_VOLUME "nanite_set_max_volume" //(amount) Sets maximum nanite volume to the given amount +#define COMSIG_NANITE_SET_CLOUD "nanite_set_cloud" //(amount(0-100)) Sets cloud ID to the given amount +#define COMSIG_NANITE_SET_SAFETY "nanite_set_safety" //(amount) Sets safety threshold to the given amount +#define COMSIG_NANITE_SET_REGEN "nanite_set_regen" //(amount) Sets regeneration rate to the given amount +#define COMSIG_NANITE_SIGNAL "nanite_signal" //(code(1-9999)) Called when sending a nanite signal to a mob. +#define COMSIG_NANITE_SCAN "nanite_scan" //(mob/user, full_scan) - sends to chat a scan of the nanites to the user, returns TRUE if nanites are detected +#define COMSIG_NANITE_UI_DATA "nanite_ui_data" //(list/data, scan_level) - adds nanite data to the given data list - made for ui_data procs +#define COMSIG_NANITE_ADD_PROGRAM "nanite_add_program" //(datum/nanite_program/new_program, datum/nanite_program/source_program) Called when adding a program to a nanite component + #define COMPONENT_PROGRAM_INSTALLED 1 //Installation successful + #define COMPONENT_PROGRAM_NOT_INSTALLED 2 //Installation failed, but there are still nanites +#define COMSIG_NANITE_SYNC "nanite_sync" //(datum/component/nanites, full_overwrite, copy_activation) Called to sync the target's nanites to a given nanite component + +// /datum/component/storage signals +#define COMSIG_CONTAINS_STORAGE "is_storage" //() - returns bool. +#define COMSIG_TRY_STORAGE_INSERT "storage_try_insert" //(obj/item/inserting, mob/user, silent, force) - returns bool +#define COMSIG_TRY_STORAGE_SHOW "storage_show_to" //(mob/show_to, force) - returns bool. +#define COMSIG_TRY_STORAGE_HIDE_FROM "storage_hide_from" //(mob/hide_from) - returns bool +#define COMSIG_TRY_STORAGE_HIDE_ALL "storage_hide_all" //returns bool +#define COMSIG_TRY_STORAGE_SET_LOCKSTATE "storage_lock_set_state" //(newstate) +#define COMSIG_IS_STORAGE_LOCKED "storage_get_lockstate" //() - returns bool. MUST CHECK IF STORAGE IS THERE FIRST! +#define COMSIG_TRY_STORAGE_TAKE_TYPE "storage_take_type" //(type, atom/destination, amount = INFINITY, check_adjacent, force, mob/user, list/inserted) - returns bool - type can be a list of types. +#define COMSIG_TRY_STORAGE_FILL_TYPE "storage_fill_type" //(type, amount = INFINITY, force = FALSE) //don't fuck this up. Force will ignore max_items, and amount is normally clamped to max_items. +#define COMSIG_TRY_STORAGE_TAKE "storage_take_obj" //(obj, new_loc, force = FALSE) - returns bool +#define COMSIG_TRY_STORAGE_QUICK_EMPTY "storage_quick_empty" //(loc) - returns bool - if loc is null it will dump at parent location. +#define COMSIG_TRY_STORAGE_RETURN_INVENTORY "storage_return_inventory" //(list/list_to_inject_results_into, recursively_search_inside_storages = TRUE) +#define COMSIG_TRY_STORAGE_CAN_INSERT "storage_can_equip" //(obj/item/insertion_candidate, mob/user, silent) - returns bool + +// /datum/action signals +#define COMSIG_ACTION_TRIGGER "action_trigger" //from base of datum/action/proc/Trigger(): (datum/action) + #define COMPONENT_ACTION_BLOCK_TRIGGER 1 + +/*******Non-Signal Component Related Defines*******/ + +//Redirection component init flags +#define REDIRECT_TRANSFER_WITH_TURF 1 + +//Arch +#define ARCH_PROB "probability" //Probability for each item +#define ARCH_MAXDROP "max_drop_amount" //each item's max drop amount + +//Ouch my toes! +#define CALTROP_BYPASS_SHOES 1 +#define CALTROP_IGNORE_WALKERS 2 + +//Xenobio hotkeys +#define COMSIG_XENO_SLIME_CLICK_CTRL "xeno_slime_click_ctrl" //from slime CtrlClickOn(): (/mob) +#define COMSIG_XENO_SLIME_CLICK_ALT "xeno_slime_click_alt" //from slime AltClickOn(): (/mob) +#define COMSIG_XENO_SLIME_CLICK_SHIFT "xeno_slime_click_shift" //from slime ShiftClickOn(): (/mob) +#define COMSIG_XENO_TURF_CLICK_SHIFT "xeno_turf_click_shift" //from turf ShiftClickOn(): (/mob) +#define COMSIG_XENO_TURF_CLICK_CTRL "xeno_turf_click_alt" //from turf AltClickOn(): (/mob) +#define COMSIG_XENO_MONKEY_CLICK_CTRL "xeno_monkey_click_ctrl" //from monkey CtrlClickOn(): (/mob) diff --git a/code/__DEFINES/configuration.dm b/code/__DEFINES/configuration.dm index c4ef8e6606..3034876e36 100644 --- a/code/__DEFINES/configuration.dm +++ b/code/__DEFINES/configuration.dm @@ -1,9 +1,9 @@ -//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 -#define CONFIG_ENTRY_LOCKED 1 //can't edit -#define CONFIG_ENTRY_HIDDEN 2 //can't see value +//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 +#define CONFIG_ENTRY_LOCKED 1 //can't edit +#define CONFIG_ENTRY_HIDDEN 2 //can't see value diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 55962bf78c..839b690b37 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -1,93 +1,93 @@ -/* - These defines are specific to the atom/flags_1 bitmask -*/ -#define ALL (~0) //For convenience. -#define NONE 0 - -//for convenience -#define ENABLE_BITFIELD(variable, flag) (variable |= (flag)) -#define DISABLE_BITFIELD(variable, flag) (variable &= ~(flag)) -#define CHECK_BITFIELD(variable, flag) (variable & (flag)) -#define TOGGLE_BITFIELD(variable, flag) (variable ^= (flag)) - -#define CHECK_MULTIPLE_BITFIELDS(flagvar, flags) (((flagvar) & (flags)) == (flags)) - -GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)) - -// for /datum/var/datum_flags -#define DF_USE_TAG (1<<0) -#define DF_VAR_EDITED (1<<1) -#define DF_ISPROCESSING (1<<2) - -//FLAGS BITMASK - -#define HEAR_1 (1<<3) // This flag is what recursive_hear_check() uses to determine wether to add an item to the hearer list or not. -#define CHECK_RICOCHET_1 (1<<4) // Projectiels will check ricochet on things impacted that have this. -#define CONDUCT_1 (1<<5) // conducts electricity (metal etc.) -#define NODECONSTRUCT_1 (1<<7) // For machines and structures that should not break into parts, eg, holodeck stuff -#define OVERLAY_QUEUED_1 (1<<8) // atom queued to SSoverlay -#define ON_BORDER_1 (1<<9) // item has priority to check when entering or leaving -#define PREVENT_CLICK_UNDER_1 (1<<11) //Prevent clicking things below it on the same turf eg. doors/ fulltile windows -#define HOLOGRAM_1 (1<<12) -#define TESLA_IGNORE_1 (1<<13) // TESLA_IGNORE grants immunity from being targeted by tesla-style electricity -#define INITIALIZED_1 (1<<14) //Whether /atom/Initialize() has already run for the object -#define ADMIN_SPAWNED_1 (1<<15) //was this spawned by an admin? used for stat tracking stuff. -#define PREVENT_CONTENTS_EXPLOSION_1 (1<<16) /// should not get harmed if this gets caught by an explosion? - -//turf-only flags -#define NOJAUNT_1 (1<<0) -#define UNUSED_RESERVATION_TURF_1 (1<<1) -#define CAN_BE_DIRTY_1 (1<<2) // If a turf can be made dirty at roundstart. This is also used in areas. -#define NO_LAVA_GEN_1 (1<<6) //Blocks lava rivers being generated on the turf -#define NO_RUINS_1 (1<<10) //Blocks ruins spawning on the turf - -/* - These defines are used specifically with the atom/pass_flags bitmask - the atom/checkpass() proc uses them (tables will call movable atom checkpass(PASSTABLE) for example) -*/ -//flags for pass_flags -#define PASSTABLE (1<<0) -#define PASSGLASS (1<<1) -#define PASSGRILLE (1<<2) -#define PASSBLOB (1<<3) -#define PASSMOB (1<<4) -#define PASSCLOSEDTURF (1<<5) -#define LETPASSTHROW (1<<6) - - -//Movement Types -#define GROUND (1<<0) -#define FLYING (1<<1) -#define VENTCRAWLING (1<<2) -#define FLOATING (1<<3) - -//Fire and Acid stuff, for resistance_flags -#define LAVA_PROOF (1<<0) -#define FIRE_PROOF (1<<1) //100% immune to fire damage (but not necessarily to lava or heat) -#define FLAMMABLE (1<<2) -#define ON_FIRE (1<<3) -#define UNACIDABLE (1<<4) //acid can't even appear on it, let alone melt it. -#define ACID_PROOF (1<<5) //acid stuck on it doesn't melt it. -#define INDESTRUCTIBLE (1<<6) //doesn't take damage -#define FREEZE_PROOF (1<<7) //can't be frozen -#define GOLIATH_RESISTANCE (1<<8) //CIT CHANGE -#define GOLIATH_WEAKNESS (1<<9) //CIT CHANGE - -//tesla_zap -#define TESLA_MACHINE_EXPLOSIVE (1<<0) -#define TESLA_ALLOW_DUPLICATES (1<<1) -#define TESLA_OBJ_DAMAGE (1<<2) -#define TESLA_MOB_DAMAGE (1<<3) -#define TESLA_MOB_STUN (1<<4) - -#define TESLA_DEFAULT_FLAGS ALL -#define TESLA_FUSION_FLAGS TESLA_OBJ_DAMAGE | TESLA_MOB_DAMAGE | TESLA_MOB_STUN - -//EMP protection -#define EMP_PROTECT_SELF (1<<0) -#define EMP_PROTECT_CONTENTS (1<<1) -#define EMP_PROTECT_WIRES (1<<2) - -// radiation -#define RAD_PROTECT_CONTENTS (1<<0) -#define RAD_NO_CONTAMINATE (1<<1) +/* + These defines are specific to the atom/flags_1 bitmask +*/ +#define ALL (~0) //For convenience. +#define NONE 0 + +//for convenience +#define ENABLE_BITFIELD(variable, flag) (variable |= (flag)) +#define DISABLE_BITFIELD(variable, flag) (variable &= ~(flag)) +#define CHECK_BITFIELD(variable, flag) (variable & (flag)) +#define TOGGLE_BITFIELD(variable, flag) (variable ^= (flag)) + +#define CHECK_MULTIPLE_BITFIELDS(flagvar, flags) (((flagvar) & (flags)) == (flags)) + +GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768)) + +// for /datum/var/datum_flags +#define DF_USE_TAG (1<<0) +#define DF_VAR_EDITED (1<<1) +#define DF_ISPROCESSING (1<<2) + +//FLAGS BITMASK + +#define HEAR_1 (1<<3) // This flag is what recursive_hear_check() uses to determine wether to add an item to the hearer list or not. +#define CHECK_RICOCHET_1 (1<<4) // Projectiels will check ricochet on things impacted that have this. +#define CONDUCT_1 (1<<5) // conducts electricity (metal etc.) +#define NODECONSTRUCT_1 (1<<7) // For machines and structures that should not break into parts, eg, holodeck stuff +#define OVERLAY_QUEUED_1 (1<<8) // atom queued to SSoverlay +#define ON_BORDER_1 (1<<9) // item has priority to check when entering or leaving +#define PREVENT_CLICK_UNDER_1 (1<<11) //Prevent clicking things below it on the same turf eg. doors/ fulltile windows +#define HOLOGRAM_1 (1<<12) +#define TESLA_IGNORE_1 (1<<13) // TESLA_IGNORE grants immunity from being targeted by tesla-style electricity +#define INITIALIZED_1 (1<<14) //Whether /atom/Initialize() has already run for the object +#define ADMIN_SPAWNED_1 (1<<15) //was this spawned by an admin? used for stat tracking stuff. +#define PREVENT_CONTENTS_EXPLOSION_1 (1<<16) /// should not get harmed if this gets caught by an explosion? + +//turf-only flags +#define NOJAUNT_1 (1<<0) +#define UNUSED_RESERVATION_TURF_1 (1<<1) +#define CAN_BE_DIRTY_1 (1<<2) // If a turf can be made dirty at roundstart. This is also used in areas. +#define NO_LAVA_GEN_1 (1<<6) //Blocks lava rivers being generated on the turf +#define NO_RUINS_1 (1<<10) //Blocks ruins spawning on the turf + +/* + These defines are used specifically with the atom/pass_flags bitmask + the atom/checkpass() proc uses them (tables will call movable atom checkpass(PASSTABLE) for example) +*/ +//flags for pass_flags +#define PASSTABLE (1<<0) +#define PASSGLASS (1<<1) +#define PASSGRILLE (1<<2) +#define PASSBLOB (1<<3) +#define PASSMOB (1<<4) +#define PASSCLOSEDTURF (1<<5) +#define LETPASSTHROW (1<<6) + + +//Movement Types +#define GROUND (1<<0) +#define FLYING (1<<1) +#define VENTCRAWLING (1<<2) +#define FLOATING (1<<3) + +//Fire and Acid stuff, for resistance_flags +#define LAVA_PROOF (1<<0) +#define FIRE_PROOF (1<<1) //100% immune to fire damage (but not necessarily to lava or heat) +#define FLAMMABLE (1<<2) +#define ON_FIRE (1<<3) +#define UNACIDABLE (1<<4) //acid can't even appear on it, let alone melt it. +#define ACID_PROOF (1<<5) //acid stuck on it doesn't melt it. +#define INDESTRUCTIBLE (1<<6) //doesn't take damage +#define FREEZE_PROOF (1<<7) //can't be frozen +#define GOLIATH_RESISTANCE (1<<8) //CIT CHANGE +#define GOLIATH_WEAKNESS (1<<9) //CIT CHANGE + +//tesla_zap +#define TESLA_MACHINE_EXPLOSIVE (1<<0) +#define TESLA_ALLOW_DUPLICATES (1<<1) +#define TESLA_OBJ_DAMAGE (1<<2) +#define TESLA_MOB_DAMAGE (1<<3) +#define TESLA_MOB_STUN (1<<4) + +#define TESLA_DEFAULT_FLAGS ALL +#define TESLA_FUSION_FLAGS TESLA_OBJ_DAMAGE | TESLA_MOB_DAMAGE | TESLA_MOB_STUN + +//EMP protection +#define EMP_PROTECT_SELF (1<<0) +#define EMP_PROTECT_CONTENTS (1<<1) +#define EMP_PROTECT_WIRES (1<<2) + +// radiation +#define RAD_PROTECT_CONTENTS (1<<0) +#define RAD_NO_CONTAMINATE (1<<1) diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index ed45da9923..9bc8127c3d 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -1,89 +1,89 @@ - -#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 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 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 JOB_UNAVAILABLE_SPECIESLOCK 6 - -#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_BARTENDER 4 -#define JOB_DISPLAY_ORDER_COOK 5 -#define JOB_DISPLAY_ORDER_BOTANIST 6 -#define JOB_DISPLAY_ORDER_JANITOR 7 -#define JOB_DISPLAY_ORDER_CLOWN 8 -#define JOB_DISPLAY_ORDER_MIME 9 -#define JOB_DISPLAY_ORDER_CURATOR 10 -#define JOB_DISPLAY_ORDER_LAWYER 11 -#define JOB_DISPLAY_ORDER_CHAPLAIN 12 -#define JOB_DISPLAY_ORDER_QUARTERMASTER 13 -#define JOB_DISPLAY_ORDER_CARGO_TECHNICIAN 14 -#define JOB_DISPLAY_ORDER_SHAFT_MINER 15 -#define JOB_DISPLAY_ORDER_CHIEF_ENGINEER 16 -#define JOB_DISPLAY_ORDER_STATION_ENGINEER 17 -#define JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN 18 -#define JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER 19 -#define JOB_DISPLAY_ORDER_MEDICAL_DOCTOR 20 -#define JOB_DISPLAY_ORDER_CHEMIST 21 -#define JOB_DISPLAY_ORDER_GENETICIST 22 -#define JOB_DISPLAY_ORDER_VIROLOGIST 23 -#define JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR 24 -#define JOB_DISPLAY_ORDER_SCIENTIST 25 -#define JOB_DISPLAY_ORDER_ROBOTICIST 26 -#define JOB_DISPLAY_ORDER_HEAD_OF_SECURITY 27 -#define JOB_DISPLAY_ORDER_WARDEN 28 -#define JOB_DISPLAY_ORDER_DETECTIVE 29 -#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 30 -#define JOB_DISPLAY_ORDER_AI 31 -#define JOB_DISPLAY_ORDER_CYBORG 32 + +#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 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 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 JOB_UNAVAILABLE_SPECIESLOCK 6 + +#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_BARTENDER 4 +#define JOB_DISPLAY_ORDER_COOK 5 +#define JOB_DISPLAY_ORDER_BOTANIST 6 +#define JOB_DISPLAY_ORDER_JANITOR 7 +#define JOB_DISPLAY_ORDER_CLOWN 8 +#define JOB_DISPLAY_ORDER_MIME 9 +#define JOB_DISPLAY_ORDER_CURATOR 10 +#define JOB_DISPLAY_ORDER_LAWYER 11 +#define JOB_DISPLAY_ORDER_CHAPLAIN 12 +#define JOB_DISPLAY_ORDER_QUARTERMASTER 13 +#define JOB_DISPLAY_ORDER_CARGO_TECHNICIAN 14 +#define JOB_DISPLAY_ORDER_SHAFT_MINER 15 +#define JOB_DISPLAY_ORDER_CHIEF_ENGINEER 16 +#define JOB_DISPLAY_ORDER_STATION_ENGINEER 17 +#define JOB_DISPLAY_ORDER_ATMOSPHERIC_TECHNICIAN 18 +#define JOB_DISPLAY_ORDER_CHIEF_MEDICAL_OFFICER 19 +#define JOB_DISPLAY_ORDER_MEDICAL_DOCTOR 20 +#define JOB_DISPLAY_ORDER_CHEMIST 21 +#define JOB_DISPLAY_ORDER_GENETICIST 22 +#define JOB_DISPLAY_ORDER_VIROLOGIST 23 +#define JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR 24 +#define JOB_DISPLAY_ORDER_SCIENTIST 25 +#define JOB_DISPLAY_ORDER_ROBOTICIST 26 +#define JOB_DISPLAY_ORDER_HEAD_OF_SECURITY 27 +#define JOB_DISPLAY_ORDER_WARDEN 28 +#define JOB_DISPLAY_ORDER_DETECTIVE 29 +#define JOB_DISPLAY_ORDER_SECURITY_OFFICER 30 +#define JOB_DISPLAY_ORDER_AI 31 +#define JOB_DISPLAY_ORDER_CYBORG 32 diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm index db13c21c44..8ae4e5c167 100644 --- a/code/__DEFINES/machines.dm +++ b/code/__DEFINES/machines.dm @@ -1,107 +1,107 @@ -// channel numbers for power -#define EQUIP 1 -#define LIGHT 2 -#define ENVIRON 3 -#define TOTAL 4 //for total power used only -#define STATIC_EQUIP 5 -#define STATIC_LIGHT 6 -#define STATIC_ENVIRON 7 - -//Power use -#define NO_POWER_USE 0 -#define IDLE_POWER_USE 1 -#define ACTIVE_POWER_USE 2 - - -//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 AUTOYLATHE (1<<8) // CITADEL ADD -#define NANITE_COMPILER (1<<9) //Prints nanite disks -#define AUTOBOTTLER (1<<10) //Uses booze, for printing -//Note: More then 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 NUKE_OFF_LOCKED 0 -#define NUKE_OFF_UNLOCKED 1 -#define NUKE_ON_TIMING 2 -#define NUKE_ON_EXPLODING 3 - - -//these flags are used to tell the DNA modifier if a plant gene cannot be extracted or modified. -#define PLANT_GENE_REMOVABLE (1<<0) +// channel numbers for power +#define EQUIP 1 +#define LIGHT 2 +#define ENVIRON 3 +#define TOTAL 4 //for total power used only +#define STATIC_EQUIP 5 +#define STATIC_LIGHT 6 +#define STATIC_ENVIRON 7 + +//Power use +#define NO_POWER_USE 0 +#define IDLE_POWER_USE 1 +#define ACTIVE_POWER_USE 2 + + +//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 AUTOYLATHE (1<<8) // CITADEL ADD +#define NANITE_COMPILER (1<<9) //Prints nanite disks +#define AUTOBOTTLER (1<<10) //Uses booze, for printing +//Note: More then 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 NUKE_OFF_LOCKED 0 +#define NUKE_OFF_UNLOCKED 1 +#define NUKE_ON_TIMING 2 +#define NUKE_ON_EXPLODING 3 + + +//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) \ No newline at end of file diff --git a/code/__DEFINES/medal.dm b/code/__DEFINES/medal.dm index 2a545d9c64..89fc098e3d 100644 --- a/code/__DEFINES/medal.dm +++ b/code/__DEFINES/medal.dm @@ -1,29 +1,29 @@ -// Medal names -#define BOSS_KILL_MEDAL "Killer" -#define ALL_KILL_MEDAL "Exterminator" //Killing all of x type -#define BOSS_KILL_MEDAL_CRUSHER "Crusher" - -//Defines for boss medals -#define BOSS_MEDAL_MINER "Blood-drunk Miner" -#define BOSS_MEDAL_BUBBLEGUM "Bubblegum" -#define BOSS_MEDAL_COLOSSUS "Colossus" -#define BOSS_MEDAL_DRAKE "Drake" -#define BOSS_MEDAL_HIEROPHANT "Hierophant" -#define BOSS_MEDAL_LEGION "Legion" -#define BOSS_MEDAL_TENDRIL "Tendril" -#define BOSS_MEDAL_SWARMERS "Swarmer Beacon" - -// Score names -#define HIEROPHANT_SCORE "Hierophants Killed" -#define BOSS_SCORE "Bosses Killed" -#define BUBBLEGUM_SCORE "Bubblegum Killed" -#define COLOSSUS_SCORE "Colossus Killed" -#define DRAKE_SCORE "Drakes Killed" -#define LEGION_SCORE "Legion Killed" -#define SWARMER_BEACON_SCORE "Swarmer Beacons Killed" -#define TENDRIL_CLEAR_SCORE "Tendrils Killed" - -//Misc medals -#define MEDAL_METEOR "Your Life Before Your Eyes" -#define MEDAL_PULSE "Jackpot" +// Medal names +#define BOSS_KILL_MEDAL "Killer" +#define ALL_KILL_MEDAL "Exterminator" //Killing all of x type +#define BOSS_KILL_MEDAL_CRUSHER "Crusher" + +//Defines for boss medals +#define BOSS_MEDAL_MINER "Blood-drunk Miner" +#define BOSS_MEDAL_BUBBLEGUM "Bubblegum" +#define BOSS_MEDAL_COLOSSUS "Colossus" +#define BOSS_MEDAL_DRAKE "Drake" +#define BOSS_MEDAL_HIEROPHANT "Hierophant" +#define BOSS_MEDAL_LEGION "Legion" +#define BOSS_MEDAL_TENDRIL "Tendril" +#define BOSS_MEDAL_SWARMERS "Swarmer Beacon" + +// Score names +#define HIEROPHANT_SCORE "Hierophants Killed" +#define BOSS_SCORE "Bosses Killed" +#define BUBBLEGUM_SCORE "Bubblegum Killed" +#define COLOSSUS_SCORE "Colossus Killed" +#define DRAKE_SCORE "Drakes Killed" +#define LEGION_SCORE "Legion Killed" +#define SWARMER_BEACON_SCORE "Swarmer Beacons Killed" +#define TENDRIL_CLEAR_SCORE "Tendrils Killed" + +//Misc medals +#define MEDAL_METEOR "Your Life Before Your Eyes" +#define MEDAL_PULSE "Jackpot" #define MEDAL_TIMEWASTE "Overextended The Joke" \ No newline at end of file diff --git a/code/__DEFINES/networks.dm b/code/__DEFINES/networks.dm index 09b9e4cadf..115c165349 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 7a284ff1a0..06926317ee 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -1,84 +1,84 @@ - -//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 TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|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_LOOC (1<<10) - -#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_LOOC) - -#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 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 - -//Chaos levels for dynamic voting -#define CHAOS_NONE "None (Extended)" -#define CHAOS_LOW "Low" -#define CHAOS_MED "Medium" -#define CHAOS_HIGH "High" -#define CHAOS_MAX "Maximum" + +//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 TOGGLES_DEFAULT (SOUND_ADMINHELP|SOUND_MIDI|SOUND_AMBIENCE|SOUND_LOBBY|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_LOOC (1<<10) + +#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_LOOC) + +#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 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 + +//Chaos levels for dynamic voting +#define CHAOS_NONE "None (Extended)" +#define CHAOS_LOW "Low" +#define CHAOS_MED "Medium" +#define CHAOS_HIGH "High" +#define CHAOS_MAX "Maximum" diff --git a/code/__DEFINES/research.dm b/code/__DEFINES/research.dm index 95b89ae04e..9feb5f40c5 100644 --- a/code/__DEFINES/research.dm +++ b/code/__DEFINES/research.dm @@ -1,75 +1,75 @@ - -#define RDCONSOLE_UI_MODE_NORMAL 1 -#define RDCONSOLE_UI_MODE_EXPERT 2 -#define RDCONSOLE_UI_MODE_LIST 3 - -//RDSCREEN screens -#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 "" - -#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 } - -#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 - -#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 DEPARTMENTAL_FLAG_ALL (1<<6) //NO THIS DOESN'T ALLOW YOU TO PRINT EVERYTHING, IT'S FOR ALL DEPARTMENTS! -//#define DEPARTMENTAL_FLAG_MINING (1<<7) - -#define DESIGN_ID_IGNORE "IGNORE_THIS_DESIGN" - -#define RESEARCH_MATERIAL_RECLAMATION_ID "__materials" - -//When adding new types, update the list below! -#define TECHWEB_POINT_TYPE_GENERIC "General Research" - -#define TECHWEB_POINT_TYPE_DEFAULT TECHWEB_POINT_TYPE_GENERIC - -//defined here so people don't forget to change this! -#define TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES list(\ - TECHWEB_POINT_TYPE_GENERIC = "General Research"\ - ) - -#define TECHWEB_BOMB_POINTCAP 50000 //Adjust as needed; Stops toxins from nullifying RND progression mechanics. Current Value Cap Radius: 100 + +#define RDCONSOLE_UI_MODE_NORMAL 1 +#define RDCONSOLE_UI_MODE_EXPERT 2 +#define RDCONSOLE_UI_MODE_LIST 3 + +//RDSCREEN screens +#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 "" + +#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 } + +#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 + +#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 DEPARTMENTAL_FLAG_ALL (1<<6) //NO THIS DOESN'T ALLOW YOU TO PRINT EVERYTHING, IT'S FOR ALL DEPARTMENTS! +//#define DEPARTMENTAL_FLAG_MINING (1<<7) + +#define DESIGN_ID_IGNORE "IGNORE_THIS_DESIGN" + +#define RESEARCH_MATERIAL_RECLAMATION_ID "__materials" + +//When adding new types, update the list below! +#define TECHWEB_POINT_TYPE_GENERIC "General Research" + +#define TECHWEB_POINT_TYPE_DEFAULT TECHWEB_POINT_TYPE_GENERIC + +//defined here so people don't forget to change this! +#define TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES list(\ + TECHWEB_POINT_TYPE_GENERIC = "General Research"\ + ) + +#define TECHWEB_BOMB_POINTCAP 50000 //Adjust as needed; Stops toxins from nullifying RND progression mechanics. Current Value Cap Radius: 100 diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm index f6302813f5..581bb2bc29 100644 --- a/code/__DEFINES/sound.dm +++ b/code/__DEFINES/sound.dm @@ -1,81 +1,81 @@ -//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_JUKEBOX_START 1016 //The gap between this and CHANNEL_JUKEBOX determines the amount of free jukebox channels. This currently allows 6 jukebox channels to exist. -#define CHANNEL_JUSTICAR_ARK 1015 -#define CHANNEL_HEARTBEAT 1014 //sound channel for heartbeats -#define CHANNEL_AMBIENCE 1013 -#define CHANNEL_BUZZ 1012 -#define CHANNEL_BICYCLE 1011 - -//CIT CHANNELS - TRY NOT TO REGRESS -#define CHANNEL_PRED 1010 -#define CHANNEL_DIGEST 1009 -#define CHANNEL_PREYLOOP 1008 - -//THIS SHOULD ALWAYS BE THE LOWEST ONE! -//KEEP IT UPDATED - -#define CHANNEL_HIGHEST_AVAILABLE 1008 //CIT CHANGE - COMPENSATES FOR VORESOUND CHANNELS - - -#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/ambilava.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') +//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_JUKEBOX_START 1016 //The gap between this and CHANNEL_JUKEBOX determines the amount of free jukebox channels. This currently allows 6 jukebox channels to exist. +#define CHANNEL_JUSTICAR_ARK 1015 +#define CHANNEL_HEARTBEAT 1014 //sound channel for heartbeats +#define CHANNEL_AMBIENCE 1013 +#define CHANNEL_BUZZ 1012 +#define CHANNEL_BICYCLE 1011 + +//CIT CHANNELS - TRY NOT TO REGRESS +#define CHANNEL_PRED 1010 +#define CHANNEL_DIGEST 1009 +#define CHANNEL_PREYLOOP 1008 + +//THIS SHOULD ALWAYS BE THE LOWEST ONE! +//KEEP IT UPDATED + +#define CHANNEL_HIGHEST_AVAILABLE 1008 //CIT CHANGE - COMPENSATES FOR VORESOUND CHANNELS + + +#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/ambilava.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') diff --git a/code/__DEFINES/stat.dm b/code/__DEFINES/stat.dm index b975aa1f89..2dade0825f 100644 --- a/code/__DEFINES/stat.dm +++ b/code/__DEFINES/stat.dm @@ -1,22 +1,22 @@ -/* - 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 -#define POWER_REQ_CLOCKCULT 2 +/* + 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 +#define POWER_REQ_CLOCKCULT 2 diff --git a/code/__DEFINES/tgs.config.dm b/code/__DEFINES/tgs.config.dm index b05cff7e01..a40b5d4663 100644 --- a/code/__DEFINES/tgs.config.dm +++ b/code/__DEFINES/tgs.config.dm @@ -1,10 +1,10 @@ -#define TGS_EXTERNAL_CONFIGURATION -#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_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_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_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/tgs.dm b/code/__DEFINES/tgs.dm index d24054a24c..db4f046ec3 100644 --- a/code/__DEFINES/tgs.dm +++ b/code/__DEFINES/tgs.dm @@ -1,242 +1,242 @@ -//tgstation-server DMAPI - -//All functions and datums outside this document are subject to change with any version and should not be relied on - -//CONFIGURATION - -//create this define if you want to do configuration outside of this file -#ifndef TGS_EXTERNAL_CONFIGURATION - -//Comment this out once you've filled in the below -#error TGS API unconfigured - -//Uncomment this if you wish to allow the game to interact with TGS 3 -//This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()() -//#define TGS_V3_API - -//Required interfaces (fill in with your codebase equivalent): - -//create a global variable named `Name` and set it to `Value` -//These globals must not be modifiable from anywhere outside of the server tools -#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) - -//Read the value in the global variable `Name` -#define TGS_READ_GLOBAL(Name) - -//Set the value in the global variable `Name` to `Value` -#define TGS_WRITE_GLOBAL(Name, Value) - -//Disallow ANYONE from reflecting a given `path`, security measure to prevent in-game priveledge escalation -#define TGS_PROTECT_DATUM(Path) - -//display an announcement `message` from the server to all players -#define TGS_WORLD_ANNOUNCE(message) - -//Notify current in-game administrators of a string `event` -#define TGS_NOTIFY_ADMINS(event) - -//Write an info `message` to a server log -#define TGS_INFO_LOG(message) - -//Write an error `message` to a server log -#define TGS_ERROR_LOG(message) - -//Get the number of connected /clients -#define TGS_CLIENT_COUNT - -#endif - -//EVENT CODES - -#define TGS_EVENT_PORT_SWAP -2 //before a port change is about to happen, extra parameter is new port -#define TGS_EVENT_REBOOT_MODE_CHANGE -1 //before a reboot mode change, extras parameters are the current and new reboot mode enums - -//See the descriptions for these codes here: https://github.com/tgstation/tgstation-server/blob/master/src/Tgstation.Server.Host/Components/EventType.cs -#define TGS_EVENT_REPO_RESET_ORIGIN 0 -#define TGS_EVENT_REPO_CHECKOUT 1 -#define TGS_EVENT_REPO_FETCH 2 -#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3 -#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4 -#define TGS_EVENT_BYOND_INSTALL_START 5 -#define TGS_EVENT_BYOND_INSTALL_FAIL 6 -#define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7 -#define TGS_EVENT_COMPILE_START 8 -#define TGS_EVENT_COMPILE_CANCELLED 9 -#define TGS_EVENT_COMPILE_FAILURE 10 -#define TGS_EVENT_COMPILE_COMPLETE 11 -#define TGS_EVENT_INSTANCE_AUTO_UPDATE_START 12 -#define TGS_EVENT_REPO_MERGE_CONFLICT 13 - -//OTHER ENUMS - -#define TGS_REBOOT_MODE_NORMAL 0 -#define TGS_REBOOT_MODE_SHUTDOWN 1 -#define TGS_REBOOT_MODE_RESTART 2 - -#define TGS_SECURITY_TRUSTED 0 -#define TGS_SECURITY_SAFE 1 -#define TGS_SECURITY_ULTRASAFE 2 - -//REQUIRED HOOKS - -//Call this somewhere in /world/New() that is always run -//event_handler: optional user defined event handler. The default behaviour is to broadcast the event in english to all connected admin channels -//minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated -/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) - return - -//Call this when your initializations are complete and your game is ready to play before any player interactions happen -//This may use world.sleep_offline to make this happen so ensure no changes are made to it while this call is running -//Most importantly, before this point, note that any static files or directories may be in use by another server. Your code should account for this -//This function should not be called before ..() in /world/New() -/world/proc/TgsInitializationComplete() - return - -//Put this at the start of /world/Topic() -#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return - -//Call this at the beginning of world/Reboot(reason) -/world/proc/TgsReboot() - return - -//DATUM DEFINITIONS -//unless otherwise specified all datums defined here should be considered read-only, warranty void if written - -//represents git revision information about the current world build -/datum/tgs_revision_information - var/commit //full sha of compiled commit - var/origin_commit //full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch - -//represents a merge of a GitHub pull request -/datum/tgs_revision_information/test_merge - var/number //pull request number - var/title //pull request title - var/body //pull request body - var/author //pull request github author - var/url //link to pull request html - var/pull_request_commit //commit of the pull request when it was merged - var/time_merged //timestamp of when the merge commit for the pull request was created - var/comment //optional comment left by the one who initiated the test merge - -//represents a connected chat channel -/datum/tgs_chat_channel - var/id //internal channel representation - var/friendly_name //user friendly channel name - var/connection_name //the name of the configured chat connection - var/is_admin_channel //if the server operator has marked this channel for game admins only - var/is_private_channel //if this is a private chat channel - var/custom_tag //user defined string associated with channel - -//represents a chat user -/datum/tgs_chat_user - var/id //Internal user representation, requires channel to be unique - var/friendly_name //The user's public name - var/mention //The text to use to ping this user in a message - var/datum/tgs_chat_channel/channel //The /datum/tgs_chat_channel this user was from - -//user definable callback for handling events -//extra parameters may be specified depending on the event -/datum/tgs_event_handler/proc/HandleEvent(event_code, ...) - set waitfor = FALSE - return - -//user definable chat command -/datum/tgs_chat_command - var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command - var/help_text = "" //help text for this command - var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins - -//override to implement command -//sender: The tgs_chat_user who send to command -//params: The trimmed string following the command name -//The return value will be stringified and sent to the appropriate chat -/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params) - CRASH("[type] has no implementation for Run()") - -//FUNCTIONS - -//Returns the respective string version of the API -/world/proc/TgsMaximumAPIVersion() - return - -/world/proc/TgsMinimumAPIVersion() - return - -//Gets the current version of the server tools running the server -/world/proc/TgsVersion() - return - -//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise -//No function below this succeeds if it returns FALSE -/world/proc/TgsAvailable() - return - -/world/proc/TgsInstanceName() - return - -//Get the current `/datum/tgs_revision_information` -/world/proc/TgsRevision() - return - -//Get the current BYOND security level -/world/proc/TgsSecurityLevel() - return - -//Gets a list of active `/datum/tgs_revision_information/test_merge`s -/world/proc/TgsTestMerges() - return - -//Forces a hard reboot of BYOND by ending the process -//unlike del(world) clients will try to reconnect -//If the service has not requested a shutdown, the next server will take over -/world/proc/TgsEndProcess() - return - -//Gets a list of connected tgs_chat_channel -/world/proc/TgsChatChannelInfo() - return - -//Sends a message to connected game chats -//message: The message to send -//channels: optional channels to limit the broadcast to -/world/proc/TgsChatBroadcast(message, list/channels) - return - -//Send a message to non-admin connected chats -//message: The message to send -//admin_only: If TRUE, message will instead be sent to only admin connected chats -/world/proc/TgsTargetedChatBroadcast(message, admin_only) - return - -//Send a private message to a specific user -//message: The message to send -//user: The /datum/tgs_chat_user to send to -/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) - return - -/* -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//tgstation-server DMAPI + +//All functions and datums outside this document are subject to change with any version and should not be relied on + +//CONFIGURATION + +//create this define if you want to do configuration outside of this file +#ifndef TGS_EXTERNAL_CONFIGURATION + +//Comment this out once you've filled in the below +#error TGS API unconfigured + +//Uncomment this if you wish to allow the game to interact with TGS 3 +//This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()() +//#define TGS_V3_API + +//Required interfaces (fill in with your codebase equivalent): + +//create a global variable named `Name` and set it to `Value` +//These globals must not be modifiable from anywhere outside of the server tools +#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) + +//Read the value in the global variable `Name` +#define TGS_READ_GLOBAL(Name) + +//Set the value in the global variable `Name` to `Value` +#define TGS_WRITE_GLOBAL(Name, Value) + +//Disallow ANYONE from reflecting a given `path`, security measure to prevent in-game priveledge escalation +#define TGS_PROTECT_DATUM(Path) + +//display an announcement `message` from the server to all players +#define TGS_WORLD_ANNOUNCE(message) + +//Notify current in-game administrators of a string `event` +#define TGS_NOTIFY_ADMINS(event) + +//Write an info `message` to a server log +#define TGS_INFO_LOG(message) + +//Write an error `message` to a server log +#define TGS_ERROR_LOG(message) + +//Get the number of connected /clients +#define TGS_CLIENT_COUNT + +#endif + +//EVENT CODES + +#define TGS_EVENT_PORT_SWAP -2 //before a port change is about to happen, extra parameter is new port +#define TGS_EVENT_REBOOT_MODE_CHANGE -1 //before a reboot mode change, extras parameters are the current and new reboot mode enums + +//See the descriptions for these codes here: https://github.com/tgstation/tgstation-server/blob/master/src/Tgstation.Server.Host/Components/EventType.cs +#define TGS_EVENT_REPO_RESET_ORIGIN 0 +#define TGS_EVENT_REPO_CHECKOUT 1 +#define TGS_EVENT_REPO_FETCH 2 +#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3 +#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4 +#define TGS_EVENT_BYOND_INSTALL_START 5 +#define TGS_EVENT_BYOND_INSTALL_FAIL 6 +#define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7 +#define TGS_EVENT_COMPILE_START 8 +#define TGS_EVENT_COMPILE_CANCELLED 9 +#define TGS_EVENT_COMPILE_FAILURE 10 +#define TGS_EVENT_COMPILE_COMPLETE 11 +#define TGS_EVENT_INSTANCE_AUTO_UPDATE_START 12 +#define TGS_EVENT_REPO_MERGE_CONFLICT 13 + +//OTHER ENUMS + +#define TGS_REBOOT_MODE_NORMAL 0 +#define TGS_REBOOT_MODE_SHUTDOWN 1 +#define TGS_REBOOT_MODE_RESTART 2 + +#define TGS_SECURITY_TRUSTED 0 +#define TGS_SECURITY_SAFE 1 +#define TGS_SECURITY_ULTRASAFE 2 + +//REQUIRED HOOKS + +//Call this somewhere in /world/New() that is always run +//event_handler: optional user defined event handler. The default behaviour is to broadcast the event in english to all connected admin channels +//minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated +/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) + return + +//Call this when your initializations are complete and your game is ready to play before any player interactions happen +//This may use world.sleep_offline to make this happen so ensure no changes are made to it while this call is running +//Most importantly, before this point, note that any static files or directories may be in use by another server. Your code should account for this +//This function should not be called before ..() in /world/New() +/world/proc/TgsInitializationComplete() + return + +//Put this at the start of /world/Topic() +#define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return + +//Call this at the beginning of world/Reboot(reason) +/world/proc/TgsReboot() + return + +//DATUM DEFINITIONS +//unless otherwise specified all datums defined here should be considered read-only, warranty void if written + +//represents git revision information about the current world build +/datum/tgs_revision_information + var/commit //full sha of compiled commit + var/origin_commit //full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch + +//represents a merge of a GitHub pull request +/datum/tgs_revision_information/test_merge + var/number //pull request number + var/title //pull request title + var/body //pull request body + var/author //pull request github author + var/url //link to pull request html + var/pull_request_commit //commit of the pull request when it was merged + var/time_merged //timestamp of when the merge commit for the pull request was created + var/comment //optional comment left by the one who initiated the test merge + +//represents a connected chat channel +/datum/tgs_chat_channel + var/id //internal channel representation + var/friendly_name //user friendly channel name + var/connection_name //the name of the configured chat connection + var/is_admin_channel //if the server operator has marked this channel for game admins only + var/is_private_channel //if this is a private chat channel + var/custom_tag //user defined string associated with channel + +//represents a chat user +/datum/tgs_chat_user + var/id //Internal user representation, requires channel to be unique + var/friendly_name //The user's public name + var/mention //The text to use to ping this user in a message + var/datum/tgs_chat_channel/channel //The /datum/tgs_chat_channel this user was from + +//user definable callback for handling events +//extra parameters may be specified depending on the event +/datum/tgs_event_handler/proc/HandleEvent(event_code, ...) + set waitfor = FALSE + return + +//user definable chat command +/datum/tgs_chat_command + var/name = "" //the string to trigger this command on a chat bot. e.g. TGS3_BOT: do_this_command + var/help_text = "" //help text for this command + var/admin_only = FALSE //set to TRUE if this command should only be usable by registered chat admins + +//override to implement command +//sender: The tgs_chat_user who send to command +//params: The trimmed string following the command name +//The return value will be stringified and sent to the appropriate chat +/datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params) + CRASH("[type] has no implementation for Run()") + +//FUNCTIONS + +//Returns the respective string version of the API +/world/proc/TgsMaximumAPIVersion() + return + +/world/proc/TgsMinimumAPIVersion() + return + +//Gets the current version of the server tools running the server +/world/proc/TgsVersion() + return + +//Returns TRUE if the world was launched under the server tools and the API matches, FALSE otherwise +//No function below this succeeds if it returns FALSE +/world/proc/TgsAvailable() + return + +/world/proc/TgsInstanceName() + return + +//Get the current `/datum/tgs_revision_information` +/world/proc/TgsRevision() + return + +//Get the current BYOND security level +/world/proc/TgsSecurityLevel() + return + +//Gets a list of active `/datum/tgs_revision_information/test_merge`s +/world/proc/TgsTestMerges() + return + +//Forces a hard reboot of BYOND by ending the process +//unlike del(world) clients will try to reconnect +//If the service has not requested a shutdown, the next server will take over +/world/proc/TgsEndProcess() + return + +//Gets a list of connected tgs_chat_channel +/world/proc/TgsChatChannelInfo() + return + +//Sends a message to connected game chats +//message: The message to send +//channels: optional channels to limit the broadcast to +/world/proc/TgsChatBroadcast(message, list/channels) + return + +//Send a message to non-admin connected chats +//message: The message to send +//admin_only: If TRUE, message will instead be sent to only admin connected chats +/world/proc/TgsTargetedChatBroadcast(message, admin_only) + return + +//Send a private message to a specific user +//message: The message to send +//user: The /datum/tgs_chat_user to send to +/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) + return + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ \ No newline at end of file diff --git a/code/__DEFINES/tools.dm b/code/__DEFINES/tools.dm index 00e08129ae..3fd6b069d4 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" - - -// 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" + + +// 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 960d0479be..34063c1787 100644 --- a/code/__DEFINES/wires.dm +++ b/code/__DEFINES/wires.dm @@ -1,50 +1,50 @@ -//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" +//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" diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 3b8b3765ef..8e44b57396 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -1,582 +1,582 @@ -/* - * 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 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()) - -// binary search sorted insert -// IN: Object to be inserted -// LIST: List to insert object into -// TYPECONT: The typepath of the contents of the list -// COMPARE: The variable on the objects to compare -#define BINARY_INSERT(IN, LIST, TYPECONT, COMPARE) \ - var/__BIN_CTTL = length(LIST);\ - if(!__BIN_CTTL) {\ - LIST += IN;\ - } 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 = LIST[__BIN_MID];\ - if(__BIN_ITEM.##COMPARE <= IN.##COMPARE) {\ - __BIN_LEFT = __BIN_MID + 1;\ - } else {\ - __BIN_RIGHT = __BIN_MID;\ - };\ - __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ - };\ - __BIN_ITEM = LIST[__BIN_MID];\ - __BIN_MID = __BIN_ITEM.##COMPARE > IN.##COMPARE ? __BIN_MID : __BIN_MID + 1;\ - LIST.Insert(__BIN_MID, IN);\ - } - -//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 = input.len - if (!total) - return "[nothing_text]" - else if (total == 1) - return "[input[1]]" - else if (total == 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]]" - -//Returns list element or null. Should prevent "index out of bounds" error. -/proc/listgetindex(list/L, index) - if(LAZYLEN(L)) - if(isnum(index) && ISINTEGER(index)) - if(ISINRANGE(index,1,L.len)) - return L[index] - else if(index in L) - return L[index] - return - -//Return either pick(list) or null if list is not of type /list or is empty -/proc/safepick(list/L) - if(LAZYLEN(L)) - return pick(L) - -//Checks if the list is empty -/proc/isemptylist(list/L) - if(!L.len) - return TRUE - return FALSE - -//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)]) - -//Checks for a string in a list -/proc/is_string_in_list(string, list/L) - if(!LAZYLEN(L) || !string) - return - for(var/V in L) - if(string == V) - return TRUE - return - -//Removes a string from a list -/proc/remove_strings_from_list(string, list/L) - if(!LAZYLEN(L) || !string) - return - for(var/V in L) - if(V == string) - L -= V //No return here so that it removes all strings of that type - return - -//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 - -//Empties the list by setting the length to 0. Hopefully the elements get garbage collected -/proc/clearlist(list/list) - if(istype(list)) - list.len = 0 - return - -//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, 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 - -/proc/count_occurences_of_value(list/L, val, limit) //special thanks to salmonsnake - . = 0 - for (var/i in 1 to limit) - if (L[i] == val) - .++ - -/proc/find_record(field, value, list/L) - for(var/datum/data/record/R in L) - if(R.fields[field] == value) - return R - - -//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 +/* + * 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 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()) + +// binary search sorted insert +// IN: Object to be inserted +// LIST: List to insert object into +// TYPECONT: The typepath of the contents of the list +// COMPARE: The variable on the objects to compare +#define BINARY_INSERT(IN, LIST, TYPECONT, COMPARE) \ + var/__BIN_CTTL = length(LIST);\ + if(!__BIN_CTTL) {\ + LIST += IN;\ + } 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 = LIST[__BIN_MID];\ + if(__BIN_ITEM.##COMPARE <= IN.##COMPARE) {\ + __BIN_LEFT = __BIN_MID + 1;\ + } else {\ + __BIN_RIGHT = __BIN_MID;\ + };\ + __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + };\ + __BIN_ITEM = LIST[__BIN_MID];\ + __BIN_MID = __BIN_ITEM.##COMPARE > IN.##COMPARE ? __BIN_MID : __BIN_MID + 1;\ + LIST.Insert(__BIN_MID, IN);\ + } + +//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 = input.len + if (!total) + return "[nothing_text]" + else if (total == 1) + return "[input[1]]" + else if (total == 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]]" + +//Returns list element or null. Should prevent "index out of bounds" error. +/proc/listgetindex(list/L, index) + if(LAZYLEN(L)) + if(isnum(index) && ISINTEGER(index)) + if(ISINRANGE(index,1,L.len)) + return L[index] + else if(index in L) + return L[index] + return + +//Return either pick(list) or null if list is not of type /list or is empty +/proc/safepick(list/L) + if(LAZYLEN(L)) + return pick(L) + +//Checks if the list is empty +/proc/isemptylist(list/L) + if(!L.len) + return TRUE + return FALSE + +//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)]) + +//Checks for a string in a list +/proc/is_string_in_list(string, list/L) + if(!LAZYLEN(L) || !string) + return + for(var/V in L) + if(string == V) + return TRUE + return + +//Removes a string from a list +/proc/remove_strings_from_list(string, list/L) + if(!LAZYLEN(L) || !string) + return + for(var/V in L) + if(V == string) + L -= V //No return here so that it removes all strings of that type + return + +//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 + +//Empties the list by setting the length to 0. Hopefully the elements get garbage collected +/proc/clearlist(list/list) + if(istype(list)) + list.len = 0 + return + +//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, 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 + +/proc/count_occurences_of_value(list/L, val, limit) //special thanks to salmonsnake + . = 0 + for (var/i in 1 to limit) + if (L[i] == val) + .++ + +/proc/find_record(field, value, list/L) + for(var/datum/data/record/R in L) + if(R.fields[field] == value) + return R + + +//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 \ No newline at end of file diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm index 5271b407a5..4160c1976a 100644 --- a/code/__HELPERS/files.dm +++ b/code/__HELPERS/files.dm @@ -1,73 +1,73 @@ -//Sends resource files to client cache -/client/proc/getFiles() - for(var/file in args) - src << browse_rsc(file) - -/client/proc/browse_files(root="data/logs/", max_iterations=10, list/valid_extensions=list("txt","log","htm", "html", "md")) - 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="data/logs/", max_iterations=10, list/valid_extensions=list("txt","log","htm", "html", "md")) + 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 65c7145d8f..ca4b913369 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -1,570 +1,570 @@ -//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) - -// 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() - while(processing_list.len) - var/atom/A = processing_list[1] - if(A.flags_1 & HEAR_1) - . += A - processing_list.Cut(1, 2) - 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 - - -// Better recursive loop, technically sort of not actually recursive cause that shit is retarded, enjoy. -//No need for a recursive limit either -/proc/recursive_mob_check(atom/O,client_check=1,sight_check=1,include_radio=1) - - var/list/processing_list = list(O) - var/list/processed_list = list() - var/list/found_mobs = list() - - while(processing_list.len) - - var/atom/A = processing_list[1] - var/passed = 0 - - if(ismob(A)) - var/mob/A_tmp = A - passed=1 - - if(client_check && !A_tmp.client) - passed=0 - - if(sight_check && !isInSight(A_tmp, O)) - passed=0 - - else if(include_radio && istype(A, /obj/item/radio)) - passed=1 - - if(sight_check && !isInSight(A, O)) - passed=0 - - if(passed) - found_mobs |= A - - for(var/atom/B in A) - if(!processed_list[B]) - processing_list |= B - - processing_list.Cut(1, 2) - processed_list[A] = A - - return found_mobs - - -/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 - var/list/cachedview = view(R, T) - for(var/mob/M in cachedview) - processing_list += M - for(var/obj/O in cachedview) - processing_list += O - T.luminosity = lum - - while(processing_list.len) // recursive_hear_check inlined here - var/atom/A = processing_list[1] - if(A.flags_1 & HEAR_1) - . += A - SEND_SIGNAL(A, COMSIG_ATOM_HEARER_IN_VIEW, processing_list, .) - processing_list.Cut(1, 2) - 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(X1 abs (dx)) //slope is above 1:1 (move horizontally in a tie) - if(dy > 0) - return get_step(start, SOUTH) - else - return get_step(start, NORTH) - else - if(dx > 0) - return get_step(start, WEST) - else - return get_step(start, EAST) - - -/proc/try_move_adjacent(atom/movable/AM) - var/turf/T = get_turf(AM) - for(var/direction in GLOB.cardinals) - if(AM.Move(get_step(T, direction))) - break - -/proc/get_mob_by_key(key) - var/ckey = ckey(key) - for(var/i in GLOB.player_list) - var/mob/M = i - if(M.ckey == ckey) - return M - return null - -/proc/considered_alive(datum/mind/M, enforce_human = TRUE) - if(M && M.current) - if(enforce_human) - var/mob/living/carbon/human/H - if(ishuman(M.current)) - H = M.current - return M.current.stat != DEAD && !issilicon(M.current) && !isbrain(M.current) && (!H || H.dna.species.id != "memezombies") - else if(isliving(M.current)) - return M.current.stat != DEAD - return FALSE - -/proc/considered_afk(datum/mind/M) - return !M || !M.current || !M.current.client || M.current.client.is_afk() - -/proc/ScreenText(obj/O, maptext="", screen_loc="CENTER-7,CENTER-7", maptext_height=480, maptext_width=480) - if(!isobj(O)) - O = new /obj/screen/text() - O.maptext = maptext - O.maptext_height = maptext_height - O.maptext_width = maptext_width - O.screen_loc = screen_loc - return O - -/proc/remove_images_from_clients(image/I, list/show_to) - for(var/client/C in show_to) - C.images -= I - -/proc/flick_overlay(image/I, list/show_to, duration) - for(var/client/C in show_to) - C.images += I - addtimer(CALLBACK(GLOBAL_PROC, /proc/remove_images_from_clients, I, show_to), duration, TIMER_CLIENT_TIME) - -/proc/flick_overlay_view(image/I, atom/target, duration) //wrapper for the above, flicks to everyone who can see the target atom - var/list/viewing = list() - for(var/m in viewers(target)) - var/mob/M = m - if(M.client) - viewing += M.client - flick_overlay(I, viewing, duration) - -/proc/get_active_player_count(var/alive_check = 0, var/afk_check = 0, var/human_check = 0) - // Get active players who are playing in the round - var/active_players = 0 - for(var/i = 1; i <= GLOB.player_list.len; i++) - var/mob/M = GLOB.player_list[i] - if(M && M.client) - if(alive_check && M.stat) - continue - else if(afk_check && M.client.is_afk()) - continue - else if(human_check && !ishuman(M)) - continue - else if(isnewplayer(M)) // exclude people in the lobby - continue - else if(isobserver(M)) // Ghosts are fine if they were playing once (didn't start as observers) - var/mob/dead/observer/O = M - if(O.started_as_observer) // Exclude people who started as observers - continue - active_players++ - return active_players - -/proc/showCandidatePollWindow(mob/M, poll_time, Question, list/candidates, ignore_category, time_passed, flashwindow = TRUE) - set waitfor = 0 - - SEND_SOUND(M, 'sound/misc/notice2.ogg') //Alerting them to their consideration - if(flashwindow) - window_flash(M.client) - switch(ignore_category ? askuser(M,Question,"Please answer in [DisplayTimeText(poll_time)]!","Yes","No","Never for this round", StealFocus=0, Timeout=poll_time) : askuser(M,Question,"Please answer in [DisplayTimeText(poll_time)]!","Yes","No", StealFocus=0, Timeout=poll_time)) - if(1) - to_chat(M, "Choice 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/datum/element/ghost_role_eligibility/eligibility = SSdcs.GetElement(/datum/element/ghost_role_eligibility) - var/list/candidates = eligibility.get_all_ghost_role_eligible() - 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(jobban_isbanned(M, jobbanType) || QDELETED(M) || jobban_isbanned(M, 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/poll_helper(var/mob/living/M) - -/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() - G_found.transfer_ckey(new_character, FALSE) - - 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") - -/proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank) - if(!SSticker.IsRoundInProgress() || QDELETED(character)) - return - var/area/A = get_area(character) - var/message = "\ - [character.real_name] ([rank]) has arrived at the station at \ - [A.name]." - deadchat_broadcast(message, 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/GetHexColors(const/hexa) - return list( - GetRedPart(hexa)/ 255, - GetGreenPart(hexa)/ 255, - GetBluePart(hexa)/ 255 - ) - -/proc/GetRedPart(const/hexa) - return hex2num(copytext(hexa, 2, 4)) - -/proc/GetGreenPart(const/hexa) - return hex2num(copytext(hexa, 4, 6)) - -/proc/GetBluePart(const/hexa) - return hex2num(copytext(hexa, 6, 8)) - -/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 +//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) + +// 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() + while(processing_list.len) + var/atom/A = processing_list[1] + if(A.flags_1 & HEAR_1) + . += A + processing_list.Cut(1, 2) + 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 + + +// Better recursive loop, technically sort of not actually recursive cause that shit is retarded, enjoy. +//No need for a recursive limit either +/proc/recursive_mob_check(atom/O,client_check=1,sight_check=1,include_radio=1) + + var/list/processing_list = list(O) + var/list/processed_list = list() + var/list/found_mobs = list() + + while(processing_list.len) + + var/atom/A = processing_list[1] + var/passed = 0 + + if(ismob(A)) + var/mob/A_tmp = A + passed=1 + + if(client_check && !A_tmp.client) + passed=0 + + if(sight_check && !isInSight(A_tmp, O)) + passed=0 + + else if(include_radio && istype(A, /obj/item/radio)) + passed=1 + + if(sight_check && !isInSight(A, O)) + passed=0 + + if(passed) + found_mobs |= A + + for(var/atom/B in A) + if(!processed_list[B]) + processing_list |= B + + processing_list.Cut(1, 2) + processed_list[A] = A + + return found_mobs + + +/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 + var/list/cachedview = view(R, T) + for(var/mob/M in cachedview) + processing_list += M + for(var/obj/O in cachedview) + processing_list += O + T.luminosity = lum + + while(processing_list.len) // recursive_hear_check inlined here + var/atom/A = processing_list[1] + if(A.flags_1 & HEAR_1) + . += A + SEND_SIGNAL(A, COMSIG_ATOM_HEARER_IN_VIEW, processing_list, .) + processing_list.Cut(1, 2) + 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(X1 abs (dx)) //slope is above 1:1 (move horizontally in a tie) + if(dy > 0) + return get_step(start, SOUTH) + else + return get_step(start, NORTH) + else + if(dx > 0) + return get_step(start, WEST) + else + return get_step(start, EAST) + + +/proc/try_move_adjacent(atom/movable/AM) + var/turf/T = get_turf(AM) + for(var/direction in GLOB.cardinals) + if(AM.Move(get_step(T, direction))) + break + +/proc/get_mob_by_key(key) + var/ckey = ckey(key) + for(var/i in GLOB.player_list) + var/mob/M = i + if(M.ckey == ckey) + return M + return null + +/proc/considered_alive(datum/mind/M, enforce_human = TRUE) + if(M && M.current) + if(enforce_human) + var/mob/living/carbon/human/H + if(ishuman(M.current)) + H = M.current + return M.current.stat != DEAD && !issilicon(M.current) && !isbrain(M.current) && (!H || H.dna.species.id != "memezombies") + else if(isliving(M.current)) + return M.current.stat != DEAD + return FALSE + +/proc/considered_afk(datum/mind/M) + return !M || !M.current || !M.current.client || M.current.client.is_afk() + +/proc/ScreenText(obj/O, maptext="", screen_loc="CENTER-7,CENTER-7", maptext_height=480, maptext_width=480) + if(!isobj(O)) + O = new /obj/screen/text() + O.maptext = maptext + O.maptext_height = maptext_height + O.maptext_width = maptext_width + O.screen_loc = screen_loc + return O + +/proc/remove_images_from_clients(image/I, list/show_to) + for(var/client/C in show_to) + C.images -= I + +/proc/flick_overlay(image/I, list/show_to, duration) + for(var/client/C in show_to) + C.images += I + addtimer(CALLBACK(GLOBAL_PROC, /proc/remove_images_from_clients, I, show_to), duration, TIMER_CLIENT_TIME) + +/proc/flick_overlay_view(image/I, atom/target, duration) //wrapper for the above, flicks to everyone who can see the target atom + var/list/viewing = list() + for(var/m in viewers(target)) + var/mob/M = m + if(M.client) + viewing += M.client + flick_overlay(I, viewing, duration) + +/proc/get_active_player_count(var/alive_check = 0, var/afk_check = 0, var/human_check = 0) + // Get active players who are playing in the round + var/active_players = 0 + for(var/i = 1; i <= GLOB.player_list.len; i++) + var/mob/M = GLOB.player_list[i] + if(M && M.client) + if(alive_check && M.stat) + continue + else if(afk_check && M.client.is_afk()) + continue + else if(human_check && !ishuman(M)) + continue + else if(isnewplayer(M)) // exclude people in the lobby + continue + else if(isobserver(M)) // Ghosts are fine if they were playing once (didn't start as observers) + var/mob/dead/observer/O = M + if(O.started_as_observer) // Exclude people who started as observers + continue + active_players++ + return active_players + +/proc/showCandidatePollWindow(mob/M, poll_time, Question, list/candidates, ignore_category, time_passed, flashwindow = TRUE) + set waitfor = 0 + + SEND_SOUND(M, 'sound/misc/notice2.ogg') //Alerting them to their consideration + if(flashwindow) + window_flash(M.client) + switch(ignore_category ? askuser(M,Question,"Please answer in [DisplayTimeText(poll_time)]!","Yes","No","Never for this round", StealFocus=0, Timeout=poll_time) : askuser(M,Question,"Please answer in [DisplayTimeText(poll_time)]!","Yes","No", StealFocus=0, Timeout=poll_time)) + if(1) + to_chat(M, "Choice 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/datum/element/ghost_role_eligibility/eligibility = SSdcs.GetElement(/datum/element/ghost_role_eligibility) + var/list/candidates = eligibility.get_all_ghost_role_eligible() + 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(jobban_isbanned(M, jobbanType) || QDELETED(M) || jobban_isbanned(M, 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/poll_helper(var/mob/living/M) + +/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() + G_found.transfer_ckey(new_character, FALSE) + + 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") + +/proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank) + if(!SSticker.IsRoundInProgress() || QDELETED(character)) + return + var/area/A = get_area(character) + var/message = "\ + [character.real_name] ([rank]) has arrived at the station at \ + [A.name]." + deadchat_broadcast(message, 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/GetHexColors(const/hexa) + return list( + GetRedPart(hexa)/ 255, + GetGreenPart(hexa)/ 255, + GetBluePart(hexa)/ 255 + ) + +/proc/GetRedPart(const/hexa) + return hex2num(copytext(hexa, 2, 4)) + +/proc/GetGreenPart(const/hexa) + return hex2num(copytext(hexa, 4, 6)) + +/proc/GetBluePart(const/hexa) + return hex2num(copytext(hexa, 6, 8)) + +/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 diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index a7bf5da17f..ff9d1bec4c 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -1,128 +1,128 @@ -////////////////////////// -/////Initial Building///// -////////////////////////// - -/proc/make_datum_references_lists() - //hair - init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, GLOB.hair_styles_list, GLOB.hair_styles_male_list, GLOB.hair_styles_female_list) - //facial hair - init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hair_styles_list, GLOB.facial_hair_styles_male_list, GLOB.facial_hair_styles_female_list) - //underwear - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/bottom, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) - //undershirt - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/top, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) - //socks - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/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/insect_wings, GLOB.insect_wings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/insect_fluff, GLOB.insect_fluffs_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/deco_wings, GLOB.deco_wings_list) - -//CIT CHANGES START HERE, ADDS SNOWFLAKE BODYPARTS AND MORE - //mammal bodyparts (fucking furries) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_snouts, GLOB.mam_snouts_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails_animated, GLOB.mam_tails_animated_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/taur, GLOB.taur_list) - //xeno parts (hiss?) - init_sprite_accessory_subtypes(/datum/sprite_accessory/xeno_head, GLOB.xeno_head_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/xeno_tail, GLOB.xeno_tail_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/xeno_dorsal, GLOB.xeno_dorsal_list) - //ipcs - init_sprite_accessory_subtypes(/datum/sprite_accessory/screen, GLOB.ipc_screens_list, roundstart = TRUE) - init_sprite_accessory_subtypes(/datum/sprite_accessory/antenna, GLOB.ipc_antennas_list, roundstart = TRUE) - //genitals - init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list) - for(var/K in GLOB.cock_shapes_list) - var/datum/sprite_accessory/penis/value = GLOB.cock_shapes_list[K] - GLOB.cock_shapes_icons[K] = value.icon_state - - init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list) - GLOB.breasts_size_list = list ("a", "b", "c", "d", "e") //We need the list to choose from initialized, but it's no longer a sprite_accessory thing. - GLOB.gentlemans_organ_names = list("phallus", "willy", "dick", "prick", "member", "tool", "gentleman's organ", - "cock", "wang", "knob", "dong", "joystick", "pecker", "johnson", "weenie", "tadger", "schlong", "thirsty ferret", - "baloney pony", "schlanger", "Mutton dagger", "old blind bob","Hanging Johnny", "fishing rod", "Tally whacker", "polly rocket", - "One eyed trouser trout", "Ding dong", "ankle spanker", "Pork sword", "engine cranker", "Harry hot dog", "Davy Crockett", - "Kidney cracker", "Heat seeking moisture missile", "Giggle stick", "love whistle", "Tube steak", "Uncle Dick", "Purple helmet warrior") - for(var/K in GLOB.breasts_shapes_list) - var/datum/sprite_accessory/breasts/value = GLOB.breasts_shapes_list[K] - GLOB.breasts_shapes_icons[K] = value.icon_state - - init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list) - for(var/K in GLOB.balls_shapes_list) - var/datum/sprite_accessory/testicles/value = GLOB.balls_shapes_list[K] - GLOB.balls_shapes_icons[K] = value.icon_state - - for(var/gpath in subtypesof(/obj/item/organ/genital)) - var/obj/item/organ/genital/G = gpath - if(!CHECK_BITFIELD(initial(G.genital_flags), GENITAL_BLACKLISTED)) - GLOB.genitals_list[initial(G.name)] = gpath -//END OF CIT CHANGES - - //Species - for(var/spath in subtypesof(/datum/species)) - var/datum/species/S = new spath() - GLOB.species_list[S.id] = spath - - //Surgeries - for(var/path in subtypesof(/datum/surgery)) - GLOB.surgeries_list += new path() - - //Materials - for(var/path in subtypesof(/datum/material)) - var/datum/material/D = new path() - GLOB.materials_list[D.id] = D - - //Emotes - for(var/path in subtypesof(/datum/emote)) - var/datum/emote/E = new path() - E.emote_list[E.key] = E - - //Uplink Items - for(var/path in subtypesof(/datum/uplink_item)) - var/datum/uplink_item/I = path - if(!initial(I.item)) //We add categories to a separate list. - GLOB.uplink_categories |= initial(I.category) - continue - GLOB.uplink_items += path - //(sub)typesof entries are listed by the order they are loaded in the code, so we'll have to rearrange them here. - GLOB.uplink_items = sortList(GLOB.uplink_items, /proc/cmp_uplink_items_dsc) - - 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.hair_styles_list, GLOB.hair_styles_male_list, GLOB.hair_styles_female_list) + //facial hair + init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hair_styles_list, GLOB.facial_hair_styles_male_list, GLOB.facial_hair_styles_female_list) + //underwear + init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/bottom, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) + //undershirt + init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/top, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) + //socks + init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/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/insect_wings, GLOB.insect_wings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/insect_fluff, GLOB.insect_fluffs_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/deco_wings, GLOB.deco_wings_list) + +//CIT CHANGES START HERE, ADDS SNOWFLAKE BODYPARTS AND MORE + //mammal bodyparts (fucking furries) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_snouts, GLOB.mam_snouts_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails_animated, GLOB.mam_tails_animated_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/taur, GLOB.taur_list) + //xeno parts (hiss?) + init_sprite_accessory_subtypes(/datum/sprite_accessory/xeno_head, GLOB.xeno_head_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/xeno_tail, GLOB.xeno_tail_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/xeno_dorsal, GLOB.xeno_dorsal_list) + //ipcs + init_sprite_accessory_subtypes(/datum/sprite_accessory/screen, GLOB.ipc_screens_list, roundstart = TRUE) + init_sprite_accessory_subtypes(/datum/sprite_accessory/antenna, GLOB.ipc_antennas_list, roundstart = TRUE) + //genitals + init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list) + for(var/K in GLOB.cock_shapes_list) + var/datum/sprite_accessory/penis/value = GLOB.cock_shapes_list[K] + GLOB.cock_shapes_icons[K] = value.icon_state + + init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list) + init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list) + GLOB.breasts_size_list = list ("a", "b", "c", "d", "e") //We need the list to choose from initialized, but it's no longer a sprite_accessory thing. + GLOB.gentlemans_organ_names = list("phallus", "willy", "dick", "prick", "member", "tool", "gentleman's organ", + "cock", "wang", "knob", "dong", "joystick", "pecker", "johnson", "weenie", "tadger", "schlong", "thirsty ferret", + "baloney pony", "schlanger", "Mutton dagger", "old blind bob","Hanging Johnny", "fishing rod", "Tally whacker", "polly rocket", + "One eyed trouser trout", "Ding dong", "ankle spanker", "Pork sword", "engine cranker", "Harry hot dog", "Davy Crockett", + "Kidney cracker", "Heat seeking moisture missile", "Giggle stick", "love whistle", "Tube steak", "Uncle Dick", "Purple helmet warrior") + for(var/K in GLOB.breasts_shapes_list) + var/datum/sprite_accessory/breasts/value = GLOB.breasts_shapes_list[K] + GLOB.breasts_shapes_icons[K] = value.icon_state + + init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list) + for(var/K in GLOB.balls_shapes_list) + var/datum/sprite_accessory/testicles/value = GLOB.balls_shapes_list[K] + GLOB.balls_shapes_icons[K] = value.icon_state + + for(var/gpath in subtypesof(/obj/item/organ/genital)) + var/obj/item/organ/genital/G = gpath + if(!CHECK_BITFIELD(initial(G.genital_flags), GENITAL_BLACKLISTED)) + GLOB.genitals_list[initial(G.name)] = gpath +//END OF CIT CHANGES + + //Species + for(var/spath in subtypesof(/datum/species)) + var/datum/species/S = new spath() + GLOB.species_list[S.id] = spath + + //Surgeries + for(var/path in subtypesof(/datum/surgery)) + GLOB.surgeries_list += new path() + + //Materials + for(var/path in subtypesof(/datum/material)) + var/datum/material/D = new path() + GLOB.materials_list[D.id] = D + + //Emotes + for(var/path in subtypesof(/datum/emote)) + var/datum/emote/E = new path() + E.emote_list[E.key] = E + + //Uplink Items + for(var/path in subtypesof(/datum/uplink_item)) + var/datum/uplink_item/I = path + if(!initial(I.item)) //We add categories to a separate list. + GLOB.uplink_categories |= initial(I.category) + continue + GLOB.uplink_items += path + //(sub)typesof entries are listed by the order they are loaded in the code, so we'll have to rearrange them here. + GLOB.uplink_items = sortList(GLOB.uplink_items, /proc/cmp_uplink_items_dsc) + + 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 1af3143d9d..8e88f869a9 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1,1211 +1,1211 @@ -/* -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/overlayTest) - - 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)) - -// Creates a single icon from a given /atom or /image. 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/effects/effects.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 = TRUE)//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 - -/proc/getPAIHologramIcon(icon/A, safety = TRUE) - var/icon/flat_icon = safety? A : new(A) - flat_icon.SetIntensity(0.75, 1, 0.75) - flat_icon.ChangeOpacity(0.7) - var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanlineslow")//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 = copytext(A.name, 1, 2) - 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 - - -//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, iconKey = "misc") - if (!isicon(icon)) - return FALSE - WRITE_FILE(GLOB.iconCache[iconKey], icon) - var/iconData = GLOB.iconCache.ExportText(iconKey) - var/list/partial = splittext(iconData, "{") - return replacetext(copytext(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") - register_asset(name, thing) - for (var/thing2 in targets) - send_asset(thing2, key, FALSE) - 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" - register_asset(key, I) - for (var/thing2 in targets) - send_asset(thing2, key, FALSE) - - 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, key) - - 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/overlayTest) + + 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)) + +// Creates a single icon from a given /atom or /image. 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/effects/effects.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 = TRUE)//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 + +/proc/getPAIHologramIcon(icon/A, safety = TRUE) + var/icon/flat_icon = safety? A : new(A) + flat_icon.SetIntensity(0.75, 1, 0.75) + flat_icon.ChangeOpacity(0.7) + var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanlineslow")//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 = copytext(A.name, 1, 2) + 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 + + +//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, iconKey = "misc") + if (!isicon(icon)) + return FALSE + WRITE_FILE(GLOB.iconCache[iconKey], icon) + var/iconData = GLOB.iconCache.ExportText(iconKey) + var/list/partial = splittext(iconData, "{") + return replacetext(copytext(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") + register_asset(name, thing) + for (var/thing2 in targets) + send_asset(thing2, key, FALSE) + 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" + register_asset(key, I) + for (var/thing2 in targets) + send_asset(thing2, key, FALSE) + + 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, key) + + 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/mobs.dm b/code/__HELPERS/mobs.dm index cdf0f604b8..339069a364 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -1,584 +1,584 @@ -/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/bottom, 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/underwear/top, 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/underwear/socks, GLOB.socks_list) - return pick(GLOB.socks_list) - -/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.ears_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.deco_wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/deco_wings, GLOB.deco_wings_list) - if(!GLOB.insect_wings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/insect_wings, GLOB.insect_wings_list) - if(!GLOB.insect_fluffs_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/insect_fluff, GLOB.insect_fluffs_list) - if(!GLOB.insect_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/insect_markings, GLOB.insect_markings_list) - - //CIT CHANGES - genitals and such - if(!GLOB.cock_shapes_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list) - if(!GLOB.balls_shapes_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list) - if(!GLOB.vagina_shapes_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list) - if(!GLOB.breasts_shapes_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list) - if(!GLOB.ipc_screens_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/screen, GLOB.ipc_screens_list) - if(!GLOB.ipc_antennas_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/antenna, GLOB.ipc_antennas_list) - if(!GLOB.mam_body_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list) - if(!GLOB.mam_tails_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list) - if(!GLOB.mam_ears_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list) - if(!GLOB.mam_snouts_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_snouts, GLOB.mam_snouts_list) - - //snowflake check so people's ckey features don't get randomly put on unmonkeys/spawns - var/list/snowflake_mam_tails_list = list() - for(var/mtpath in GLOB.mam_tails_list) - var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[mtpath] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if(!S.ckeys_allowed) - snowflake_mam_tails_list[S.name] = mtpath - var/list/snowflake_markings_list = list() - for(var/mmpath in GLOB.mam_body_markings_list) - var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[mmpath] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if(!S.ckeys_allowed) - snowflake_markings_list[S.name] = mmpath - var/list/snowflake_ears_list = list() - for(var/mepath in GLOB.mam_ears_list) - var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[mepath] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if(!S.ckeys_allowed) - snowflake_ears_list[S.name] = mepath - var/list/snowflake_mam_snouts_list = list() - for(var/mspath in GLOB.mam_snouts_list) - var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[mspath] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if(!S.ckeys_allowed) - snowflake_mam_snouts_list[S.name] = mspath - var/color1 = random_short_color() - var/color2 = random_short_color() - var/color3 = random_short_color() - - //CIT CHANGE - changes this entire return to support cit's snowflake parts - return(list( - "mcolor" = color1, - "mcolor2" = color2, - "mcolor3" = color3, - "tail_lizard" = pick(GLOB.tails_list_lizard), - "tail_human" = "None", - "wings" = "None", - "deco_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" = pick("Plantigrade","Digitigrade"), - "caps" = pick(GLOB.caps_list), - "insect_wings" = pick(GLOB.insect_wings_list), - "insect_fluff" = "None", - "insect_markings" = pick(GLOB.insect_markings_list), - "taur" = "None", - "mam_body_markings" = pick(snowflake_markings_list), - "mam_ears" = pick(snowflake_ears_list), - "mam_snouts" = pick(snowflake_mam_snouts_list), - "mam_tail" = pick(snowflake_mam_tails_list), - "mam_tail_animated" = "None", - "xenodorsal" = "Standard", - "xenohead" = "Standard", - "xenotail" = "Xenomorph Tail", - "genitals_use_skintone" = FALSE, - "has_cock" = FALSE, - "cock_shape" = pick(GLOB.cock_shapes_list), - "cock_length" = 6, - "cock_girth_ratio" = COCK_GIRTH_RATIO_DEF, - "cock_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "has_sheath" = FALSE, - "sheath_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "has_balls" = FALSE, - "balls_internal" = FALSE, - "balls_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "balls_amount" = 2, - "balls_sack_size" = BALLS_SACK_SIZE_DEF, - "balls_size" = BALLS_SIZE_DEF, - "balls_shape" = "Single", - "balls_cum_rate" = CUM_RATE, - "balls_cum_mult" = CUM_RATE_MULT, - "balls_efficiency" = CUM_EFFICIENCY, - "balls_fluid" = "semen", - "has_ovi" = FALSE, - "ovi_shape" = "knotted", - "ovi_length" = 6, - "ovi_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "has_eggsack" = FALSE, - "eggsack_internal" = TRUE, - "eggsack_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "eggsack_size" = BALLS_SACK_SIZE_DEF, - "eggsack_egg_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "eggsack_egg_size" = EGG_GIRTH_DEF, - "has_breasts" = FALSE, - "breasts_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "breasts_size" = pick(GLOB.breasts_size_list), - "breasts_shape" = "Pair", - "breasts_fluid" = "milk", - "breasts_producing" = FALSE, - "has_vag" = FALSE, - "vag_shape" = pick(GLOB.vagina_shapes_list), - "vag_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), - "vag_clits" = 1, - "vag_clit_diam" = 0.25, - "vag_clit_len" = 0.25, - "has_womb" = FALSE, - "womb_cum_rate" = CUM_RATE, - "womb_cum_mult" = CUM_RATE_MULT, - "womb_efficiency" = CUM_EFFICIENCY, - "womb_fluid" = "femcum", - "ipc_screen" = "Sunburst", - "ipc_antenna" = "None", - "flavor_text" = "", - "meat_type" = "Mammalian" - )) - -/proc/random_hair_style(gender) - switch(gender) - if(MALE) - return pick(GLOB.hair_styles_male_list) - if(FEMALE) - return pick(GLOB.hair_styles_female_list) - else - return pick(GLOB.hair_styles_list) - -/proc/random_facial_hair_style(gender) - switch(gender) - if(MALE) - return pick(GLOB.facial_hair_styles_male_list) - if(FEMALE) - return pick(GLOB.facial_hair_styles_female_list) - else - return pick(GLOB.facial_hair_styles_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_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, 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" - -/proc/do_mob(mob/user , mob/target, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks = null, ignorehelditem = 0) - if(!user || !target) - return 0 - var/user_loc = user.loc - - var/drifting = 0 - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = 1 - - var/target_loc = target.loc - - var/holding = user.get_active_held_item() - var/datum/progressbar/progbar - if (progress) - progbar = new(user, time, target) - - var/endtime = world.time+time - var/starttime = world.time - . = 1 - while (world.time < endtime) - stoplag(1) - if (progress) - progbar.update(world.time - starttime) - if(QDELETED(user) || QDELETED(target)) - . = 0 - break - if(uninterruptible) - continue - - if(drifting && !user.inertia_dir) - drifting = 0 - user_loc = user.loc - - if((!drifting && user.loc != user_loc) || target.loc != target_loc || (!ignorehelditem && user.get_active_held_item() != holding) || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke())) - . = 0 - break - if (progress) - qdel(progbar) - - -//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 ..() - -/proc/do_after(mob/user, var/delay, needhand = 1, atom/target = null, progress = 1, datum/callback/extra_checks = null) - if(!user) - return 0 - var/atom/Tloc = null - if(target && !isturf(target)) - Tloc = target.loc - - var/atom/Uloc = user.loc - - var/drifting = 0 - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = 1 - - var/holding = user.get_active_held_item() - - var/holdingnull = 1 //User's hand started out empty, check for an empty hand - if(holding) - holdingnull = 0 //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) - - var/endtime = world.time + delay - var/starttime = world.time - . = 1 - while (world.time < endtime) - stoplag(1) - if (progress) - progbar.update(world.time - starttime) - - if(drifting && !user.inertia_dir) - drifting = 0 - Uloc = user.loc - - if(QDELETED(user) || user.stat || user.IsKnockdown() || user.IsStun() || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke())) - . = 0 - break - - if(isliving(user)) - var/mob/living/L = user - if(L.recoveringstam) - . = 0 - break - - if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc)) - if((Uloc != Tloc || Tloc != user) && !drifting) - . = 0 - 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) - . = 0 - break - if(user.get_active_held_item() != holding) - . = 0 - break - if (progress) - qdel(progbar) - -/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1 - . = 1 - return - -/proc/do_after_mob(mob/user, var/list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks) - if(!user || !targets) - return 0 - if(!islist(targets)) - targets = list(targets) - var/user_loc = user.loc - - var/drifting = 0 - if(!user.Process_Spacemove(0) && user.inertia_dir) - drifting = 1 - - var/list/originalloc = list() - for(var/atom/target in targets) - originalloc[target] = target.loc - - 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 - . = 1 - mainloop: - while(world.time < endtime) - stoplag(1) - if(progress) - progbar.update(world.time - starttime) - if(QDELETED(user) || !targets) - . = 0 - break - if(uninterruptible) - continue - - if(drifting && !user.inertia_dir) - drifting = 0 - user_loc = user.loc - - 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() || user.lying || (extra_checks && !extra_checks.Invoke())) - . = 0 - break mainloop - if(progbar) - qdel(progbar) - -/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 - - for(var/j in 1 to amount) - var/atom/X = new spawn_type(arglist(new_args)) - if (admin_spawn) - X.flags_1 |= ADMIN_SPAWNED_1 - -/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") - - for(var/j in 1 to amount) - var/atom/movable/X = new spawn_type(T) - if (admin_spawn) - X.flags_1 |= ADMIN_SPAWNED_1 - - 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)) - -/proc/deadchat_broadcast(message, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR) - message = "[message]" - for(var/mob/M in GLOB.player_list) - var/datum/preferences/prefs - if(M.client && M.client.prefs) - prefs = M.client.prefs - else - prefs = new - - var/override = FALSE - if(M.client && M.client.holder && (prefs.chat_toggles & CHAT_DEAD)) - override = TRUE - if(HAS_TRAIT(M, TRAIT_SIXTHSENSE)) - override = TRUE - if(isnewplayer(M) && !override) - continue - if(M.stat != DEAD && !override) - continue - if(speaker_key && speaker_key in prefs.ignoring) - continue - - switch(message_type) - if(DEADCHAT_DEATHRATTLE) - if(prefs.toggles & DISABLE_DEATHRATTLE) - continue - if(DEADCHAT_ARRIVALRATTLE) - if(prefs.toggles & DISABLE_ARRIVALRATTLE) - 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/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/bottom, 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/underwear/top, 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/underwear/socks, GLOB.socks_list) + return pick(GLOB.socks_list) + +/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.ears_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.deco_wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/deco_wings, GLOB.deco_wings_list) + if(!GLOB.insect_wings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/insect_wings, GLOB.insect_wings_list) + if(!GLOB.insect_fluffs_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/insect_fluff, GLOB.insect_fluffs_list) + if(!GLOB.insect_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/insect_markings, GLOB.insect_markings_list) + + //CIT CHANGES - genitals and such + if(!GLOB.cock_shapes_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list) + if(!GLOB.balls_shapes_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list) + if(!GLOB.vagina_shapes_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list) + if(!GLOB.breasts_shapes_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list) + if(!GLOB.ipc_screens_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/screen, GLOB.ipc_screens_list) + if(!GLOB.ipc_antennas_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/antenna, GLOB.ipc_antennas_list) + if(!GLOB.mam_body_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list) + if(!GLOB.mam_tails_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list) + if(!GLOB.mam_ears_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list) + if(!GLOB.mam_snouts_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_snouts, GLOB.mam_snouts_list) + + //snowflake check so people's ckey features don't get randomly put on unmonkeys/spawns + var/list/snowflake_mam_tails_list = list() + for(var/mtpath in GLOB.mam_tails_list) + var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[mtpath] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if(!S.ckeys_allowed) + snowflake_mam_tails_list[S.name] = mtpath + var/list/snowflake_markings_list = list() + for(var/mmpath in GLOB.mam_body_markings_list) + var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[mmpath] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if(!S.ckeys_allowed) + snowflake_markings_list[S.name] = mmpath + var/list/snowflake_ears_list = list() + for(var/mepath in GLOB.mam_ears_list) + var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[mepath] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if(!S.ckeys_allowed) + snowflake_ears_list[S.name] = mepath + var/list/snowflake_mam_snouts_list = list() + for(var/mspath in GLOB.mam_snouts_list) + var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[mspath] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if(!S.ckeys_allowed) + snowflake_mam_snouts_list[S.name] = mspath + var/color1 = random_short_color() + var/color2 = random_short_color() + var/color3 = random_short_color() + + //CIT CHANGE - changes this entire return to support cit's snowflake parts + return(list( + "mcolor" = color1, + "mcolor2" = color2, + "mcolor3" = color3, + "tail_lizard" = pick(GLOB.tails_list_lizard), + "tail_human" = "None", + "wings" = "None", + "deco_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" = pick("Plantigrade","Digitigrade"), + "caps" = pick(GLOB.caps_list), + "insect_wings" = pick(GLOB.insect_wings_list), + "insect_fluff" = "None", + "insect_markings" = pick(GLOB.insect_markings_list), + "taur" = "None", + "mam_body_markings" = pick(snowflake_markings_list), + "mam_ears" = pick(snowflake_ears_list), + "mam_snouts" = pick(snowflake_mam_snouts_list), + "mam_tail" = pick(snowflake_mam_tails_list), + "mam_tail_animated" = "None", + "xenodorsal" = "Standard", + "xenohead" = "Standard", + "xenotail" = "Xenomorph Tail", + "genitals_use_skintone" = FALSE, + "has_cock" = FALSE, + "cock_shape" = pick(GLOB.cock_shapes_list), + "cock_length" = 6, + "cock_girth_ratio" = COCK_GIRTH_RATIO_DEF, + "cock_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "has_sheath" = FALSE, + "sheath_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "has_balls" = FALSE, + "balls_internal" = FALSE, + "balls_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "balls_amount" = 2, + "balls_sack_size" = BALLS_SACK_SIZE_DEF, + "balls_size" = BALLS_SIZE_DEF, + "balls_shape" = "Single", + "balls_cum_rate" = CUM_RATE, + "balls_cum_mult" = CUM_RATE_MULT, + "balls_efficiency" = CUM_EFFICIENCY, + "balls_fluid" = "semen", + "has_ovi" = FALSE, + "ovi_shape" = "knotted", + "ovi_length" = 6, + "ovi_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "has_eggsack" = FALSE, + "eggsack_internal" = TRUE, + "eggsack_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "eggsack_size" = BALLS_SACK_SIZE_DEF, + "eggsack_egg_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "eggsack_egg_size" = EGG_GIRTH_DEF, + "has_breasts" = FALSE, + "breasts_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "breasts_size" = pick(GLOB.breasts_size_list), + "breasts_shape" = "Pair", + "breasts_fluid" = "milk", + "breasts_producing" = FALSE, + "has_vag" = FALSE, + "vag_shape" = pick(GLOB.vagina_shapes_list), + "vag_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"), + "vag_clits" = 1, + "vag_clit_diam" = 0.25, + "vag_clit_len" = 0.25, + "has_womb" = FALSE, + "womb_cum_rate" = CUM_RATE, + "womb_cum_mult" = CUM_RATE_MULT, + "womb_efficiency" = CUM_EFFICIENCY, + "womb_fluid" = "femcum", + "ipc_screen" = "Sunburst", + "ipc_antenna" = "None", + "flavor_text" = "", + "meat_type" = "Mammalian" + )) + +/proc/random_hair_style(gender) + switch(gender) + if(MALE) + return pick(GLOB.hair_styles_male_list) + if(FEMALE) + return pick(GLOB.hair_styles_female_list) + else + return pick(GLOB.hair_styles_list) + +/proc/random_facial_hair_style(gender) + switch(gender) + if(MALE) + return pick(GLOB.facial_hair_styles_male_list) + if(FEMALE) + return pick(GLOB.facial_hair_styles_female_list) + else + return pick(GLOB.facial_hair_styles_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_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, 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" + +/proc/do_mob(mob/user , mob/target, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks = null, ignorehelditem = 0) + if(!user || !target) + return 0 + var/user_loc = user.loc + + var/drifting = 0 + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = 1 + + var/target_loc = target.loc + + var/holding = user.get_active_held_item() + var/datum/progressbar/progbar + if (progress) + progbar = new(user, time, target) + + var/endtime = world.time+time + var/starttime = world.time + . = 1 + while (world.time < endtime) + stoplag(1) + if (progress) + progbar.update(world.time - starttime) + if(QDELETED(user) || QDELETED(target)) + . = 0 + break + if(uninterruptible) + continue + + if(drifting && !user.inertia_dir) + drifting = 0 + user_loc = user.loc + + if((!drifting && user.loc != user_loc) || target.loc != target_loc || (!ignorehelditem && user.get_active_held_item() != holding) || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke())) + . = 0 + break + if (progress) + qdel(progbar) + + +//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 ..() + +/proc/do_after(mob/user, var/delay, needhand = 1, atom/target = null, progress = 1, datum/callback/extra_checks = null) + if(!user) + return 0 + var/atom/Tloc = null + if(target && !isturf(target)) + Tloc = target.loc + + var/atom/Uloc = user.loc + + var/drifting = 0 + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = 1 + + var/holding = user.get_active_held_item() + + var/holdingnull = 1 //User's hand started out empty, check for an empty hand + if(holding) + holdingnull = 0 //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) + + var/endtime = world.time + delay + var/starttime = world.time + . = 1 + while (world.time < endtime) + stoplag(1) + if (progress) + progbar.update(world.time - starttime) + + if(drifting && !user.inertia_dir) + drifting = 0 + Uloc = user.loc + + if(QDELETED(user) || user.stat || user.IsKnockdown() || user.IsStun() || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke())) + . = 0 + break + + if(isliving(user)) + var/mob/living/L = user + if(L.recoveringstam) + . = 0 + break + + if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc)) + if((Uloc != Tloc || Tloc != user) && !drifting) + . = 0 + 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) + . = 0 + break + if(user.get_active_held_item() != holding) + . = 0 + break + if (progress) + qdel(progbar) + +/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1 + . = 1 + return + +/proc/do_after_mob(mob/user, var/list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks) + if(!user || !targets) + return 0 + if(!islist(targets)) + targets = list(targets) + var/user_loc = user.loc + + var/drifting = 0 + if(!user.Process_Spacemove(0) && user.inertia_dir) + drifting = 1 + + var/list/originalloc = list() + for(var/atom/target in targets) + originalloc[target] = target.loc + + 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 + . = 1 + mainloop: + while(world.time < endtime) + stoplag(1) + if(progress) + progbar.update(world.time - starttime) + if(QDELETED(user) || !targets) + . = 0 + break + if(uninterruptible) + continue + + if(drifting && !user.inertia_dir) + drifting = 0 + user_loc = user.loc + + 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() || user.lying || (extra_checks && !extra_checks.Invoke())) + . = 0 + break mainloop + if(progbar) + qdel(progbar) + +/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 + + for(var/j in 1 to amount) + var/atom/X = new spawn_type(arglist(new_args)) + if (admin_spawn) + X.flags_1 |= ADMIN_SPAWNED_1 + +/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") + + for(var/j in 1 to amount) + var/atom/movable/X = new spawn_type(T) + if (admin_spawn) + X.flags_1 |= ADMIN_SPAWNED_1 + + 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)) + +/proc/deadchat_broadcast(message, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR) + message = "[message]" + for(var/mob/M in GLOB.player_list) + var/datum/preferences/prefs + if(M.client && M.client.prefs) + prefs = M.client.prefs + else + prefs = new + + var/override = FALSE + if(M.client && M.client.holder && (prefs.chat_toggles & CHAT_DEAD)) + override = TRUE + if(HAS_TRAIT(M, TRAIT_SIXTHSENSE)) + override = TRUE + if(isnewplayer(M) && !override) + continue + if(M.stat != DEAD && !override) + continue + if(speaker_key && speaker_key in prefs.ignoring) + continue + + switch(message_type) + if(DEADCHAT_DEATHRATTLE) + if(prefs.toggles & DISABLE_DEATHRATTLE) + continue + if(DEADCHAT_ARRIVALRATTLE) + if(prefs.toggles & DISABLE_ARRIVALRATTLE) + 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 diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm index db2eba2422..8b699e3eee 100644 --- a/code/__HELPERS/names.dm +++ b/code/__HELPERS/names.dm @@ -1,248 +1,248 @@ -/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/plasmaman_name() - return "[pick(GLOB.plasmaman_names)] \Roman[rand(1,99)]" - -/proc/moth_name() - return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]" - -/proc/church_name() - var/static/church_name - if (church_name) - return church_name - - var/name = "" - - name += pick("Holy", "United", "First", "Second", "Last") - - if (prob(20)) - name += " Space" - - name += " " + pick("Church", "Cathedral", "Body", "Worshippers", "Movement", "Witnesses") - name += " of [religion_name()]" - - return name - -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/religion_name() - var/static/religion_name - if (religion_name) - return religion_name - - var/name = "" - - name += pick("bee", "science", "edu", "captain", "assistant", "monkey", "alien", "space", "unit", "sprocket", "gadget", "bomb", "revolution", "beyond", "station", "goon", "robot", "ivor", "hobnob") - name += pick("ism", "ia", "ology", "istism", "ites", "ick", "ian", "ity") - - return capitalize(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 - for(var/holiday_name in SSevents.holidays) - if(holiday_name == "Friday the 13th") - random = 13 - var/datum/holiday/holiday = SSevents.holidays[holiday_name] - 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/list/locations = GLOB.teleportlocs.len ? GLOB.teleportlocs : drinks //if null, defaults to drinks instead. - - 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 things. 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/plasmaman_name() + return "[pick(GLOB.plasmaman_names)] \Roman[rand(1,99)]" + +/proc/moth_name() + return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]" + +/proc/church_name() + var/static/church_name + if (church_name) + return church_name + + var/name = "" + + name += pick("Holy", "United", "First", "Second", "Last") + + if (prob(20)) + name += " Space" + + name += " " + pick("Church", "Cathedral", "Body", "Worshippers", "Movement", "Witnesses") + name += " of [religion_name()]" + + return name + +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/religion_name() + var/static/religion_name + if (religion_name) + return religion_name + + var/name = "" + + name += pick("bee", "science", "edu", "captain", "assistant", "monkey", "alien", "space", "unit", "sprocket", "gadget", "bomb", "revolution", "beyond", "station", "goon", "robot", "ivor", "hobnob") + name += pick("ism", "ia", "ology", "istism", "ites", "ick", "ian", "ity") + + return capitalize(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 + for(var/holiday_name in SSevents.holidays) + if(holiday_name == "Friday the 13th") + random = 13 + var/datum/holiday/holiday = SSevents.holidays[holiday_name] + 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/list/locations = GLOB.teleportlocs.len ? GLOB.teleportlocs : drinks //if null, defaults to drinks instead. + + 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 things. 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 9bd0d4c95f..0d2bf89152 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 c55fb8095d..7ab21d0402 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) - . = 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) + . = 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/text.dm b/code/__HELPERS/text.dm index 8494bbb7a9..60c43b0869 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -1,796 +1,796 @@ -/* - * Holds procs designed to help with filtering text - * Contains groups: - * SQL sanitization/formating - * Text sanitization - * Text searches - * Text modification - * Misc - */ - - -/* - * SQL sanitization - */ - -// Run all strings to be used in an SQL query through this proc first to properly escape out injection attempts. -/proc/sanitizeSQL(t) - return SSdbcore.Quote("[t]") - -/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+1) - index = findtext(t, char, index+1) - return t - -/proc/sanitize_filename(t) - return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"="")) - -//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) - if(length(text) > max_length) - return //message too long - var/non_whitespace = 0 - for(var/i=1, i<=length(text), i++) - switch(text2ascii(text,i)) - if(62,60,92,47) - return //rejects the text if it contains these bad characters: <, >, \ or / - if(127 to 255) - return //rejects weird letters like � - if(0 to 31) - return //more weird stuff - if(32) - continue //whitespace - else - non_whitespace = 1 - 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(isnull(name)) // Return null if canceled. - return null - if(no_trim) - return copytext(html_encode(name), 1, max_length) - else - return trim(html_encode(name), max_length) - -//Filters out undesirable characters from names -/proc/reject_bad_name(t_in, allow_numbers=0, max_length=MAX_NAME_LEN) - if(!t_in || length(t_in) > max_length) - return //Rejects the input if it is null or if it is longer then the max length allowed - - var/number_of_alphanumeric = 0 - var/last_char_group = 0 - var/t_out = "" - - for(var/i=1, i<=length(t_in), i++) - var/ascii_char = text2ascii(t_in,i) - switch(ascii_char) - // A .. Z - if(65 to 90) //Uppercase Letters - t_out += ascii2text(ascii_char) - number_of_alphanumeric++ - last_char_group = 4 - - // a .. z - if(97 to 122) //Lowercase Letters - if(last_char_group<2) - t_out += ascii2text(ascii_char-32) //Force uppercase first character - else - t_out += ascii2text(ascii_char) - number_of_alphanumeric++ - last_char_group = 4 - - // 0 .. 9 - if(48 to 57) //Numbers - if(!last_char_group) - continue //suppress at start of string - if(!allow_numbers) - continue - t_out += ascii2text(ascii_char) - number_of_alphanumeric++ - last_char_group = 3 - - // ' - . - if(39,45,46) //Common name punctuation - if(!last_char_group) - continue - t_out += ascii2text(ascii_char) - last_char_group = 2 - - // ~ | @ : # $ % & * + - if(126,124,64,58,35,36,37,38,42,43) //Other symbols that we'll allow (mainly for AI) - if(!last_char_group) - continue //suppress at start of string - if(!allow_numbers) - continue - t_out += ascii2text(ascii_char) - last_char_group = 2 - - //Space - if(32) - if(last_char_group <= 1) - continue //suppress double-spaces and spaces at start of string - t_out += ascii2text(ascii_char) - last_char_group = 1 - else - return - - if(number_of_alphanumeric < 2) - return //protects against tiny names like "A" and also names like "' ' ' ' ' ' ' '" - - if(last_char_group == 1) - t_out = copytext(t_out,1,length(t_out)) //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 - -//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) - -/* - * Text searches - */ - -//Checks the beginning of a string for a specified sub-string -//Returns the position of the substring or 0 if it was not found -/proc/dd_hasprefix(text, prefix) - var/start = 1 - var/end = length(prefix) + 1 - return findtext(text, prefix, start, end) - -//Checks the beginning of a string for a specified sub-string. This proc is case sensitive -//Returns the position of the substring or 0 if it was not found -/proc/dd_hasprefix_case(text, prefix) - var/start = 1 - var/end = length(prefix) + 1 - return findtextEx(text, prefix, start, end) - -//Checks the end of a string for a specified substring. -//Returns the position of the substring or 0 if it was not found -/proc/dd_hassuffix(text, suffix) - var/start = length(text) - length(suffix) - if(start) - return findtext(text, suffix, start, null) - return - -//Checks the end of a string for a specified substring. This proc is case sensitive -//Returns the position of the substring or 0 if it was not found -/proc/dd_hassuffix_case(text, suffix) - var/start = length(text) - length(suffix) - if(start) - return findtextEx(text, suffix, start, null) - -//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 'u' number of zeros ahead of the text 't' -/proc/add_zero(t, u) - while (length(t) < u) - t = "0[t]" - return t - -//Adds 'u' number of spaces ahead of the text 't' -/proc/add_lspace(t, u) - while(length(t) < u) - t = " [t]" - return t - -//Adds 'u' number of spaces behind the text 't' -/proc/add_tspace(t, u) - while(length(t) < u) - t = "[t] " - return t - -//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(text, 1, max_length) - return trim_left(trim_right(text)) - -//Returns a string with the first element of the string capitalized. -/proc/capitalize(t as text) - return uppertext(copytext(t, 1, 2)) + copytext(t, 2) - -//Centers text by adding spaces to either side of the string. -/proc/dd_centertext(message, length) - var/new_message = message - var/size = length(message) - var/delta = length - size - if(size == length) - return new_message - if(size > length) - return copytext(new_message, 1, length + 1) - if(delta == 1) - return new_message + " " - if(delta % 2) - new_message = " " + new_message - delta-- - var/spaces = add_lspace("",delta/2-1) - return spaces + new_message + spaces - -//Limits the length of the text. Note: MAX_MESSAGE_LEN and MAX_NAME_LEN are widely used for this purpose -/proc/dd_limittext(message, length) - var/size = length(message) - if(size <= length) - return message - return copytext(message, 1, length + 1) - - -/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 - if(length(text) != length(compare)) - return 0 - for(var/i = 1, i < length(text), i++) - var/a = copytext(text,i,i+1) - var/b = copytext(compare,i,i+1) -//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,i) + b + copytext(newtext, i+1) - else if(b == replace) //if B is the replacement char - newtext = copytext(newtext,1,i) + a + copytext(newtext, i+1) - else //The lists disagree, Uh-oh! - return 0 - 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 - for(var/i = 1, i <= length(text), i++) - var/a = copytext(text,i,i+1) - if(a == character) - count++ - return count - -/proc/reverse_text(text = "") - var/new_text = "" - for(var/i = length(text); i > 0; i--) - new_text += copytext(text, i, i+1) - 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) - -/proc/add_zero2(t, u) - var/temp1 - while (length(t) < u) - t = "0[t]" - temp1 = t - if (length(t) > u) - temp1 = copytext(t,2,u+1) - return temp1 - -//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/previous = 0 - var/start = 1 - var/end = length(into) + 1 - - for(var/i=1, i") - 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 - -#define string2charlist(string) (splittext(string, regex("(.)")) - splittext(string, "")) - -/proc/rot13(text = "") - var/list/textlist = string2charlist(text) - var/list/result = list() - for(var/c in textlist) - var/ca = text2ascii(c) - if(ca >= text2ascii("a") && ca <= text2ascii("m")) - ca += 13 - else if(ca >= text2ascii("n") && ca <= text2ascii("z")) - ca -= 13 - else if(ca >= text2ascii("A") && ca <= text2ascii("M")) - ca += 13 - else if(ca >= text2ascii("N") && ca <= text2ascii("Z")) - ca -= 13 - result += ascii2text(ca) - return jointext(result, "") - -//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 - var/regex/all_invalid_symbols = new("\[^ -~]+") - - 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 - for(var/pos = 1, pos <= length(string), pos++) - var/let = copytext(string, pos, (pos + 1) % length(string)) - if(early_culling && !findtext(let,GLOB.is_alphanumeric)) - continue - early_culling = FALSE - buffer += let - if(!findtext(buffer,GLOB.is_alphanumeric)) - continue - var/punctbuffer = "" - var/cutoff = length(buffer) - for(var/pos = length(buffer), pos >= 0, pos--) - var/let = copytext(buffer, pos, (pos + 1) % length(buffer)) - if(findtext(let,GLOB.is_alphanumeric)) - break - if(findtext(let,GLOB.is_punctuation)) - punctbuffer = let + punctbuffer //Note this isn't the same thing as using += - cutoff = pos - 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 - for(var/pos = length(punctbuffer), pos >= 0, pos--) - var/punct = copytext(punctbuffer, pos, (pos + 1) % length(punctbuffer)) - if(!exclaim && findtext(punct,"!")) - exclaim = TRUE - if(!question && findtext(punct,"?")) - question = TRUE - if(!exclaim && !question && findtext(punct,".")) - periods += 1 - if(exclaim) - if(question) - punctbuffer = "?!" - else - punctbuffer = "!" - else if(question) - punctbuffer = "?" - else if(periods) - if(periods > 1) - punctbuffer = "..." - else - punctbuffer = "" //Grammer nazis be damned - buffer = copytext(buffer, 1, cutoff) + punctbuffer - if(!findtext(buffer,GLOB.is_alphanumeric)) - continue - if(!buffer || length(buffer) > 280 || length(buffer) <= 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(!isemptylist(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(!isemptylist(finalized) && 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 + 1) - 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 + 1, next_space)) - var/rest = next_backslash > leng ? "" : copytext(string, next_space + 1) - - //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/unintelligize(message) - var/prefix=copytext(message,1,2) - if(prefix == ";") - message = copytext(message,2) - else if(prefix in list(":","#")) - prefix += copytext(message,2,3) - message = copytext(message,3) - else - prefix="" - - var/list/words = splittext(message," ") - var/list/rearranged = list() - for(var/i=1;i<=words.len;i++) - var/cword = pick(words) - words.Remove(cword) - var/suffix = copytext(cword,length(cword)-1,length(cword)) - while(length(cword)>0 && suffix in list(".",",",";","!",":","?")) - cword = copytext(cword,1 ,length(cword)-1) - suffix = copytext(cword,length(cword)-1,length(cword) ) - if(length(cword)) - rearranged += cword - message = "[prefix][jointext(rearranged," ")]" - . = message - -#define is_alpha(X) ((text2ascii(X) <= 122) && (text2ascii(X) >= 97)) +/* + * Holds procs designed to help with filtering text + * Contains groups: + * SQL sanitization/formating + * Text sanitization + * Text searches + * Text modification + * Misc + */ + + +/* + * SQL sanitization + */ + +// Run all strings to be used in an SQL query through this proc first to properly escape out injection attempts. +/proc/sanitizeSQL(t) + return SSdbcore.Quote("[t]") + +/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+1) + index = findtext(t, char, index+1) + return t + +/proc/sanitize_filename(t) + return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"="")) + +//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) + if(length(text) > max_length) + return //message too long + var/non_whitespace = 0 + for(var/i=1, i<=length(text), i++) + switch(text2ascii(text,i)) + if(62,60,92,47) + return //rejects the text if it contains these bad characters: <, >, \ or / + if(127 to 255) + return //rejects weird letters like � + if(0 to 31) + return //more weird stuff + if(32) + continue //whitespace + else + non_whitespace = 1 + 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(isnull(name)) // Return null if canceled. + return null + if(no_trim) + return copytext(html_encode(name), 1, max_length) + else + return trim(html_encode(name), max_length) + +//Filters out undesirable characters from names +/proc/reject_bad_name(t_in, allow_numbers=0, max_length=MAX_NAME_LEN) + if(!t_in || length(t_in) > max_length) + return //Rejects the input if it is null or if it is longer then the max length allowed + + var/number_of_alphanumeric = 0 + var/last_char_group = 0 + var/t_out = "" + + for(var/i=1, i<=length(t_in), i++) + var/ascii_char = text2ascii(t_in,i) + switch(ascii_char) + // A .. Z + if(65 to 90) //Uppercase Letters + t_out += ascii2text(ascii_char) + number_of_alphanumeric++ + last_char_group = 4 + + // a .. z + if(97 to 122) //Lowercase Letters + if(last_char_group<2) + t_out += ascii2text(ascii_char-32) //Force uppercase first character + else + t_out += ascii2text(ascii_char) + number_of_alphanumeric++ + last_char_group = 4 + + // 0 .. 9 + if(48 to 57) //Numbers + if(!last_char_group) + continue //suppress at start of string + if(!allow_numbers) + continue + t_out += ascii2text(ascii_char) + number_of_alphanumeric++ + last_char_group = 3 + + // ' - . + if(39,45,46) //Common name punctuation + if(!last_char_group) + continue + t_out += ascii2text(ascii_char) + last_char_group = 2 + + // ~ | @ : # $ % & * + + if(126,124,64,58,35,36,37,38,42,43) //Other symbols that we'll allow (mainly for AI) + if(!last_char_group) + continue //suppress at start of string + if(!allow_numbers) + continue + t_out += ascii2text(ascii_char) + last_char_group = 2 + + //Space + if(32) + if(last_char_group <= 1) + continue //suppress double-spaces and spaces at start of string + t_out += ascii2text(ascii_char) + last_char_group = 1 + else + return + + if(number_of_alphanumeric < 2) + return //protects against tiny names like "A" and also names like "' ' ' ' ' ' ' '" + + if(last_char_group == 1) + t_out = copytext(t_out,1,length(t_out)) //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 + +//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) + +/* + * Text searches + */ + +//Checks the beginning of a string for a specified sub-string +//Returns the position of the substring or 0 if it was not found +/proc/dd_hasprefix(text, prefix) + var/start = 1 + var/end = length(prefix) + 1 + return findtext(text, prefix, start, end) + +//Checks the beginning of a string for a specified sub-string. This proc is case sensitive +//Returns the position of the substring or 0 if it was not found +/proc/dd_hasprefix_case(text, prefix) + var/start = 1 + var/end = length(prefix) + 1 + return findtextEx(text, prefix, start, end) + +//Checks the end of a string for a specified substring. +//Returns the position of the substring or 0 if it was not found +/proc/dd_hassuffix(text, suffix) + var/start = length(text) - length(suffix) + if(start) + return findtext(text, suffix, start, null) + return + +//Checks the end of a string for a specified substring. This proc is case sensitive +//Returns the position of the substring or 0 if it was not found +/proc/dd_hassuffix_case(text, suffix) + var/start = length(text) - length(suffix) + if(start) + return findtextEx(text, suffix, start, null) + +//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 'u' number of zeros ahead of the text 't' +/proc/add_zero(t, u) + while (length(t) < u) + t = "0[t]" + return t + +//Adds 'u' number of spaces ahead of the text 't' +/proc/add_lspace(t, u) + while(length(t) < u) + t = " [t]" + return t + +//Adds 'u' number of spaces behind the text 't' +/proc/add_tspace(t, u) + while(length(t) < u) + t = "[t] " + return t + +//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(text, 1, max_length) + return trim_left(trim_right(text)) + +//Returns a string with the first element of the string capitalized. +/proc/capitalize(t as text) + return uppertext(copytext(t, 1, 2)) + copytext(t, 2) + +//Centers text by adding spaces to either side of the string. +/proc/dd_centertext(message, length) + var/new_message = message + var/size = length(message) + var/delta = length - size + if(size == length) + return new_message + if(size > length) + return copytext(new_message, 1, length + 1) + if(delta == 1) + return new_message + " " + if(delta % 2) + new_message = " " + new_message + delta-- + var/spaces = add_lspace("",delta/2-1) + return spaces + new_message + spaces + +//Limits the length of the text. Note: MAX_MESSAGE_LEN and MAX_NAME_LEN are widely used for this purpose +/proc/dd_limittext(message, length) + var/size = length(message) + if(size <= length) + return message + return copytext(message, 1, length + 1) + + +/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 + if(length(text) != length(compare)) + return 0 + for(var/i = 1, i < length(text), i++) + var/a = copytext(text,i,i+1) + var/b = copytext(compare,i,i+1) +//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,i) + b + copytext(newtext, i+1) + else if(b == replace) //if B is the replacement char + newtext = copytext(newtext,1,i) + a + copytext(newtext, i+1) + else //The lists disagree, Uh-oh! + return 0 + 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 + for(var/i = 1, i <= length(text), i++) + var/a = copytext(text,i,i+1) + if(a == character) + count++ + return count + +/proc/reverse_text(text = "") + var/new_text = "" + for(var/i = length(text); i > 0; i--) + new_text += copytext(text, i, i+1) + 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) + +/proc/add_zero2(t, u) + var/temp1 + while (length(t) < u) + t = "0[t]" + temp1 = t + if (length(t) > u) + temp1 = copytext(t,2,u+1) + return temp1 + +//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/previous = 0 + var/start = 1 + var/end = length(into) + 1 + + for(var/i=1, i") + 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 + +#define string2charlist(string) (splittext(string, regex("(.)")) - splittext(string, "")) + +/proc/rot13(text = "") + var/list/textlist = string2charlist(text) + var/list/result = list() + for(var/c in textlist) + var/ca = text2ascii(c) + if(ca >= text2ascii("a") && ca <= text2ascii("m")) + ca += 13 + else if(ca >= text2ascii("n") && ca <= text2ascii("z")) + ca -= 13 + else if(ca >= text2ascii("A") && ca <= text2ascii("M")) + ca += 13 + else if(ca >= text2ascii("N") && ca <= text2ascii("Z")) + ca -= 13 + result += ascii2text(ca) + return jointext(result, "") + +//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 + var/regex/all_invalid_symbols = new("\[^ -~]+") + + 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 + for(var/pos = 1, pos <= length(string), pos++) + var/let = copytext(string, pos, (pos + 1) % length(string)) + if(early_culling && !findtext(let,GLOB.is_alphanumeric)) + continue + early_culling = FALSE + buffer += let + if(!findtext(buffer,GLOB.is_alphanumeric)) + continue + var/punctbuffer = "" + var/cutoff = length(buffer) + for(var/pos = length(buffer), pos >= 0, pos--) + var/let = copytext(buffer, pos, (pos + 1) % length(buffer)) + if(findtext(let,GLOB.is_alphanumeric)) + break + if(findtext(let,GLOB.is_punctuation)) + punctbuffer = let + punctbuffer //Note this isn't the same thing as using += + cutoff = pos + 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 + for(var/pos = length(punctbuffer), pos >= 0, pos--) + var/punct = copytext(punctbuffer, pos, (pos + 1) % length(punctbuffer)) + if(!exclaim && findtext(punct,"!")) + exclaim = TRUE + if(!question && findtext(punct,"?")) + question = TRUE + if(!exclaim && !question && findtext(punct,".")) + periods += 1 + if(exclaim) + if(question) + punctbuffer = "?!" + else + punctbuffer = "!" + else if(question) + punctbuffer = "?" + else if(periods) + if(periods > 1) + punctbuffer = "..." + else + punctbuffer = "" //Grammer nazis be damned + buffer = copytext(buffer, 1, cutoff) + punctbuffer + if(!findtext(buffer,GLOB.is_alphanumeric)) + continue + if(!buffer || length(buffer) > 280 || length(buffer) <= 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(!isemptylist(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(!isemptylist(finalized) && 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 + 1) + 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 + 1, next_space)) + var/rest = next_backslash > leng ? "" : copytext(string, next_space + 1) + + //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/unintelligize(message) + var/prefix=copytext(message,1,2) + if(prefix == ";") + message = copytext(message,2) + else if(prefix in list(":","#")) + prefix += copytext(message,2,3) + message = copytext(message,3) + else + prefix="" + + var/list/words = splittext(message," ") + var/list/rearranged = list() + for(var/i=1;i<=words.len;i++) + var/cword = pick(words) + words.Remove(cword) + var/suffix = copytext(cword,length(cword)-1,length(cword)) + while(length(cword)>0 && suffix in list(".",",",";","!",":","?")) + cword = copytext(cword,1 ,length(cword)-1) + suffix = copytext(cword,length(cword)-1,length(cword) ) + if(length(cword)) + rearranged += cword + message = "[prefix][jointext(rearranged," ")]" + . = message + +#define is_alpha(X) ((text2ascii(X) <= 122) && (text2ascii(X) >= 97)) #define is_digit(X) ((length(X) == 1) && (length(text2num(X)) == 1)) \ No newline at end of file diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index f0d5a7b252..52fe90ed2d 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -1,83 +1,83 @@ -/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 1 if it is the selected month and day */ -/proc/isDay(month, day) - if(isnum(month) && isnum(day)) - var/MM = text2num(time2text(world.timeofday, "MM")) // get the current month - var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day - if(month == MM && day == DD) - return 1 - -//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! - return GLOB.midnight_rollovers++ - 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)) - -/proc/worldtime2text() - return gameTimestamp("hh:mm:ss", world.time) - -/proc/gameTimestamp(format = "hh:mm:ss", wtime=null) - if(!wtime) - wtime = world.time - return time2text(wtime - GLOB.timezoneOffset, 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 1 if it is the selected month and day */ +/proc/isDay(month, day) + if(isnum(month) && isnum(day)) + var/MM = text2num(time2text(world.timeofday, "MM")) // get the current month + var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day + if(month == MM && day == DD) + return 1 + +//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! + return GLOB.midnight_rollovers++ + 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)) + +/proc/worldtime2text() + return gameTimestamp("hh:mm:ss", world.time) + +/proc/gameTimestamp(format = "hh:mm:ss", wtime=null) + if(!wtime) + wtime = world.time + return time2text(wtime - GLOB.timezoneOffset, format) diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm index d6942f1c40..823e893670 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -1,631 +1,631 @@ -/* - * Holds procs designed to change one type of value, into another. - * Contains: - * hex2num & num2hex - * file2list - * angle2dir - * angle2text - * worldtime2text - * text2dir_extended & dir2text_short - */ - -//Returns an integer given a hex input, supports negative values "-ff" -//skips preceding invalid characters -//breaks when hittin invalid characters thereafter -// If safe=TRUE, returns null on incorrect input strings instead of CRASHing -/proc/hex2num(hex, safe=FALSE) - . = 0 - var/place = 1 - for(var/i in length(hex) to 1 step -1) - var/num = text2ascii(hex, i) - switch(num) - if(48 to 57) - num -= 48 //0-9 - if(97 to 102) - num -= 87 //a-f - if(65 to 70) - num -= 55 //A-F - if(45) - return . * -1 // - - else - if(safe) - return null - else - CRASH("Malformed hex number") - - . += num * place - place *= 16 - -//Returns the hex value of a decimal number -//len == length of returned string -//if len < 0 then the returned string will be as long as it needs to be to contain the data -//Only supports positive numbers -//if an invalid number is provided, it assumes num==0 -//Note, unlike previous versions, this one works from low to high <-- that way -/proc/num2hex(num, len=2) - if(!isnum(num)) - num = 0 - num = round(abs(num)) - . = "" - var/i=0 - while(1) - if(len<=0) - if(!num) - break - else - if(i>=len) - break - var/remainder = num/16 - num = round(remainder) - remainder = (remainder - num) * 16 - switch(remainder) - if(9,8,7,6,5,4,3,2,1) - . = "[remainder]" + . - if(10,11,12,13,14,15) - . = ascii2text(remainder+87) + . - else - . = "0" + . - i++ - return . - -//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(1) - return "north" - if(2) - return "south" - if(4) - return "east" - if(8) - return "west" - if(5) - return "northeast" - if(6) - return "southeast" - if(9) - return "northwest" - if(10) - return "southwest" - else - return - -//Turns text into proper directions -/proc/text2dir(direction) - switch(uppertext(direction)) - if("NORTH") - return 1 - if("SOUTH") - return 2 - if("EAST") - return 4 - if("WEST") - return 8 - if("NORTHEAST") - return 5 - if("NORTHWEST") - return 9 - if("SOUTHEAST") - return 6 - if("SOUTHWEST") - return 10 - else - return - -//Converts an angle (degrees) into an ss13 direction -/proc/angle2dir(degree) - - degree = SIMPLIFY_DEGREES(degree) - switch(degree) - if(0 to 22.5) //north requires two angle ranges - return NORTH - if(22.5 to 67.5) //each range covers 45 degrees - return NORTHEAST - if(67.5 to 112.5) - return EAST - if(112.5 to 157.5) - return SOUTHEAST - if(157.5 to 202.5) - return SOUTH - if(202.5 to 247.5) - return SOUTHWEST - if(247.5 to 292.5) - return WEST - if(292.5 to 337.5) - return NORTHWEST - if(337.5 to 360) - return NORTH - -/proc/angle2dir_cardinal(angle) - switch(round(angle, 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_BUILDMODE) - . += "[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_SOUNDS) - . += "[seperator]SOUND" - if(rights & R_SPAWN) - . += "[seperator]SPAWN" - if(rights & R_AUTOLOGIN) - . += "[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 - -// Very ugly, BYOND doesn't support unix time and rounding errors make it really hard to convert it to BYOND time. -// returns "YYYY-MM-DD" by default -/proc/unix2date(timestamp, seperator = "-") - - if(timestamp < 0) - return 0 //Do not accept negative values - - var/year = 1970 //Unix Epoc begins 1970-01-01 - var/dayInSeconds = 86400 //60secs*60mins*24hours - var/daysInYear = 365 //Non Leap Year - var/daysInLYear = daysInYear + 1//Leap year - var/days = round(timestamp / dayInSeconds) //Days passed since UNIX Epoc - var/tmpDays = days + 1 //If passed (timestamp < dayInSeconds), it will return 0, so add 1 - var/monthsInDays = list() //Months will be in here ***Taken from the PHP source code*** - var/month = 1 //This will be the returned MONTH NUMBER. - var/day //This will be the returned day number. - - while(tmpDays > daysInYear) //Start adding years to 1970 - year++ - if(isLeap(year)) - tmpDays -= daysInLYear - else - tmpDays -= daysInYear - - if(isLeap(year)) //The year is a leap year - monthsInDays = list(-1,30,59,90,120,151,181,212,243,273,304,334) - else - monthsInDays = list(0,31,59,90,120,151,181,212,243,273,304,334) - - var/mDays = 0; - var/monthIndex = 0; - - for(var/m in monthsInDays) - monthIndex++ - if(tmpDays > m) - mDays = m - month = monthIndex - - day = tmpDays - mDays //Setup the date - - return "[year][seperator][((month < 10) ? "0[month]" : month)][seperator][((day < 10) ? "0[day]" : day)]" - -/proc/isLeap(y) - return ((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0)) - - - -//Turns a Body_parts_covered bitfield into a list of organ/limb names. -//(I challenge you to find a use for this) -/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(SLOT_BACK, SLOT_WEAR_SUIT, SLOT_W_UNIFORM, SLOT_BELT, SLOT_WEAR_ID) - return BODY_ZONE_CHEST - - if(SLOT_GLOVES, SLOT_HANDS, SLOT_HANDCUFFED) - return pick(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) - - if(SLOT_HEAD, SLOT_NECK, SLOT_NECK, SLOT_EARS) - return BODY_ZONE_HEAD - - if(SLOT_WEAR_MASK) - return BODY_ZONE_PRECISE_MOUTH - - if(SLOT_GLASSES) - return BODY_ZONE_PRECISE_EYES - - if(SLOT_SHOES) - return pick(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT) - - if(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/fusionpower2text(power) //used when displaying fusion power on analyzers - switch(power) - if(0 to 5) - return "low" - if(5 to 20) - return "mid" - if(20 to 50) - return "high" - if(50 to INFINITY) - return "super" - -/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(var/t_string, var/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) - return 0 - var/R = hex2num(copytext(A,2,4)) - var/G = hex2num(copytext(A,4,6)) - var/B = hex2num(copytext(A,6,0)) - 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) - 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)]/", "")) - -/proc/strtohex(str) - if(!istext(str)||!str) - return - var/r - var/c - for(var/i = 1 to length(str)) - c= text2ascii(str,i) - r+= num2hex(c) - return r - -// Decodes hex to raw byte string. -// If safe=TRUE, returns null on incorrect input strings instead of CRASHing -/proc/hextostr(str, safe=FALSE) - if(!istext(str)||!str) - return - var/r - var/c - for(var/i = 1 to length(str)/2) - c = hex2num(copytext(str,i*2-1,i*2+1), safe) - if(isnull(c)) - return null - r += ascii2text(c) - return r +/* + * Holds procs designed to change one type of value, into another. + * Contains: + * hex2num & num2hex + * file2list + * angle2dir + * angle2text + * worldtime2text + * text2dir_extended & dir2text_short + */ + +//Returns an integer given a hex input, supports negative values "-ff" +//skips preceding invalid characters +//breaks when hittin invalid characters thereafter +// If safe=TRUE, returns null on incorrect input strings instead of CRASHing +/proc/hex2num(hex, safe=FALSE) + . = 0 + var/place = 1 + for(var/i in length(hex) to 1 step -1) + var/num = text2ascii(hex, i) + switch(num) + if(48 to 57) + num -= 48 //0-9 + if(97 to 102) + num -= 87 //a-f + if(65 to 70) + num -= 55 //A-F + if(45) + return . * -1 // - + else + if(safe) + return null + else + CRASH("Malformed hex number") + + . += num * place + place *= 16 + +//Returns the hex value of a decimal number +//len == length of returned string +//if len < 0 then the returned string will be as long as it needs to be to contain the data +//Only supports positive numbers +//if an invalid number is provided, it assumes num==0 +//Note, unlike previous versions, this one works from low to high <-- that way +/proc/num2hex(num, len=2) + if(!isnum(num)) + num = 0 + num = round(abs(num)) + . = "" + var/i=0 + while(1) + if(len<=0) + if(!num) + break + else + if(i>=len) + break + var/remainder = num/16 + num = round(remainder) + remainder = (remainder - num) * 16 + switch(remainder) + if(9,8,7,6,5,4,3,2,1) + . = "[remainder]" + . + if(10,11,12,13,14,15) + . = ascii2text(remainder+87) + . + else + . = "0" + . + i++ + return . + +//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(1) + return "north" + if(2) + return "south" + if(4) + return "east" + if(8) + return "west" + if(5) + return "northeast" + if(6) + return "southeast" + if(9) + return "northwest" + if(10) + return "southwest" + else + return + +//Turns text into proper directions +/proc/text2dir(direction) + switch(uppertext(direction)) + if("NORTH") + return 1 + if("SOUTH") + return 2 + if("EAST") + return 4 + if("WEST") + return 8 + if("NORTHEAST") + return 5 + if("NORTHWEST") + return 9 + if("SOUTHEAST") + return 6 + if("SOUTHWEST") + return 10 + else + return + +//Converts an angle (degrees) into an ss13 direction +/proc/angle2dir(degree) + + degree = SIMPLIFY_DEGREES(degree) + switch(degree) + if(0 to 22.5) //north requires two angle ranges + return NORTH + if(22.5 to 67.5) //each range covers 45 degrees + return NORTHEAST + if(67.5 to 112.5) + return EAST + if(112.5 to 157.5) + return SOUTHEAST + if(157.5 to 202.5) + return SOUTH + if(202.5 to 247.5) + return SOUTHWEST + if(247.5 to 292.5) + return WEST + if(292.5 to 337.5) + return NORTHWEST + if(337.5 to 360) + return NORTH + +/proc/angle2dir_cardinal(angle) + switch(round(angle, 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_BUILDMODE) + . += "[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_SOUNDS) + . += "[seperator]SOUND" + if(rights & R_SPAWN) + . += "[seperator]SPAWN" + if(rights & R_AUTOLOGIN) + . += "[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 + +// Very ugly, BYOND doesn't support unix time and rounding errors make it really hard to convert it to BYOND time. +// returns "YYYY-MM-DD" by default +/proc/unix2date(timestamp, seperator = "-") + + if(timestamp < 0) + return 0 //Do not accept negative values + + var/year = 1970 //Unix Epoc begins 1970-01-01 + var/dayInSeconds = 86400 //60secs*60mins*24hours + var/daysInYear = 365 //Non Leap Year + var/daysInLYear = daysInYear + 1//Leap year + var/days = round(timestamp / dayInSeconds) //Days passed since UNIX Epoc + var/tmpDays = days + 1 //If passed (timestamp < dayInSeconds), it will return 0, so add 1 + var/monthsInDays = list() //Months will be in here ***Taken from the PHP source code*** + var/month = 1 //This will be the returned MONTH NUMBER. + var/day //This will be the returned day number. + + while(tmpDays > daysInYear) //Start adding years to 1970 + year++ + if(isLeap(year)) + tmpDays -= daysInLYear + else + tmpDays -= daysInYear + + if(isLeap(year)) //The year is a leap year + monthsInDays = list(-1,30,59,90,120,151,181,212,243,273,304,334) + else + monthsInDays = list(0,31,59,90,120,151,181,212,243,273,304,334) + + var/mDays = 0; + var/monthIndex = 0; + + for(var/m in monthsInDays) + monthIndex++ + if(tmpDays > m) + mDays = m + month = monthIndex + + day = tmpDays - mDays //Setup the date + + return "[year][seperator][((month < 10) ? "0[month]" : month)][seperator][((day < 10) ? "0[day]" : day)]" + +/proc/isLeap(y) + return ((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0)) + + + +//Turns a Body_parts_covered bitfield into a list of organ/limb names. +//(I challenge you to find a use for this) +/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(SLOT_BACK, SLOT_WEAR_SUIT, SLOT_W_UNIFORM, SLOT_BELT, SLOT_WEAR_ID) + return BODY_ZONE_CHEST + + if(SLOT_GLOVES, SLOT_HANDS, SLOT_HANDCUFFED) + return pick(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND) + + if(SLOT_HEAD, SLOT_NECK, SLOT_NECK, SLOT_EARS) + return BODY_ZONE_HEAD + + if(SLOT_WEAR_MASK) + return BODY_ZONE_PRECISE_MOUTH + + if(SLOT_GLASSES) + return BODY_ZONE_PRECISE_EYES + + if(SLOT_SHOES) + return pick(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT) + + if(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/fusionpower2text(power) //used when displaying fusion power on analyzers + switch(power) + if(0 to 5) + return "low" + if(5 to 20) + return "mid" + if(20 to 50) + return "high" + if(50 to INFINITY) + return "super" + +/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(var/t_string, var/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) + return 0 + var/R = hex2num(copytext(A,2,4)) + var/G = hex2num(copytext(A,4,6)) + var/B = hex2num(copytext(A,6,0)) + 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) + 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)]/", "")) + +/proc/strtohex(str) + if(!istext(str)||!str) + return + var/r + var/c + for(var/i = 1 to length(str)) + c= text2ascii(str,i) + r+= num2hex(c) + return r + +// Decodes hex to raw byte string. +// If safe=TRUE, returns null on incorrect input strings instead of CRASHing +/proc/hextostr(str, safe=FALSE) + if(!istext(str)||!str) + return + var/r + var/c + for(var/i = 1 to length(str)/2) + c = hex2num(copytext(str,i*2-1,i*2+1), safe) + if(isnull(c)) + return null + r += ascii2text(c) + return r diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 3ec2510cc7..af948086df 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -1,57 +1,57 @@ -//#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 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 512 -#if DM_VERSION < MIN_COMPILER_VERSION -//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 512 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 +//#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 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 512 +#if DM_VERSION < MIN_COMPILER_VERSION +//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 512 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 diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index bf179760c0..5d95a4eaae 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -1,215 +1,215 @@ -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, - "SHOVABLE_ONTO" = SHOVABLE_ONTO - ), - "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, - ), - "admin_flags" = list( - "BUILDMODE" = R_BUILDMODE, - "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_SOUNDS, - "SPAWN" = R_SPAWN, - "AUTOLOGIN" = R_AUTOLOGIN, - "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 - ), - "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, - "GOLIATH_RESISTANCE" = GOLIATH_RESISTANCE, - "GOLIATH_WEAKNESS" = GOLIATH_WEAKNESS - ), - "flags_1" = list( - "NOJAUNT_1" = NOJAUNT_1, - "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1, - "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1, - "HEAR_1" = HEAR_1, - "CHECK_RICOCHET_1" = CHECK_RICOCHET_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, - "TESLA_IGNORE_1" = TESLA_IGNORE_1, - "INITIALIZED_1" = INITIALIZED_1, - "ADMIN_SPAWNED_1" = ADMIN_SPAWNED_1, - ), - "clothing_flags" = list( - "LAVAPROTECT" = LAVAPROTECT, - "STOPSPRESSUREDAMAGE" = STOPSPRESSUREDAMAGE, - "BLOCK_GAS_SMOKE_EFFECT" = BLOCK_GAS_SMOKE_EFFECT, - "ALLOWINTERNALS" = ALLOWINTERNALS, - "NOSLIP" = NOSLIP, - "THICKMATERIAL" = THICKMATERIAL, - "VOICEBOX_TOGGLABLE" = VOICEBOX_TOGGLABLE, - "VOICEBOX_DISABLED" = VOICEBOX_DISABLED, - "IGNORE_HAT_TOSS" = IGNORE_HAT_TOSS, - "SCAN_REAGENTS" = SCAN_REAGENTS - ), - "tesla_flags" = list( - "TESLA_MOB_DAMAGE" = TESLA_MOB_DAMAGE, - "TESLA_OBJ_DAMAGE" = TESLA_OBJ_DAMAGE, - "TESLA_MOB_STUN" = TESLA_MOB_STUN, - "TESLA_ALLOW_DUPLICATES" = TESLA_ALLOW_DUPLICATES, - "TESLA_MACHINE_EXPLOSIVE" = TESLA_MACHINE_EXPLOSIVE, - ), - "smooth" = list( - "SMOOTH_TRUE" = SMOOTH_TRUE, - "SMOOTH_MORE" = SMOOTH_MORE, - "SMOOTH_DIAGONAL" = SMOOTH_DIAGONAL, - "SMOOTH_BORDER" = SMOOTH_BORDER, - "SMOOTH_QUEUED" = SMOOTH_QUEUED, - ), - "reagents_holder_flags" = list( - "INJECTABLE" = INJECTABLE, - "DRAWABLE" = DRAWABLE, - "REFILLABLE" = REFILLABLE, - "DRAINABLE" = DRAINABLE, - "TRANSPARENT" = TRANSPARENT, - "AMOUNT_VISIBLE" = AMOUNT_VISIBLE, - "NO_REACT" = NO_REACT, - ), - "car_traits" = list( - "CAN_KIDNAP" = CAN_KIDNAP, - ), - "rad_flags" = list( - "RAD_PROTECT_CONTENTS" = RAD_PROTECT_CONTENTS, - "RAD_NO_CONTAMINATE" = RAD_NO_CONTAMINATE, - ), - "disease_flags" = list( - "CURABLE" = CURABLE, - "CAN_CARRY" = CAN_CARRY, - "CAN_RESIST" = CAN_RESIST - ), - "chemical_flags" = list( - "REAGENT_DEAD_PROCESS" = REAGENT_DEAD_PROCESS, - "REAGENT_DONOTSPLIT" = REAGENT_DONOTSPLIT, - "REAGENT_ONLYINVERSE" = REAGENT_ONLYINVERSE, - "REAGENT_ONMOBMERGE" = REAGENT_ONMOBMERGE, - "REAGENT_INVISIBLE" = REAGENT_INVISIBLE, - "REAGENT_FORCEONNEW" = REAGENT_FORCEONNEW, - "REAGENT_SNEAKYNAME" = REAGENT_SNEAKYNAME, - "REAGENT_SPLITRETAINVOL" = REAGENT_SPLITRETAINVOL - ), - "clear_conversion" = list( - "REACTION_CLEAR_IMPURE" = REACTION_CLEAR_IMPURE, - "REACTION_CLEAR_INVERSE" = REACTION_CLEAR_INVERSE - ), - "organ_flags" = list( - "ORGAN_SYNTHETIC" = ORGAN_SYNTHETIC, - "ORGAN_FROZEN" = ORGAN_FROZEN, - "ORGAN_FAILING" = ORGAN_FAILING, - "ORGAN_EXTERNAL" = ORGAN_EXTERNAL, - "ORGAN_VITAL" = ORGAN_VITAL, - "ORGAN_NO_SPOIL" = ORGAN_NO_SPOIL - ), - "genital_flags" = list( - "GENITAL_BLACKLISTED" = GENITAL_BLACKLISTED, - "GENITAL_INTERNAL" = GENITAL_INTERNAL, - "GENITAL_HIDDEN" = GENITAL_HIDDEN, - "GENITAL_THROUGH_CLOTHES" = GENITAL_THROUGH_CLOTHES, - "GENITAL_FUID_PRODUCTION" = GENITAL_FUID_PRODUCTION, - "CAN_MASTURBATE_WITH" = CAN_MASTURBATE_WITH, - "MASTURBATE_LINKED_ORGAN" = MASTURBATE_LINKED_ORGAN, - "CAN_CLIMAX_WITH" = CAN_CLIMAX_WITH - ) +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, + "SHOVABLE_ONTO" = SHOVABLE_ONTO + ), + "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, + ), + "admin_flags" = list( + "BUILDMODE" = R_BUILDMODE, + "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_SOUNDS, + "SPAWN" = R_SPAWN, + "AUTOLOGIN" = R_AUTOLOGIN, + "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 + ), + "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, + "GOLIATH_RESISTANCE" = GOLIATH_RESISTANCE, + "GOLIATH_WEAKNESS" = GOLIATH_WEAKNESS + ), + "flags_1" = list( + "NOJAUNT_1" = NOJAUNT_1, + "UNUSED_RESERVATION_TURF_1" = UNUSED_RESERVATION_TURF_1, + "CAN_BE_DIRTY_1" = CAN_BE_DIRTY_1, + "HEAR_1" = HEAR_1, + "CHECK_RICOCHET_1" = CHECK_RICOCHET_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, + "TESLA_IGNORE_1" = TESLA_IGNORE_1, + "INITIALIZED_1" = INITIALIZED_1, + "ADMIN_SPAWNED_1" = ADMIN_SPAWNED_1, + ), + "clothing_flags" = list( + "LAVAPROTECT" = LAVAPROTECT, + "STOPSPRESSUREDAMAGE" = STOPSPRESSUREDAMAGE, + "BLOCK_GAS_SMOKE_EFFECT" = BLOCK_GAS_SMOKE_EFFECT, + "ALLOWINTERNALS" = ALLOWINTERNALS, + "NOSLIP" = NOSLIP, + "THICKMATERIAL" = THICKMATERIAL, + "VOICEBOX_TOGGLABLE" = VOICEBOX_TOGGLABLE, + "VOICEBOX_DISABLED" = VOICEBOX_DISABLED, + "IGNORE_HAT_TOSS" = IGNORE_HAT_TOSS, + "SCAN_REAGENTS" = SCAN_REAGENTS + ), + "tesla_flags" = list( + "TESLA_MOB_DAMAGE" = TESLA_MOB_DAMAGE, + "TESLA_OBJ_DAMAGE" = TESLA_OBJ_DAMAGE, + "TESLA_MOB_STUN" = TESLA_MOB_STUN, + "TESLA_ALLOW_DUPLICATES" = TESLA_ALLOW_DUPLICATES, + "TESLA_MACHINE_EXPLOSIVE" = TESLA_MACHINE_EXPLOSIVE, + ), + "smooth" = list( + "SMOOTH_TRUE" = SMOOTH_TRUE, + "SMOOTH_MORE" = SMOOTH_MORE, + "SMOOTH_DIAGONAL" = SMOOTH_DIAGONAL, + "SMOOTH_BORDER" = SMOOTH_BORDER, + "SMOOTH_QUEUED" = SMOOTH_QUEUED, + ), + "reagents_holder_flags" = list( + "INJECTABLE" = INJECTABLE, + "DRAWABLE" = DRAWABLE, + "REFILLABLE" = REFILLABLE, + "DRAINABLE" = DRAINABLE, + "TRANSPARENT" = TRANSPARENT, + "AMOUNT_VISIBLE" = AMOUNT_VISIBLE, + "NO_REACT" = NO_REACT, + ), + "car_traits" = list( + "CAN_KIDNAP" = CAN_KIDNAP, + ), + "rad_flags" = list( + "RAD_PROTECT_CONTENTS" = RAD_PROTECT_CONTENTS, + "RAD_NO_CONTAMINATE" = RAD_NO_CONTAMINATE, + ), + "disease_flags" = list( + "CURABLE" = CURABLE, + "CAN_CARRY" = CAN_CARRY, + "CAN_RESIST" = CAN_RESIST + ), + "chemical_flags" = list( + "REAGENT_DEAD_PROCESS" = REAGENT_DEAD_PROCESS, + "REAGENT_DONOTSPLIT" = REAGENT_DONOTSPLIT, + "REAGENT_ONLYINVERSE" = REAGENT_ONLYINVERSE, + "REAGENT_ONMOBMERGE" = REAGENT_ONMOBMERGE, + "REAGENT_INVISIBLE" = REAGENT_INVISIBLE, + "REAGENT_FORCEONNEW" = REAGENT_FORCEONNEW, + "REAGENT_SNEAKYNAME" = REAGENT_SNEAKYNAME, + "REAGENT_SPLITRETAINVOL" = REAGENT_SPLITRETAINVOL + ), + "clear_conversion" = list( + "REACTION_CLEAR_IMPURE" = REACTION_CLEAR_IMPURE, + "REACTION_CLEAR_INVERSE" = REACTION_CLEAR_INVERSE + ), + "organ_flags" = list( + "ORGAN_SYNTHETIC" = ORGAN_SYNTHETIC, + "ORGAN_FROZEN" = ORGAN_FROZEN, + "ORGAN_FAILING" = ORGAN_FAILING, + "ORGAN_EXTERNAL" = ORGAN_EXTERNAL, + "ORGAN_VITAL" = ORGAN_VITAL, + "ORGAN_NO_SPOIL" = ORGAN_NO_SPOIL + ), + "genital_flags" = list( + "GENITAL_BLACKLISTED" = GENITAL_BLACKLISTED, + "GENITAL_INTERNAL" = GENITAL_INTERNAL, + "GENITAL_HIDDEN" = GENITAL_HIDDEN, + "GENITAL_THROUGH_CLOTHES" = GENITAL_THROUGH_CLOTHES, + "GENITAL_FUID_PRODUCTION" = GENITAL_FUID_PRODUCTION, + "CAN_MASTURBATE_WITH" = CAN_MASTURBATE_WITH, + "MASTURBATE_LINKED_ORGAN" = MASTURBATE_LINKED_ORGAN, + "CAN_CLIMAX_WITH" = CAN_CLIMAX_WITH + ) )) \ No newline at end of file diff --git a/code/_globalvars/configuration.dm b/code/_globalvars/configuration.dm index 2334ce6bca..ed58f5779c 100644 --- a/code/_globalvars/configuration.dm +++ b/code/_globalvars/configuration.dm @@ -1,38 +1,38 @@ -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(looc_allowed, TRUE) -GLOBAL_VAR_INIT(dooc_allowed, TRUE) -GLOBAL_VAR_INIT(aooc_allowed, FALSE) -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(looc_allowed, TRUE) +GLOBAL_VAR_INIT(dooc_allowed, TRUE) +GLOBAL_VAR_INIT(aooc_allowed, FALSE) +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 f6022cdd59..01a34c2ab7 100644 --- a/code/_globalvars/game_modes.dm +++ b/code/_globalvars/game_modes.dm @@ -1,12 +1,12 @@ -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_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) \ No newline at end of file diff --git a/code/_globalvars/genetics.dm b/code/_globalvars/genetics.dm index a0557e8e1b..7016415d48 100644 --- a/code/_globalvars/genetics.dm +++ b/code/_globalvars/genetics.dm @@ -1,28 +1,28 @@ - ////////////// -GLOBAL_VAR_INIT(NEARSIGHTBLOCK, 0) -GLOBAL_VAR_INIT(EPILEPSYBLOCK, 0) -GLOBAL_VAR_INIT(COUGHBLOCK, 0) -GLOBAL_VAR_INIT(TOURETTESBLOCK, 0) -GLOBAL_VAR_INIT(NERVOUSBLOCK, 0) -GLOBAL_VAR_INIT(BLINDBLOCK, 0) -GLOBAL_VAR_INIT(DEAFBLOCK, 0) -GLOBAL_VAR_INIT(HULKBLOCK, 0) -GLOBAL_VAR_INIT(TELEBLOCK, 0) -GLOBAL_VAR_INIT(FIREBLOCK, 0) -GLOBAL_VAR_INIT(XRAYBLOCK, 0) -GLOBAL_VAR_INIT(CLUMSYBLOCK, 0) -GLOBAL_VAR_INIT(STRANGEBLOCK, 0) -GLOBAL_VAR_INIT(RACEBLOCK, 0) - -GLOBAL_LIST(bad_se_blocks) -GLOBAL_LIST(good_se_blocks) -GLOBAL_LIST(op_se_blocks) - -GLOBAL_VAR(NULLED_SE) -GLOBAL_VAR(NULLED_UI) - -GLOBAL_LIST_EMPTY(global_mutations) // list of hidden mutation things - -GLOBAL_LIST_EMPTY(bad_mutations) -GLOBAL_LIST_EMPTY(good_mutations) + ////////////// +GLOBAL_VAR_INIT(NEARSIGHTBLOCK, 0) +GLOBAL_VAR_INIT(EPILEPSYBLOCK, 0) +GLOBAL_VAR_INIT(COUGHBLOCK, 0) +GLOBAL_VAR_INIT(TOURETTESBLOCK, 0) +GLOBAL_VAR_INIT(NERVOUSBLOCK, 0) +GLOBAL_VAR_INIT(BLINDBLOCK, 0) +GLOBAL_VAR_INIT(DEAFBLOCK, 0) +GLOBAL_VAR_INIT(HULKBLOCK, 0) +GLOBAL_VAR_INIT(TELEBLOCK, 0) +GLOBAL_VAR_INIT(FIREBLOCK, 0) +GLOBAL_VAR_INIT(XRAYBLOCK, 0) +GLOBAL_VAR_INIT(CLUMSYBLOCK, 0) +GLOBAL_VAR_INIT(STRANGEBLOCK, 0) +GLOBAL_VAR_INIT(RACEBLOCK, 0) + +GLOBAL_LIST(bad_se_blocks) +GLOBAL_LIST(good_se_blocks) +GLOBAL_LIST(op_se_blocks) + +GLOBAL_VAR(NULLED_SE) +GLOBAL_VAR(NULLED_UI) + +GLOBAL_LIST_EMPTY(global_mutations) // list of hidden mutation things + +GLOBAL_LIST_EMPTY(bad_mutations) +GLOBAL_LIST_EMPTY(good_mutations) GLOBAL_LIST_EMPTY(not_good_mutations) \ No newline at end of file diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm index 2391d39b3a..968995e81d 100644 --- a/code/_globalvars/lists/flavor_misc.dm +++ b/code/_globalvars/lists/flavor_misc.dm @@ -1,223 +1,223 @@ -//Preferences stuff - //Hairstyles -GLOBAL_LIST_EMPTY(hair_styles_list) //stores /datum/sprite_accessory/hair indexed by name -GLOBAL_LIST_EMPTY(hair_styles_male_list) //stores only hair names -GLOBAL_LIST_EMPTY(hair_styles_female_list) //stores only hair names -GLOBAL_LIST_EMPTY(facial_hair_styles_list) //stores /datum/sprite_accessory/facial_hair indexed by name -GLOBAL_LIST_EMPTY(facial_hair_styles_male_list) //stores only hair names -GLOBAL_LIST_EMPTY(facial_hair_styles_female_list) //stores only hair names - //Underwear -GLOBAL_LIST_EMPTY_TYPED(underwear_list, /datum/sprite_accessory/underwear/bottom) //stores bottoms 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_TYPED(undershirt_list, /datum/sprite_accessory/underwear/top) //stores tops 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_TYPED(socks_list, /datum/sprite_accessory/underwear/socks) //stores 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(deco_wings_list) -GLOBAL_LIST_EMPTY(r_wings_list) -GLOBAL_LIST_EMPTY(insect_wings_list) -GLOBAL_LIST_EMPTY(insect_fluffs_list) -GLOBAL_LIST_EMPTY(insect_markings_list) -GLOBAL_LIST_EMPTY(caps_list) - -GLOBAL_LIST_INIT(ghost_forms_with_directions_list, list("ghost")) //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, list( - ":thinking:", - "Alien", - "Angel", - "Angryface", - "AtlantisCZE", - "Banned", - "Bliss", - "Blue", - "Boy", - "Boy-Malf", - "Girl", - "Girl-Malf", - "Database", - "Dorf", - "Firewall", - "Fuzzy", - "Gentoo", - "Glitchman", - "Gondola", - "Goon", - "Hades", - "Heartline", - "Helios", - "Hotdog", - "Hourglass", - "House", - "Inverted", - "Jack", - "Matrix", - "Monochrome", - "Mothman", - "Murica", - "Nanotrasen", - "Not Malf", - "Patriot", - "Pirate", - "President", - "Rainbow", - "Clown", - "Random", - "Ravensdale", - "Red October", - "Red", - "Royal", - "Searif", - "Serithi", - "SilveryFerret", - "Smiley", - "Static", - "Syndicat Meow", - "TechDemon", - "Terminal", - "Text", - "Too Deep", - "Triumvirate", - "Triumvirate-M", - "Wasp", - "Weird", - "Xerxes", - "Yes-Man" - )) - -/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, 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(backbaglist, 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 Circuitry -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", - "Circuitry", "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")) - -/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(hair_styles_list) //stores /datum/sprite_accessory/hair indexed by name +GLOBAL_LIST_EMPTY(hair_styles_male_list) //stores only hair names +GLOBAL_LIST_EMPTY(hair_styles_female_list) //stores only hair names +GLOBAL_LIST_EMPTY(facial_hair_styles_list) //stores /datum/sprite_accessory/facial_hair indexed by name +GLOBAL_LIST_EMPTY(facial_hair_styles_male_list) //stores only hair names +GLOBAL_LIST_EMPTY(facial_hair_styles_female_list) //stores only hair names + //Underwear +GLOBAL_LIST_EMPTY_TYPED(underwear_list, /datum/sprite_accessory/underwear/bottom) //stores bottoms 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_TYPED(undershirt_list, /datum/sprite_accessory/underwear/top) //stores tops 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_TYPED(socks_list, /datum/sprite_accessory/underwear/socks) //stores 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(deco_wings_list) +GLOBAL_LIST_EMPTY(r_wings_list) +GLOBAL_LIST_EMPTY(insect_wings_list) +GLOBAL_LIST_EMPTY(insect_fluffs_list) +GLOBAL_LIST_EMPTY(insect_markings_list) +GLOBAL_LIST_EMPTY(caps_list) + +GLOBAL_LIST_INIT(ghost_forms_with_directions_list, list("ghost")) //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, list( + ":thinking:", + "Alien", + "Angel", + "Angryface", + "AtlantisCZE", + "Banned", + "Bliss", + "Blue", + "Boy", + "Boy-Malf", + "Girl", + "Girl-Malf", + "Database", + "Dorf", + "Firewall", + "Fuzzy", + "Gentoo", + "Glitchman", + "Gondola", + "Goon", + "Hades", + "Heartline", + "Helios", + "Hotdog", + "Hourglass", + "House", + "Inverted", + "Jack", + "Matrix", + "Monochrome", + "Mothman", + "Murica", + "Nanotrasen", + "Not Malf", + "Patriot", + "Pirate", + "President", + "Rainbow", + "Clown", + "Random", + "Ravensdale", + "Red October", + "Red", + "Royal", + "Searif", + "Serithi", + "SilveryFerret", + "Smiley", + "Static", + "Syndicat Meow", + "TechDemon", + "Terminal", + "Text", + "Too Deep", + "Triumvirate", + "Triumvirate-M", + "Wasp", + "Weird", + "Xerxes", + "Yes-Man" + )) + +/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, 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(backbaglist, 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 Circuitry +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", + "Circuitry", "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")) + +/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 7340581d25..fb8863ebe7 100644 --- a/code/_globalvars/lists/mapping.dm +++ b/code/_globalvars/lists/mapping.dm @@ -1,52 +1,52 @@ -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) //list of all spawns for events -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) //prisoners go to these -GLOBAL_LIST_EMPTY(holdingfacility) //captured people go here -GLOBAL_LIST_EMPTY(xeno_spawn)//Aliens 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) -GLOBAL_LIST_EMPTY(secequipment) -GLOBAL_LIST_EMPTY(deathsquadspawn) -GLOBAL_LIST_EMPTY(emergencyresponseteamspawn) -GLOBAL_LIST_EMPTY(servant_spawns) //Servants of Ratvar spawn here -GLOBAL_LIST_EMPTY(city_of_cogs_spawns) //Anyone entering the City of Cogs spawns here -GLOBAL_LIST_EMPTY(ruin_landmarks) - - //away missions -GLOBAL_LIST_EMPTY(awaydestinations) //a list of landmarks that the warpgate can take you to -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_EMPTY(stationroom_landmarks) //List of all spawns for stationrooms +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) //list of all spawns for events +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) //prisoners go to these +GLOBAL_LIST_EMPTY(holdingfacility) //captured people go here +GLOBAL_LIST_EMPTY(xeno_spawn)//Aliens 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) +GLOBAL_LIST_EMPTY(secequipment) +GLOBAL_LIST_EMPTY(deathsquadspawn) +GLOBAL_LIST_EMPTY(emergencyresponseteamspawn) +GLOBAL_LIST_EMPTY(servant_spawns) //Servants of Ratvar spawn here +GLOBAL_LIST_EMPTY(city_of_cogs_spawns) //Anyone entering the City of Cogs spawns here +GLOBAL_LIST_EMPTY(ruin_landmarks) + + //away missions +GLOBAL_LIST_EMPTY(awaydestinations) //a list of landmarks that the warpgate can take you to +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_EMPTY(stationroom_landmarks) //List of all spawns for stationrooms diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 3577402b36..ae385339be 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -1,103 +1,103 @@ -GLOBAL_LIST_EMPTY(clients) //all clients -GLOBAL_LIST_EMPTY(admins) //all clients whom are admins -GLOBAL_PROTECT(admins) -GLOBAL_LIST_EMPTY(mentors) //all clients whom are mentors -GLOBAL_PROTECT(mentors) -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 - -GLOBAL_LIST_EMPTY(bunker_passthrough) - -//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(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(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(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(living_cameras) -GLOBAL_LIST_EMPTY(aiEyes) - -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(latejoiners) //CIT CHANGE - All latejoining people, for traitor-target purposes. - -/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() - - //blood types -GLOBAL_LIST_INIT(regular_bloods,list( - "O-", - "O+", - "A-", - "A+", - "B-", - "B+", - "AB-", - "AB+" - )) - -GLOBAL_LIST_INIT(all_types_bloods,list( - "O-", - "O+", - "A-", - "A+", - "B-", - "B+", - "AB-", - "AB+", - "SY", - "X*", - "HF", - "L", - "U", - "GEL", - "BUG" - )) - -GLOBAL_LIST_INIT(blood_types, list( - "blood", - "jellyblood" - )) - -GLOBAL_LIST_INIT(blood_id_types, list( - "blood" = /datum/reagent/blood, - "jellyblood" = /datum/reagent/blood/jellyblood - )) - +GLOBAL_LIST_EMPTY(clients) //all clients +GLOBAL_LIST_EMPTY(admins) //all clients whom are admins +GLOBAL_PROTECT(admins) +GLOBAL_LIST_EMPTY(mentors) //all clients whom are mentors +GLOBAL_PROTECT(mentors) +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 + +GLOBAL_LIST_EMPTY(bunker_passthrough) + +//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(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(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(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(living_cameras) +GLOBAL_LIST_EMPTY(aiEyes) + +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(latejoiners) //CIT CHANGE - All latejoining people, for traitor-target purposes. + +/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() + + //blood types +GLOBAL_LIST_INIT(regular_bloods,list( + "O-", + "O+", + "A-", + "A+", + "B-", + "B+", + "AB-", + "AB+" + )) + +GLOBAL_LIST_INIT(all_types_bloods,list( + "O-", + "O+", + "A-", + "A+", + "B-", + "B+", + "AB-", + "AB+", + "SY", + "X*", + "HF", + "L", + "U", + "GEL", + "BUG" + )) + +GLOBAL_LIST_INIT(blood_types, list( + "blood", + "jellyblood" + )) + +GLOBAL_LIST_INIT(blood_id_types, list( + "blood" = /datum/reagent/blood, + "jellyblood" = /datum/reagent/blood/jellyblood + )) + diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm index 63ab782dd4..7ae197a5d1 100644 --- a/code/_globalvars/lists/names.dm +++ b/code/_globalvars/lists/names.dm @@ -1,50 +1,50 @@ -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_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(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_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(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 fa68fab85f..6a0d8201a7 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(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(materials_list) //list of all /datum/material datums indexed by material id. -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(uplink_items) //list of all uplink item typepaths, ascendingly sorted by their initial name. -GLOBAL_LIST_EMPTY(uplink_categories) //list of all uplink categories, listed by the order they are loaded in code. Be careful. -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) - -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(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(materials_list) //list of all /datum/material datums indexed by material id. +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(uplink_items) //list of all uplink item typepaths, ascendingly sorted by their initial name. +GLOBAL_LIST_EMPTY(uplink_categories) //list of all uplink categories, listed by the order they are loaded in code. Be careful. +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) + +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 01d5051dba..2a97c596b2 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -1,64 +1,64 @@ -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_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_virus_log) -GLOBAL_PROTECT(world_virus_log) -GLOBAL_VAR(world_map_error_log) -GLOBAL_PROTECT(world_map_error_log) -GLOBAL_VAR(subsystem_log) -GLOBAL_PROTECT(subsystem_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_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_virus_log) +GLOBAL_PROTECT(world_virus_log) +GLOBAL_VAR(world_map_error_log) +GLOBAL_PROTECT(world_map_error_log) +GLOBAL_VAR(subsystem_log) +GLOBAL_PROTECT(subsystem_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 ec3593ee9d..21e3663cca 100644 --- a/code/_globalvars/misc.dm +++ b/code/_globalvars/misc.dm @@ -1,35 +1,35 @@ -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. - -GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY")) -GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? - - -GLOBAL_VAR_INIT(announcertype, "standard") - - // 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 - -GLOBAL_LIST_EMPTY(clientless_round_timeouts) // ckey -> time that ckey can rejoin round - -// All religion stuff -GLOBAL_VAR(religion) -GLOBAL_VAR(deity) -GLOBAL_VAR(bible_name) -GLOBAL_VAR(bible_icon_state) -GLOBAL_VAR(bible_item_state) -GLOBAL_VAR(holy_weapon_type) -GLOBAL_VAR(holy_armor_type) +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. + +GLOBAL_VAR_INIT(year, time2text(world.realtime,"YYYY")) +GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? + + +GLOBAL_VAR_INIT(announcertype, "standard") + + // 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 + +GLOBAL_LIST_EMPTY(clientless_round_timeouts) // ckey -> time that ckey can rejoin round + +// All religion stuff +GLOBAL_VAR(religion) +GLOBAL_VAR(deity) +GLOBAL_VAR(bible_name) +GLOBAL_VAR(bible_icon_state) +GLOBAL_VAR(bible_item_state) +GLOBAL_VAR(holy_weapon_type) +GLOBAL_VAR(holy_armor_type) diff --git a/code/_js/byjax.dm b/code/_js/byjax.dm index 9d96bbc412..7ac9bbff18 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/_onclick/adjacent.dm b/code/_onclick/adjacent.dm index 9485ea3374..3f3eecb676 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(var/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(var/atom/neighbor) - if(neighbor == loc) - return TRUE - var/turf/T = loc - if(!istype(T)) - return FALSE - if(T.Adjacent(neighbor, neighbor, src)) - return TRUE - return FALSE - -// This is necessary for storage items not on your person. -/obj/item/Adjacent(var/atom/neighbor, var/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(var/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(var/atom/neighbor) + if(neighbor == loc) + return TRUE + var/turf/T = loc + if(!istype(T)) + return FALSE + if(T.Adjacent(neighbor, neighbor, src)) + return TRUE + return FALSE + +// This is necessary for storage items not on your person. +/obj/item/Adjacent(var/atom/neighbor, var/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 4c477f3872..9bf59d5e76 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -1,520 +1,520 @@ -/* - 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 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/UnarmedAttack(atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves - * atom/attackby(item,user) - used only when adjacent - * item/afterattack(atom,user,adjacent,params) - used both ranged and adjacent - * mob/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 + world.tick_lag - - if(check_click_intercept(params,A)) - return - - if(notransform) - 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"] && (client && client.show_popup_menus || modifiers["right"])) //CIT CHANGE - makes shift-click examine use right click instead of left click in combat mode - ShiftClickOn(A) - return - if(modifiers["alt"]) // alt and alt-gr (rightalt) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(modifiers["right"]) //CIT CHANGE - allows right clicking to perform actions - RightClickOn(A,params) //CIT CHANGE - ditto - return //CIT CHANGE - ditto - - 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() - 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/atom/movable/AM in src) - if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density) - return TRUE - return FALSE - -/atom/movable/proc/CanReach(atom/ultimate_target, obj/item/tool, view_only = FALSE) - // A backwards depth-limited breadth-first-search to see if the target is - // logically "in" anything adjacent to us. - 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 - - if(!(SEND_SIGNAL(target.loc, COMSIG_ATOM_CANREACH, next) & COMPONENT_BLOCK_REACH) && target.loc.canReachInto(src, ultimate_target, next, view_only, tool)) - 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() - -//This is called reach into but it's called on the deepest things first so uh, make sure to account for that! -/atom/proc/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool) - return TRUE - -/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 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 - Only used for swapping hands -*/ -/mob/proc/MiddleClickOn(atom/A) - return - -/mob/living/carbon/MiddleClickOn(atom/A) - if(!stat && mind && iscarbon(A) && A != src) - var/datum/antagonist/changeling/C = mind.has_antag_datum(/datum/antagonist/changeling) - if(C && C.chosen_sting) - C.chosen_sting.try_to_sting(src,A) - next_click = world.time + 5 - return - swap_hand() - -/mob/living/simple_animal/drone/MiddleClickOn(atom/A) - swap_hand() - -// In case of use break glass -/* -/atom/proc/MiddleClick(mob/M as mob) - return -*/ - -/* - 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) - SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user) - 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) - if(!A.AltClick(src)) - altclick_listed_turf(A) - -/mob/proc/altclick_listed_turf(atom/A) - var/turf/T = get_turf(A) - if(T == A.loc || T == A) - if(T == listed_turf) - listed_turf = null - else if(TurfAdjacent(T)) - listed_turf = T - client.statpanel = T.name - -/mob/living/carbon/AltClickOn(atom/A) - if(!stat && mind && iscarbon(A) && A != src) - var/datum/antagonist/changeling/C = mind.has_antag_datum(/datum/antagonist/changeling) - if(C && C.chosen_sting) - C.chosen_sting.try_to_sting(src,A) - next_click = world.time + 5 - return - ..() - -/atom/proc/AltClick(mob/user) - . = SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) - -/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 - - Laser Eyes: as the name implies, handles this since nothing else does currently - face_atom: turns the mob towards what you clicked on -*/ -/mob/proc/LaserEyes(atom/A, params) - return - -/mob/living/LaserEyes(atom/A, params) - changeNext_move(CLICK_CD_RANGE) - - var/obj/item/projectile/beam/LE = new /obj/item/projectile/beam( loc ) - LE.icon = 'icons/effects/genetics.dmi' - LE.icon_state = "eyelasers" - playsound(usr.loc, 'sound/weapons/taser2.ogg', 75, 1) - - LE.firer = src - LE.def_zone = get_organ_target() - LE.preparePixelProjectile(A, src, params) - LE.fire() - -// 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 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/UnarmedAttack(atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves + * atom/attackby(item,user) - used only when adjacent + * item/afterattack(atom,user,adjacent,params) - used both ranged and adjacent + * mob/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 + world.tick_lag + + if(check_click_intercept(params,A)) + return + + if(notransform) + 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"] && (client && client.show_popup_menus || modifiers["right"])) //CIT CHANGE - makes shift-click examine use right click instead of left click in combat mode + ShiftClickOn(A) + return + if(modifiers["alt"]) // alt and alt-gr (rightalt) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(modifiers["right"]) //CIT CHANGE - allows right clicking to perform actions + RightClickOn(A,params) //CIT CHANGE - ditto + return //CIT CHANGE - ditto + + 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() + 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/atom/movable/AM in src) + if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density) + return TRUE + return FALSE + +/atom/movable/proc/CanReach(atom/ultimate_target, obj/item/tool, view_only = FALSE) + // A backwards depth-limited breadth-first-search to see if the target is + // logically "in" anything adjacent to us. + 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 + + if(!(SEND_SIGNAL(target.loc, COMSIG_ATOM_CANREACH, next) & COMPONENT_BLOCK_REACH) && target.loc.canReachInto(src, ultimate_target, next, view_only, tool)) + 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() + +//This is called reach into but it's called on the deepest things first so uh, make sure to account for that! +/atom/proc/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool) + return TRUE + +/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 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 + Only used for swapping hands +*/ +/mob/proc/MiddleClickOn(atom/A) + return + +/mob/living/carbon/MiddleClickOn(atom/A) + if(!stat && mind && iscarbon(A) && A != src) + var/datum/antagonist/changeling/C = mind.has_antag_datum(/datum/antagonist/changeling) + if(C && C.chosen_sting) + C.chosen_sting.try_to_sting(src,A) + next_click = world.time + 5 + return + swap_hand() + +/mob/living/simple_animal/drone/MiddleClickOn(atom/A) + swap_hand() + +// In case of use break glass +/* +/atom/proc/MiddleClick(mob/M as mob) + return +*/ + +/* + 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) + SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user) + 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) + if(!A.AltClick(src)) + altclick_listed_turf(A) + +/mob/proc/altclick_listed_turf(atom/A) + var/turf/T = get_turf(A) + if(T == A.loc || T == A) + if(T == listed_turf) + listed_turf = null + else if(TurfAdjacent(T)) + listed_turf = T + client.statpanel = T.name + +/mob/living/carbon/AltClickOn(atom/A) + if(!stat && mind && iscarbon(A) && A != src) + var/datum/antagonist/changeling/C = mind.has_antag_datum(/datum/antagonist/changeling) + if(C && C.chosen_sting) + C.chosen_sting.try_to_sting(src,A) + next_click = world.time + 5 + return + ..() + +/atom/proc/AltClick(mob/user) + . = SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) + +/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 + + Laser Eyes: as the name implies, handles this since nothing else does currently + face_atom: turns the mob towards what you clicked on +*/ +/mob/proc/LaserEyes(atom/A, params) + return + +/mob/living/LaserEyes(atom/A, params) + changeNext_move(CLICK_CD_RANGE) + + var/obj/item/projectile/beam/LE = new /obj/item/projectile/beam( loc ) + LE.icon = 'icons/effects/genetics.dmi' + LE.icon_state = "eyelasers" + playsound(usr.loc, 'sound/weapons/taser2.ogg', 75, 1) + + LE.firer = src + LE.def_zone = get_organ_target() + LE.preparePixelProjectile(A, src, params) + LE.fire() + +// 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 a2d566d566..6e3cfea424 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -1,185 +1,185 @@ -/* - 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(var/atom/A, var/params) - if(world.time <= next_click) - return - next_click = world.time + 1 - - if(check_click_intercept(params,A)) - return - - if(stat || lockcharge || IsKnockdown() || IsStun() || IsUnconscious()) - 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 && A.Adjacent(src) && (isobj(A) || ismob(A))) - var/atom/movable/C = A - if(C.can_buckle && C.has_buckled_mobs()) - if(C.buckled_mobs.len > 1) - var/unbuckled = input(src, "Who do you wish to unbuckle?","Unbuckle Who?") as null|mob in C.buckled_mobs - if(C.user_unbuckle_mob(unbuckled,src)) - return - else - if(C.user_unbuckle_mob(C.buckled_mobs[1],src)) - return - - 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() - return - -//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) - if(!A.BorgAltClick(src)) - altclick_listed_turf(A) - -/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) - return AltClick(user) - -/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - return AIAltClick() - return ..() - -/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) - return AIAltClick() - return ..() - -/* - 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(var/atom/A, var/params) + if(world.time <= next_click) + return + next_click = world.time + 1 + + if(check_click_intercept(params,A)) + return + + if(stat || lockcharge || IsKnockdown() || IsStun() || IsUnconscious()) + 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 && A.Adjacent(src) && (isobj(A) || ismob(A))) + var/atom/movable/C = A + if(C.can_buckle && C.has_buckled_mobs()) + if(C.buckled_mobs.len > 1) + var/unbuckled = input(src, "Who do you wish to unbuckle?","Unbuckle Who?") as null|mob in C.buckled_mobs + if(C.user_unbuckle_mob(unbuckled,src)) + return + else + if(C.user_unbuckle_mob(C.buckled_mobs[1],src)) + return + + 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() + return + +//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) + if(!A.BorgAltClick(src)) + altclick_listed_turf(A) + +/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) + return AltClick(user) + +/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + return AIAltClick() + return ..() + +/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) + return AIAltClick() + return ..() + +/* + 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 e5c0ffc1ab..dc18107a95 100644 --- a/code/_onclick/drag_drop.dm +++ b/code/_onclick/drag_drop.dm @@ -1,148 +1,148 @@ -/* - 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 - var/list/atom/selected_target[2] - var/obj/item/active_mousedown_item = null - var/mouseParams = "" - var/mouseLocation = null - var/mouseObject = null - var/mouseControlObject = null - var/middragtime = 0 - var/atom/middragatom - -/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 - var/canMouseDown = FALSE - -/obj/item/gun - var/automatic = 0 //can gun use it, 0 is no, anything above 0 is the delay between clicks in ds - -/obj/item/gun/CanItemAutoclick(object, location, params) - . = automatic - -/atom/proc/IsAutoclickable() - . = 1 - -/obj/screen/IsAutoclickable() - . = 0 - -/obj/screen/click_catcher/IsAutoclickable() - . = 1 - -//Please don't roast me too hard -/client/MouseMove(object,location,control,params) - mouseParams = params - mouseLocation = location - mouseObject = object - mouseControlObject = control - if(mob && LAZYLEN(mob.mousemove_intercept_objects)) - for(var/datum/D in mob.mousemove_intercept_objects) - D.onMouseMove(object, location, control, params) - if(!show_popup_menus && mob) //CIT CHANGE - passes onmousemove() to mobs - mob.onMouseMove(object, location, control, params) //CIT CHANGE - ditto - ..() - -/datum/proc/onMouseMove(object, location, control, params) - return - -/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 - mouseControlObject = over_control - 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 + var/list/atom/selected_target[2] + var/obj/item/active_mousedown_item = null + var/mouseParams = "" + var/mouseLocation = null + var/mouseObject = null + var/mouseControlObject = null + var/middragtime = 0 + var/atom/middragatom + +/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 + var/canMouseDown = FALSE + +/obj/item/gun + var/automatic = 0 //can gun use it, 0 is no, anything above 0 is the delay between clicks in ds + +/obj/item/gun/CanItemAutoclick(object, location, params) + . = automatic + +/atom/proc/IsAutoclickable() + . = 1 + +/obj/screen/IsAutoclickable() + . = 0 + +/obj/screen/click_catcher/IsAutoclickable() + . = 1 + +//Please don't roast me too hard +/client/MouseMove(object,location,control,params) + mouseParams = params + mouseLocation = location + mouseObject = object + mouseControlObject = control + if(mob && LAZYLEN(mob.mousemove_intercept_objects)) + for(var/datum/D in mob.mousemove_intercept_objects) + D.onMouseMove(object, location, control, params) + if(!show_popup_menus && mob) //CIT CHANGE - passes onmousemove() to mobs + mob.onMouseMove(object, location, control, params) //CIT CHANGE - ditto + ..() + +/datum/proc/onMouseMove(object, location, control, params) + return + +/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 + mouseControlObject = over_control + 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 ..() \ No newline at end of file diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm index 9d53703cdd..12f7986357 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -1,165 +1,165 @@ -/* - 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" -*/ - -//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" - -/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" - -#define ui_storage1 "CENTER+1:18,SOUTH:5" -#define ui_storage2 "CENTER+2:20,SOUTH:5" - -#define ui_borg_sensor "CENTER-3:15, SOUTH:5" //borgs -#define ui_borg_lamp "CENTER-4:15, SOUTH:5" //borgs -#define ui_borg_thrusters "CENTER-5:15, SOUTH:5" //borgs -#define ui_inv1 "CENTER-2:16,SOUTH:5" //borgs -#define ui_inv2 "CENTER-1 :16,SOUTH:5" //borgs -#define ui_inv3 "CENTER :16,SOUTH:5" //borgs -#define ui_borg_module "CENTER+1:16,SOUTH:5" //borgs -#define ui_borg_store "CENTER+2:16,SOUTH:5" //borgs -#define ui_borg_camera "CENTER+3:21,SOUTH:5" //borgs -#define ui_borg_album "CENTER+4:21,SOUTH:5" //borgs -#define ui_borg_language_menu "EAST-1:27,SOUTH+2:8" //borgs - -#define ui_monkey_head "CENTER-5:13,SOUTH:5" //monkey -#define ui_monkey_mask "CENTER-4:14,SOUTH:5" //monkey -#define ui_monkey_neck "CENTER-3:15,SOUTH:5" //monkey -#define ui_monkey_back "CENTER-2:16,SOUTH:5" //monkey - -//#define ui_alien_storage_l "CENTER-2:14,SOUTH:5"//alien -#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"//alien -#define ui_alien_language_menu "EAST-3:26,SOUTH:5" //alien - -#define ui_drone_drop "CENTER+1:18,SOUTH:5" //maintenance drones -#define ui_drone_pull "CENTER+2:2,SOUTH:5" //maintenance drones -#define ui_drone_storage "CENTER-2:14,SOUTH:5" //maintenance drones -#define ui_drone_head "CENTER-3:14,SOUTH:5" //maintenance drones - -//Lower right, persistent menu -#define ui_drop_throw "EAST-1:28,SOUTH+1:7" -#define ui_pull_resist "EAST-2:26,SOUTH+1:7" -#define ui_movi "EAST-2:26,SOUTH:5" -#define ui_sprintbufferloc "EAST-2:26,SOUTH:18" -#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-5:20,SOUTH:5"//CIT CHANGE - moves this over one tile to accommodate for combat mode toggle -#define ui_building "EAST-5:20,SOUTH:21"//CIT CHANGE - ditto -#define ui_language_menu "EAST-5:4,SOUTH:21"//CIT CHANGE - ditto -#define ui_voremode "EAST-5:20,SOUTH:5" - -#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" - - -//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+1:19"//CIT CHANGE - moves internal icon up a little bit to accommodate for the stamina meter -#define ui_mood "EAST-1:28,CENTER-3:10" - -//living -#define ui_living_pull "EAST-1:28,CENTER-2:15" -#define ui_living_health "EAST-1:28,CENTER:15" - -//borgs -#define ui_borg_health "EAST-1:28,CENTER-1:15" //borgs have the health display where humans have the pressure damage indicator. - -//aliens -#define ui_alien_health "EAST,CENTER-1:15" //aliens have the health display where humans have the pressure damage indicator. -#define ui_alienplasmadisplay "EAST,CENTER-2:15" -#define ui_alien_queen_finder "EAST,CENTER-3:15" - -//constructs -#define ui_construct_pull "EAST,CENTER-2:15" -#define ui_construct_health "EAST,CENTER:15" //same as borgs and humans - -// 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" - -//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" - -//Ghosts - -#define ui_ghost_jumptomob "SOUTH:6,CENTER-2:24" -#define ui_ghost_orbit "SOUTH:6,CENTER-1:24" -#define ui_ghost_reenter_corpse "SOUTH:6,CENTER:24" -#define ui_ghost_teleport "SOUTH:6,CENTER+1:24" -#define ui_ghost_pai "SOUTH: 6, CENTER+2:24" +/* + 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" +*/ + +//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" + +/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" + +#define ui_storage1 "CENTER+1:18,SOUTH:5" +#define ui_storage2 "CENTER+2:20,SOUTH:5" + +#define ui_borg_sensor "CENTER-3:15, SOUTH:5" //borgs +#define ui_borg_lamp "CENTER-4:15, SOUTH:5" //borgs +#define ui_borg_thrusters "CENTER-5:15, SOUTH:5" //borgs +#define ui_inv1 "CENTER-2:16,SOUTH:5" //borgs +#define ui_inv2 "CENTER-1 :16,SOUTH:5" //borgs +#define ui_inv3 "CENTER :16,SOUTH:5" //borgs +#define ui_borg_module "CENTER+1:16,SOUTH:5" //borgs +#define ui_borg_store "CENTER+2:16,SOUTH:5" //borgs +#define ui_borg_camera "CENTER+3:21,SOUTH:5" //borgs +#define ui_borg_album "CENTER+4:21,SOUTH:5" //borgs +#define ui_borg_language_menu "EAST-1:27,SOUTH+2:8" //borgs + +#define ui_monkey_head "CENTER-5:13,SOUTH:5" //monkey +#define ui_monkey_mask "CENTER-4:14,SOUTH:5" //monkey +#define ui_monkey_neck "CENTER-3:15,SOUTH:5" //monkey +#define ui_monkey_back "CENTER-2:16,SOUTH:5" //monkey + +//#define ui_alien_storage_l "CENTER-2:14,SOUTH:5"//alien +#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"//alien +#define ui_alien_language_menu "EAST-3:26,SOUTH:5" //alien + +#define ui_drone_drop "CENTER+1:18,SOUTH:5" //maintenance drones +#define ui_drone_pull "CENTER+2:2,SOUTH:5" //maintenance drones +#define ui_drone_storage "CENTER-2:14,SOUTH:5" //maintenance drones +#define ui_drone_head "CENTER-3:14,SOUTH:5" //maintenance drones + +//Lower right, persistent menu +#define ui_drop_throw "EAST-1:28,SOUTH+1:7" +#define ui_pull_resist "EAST-2:26,SOUTH+1:7" +#define ui_movi "EAST-2:26,SOUTH:5" +#define ui_sprintbufferloc "EAST-2:26,SOUTH:18" +#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-5:20,SOUTH:5"//CIT CHANGE - moves this over one tile to accommodate for combat mode toggle +#define ui_building "EAST-5:20,SOUTH:21"//CIT CHANGE - ditto +#define ui_language_menu "EAST-5:4,SOUTH:21"//CIT CHANGE - ditto +#define ui_voremode "EAST-5:20,SOUTH:5" + +#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" + + +//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+1:19"//CIT CHANGE - moves internal icon up a little bit to accommodate for the stamina meter +#define ui_mood "EAST-1:28,CENTER-3:10" + +//living +#define ui_living_pull "EAST-1:28,CENTER-2:15" +#define ui_living_health "EAST-1:28,CENTER:15" + +//borgs +#define ui_borg_health "EAST-1:28,CENTER-1:15" //borgs have the health display where humans have the pressure damage indicator. + +//aliens +#define ui_alien_health "EAST,CENTER-1:15" //aliens have the health display where humans have the pressure damage indicator. +#define ui_alienplasmadisplay "EAST,CENTER-2:15" +#define ui_alien_queen_finder "EAST,CENTER-3:15" + +//constructs +#define ui_construct_pull "EAST,CENTER-2:15" +#define ui_construct_health "EAST,CENTER:15" //same as borgs and humans + +// 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" + +//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" + +//Ghosts + +#define ui_ghost_jumptomob "SOUTH:6,CENTER-2:24" +#define ui_ghost_orbit "SOUTH:6,CENTER-1:24" +#define ui_ghost_reenter_corpse "SOUTH:6,CENTER:24" +#define ui_ghost_teleport "SOUTH:6,CENTER+1:24" +#define ui_ghost_pai "SOUTH: 6, CENTER+2:24" diff --git a/code/_onclick/hud/credits.dm b/code/_onclick/hud/credits.dm index 702aa1cdcb..2cd0ddedaa 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 a34a53125b..3cfa71e19d 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -1,181 +1,181 @@ - -/mob - var/list/screens = list() - -/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/blurry - icon = 'icons/mob/screen_gen.dmi' - screen_loc = "WEST,SOUTH to EAST,NORTH" - icon_state = "blurry" - -/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 + var/list/screens = list() + +/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/blurry + icon = 'icons/mob/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "blurry" + +/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 549b6346ce..14da637f18 100644 --- a/code/_onclick/hud/ghost.dm +++ b/code/_onclick/hud/ghost.dm @@ -1,95 +1,95 @@ -/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() - -/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/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 +/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() + +/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/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 diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 09794444d1..f8df958eea 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -1,297 +1,297 @@ -/* - 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 - - // subtypes can override this to force a specific UI style - var/ui_style - - //Citadel stuff - var/obj/screen/arousal - -/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) - -/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 - internals = 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 - var/hud_type = /datum/hud - -/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.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 + + // subtypes can override this to force a specific UI style + var/ui_style + + //Citadel stuff + var/obj/screen/arousal + +/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) + +/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 + internals = 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 + var/hud_type = /datum/hud + +/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.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 78a2d2fd17..04da0ef03d 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -1,528 +1,528 @@ -/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 - invisibility = INVISIBILITY_ABSTRACT - -/obj/screen/devil/soul_counter - icon = 'icons/mob/screen_gen.dmi' - 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 - 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) - ..() - owner.overlay_fullscreen("see_through_darkness", /obj/screen/fullscreen/see_through_darkness) - - var/widescreenlayout = FALSE //CIT CHANGE - adds support for different hud layouts depending on widescreen pref - if(owner.client && owner.client.prefs && owner.client.prefs.widescreenpref) //CIT CHANGE - ditto - widescreenlayout = TRUE // CIT CHANGE - ditto - - var/obj/screen/using - var/obj/screen/inventory/inv_box - - using = new /obj/screen/craft - using.icon = ui_style - if(!widescreenlayout) // CIT CHANGE - using.screen_loc = ui_boxcraft // CIT CHANGE - using.hud = src - static_inventory += using - - using = new/obj/screen/language_menu - using.icon = ui_style - if(!widescreenlayout) // CIT CHANGE - using.screen_loc = ui_boxlang // CIT CHANGE - using.hud = src - static_inventory += using - - using = new /obj/screen/area_creator - using.icon = ui_style - if(!widescreenlayout) // CIT CHANGE - using.screen_loc = ui_boxarea // CIT CHANGE - using.hud = src - static_inventory += using - - using = new /obj/screen/voretoggle() //We fancy Vore now - using.icon = tg_ui_icon_to_cit_ui(ui_style) - using.screen_loc = ui_voremode - if(!widescreenlayout) - using.screen_loc = ui_boxvore - 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 = tg_ui_icon_to_cit_ui(ui_style) // CIT CHANGE - overrides mov intent icon - using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking") - using.screen_loc = ui_movi - using.hud = src - static_inventory += using - - //CITADEL CHANGES - sprint button - using = new /obj/screen/sprintbutton - using.icon = tg_ui_icon_to_cit_ui(ui_style) - using.icon_state = (owner.sprinting ? "act_sprint_on" : "act_sprint") - using.screen_loc = ui_movi - using.hud = src - static_inventory += using - //END OF CITADEL CHANGES - - //same as above but buffer. - sprint_buffer = new /obj/screen/sprint_buffer - sprint_buffer.screen_loc = ui_sprintbufferloc - sprint_buffer.hud = src - static_inventory += sprint_buffer - - - 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 = SLOT_W_UNIFORM - inv_box.icon_state = "uniform" - inv_box.screen_loc = ui_iclothing - toggleable_inventory += inv_box - - inv_box = new /obj/screen/inventory() - inv_box.name = "o_clothing" - inv_box.icon = ui_style - inv_box.slot_id = SLOT_WEAR_SUIT - inv_box.icon_state = "suit" - inv_box.screen_loc = ui_oclothing - 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 = SLOT_WEAR_ID - 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 = SLOT_WEAR_MASK - 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 = SLOT_NECK - 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 = SLOT_BACK - 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 = SLOT_L_STORE - 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 = SLOT_R_STORE - 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 = SLOT_S_STORE - static_inventory += inv_box - - using = new /obj/screen/resist() - using.icon = ui_style - using.screen_loc = ui_overridden_resist // CIT CHANGE - changes this to overridden resist - using.hud = src - hotkeybuttons += using - - //CIT CHANGES - rest and combat mode buttons - using = new /obj/screen/restbutton() - using.icon = tg_ui_icon_to_cit_ui(ui_style) - using.screen_loc = ui_pull_resist - using.hud = src - static_inventory += using - - using = new /obj/screen/combattoggle() - using.icon = tg_ui_icon_to_cit_ui(ui_style) - using.screen_loc = ui_combat_toggle - using.hud = src - static_inventory += using - //END OF CIT CHANGES - - 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 = SLOT_GLOVES - 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 = SLOT_GLASSES - 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 = SLOT_EARS - 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 = SLOT_HEAD - 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 = SLOT_SHOES - 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 = SLOT_BELT - 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 - //CIT CHANGE - adds arousal and stamina to hud - arousal = new /obj/screen/arousal() - arousal.icon_state = (owner.canbearoused == 1 ? "arousal0" : "") - arousal.hud = src - infodisplay += arousal - - staminas = new /obj/screen/staminas() - staminas.hud = src - infodisplay += staminas - - if(!CONFIG_GET(flag/disable_stambuffer)) - staminabuffer = new /obj/screen/staminabuffer() - staminabuffer.hud = src - infodisplay += staminabuffer - //END OF CIT CHANGES - - healthdoll = new /obj/screen/healthdoll() - healthdoll.hud = src - infodisplay += healthdoll - - pull_icon = new /obj/screen/pull() - pull_icon.icon = ui_style - pull_icon.hud = src - pull_icon.update_icon() - pull_icon.screen_loc = ui_pull_resist - 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 - - blood_display = new /obj/screen/bloodsucker/blood_counter // Blood Volume - blood_display.hud = src - infodisplay += blood_display - - vamprank_display = new /obj/screen/bloodsucker/rank_counter // Vampire Rank - vamprank_display.hud = src - infodisplay += vamprank_display - - sunlight_display = new /obj/screen/bloodsucker/sunlight_counter // Sunlight - sunlight_display.hud = src - infodisplay += sunlight_display - - 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[inv.slot_id] = 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 + invisibility = INVISIBILITY_ABSTRACT + +/obj/screen/devil/soul_counter + icon = 'icons/mob/screen_gen.dmi' + 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 + 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) + ..() + owner.overlay_fullscreen("see_through_darkness", /obj/screen/fullscreen/see_through_darkness) + + var/widescreenlayout = FALSE //CIT CHANGE - adds support for different hud layouts depending on widescreen pref + if(owner.client && owner.client.prefs && owner.client.prefs.widescreenpref) //CIT CHANGE - ditto + widescreenlayout = TRUE // CIT CHANGE - ditto + + var/obj/screen/using + var/obj/screen/inventory/inv_box + + using = new /obj/screen/craft + using.icon = ui_style + if(!widescreenlayout) // CIT CHANGE + using.screen_loc = ui_boxcraft // CIT CHANGE + using.hud = src + static_inventory += using + + using = new/obj/screen/language_menu + using.icon = ui_style + if(!widescreenlayout) // CIT CHANGE + using.screen_loc = ui_boxlang // CIT CHANGE + using.hud = src + static_inventory += using + + using = new /obj/screen/area_creator + using.icon = ui_style + if(!widescreenlayout) // CIT CHANGE + using.screen_loc = ui_boxarea // CIT CHANGE + using.hud = src + static_inventory += using + + using = new /obj/screen/voretoggle() //We fancy Vore now + using.icon = tg_ui_icon_to_cit_ui(ui_style) + using.screen_loc = ui_voremode + if(!widescreenlayout) + using.screen_loc = ui_boxvore + 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 = tg_ui_icon_to_cit_ui(ui_style) // CIT CHANGE - overrides mov intent icon + using.icon_state = (mymob.m_intent == MOVE_INTENT_RUN ? "running" : "walking") + using.screen_loc = ui_movi + using.hud = src + static_inventory += using + + //CITADEL CHANGES - sprint button + using = new /obj/screen/sprintbutton + using.icon = tg_ui_icon_to_cit_ui(ui_style) + using.icon_state = (owner.sprinting ? "act_sprint_on" : "act_sprint") + using.screen_loc = ui_movi + using.hud = src + static_inventory += using + //END OF CITADEL CHANGES + + //same as above but buffer. + sprint_buffer = new /obj/screen/sprint_buffer + sprint_buffer.screen_loc = ui_sprintbufferloc + sprint_buffer.hud = src + static_inventory += sprint_buffer + + + 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 = SLOT_W_UNIFORM + inv_box.icon_state = "uniform" + inv_box.screen_loc = ui_iclothing + toggleable_inventory += inv_box + + inv_box = new /obj/screen/inventory() + inv_box.name = "o_clothing" + inv_box.icon = ui_style + inv_box.slot_id = SLOT_WEAR_SUIT + inv_box.icon_state = "suit" + inv_box.screen_loc = ui_oclothing + 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 = SLOT_WEAR_ID + 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 = SLOT_WEAR_MASK + 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 = SLOT_NECK + 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 = SLOT_BACK + 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 = SLOT_L_STORE + 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 = SLOT_R_STORE + 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 = SLOT_S_STORE + static_inventory += inv_box + + using = new /obj/screen/resist() + using.icon = ui_style + using.screen_loc = ui_overridden_resist // CIT CHANGE - changes this to overridden resist + using.hud = src + hotkeybuttons += using + + //CIT CHANGES - rest and combat mode buttons + using = new /obj/screen/restbutton() + using.icon = tg_ui_icon_to_cit_ui(ui_style) + using.screen_loc = ui_pull_resist + using.hud = src + static_inventory += using + + using = new /obj/screen/combattoggle() + using.icon = tg_ui_icon_to_cit_ui(ui_style) + using.screen_loc = ui_combat_toggle + using.hud = src + static_inventory += using + //END OF CIT CHANGES + + 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 = SLOT_GLOVES + 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 = SLOT_GLASSES + 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 = SLOT_EARS + 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 = SLOT_HEAD + 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 = SLOT_SHOES + 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 = SLOT_BELT + 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 + //CIT CHANGE - adds arousal and stamina to hud + arousal = new /obj/screen/arousal() + arousal.icon_state = (owner.canbearoused == 1 ? "arousal0" : "") + arousal.hud = src + infodisplay += arousal + + staminas = new /obj/screen/staminas() + staminas.hud = src + infodisplay += staminas + + if(!CONFIG_GET(flag/disable_stambuffer)) + staminabuffer = new /obj/screen/staminabuffer() + staminabuffer.hud = src + infodisplay += staminabuffer + //END OF CIT CHANGES + + healthdoll = new /obj/screen/healthdoll() + healthdoll.hud = src + infodisplay += healthdoll + + pull_icon = new /obj/screen/pull() + pull_icon.icon = ui_style + pull_icon.hud = src + pull_icon.update_icon() + pull_icon.screen_loc = ui_pull_resist + 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 + + blood_display = new /obj/screen/bloodsucker/blood_counter // Blood Volume + blood_display.hud = src + infodisplay += blood_display + + vamprank_display = new /obj/screen/bloodsucker/rank_counter // Vampire Rank + vamprank_display.hud = src + infodisplay += vamprank_display + + sunlight_display = new /obj/screen/bloodsucker/sunlight_counter // Sunlight + sunlight_display.hud = src + infodisplay += sunlight_display + + 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[inv.slot_id] = 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 3d3c3cf64a..d665330aac 100644 --- a/code/_onclick/hud/monkey.dm +++ b/code/_onclick/hud/monkey.dm @@ -1,165 +1,165 @@ -/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 = SLOT_WEAR_MASK - 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 = SLOT_NECK - 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 = SLOT_HEAD - 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 = SLOT_BACK - 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.screen_loc = ui_pull_resist - pull_icon.hud = src - pull_icon.update_icon() - 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_pull_resist - 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[inv.slot_id] = 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 = SLOT_WEAR_MASK + 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 = SLOT_NECK + 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 = SLOT_HEAD + 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 = SLOT_BACK + 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.screen_loc = ui_pull_resist + pull_icon.hud = src + pull_icon.update_icon() + 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_pull_resist + 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[inv.slot_id] = 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 00c89644b6..9859e7acdf 100644 --- a/code/_onclick/hud/robot.dm +++ b/code/_onclick/hud/robot.dm @@ -1,307 +1,307 @@ -/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() - -/obj/screen/robot/sensors - name = "Sensor Augmentation" - icon_state = "cyborg_sensor" - -/obj/screen/robot/sensors/Click() - if(..()) - return - var/mob/living/silicon/S = usr - S.toggle_sensors() - -/obj/screen/robot/language_menu - name = "silicon language selection" - icon_state = "talk_wheel" - -/obj/screen/robot/language_menu/Click() - if(..()) - return - var/mob/living/silicon/S = usr - S.open_language_menu(usr) - -/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/robot/language_menu - using.screen_loc = ui_borg_language_menu - using.hud = src - 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/robot/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 - static_inventory += mymobR.hands - -//Store - module_store_icon = new /obj/screen/robot/store() - module_store_icon.hud = src - module_store_icon.screen_loc = ui_borg_store - - pull_icon = new /obj/screen/pull() - pull_icon.icon = 'icons/mob/screen_cyborg.dmi' - pull_icon.hud = src - pull_icon.update_icon() - pull_icon.screen_loc = ui_borg_pull - 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() + +/obj/screen/robot/sensors + name = "Sensor Augmentation" + icon_state = "cyborg_sensor" + +/obj/screen/robot/sensors/Click() + if(..()) + return + var/mob/living/silicon/S = usr + S.toggle_sensors() + +/obj/screen/robot/language_menu + name = "silicon language selection" + icon_state = "talk_wheel" + +/obj/screen/robot/language_menu/Click() + if(..()) + return + var/mob/living/silicon/S = usr + S.open_language_menu(usr) + +/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/robot/language_menu + using.screen_loc = ui_borg_language_menu + using.hud = src + 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/robot/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 + static_inventory += mymobR.hands + +//Store + module_store_icon = new /obj/screen/robot/store() + module_store_icon.hud = src + module_store_icon.screen_loc = ui_borg_store + + pull_icon = new /obj/screen/pull() + pull_icon.icon = 'icons/mob/screen_cyborg.dmi' + pull_icon.hud = src + pull_icon.update_icon() + pull_icon.screen_loc = ui_borg_pull + 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 dec743d35c..c8926507bc 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -1,715 +1,715 @@ -/* - 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. - -/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/craft - name = "crafting menu" - icon = 'icons/mob/screen_midnight.dmi' - icon_state = "craft" - screen_loc = ui_crafting - -/obj/screen/craft/Click() - var/mob/living/M = usr - if(isobserver(usr)) - return - M.OpenCraftingMenu() - -/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_icon() - . = ..() - - if(!handcuff_overlay) - var/state = (!(held_index % 2)) ? "markus" : "gabrielle" - handcuff_overlay = mutable_appearance('icons/mob/screen_gen.dmi', state) - - cut_overlay(list(handcuff_overlay, blocked_overlay, "hand_active")) - - if(!hud?.mymob) - return - - if(iscarbon(hud.mymob)) - var/mob/living/carbon/C = hud.mymob - if(C.handcuffed) - add_overlay(handcuff_overlay) - - if(held_index) - if(!C.has_hand_for_held_index(held_index)) - add_overlay(blocked_overlay) - - if(held_index == hud.mymob.active_hand_index) - add_overlay("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)) - var/obj/item/clothing/check - var/internals = FALSE - - for(check in GET_INTERNAL_SLOTS(C)) - if(istype(check, /obj/item/clothing/mask)) - var/obj/item/clothing/mask/M = check - if(M.mask_adjusted) - M.adjustmask(C) - if(CHECK_BITFIELD(check.clothing_flags, ALLOWINTERNALS)) - internals = TRUE - if(!internals) - 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/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" - screen_loc = ui_health - icon_state = "corehealth" - -/obj/screen/healths/guardian - name = "summoner health" - icon = 'icons/mob/guardian.dmi' - icon_state = "base" - screen_loc = ui_health - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healths/clock - icon = 'icons/mob/actions.dmi' - icon_state = "bg_clock" - screen_loc = ui_health - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healths/clock/gear - icon = 'icons/mob/clockwork_mobs.dmi' - icon_state = "bg_gear" - screen_loc = ui_internal - -/obj/screen/healths/revenant - name = "essence" - icon = 'icons/mob/actions.dmi' - icon_state = "bg_revenant" - screen_loc = ui_health - 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/healths/lavaland_elite - icon = 'icons/mob/screen_elite.dmi' - icon_state = "elite_health0" - screen_loc = ui_health - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - -/obj/screen/healthdoll - name = "health doll" - screen_loc = ui_healthdoll - -/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. + +/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/craft + name = "crafting menu" + icon = 'icons/mob/screen_midnight.dmi' + icon_state = "craft" + screen_loc = ui_crafting + +/obj/screen/craft/Click() + var/mob/living/M = usr + if(isobserver(usr)) + return + M.OpenCraftingMenu() + +/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_icon() + . = ..() + + if(!handcuff_overlay) + var/state = (!(held_index % 2)) ? "markus" : "gabrielle" + handcuff_overlay = mutable_appearance('icons/mob/screen_gen.dmi', state) + + cut_overlay(list(handcuff_overlay, blocked_overlay, "hand_active")) + + if(!hud?.mymob) + return + + if(iscarbon(hud.mymob)) + var/mob/living/carbon/C = hud.mymob + if(C.handcuffed) + add_overlay(handcuff_overlay) + + if(held_index) + if(!C.has_hand_for_held_index(held_index)) + add_overlay(blocked_overlay) + + if(held_index == hud.mymob.active_hand_index) + add_overlay("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)) + var/obj/item/clothing/check + var/internals = FALSE + + for(check in GET_INTERNAL_SLOTS(C)) + if(istype(check, /obj/item/clothing/mask)) + var/obj/item/clothing/mask/M = check + if(M.mask_adjusted) + M.adjustmask(C) + if(CHECK_BITFIELD(check.clothing_flags, ALLOWINTERNALS)) + internals = TRUE + if(!internals) + 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/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" + screen_loc = ui_health + icon_state = "corehealth" + +/obj/screen/healths/guardian + name = "summoner health" + icon = 'icons/mob/guardian.dmi' + icon_state = "base" + screen_loc = ui_health + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healths/clock + icon = 'icons/mob/actions.dmi' + icon_state = "bg_clock" + screen_loc = ui_health + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healths/clock/gear + icon = 'icons/mob/clockwork_mobs.dmi' + icon_state = "bg_gear" + screen_loc = ui_internal + +/obj/screen/healths/revenant + name = "essence" + icon = 'icons/mob/actions.dmi' + icon_state = "bg_revenant" + screen_loc = ui_health + 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/healths/lavaland_elite + icon = 'icons/mob/screen_elite.dmi' + icon_state = "elite_health0" + screen_loc = ui_health + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/obj/screen/healthdoll + name = "health doll" + screen_loc = ui_healthdoll + +/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 e6b2a63673..cda19dbaac 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -1,167 +1,167 @@ - -/obj/item/proc/melee_attack_chain(mob/user, atom/target, params) - if(!tool_attack_chain(user, target) && pre_attack(target, user, params)) - // Return 1 in attackby() to prevent afterattack() effects (when safely moving items for example) - var/resolved = target.attackby(src, user, params) - if(!resolved && target && !QDELETED(src)) - afterattack(target, user, 1, params) // 1: clicking something Adjacent - - -//Checks if the item can work as a tool, calling the appropriate tool behavior on the target -/obj/item/proc/tool_attack_chain(mob/user, atom/target) - if(!tool_behaviour) - return FALSE - - return target.tool_act(user, src, tool_behaviour) - - -// 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) - -/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 FALSE - return TRUE //return FALSE to avoid calling attackby after this proc does stuff - -// No comment -/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) - - -/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(user.getStaminaLoss() >= STAMINA_SOFTCRIT) // CIT CHANGE - makes it impossible to attack in stamina softcrit - to_chat(user, "You're too exhausted.") // CIT CHANGE - ditto - return // CIT CHANGE - ditto - - if(force && damtype != STAMINA && HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You don't want to harm other living beings!") - return - - if(!force) - playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), 1, -1) - else if(hitsound) - playsound(loc, hitsound, get_clamped_volume(), 1, -1) - - M.lastattacker = user.real_name - M.lastattackerckey = user.ckey - - 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) - - user.adjustStaminaLossBuffered(getweight()*0.8)//CIT CHANGE - makes attacking things cause stamina loss - -//the equivalent of the standard version of 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 - if(user.getStaminaLoss() >= STAMINA_SOFTCRIT) // CIT CHANGE - makes it impossible to attack in stamina softcrit - to_chat(user, "You're too exhausted.") // CIT CHANGE - ditto - return // CIT CHANGE - ditto - user.adjustStaminaLossBuffered(getweight()*1.2)//CIT CHANGE - makes attacking things cause stamina loss - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(O) - O.attacked_by(src, user) - -/atom/movable/proc/attacked_by() - return - -/obj/attacked_by(obj/item/I, mob/living/user) - if(I.force) - visible_message("[user] has hit [src] with [I]!", null, 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) - //CIT CHANGES START HERE - combatmode and resting checks - var/totitemdamage = I.force - if(iscarbon(user)) - var/mob/living/carbon/tempcarb = user - if(!tempcarb.combatmode) - totitemdamage *= 0.5 - if(user.resting) - totitemdamage *= 0.5 - //CIT CHANGES END HERE - if(user != src && check_shields(I, totitemdamage, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) - return FALSE - send_item_attack_message(I, user) - if(I.force) - apply_damage(totitemdamage, I.damtype) //CIT CHANGE - replaces I.force with totitemdamage - if(I.damtype == BRUTE && !HAS_TRAIT(src, TRAIT_NOMARROW)) - if(prob(33)) - I.add_mob_blood(src) - var/turf/location = get_turf(src) - if(iscarbon(src)) - var/mob/living/carbon/C = src - C.bleed(totitemdamage) - else - add_splatter_floor(location) - if(totitemdamage >= 10 && 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(), 1, -1) - else - return ..() - -// 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 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) - -/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] has been [message_verb][message_hit_area] with [I]." - if(user in viewers(src, null)) - attack_message = "[user] has [message_verb] [src][message_hit_area] with [I]!" - visible_message("[attack_message]",\ - "[attack_message]", null, COMBAT_MESSAGE_RANGE) - if(hit_area == BODY_ZONE_HEAD) - if(prob(2)) - playsound(src, 'sound/weapons/dink.ogg', 30, 1) - return 1 - -/obj/item/proc/getweight() - return total_mass || w_class * 1.25 + +/obj/item/proc/melee_attack_chain(mob/user, atom/target, params) + if(!tool_attack_chain(user, target) && pre_attack(target, user, params)) + // Return 1 in attackby() to prevent afterattack() effects (when safely moving items for example) + var/resolved = target.attackby(src, user, params) + if(!resolved && target && !QDELETED(src)) + afterattack(target, user, 1, params) // 1: clicking something Adjacent + + +//Checks if the item can work as a tool, calling the appropriate tool behavior on the target +/obj/item/proc/tool_attack_chain(mob/user, atom/target) + if(!tool_behaviour) + return FALSE + + return target.tool_act(user, src, tool_behaviour) + + +// 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) + +/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 FALSE + return TRUE //return FALSE to avoid calling attackby after this proc does stuff + +// No comment +/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) + + +/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(user.getStaminaLoss() >= STAMINA_SOFTCRIT) // CIT CHANGE - makes it impossible to attack in stamina softcrit + to_chat(user, "You're too exhausted.") // CIT CHANGE - ditto + return // CIT CHANGE - ditto + + if(force && damtype != STAMINA && HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to harm other living beings!") + return + + if(!force) + playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), 1, -1) + else if(hitsound) + playsound(loc, hitsound, get_clamped_volume(), 1, -1) + + M.lastattacker = user.real_name + M.lastattackerckey = user.ckey + + 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) + + user.adjustStaminaLossBuffered(getweight()*0.8)//CIT CHANGE - makes attacking things cause stamina loss + +//the equivalent of the standard version of 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 + if(user.getStaminaLoss() >= STAMINA_SOFTCRIT) // CIT CHANGE - makes it impossible to attack in stamina softcrit + to_chat(user, "You're too exhausted.") // CIT CHANGE - ditto + return // CIT CHANGE - ditto + user.adjustStaminaLossBuffered(getweight()*1.2)//CIT CHANGE - makes attacking things cause stamina loss + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(O) + O.attacked_by(src, user) + +/atom/movable/proc/attacked_by() + return + +/obj/attacked_by(obj/item/I, mob/living/user) + if(I.force) + visible_message("[user] has hit [src] with [I]!", null, 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) + //CIT CHANGES START HERE - combatmode and resting checks + var/totitemdamage = I.force + if(iscarbon(user)) + var/mob/living/carbon/tempcarb = user + if(!tempcarb.combatmode) + totitemdamage *= 0.5 + if(user.resting) + totitemdamage *= 0.5 + //CIT CHANGES END HERE + if(user != src && check_shields(I, totitemdamage, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) + return FALSE + send_item_attack_message(I, user) + if(I.force) + apply_damage(totitemdamage, I.damtype) //CIT CHANGE - replaces I.force with totitemdamage + if(I.damtype == BRUTE && !HAS_TRAIT(src, TRAIT_NOMARROW)) + if(prob(33)) + I.add_mob_blood(src) + var/turf/location = get_turf(src) + if(iscarbon(src)) + var/mob/living/carbon/C = src + C.bleed(totitemdamage) + else + add_splatter_floor(location) + if(totitemdamage >= 10 && 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(), 1, -1) + else + return ..() + +// 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 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) + +/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] has been [message_verb][message_hit_area] with [I]." + if(user in viewers(src, null)) + attack_message = "[user] has [message_verb] [src][message_hit_area] with [I]!" + visible_message("[attack_message]",\ + "[attack_message]", null, COMBAT_MESSAGE_RANGE) + if(hit_area == BODY_ZONE_HEAD) + if(prob(2)) + playsound(src, 'sound/weapons/dink.ogg', 30, 1) + return 1 + +/obj/item/proc/getweight() + return total_mass || w_class * 1.25 diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index f76a745fd2..d596b5fabf 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -1,83 +1,83 @@ -/mob/dead/observer/DblClickOn(var/atom/A, var/params) - 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() // (cloning scanner, body bag, closet, mech, etc) - return // seems legit. - - // Things you might plausibly want to follow - if(ismovableatom(A)) - ManualFollow(A) - - // Otherwise jump - else if(A.loc) - forceMove(get_turf(A)) - update_parallax_contents() - -/mob/dead/observer/ClickOn(var/atom/A, var/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"]) - altclick_listed_turf(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(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) - return ..() - -// --------------------------------------- -// And here are some good things for free: -// Now you can click through portals, wormholes, gateways, and teleporters while observing. -Sayu - -/obj/machinery/gateway/centerstation/attack_ghost(mob/user) - if(awaygate) - user.forceMove(awaygate.loc) - else - to_chat(user, "[src] has no destination.") - return ..() - -/obj/machinery/gateway/centeraway/attack_ghost(mob/user) - if(stationgate) - user.forceMove(stationgate.loc) - else - to_chat(user, "[src] has no destination.") - 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(var/atom/A, var/params) + 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() // (cloning scanner, body bag, closet, mech, etc) + return // seems legit. + + // Things you might plausibly want to follow + if(ismovableatom(A)) + ManualFollow(A) + + // Otherwise jump + else if(A.loc) + forceMove(get_turf(A)) + update_parallax_contents() + +/mob/dead/observer/ClickOn(var/atom/A, var/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"]) + altclick_listed_turf(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(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) + return ..() + +// --------------------------------------- +// And here are some good things for free: +// Now you can click through portals, wormholes, gateways, and teleporters while observing. -Sayu + +/obj/machinery/gateway/centerstation/attack_ghost(mob/user) + if(awaygate) + user.forceMove(awaygate.loc) + else + to_chat(user, "[src] has no destination.") + return ..() + +/obj/machinery/gateway/centeraway/attack_ghost(mob/user) + if(stationgate) + user.forceMove(stationgate.loc) + else + to_chat(user, "[src] has no destination.") + 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 6ee7d51091..7e194d5889 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. - 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 - - var/override = 0 - - for(var/datum/mutation/human/HM in dna.mutations) - override += HM.on_attack_hand(src, A, proximity) - - if(override) - return - - SEND_SIGNAL(src, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, A) - 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(var/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 (istype(glasses) && glasses.ranged_attack(src,A,mouseparams)) - return - - for(var/datum/mutation/human/HM in dna.mutations) - HM.on_ranged_attack(src, A, mouseparams) - - 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) - return - -/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 [ML]!") - if(armor >= 2) - return - for(var/thing in diseases) - var/datum/disease/D = thing - ML.ForceContractDisease(D) - else - ML.visible_message("[src] has attempted to bite [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) - 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. + 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 + + var/override = 0 + + for(var/datum/mutation/human/HM in dna.mutations) + override += HM.on_attack_hand(src, A, proximity) + + if(override) + return + + SEND_SIGNAL(src, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, A) + 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(var/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 (istype(glasses) && glasses.ranged_attack(src,A,mouseparams)) + return + + for(var/datum/mutation/human/HM in dna.mutations) + HM.on_ranged_attack(src, A, mouseparams) + + 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) + return + +/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 [ML]!") + if(armor >= 2) + return + for(var/thing in diseases) + var/datum/disease/D = thing + ML.ForceContractDisease(D) + else + ML.visible_message("[src] has attempted to bite [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) + 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/controllers/configuration/config_entry.dm b/code/controllers/configuration/config_entry.dm index 3f07eed029..d97e7c379a 100644 --- a/code/controllers/configuration/config_entry.dm +++ b/code/controllers/configuration/config_entry.dm @@ -1,240 +1,240 @@ -#define VALUE_MODE_NUM 0 -#define VALUE_MODE_TEXT 1 -#define VALUE_MODE_FLAG 2 -#define VALUE_MODE_NUM_LIST 3 - -#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/postload_required = FALSE //requires running OnPostload() - - 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/proc/OnPostload() - 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 != "auto_trim" && ..() - -/datum/config_entry/string/ValidateAndSet(str_val, during_load) - 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("max_val", "min_val", "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 = " " - var/lowercase = TRUE - -/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 = copytext(str_val, 1, key_pos) - if(lowercase) - key_name = lowertext(key_name) - key_value = copytext(str_val, key_pos + 1) - 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(VALUE_MODE_NUM_LIST) - // this is all copy+pasted from number list up there, but it's super basic so I don't see it being changed soon - var/list/new_list = list() - var/list/values = splittext(key_value," ") - for(var/I in values) - var/temp = text2num(I) - if(isnull(temp)) - log_admin("invalid number list entry in [key_name]: [I]") - continue_check_value = FALSE - new_list += temp - new_value = new_list - continue_check_value = new_list.len - 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 != "splitter" && ..() - -//snowflake for donator things being on one line smh -/datum/config_entry/multi_keyed_flag - vv_VAS = FALSE - abstract_type = /datum/config_entry/multi_keyed_flag - config_entry_value = list() - var/delimiter = "|" - -/datum/config_entry/multi_keyed_flag/vv_edit_var(var_name, var_value) - if(var_name == NAMEOF(src, delimiter)) - return FALSE - return ..() - -/datum/config_entry/multi_keyed_flag/ValidateAndSet(str_val) - if(!VASProcCallGuard(str_val)) - return FALSE - str_val = trim(str_val) - var/list/keys = splittext(str_val, delimiter) - for(var/i in keys) - config_entry_value[process_key(i)] = TRUE - return length(keys)? TRUE : FALSE - -/datum/config_entry/multi_keyed_flag/proc/process_key(key) - return trim(key) +#define VALUE_MODE_NUM 0 +#define VALUE_MODE_TEXT 1 +#define VALUE_MODE_FLAG 2 +#define VALUE_MODE_NUM_LIST 3 + +#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/postload_required = FALSE //requires running OnPostload() + + 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/proc/OnPostload() + 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 != "auto_trim" && ..() + +/datum/config_entry/string/ValidateAndSet(str_val, during_load) + 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("max_val", "min_val", "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 = " " + var/lowercase = TRUE + +/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 = copytext(str_val, 1, key_pos) + if(lowercase) + key_name = lowertext(key_name) + key_value = copytext(str_val, key_pos + 1) + 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(VALUE_MODE_NUM_LIST) + // this is all copy+pasted from number list up there, but it's super basic so I don't see it being changed soon + var/list/new_list = list() + var/list/values = splittext(key_value," ") + for(var/I in values) + var/temp = text2num(I) + if(isnull(temp)) + log_admin("invalid number list entry in [key_name]: [I]") + continue_check_value = FALSE + new_list += temp + new_value = new_list + continue_check_value = new_list.len + 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 != "splitter" && ..() + +//snowflake for donator things being on one line smh +/datum/config_entry/multi_keyed_flag + vv_VAS = FALSE + abstract_type = /datum/config_entry/multi_keyed_flag + config_entry_value = list() + var/delimiter = "|" + +/datum/config_entry/multi_keyed_flag/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, delimiter)) + return FALSE + return ..() + +/datum/config_entry/multi_keyed_flag/ValidateAndSet(str_val) + if(!VASProcCallGuard(str_val)) + return FALSE + str_val = trim(str_val) + var/list/keys = splittext(str_val, delimiter) + for(var/i in keys) + config_entry_value[process_key(i)] = TRUE + return length(keys)? TRUE : FALSE + +/datum/config_entry/multi_keyed_flag/proc/process_key(key) + return trim(key) diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index 730a3f17f4..0caa86f68c 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -1,384 +1,384 @@ -/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/storyteller_cache - var/list/mode_names - var/list/mode_reports - var/list/mode_false_report_weight - - var/motd - -/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() - storyteller_cache = typecacheof(/datum/dynamic_storyteller, TRUE) - 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() - -/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 - var/list/postload_required = list() - for(var/L in lines) - L = trim(L) - if(!L) - continue - - var/firstchar = copytext(L, 1, 2) - if(firstchar == "#") - continue - - var/lockthis = firstchar == "@" - if(lockthis) - L = copytext(L, 2) - - var/pos = findtext(L, " ") - var/entry = null - var/value = null - - if(pos) - entry = lowertext(copytext(L, 1, pos)) - value = copytext(L, pos + 1) - 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) - addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, "This server is using deprecated configuration settings. Please check the logs and update accordingly."), 0) - 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, TRUE) - 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.") - if(E.postload_required) - postload_required[E] = TRUE - - E.resident_file = filename - - if(validated) - E.modified = TRUE - - for(var/i in postload_required) - var/datum/config_entry/E = i - E.OnPostload() - - ++. - -/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). - // for future reference: just use initial() lol - 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 - mode_reports[M.config_tag] = M.generate_report() - if(probabilities[M.config_tag]>0) - mode_false_report_weight[M.config_tag] = M.false_report_weight - else - mode_false_report_weight[M.config_tag] = 1 - 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 - -/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(copytext(t, 1, 2) == "#") - continue - - var/pos = findtext(t, " ") - var/command = null - var/data = null - - if(pos) - command = lowertext(copytext(t, 1, pos)) - data = copytext(t, pos + 1) - 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 ("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/pick_storyteller(storyteller_name) - for(var/T in storyteller_cache) - var/datum/dynamic_storyteller/S = T - var/name = initial(S.name) - if(name && name == storyteller_name) - return T - return /datum/dynamic_storyteller/classic - -/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(M.config_tag in SSvote.stored_modetier_results && SSvote.stored_modetier_results[M.config_tag] < Get(/datum/config_entry/number/dropped_modes)) - 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 + 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/storyteller_cache + var/list/mode_names + var/list/mode_reports + var/list/mode_false_report_weight + + var/motd + +/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() + storyteller_cache = typecacheof(/datum/dynamic_storyteller, TRUE) + 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() + +/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 + var/list/postload_required = list() + for(var/L in lines) + L = trim(L) + if(!L) + continue + + var/firstchar = copytext(L, 1, 2) + if(firstchar == "#") + continue + + var/lockthis = firstchar == "@" + if(lockthis) + L = copytext(L, 2) + + var/pos = findtext(L, " ") + var/entry = null + var/value = null + + if(pos) + entry = lowertext(copytext(L, 1, pos)) + value = copytext(L, pos + 1) + 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) + addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, "This server is using deprecated configuration settings. Please check the logs and update accordingly."), 0) + 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, TRUE) + 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.") + if(E.postload_required) + postload_required[E] = TRUE + + E.resident_file = filename + + if(validated) + E.modified = TRUE + + for(var/i in postload_required) + var/datum/config_entry/E = i + E.OnPostload() + + ++. + +/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). + // for future reference: just use initial() lol + 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 + mode_reports[M.config_tag] = M.generate_report() + if(probabilities[M.config_tag]>0) + mode_false_report_weight[M.config_tag] = M.false_report_weight + else + mode_false_report_weight[M.config_tag] = 1 + 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 + +/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(copytext(t, 1, 2) == "#") + continue + + var/pos = findtext(t, " ") + var/command = null + var/data = null + + if(pos) + command = lowertext(copytext(t, 1, pos)) + data = copytext(t, pos + 1) + 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 ("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/pick_storyteller(storyteller_name) + for(var/T in storyteller_cache) + var/datum/dynamic_storyteller/S = T + var/name = initial(S.name) + if(name && name == storyteller_name) + return T + return /datum/dynamic_storyteller/classic + +/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(M.config_tag in SSvote.stored_modetier_results && SSvote.stored_modetier_results[M.config_tag] < Get(/datum/config_entry/number/dropped_modes)) + 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 diff --git a/code/controllers/configuration/entries/comms.dm b/code/controllers/configuration/entries/comms.dm index a1de1c962e..012c3ec9fe 100644 --- a/code/controllers/configuration/entries/comms.dm +++ b/code/controllers/configuration/entries/comms.dm @@ -1,28 +1,28 @@ -/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/medal_hub_address - -/datum/config_entry/string/medal_hub_password +/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/medal_hub_address + +/datum/config_entry/string/medal_hub_password protection = CONFIG_ENTRY_HIDDEN \ No newline at end of file diff --git a/code/controllers/configuration/entries/dbconfig.dm b/code/controllers/configuration/entries/dbconfig.dm index b034195241..3dd4f4b0bd 100644 --- a/code/controllers/configuration/entries/dbconfig.dm +++ b/code/controllers/configuration/entries/dbconfig.dm @@ -1,51 +1,51 @@ -/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/bsql_debug +/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/bsql_debug diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 8b6bbe83b4..940ba52946 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -1,402 +1,402 @@ -/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/keyed_list/policy - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_TEXT - -/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 //whether the AI can use their multicam - -/datum/config_entry/flag/disable_human_mood - -/datum/config_entry/flag/disable_borg_flash_knockdown //Should borg flashes be capable of knocking humanoid entities down? - -/datum/config_entry/flag/weaken_secborg //Brings secborgs and k9s back in-line with the other borg modules - -/datum/config_entry/flag/disable_secborg // disallow secborg module to be chosen. - -/datum/config_entry/flag/disable_peaceborg - -/datum/config_entry/number/minimum_secborg_alert //Minimum alert level for secborgs to be chosen. - config_entry_value = 3 - -/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors - config_entry_value = 6 - min_val = 1 - -/datum/config_entry/number/brother_scaling_coeff //how many players per brother team - config_entry_value = 25 - 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 - 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 - min_val = 1 - -/datum/config_entry/number/abductor_scaling_coeff //how many players per abductor team - config_entry_value = 15 - 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 - min_val = 0 - -/datum/config_entry/number/default_antag_tickets - config_entry_value = 100 - min_val = 0 - -/datum/config_entry/number/max_tickets_per_roll - config_entry_value = 100 - 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 - 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/suicide_reenter_round_timer - config_entry_value = 30 - min_val = 0 - -/datum/config_entry/number/roundstart_suicide_time_limit - config_entry_value = 30 - min_val = 0 - -/datum/config_entry/number/shuttle_refuel_delay - config_entry_value = 12000 - 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/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 - 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 potential threats to the station. Security staff may have weapons visible, random searches are permitted." - -/datum/config_entry/string/alert_blue_downto - config_entry_value = "Significant confirmed threats have been neutralized. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still permitted." - -/datum/config_entry/string/alert_amber_upto - config_entry_value = "There are significant confirmed threats to the station. Security staff may have weapons unholstered at all times. Random searches are allowed and advised." - -/datum/config_entry/string/alert_amber_downto - config_entry_value = "The immediate threat has passed. Security is no longer authorized to use lethal force, but may continue to have weapons drawn. Access requirements have been restored." - -/datum/config_entry/string/alert_red_upto - config_entry_value = "There is an immediate serious threat to the station. Security is now authorized to use lethal force. Additionally, access requirements on some machines have been lifted." - -/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 is still authorized to use lethal force." - -/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/flag/revival_cloning - -/datum/config_entry/number/revival_brain_life - config_entry_value = -1 - min_val = -1 - -/datum/config_entry/flag/ooc_during_round - -/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 - -/datum/config_entry/number/movedelay/walk_delay - -/////////////////////////////////////////////////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 - - 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/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 - min_val = 0 - -/datum/config_entry/flag/ghost_interaction - -/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/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 - min_val = 0 - -/datum/config_entry/number/space_budget - config_entry_value = 16 - 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/ic_printing - -/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/flag/disable_stambuffer - -/datum/config_entry/keyed_list/box_random_engine - key_mode = KEY_MODE_TEXT - value_mode = VALUE_MODE_NUM - lowercase = FALSE - splitter = "," - -/datum/config_entry/number/auto_transfer_delay - config_entry_value = 72000 - min_val = 0 - -/datum/config_entry/flag/pai_custom_holoforms - -/datum/config_entry/number/marauder_delay_non_reebe - config_entry_value = 1800 - min_val = 0 - -/datum/config_entry/flag/allow_clockwork_marauder_on_station - config_entry_value = TRUE - -/datum/config_entry/flag/modetier_voting - config_entry_value = TRUE - -/datum/config_entry/number/dropped_modes - config_entry_value = 3 +/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/keyed_list/policy + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_TEXT + +/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 //whether the AI can use their multicam + +/datum/config_entry/flag/disable_human_mood + +/datum/config_entry/flag/disable_borg_flash_knockdown //Should borg flashes be capable of knocking humanoid entities down? + +/datum/config_entry/flag/weaken_secborg //Brings secborgs and k9s back in-line with the other borg modules + +/datum/config_entry/flag/disable_secborg // disallow secborg module to be chosen. + +/datum/config_entry/flag/disable_peaceborg + +/datum/config_entry/number/minimum_secborg_alert //Minimum alert level for secborgs to be chosen. + config_entry_value = 3 + +/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors + config_entry_value = 6 + min_val = 1 + +/datum/config_entry/number/brother_scaling_coeff //how many players per brother team + config_entry_value = 25 + 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 + 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 + min_val = 1 + +/datum/config_entry/number/abductor_scaling_coeff //how many players per abductor team + config_entry_value = 15 + 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 + min_val = 0 + +/datum/config_entry/number/default_antag_tickets + config_entry_value = 100 + min_val = 0 + +/datum/config_entry/number/max_tickets_per_roll + config_entry_value = 100 + 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 + 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/suicide_reenter_round_timer + config_entry_value = 30 + min_val = 0 + +/datum/config_entry/number/roundstart_suicide_time_limit + config_entry_value = 30 + min_val = 0 + +/datum/config_entry/number/shuttle_refuel_delay + config_entry_value = 12000 + 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/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 + 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 potential threats to the station. Security staff may have weapons visible, random searches are permitted." + +/datum/config_entry/string/alert_blue_downto + config_entry_value = "Significant confirmed threats have been neutralized. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still permitted." + +/datum/config_entry/string/alert_amber_upto + config_entry_value = "There are significant confirmed threats to the station. Security staff may have weapons unholstered at all times. Random searches are allowed and advised." + +/datum/config_entry/string/alert_amber_downto + config_entry_value = "The immediate threat has passed. Security is no longer authorized to use lethal force, but may continue to have weapons drawn. Access requirements have been restored." + +/datum/config_entry/string/alert_red_upto + config_entry_value = "There is an immediate serious threat to the station. Security is now authorized to use lethal force. Additionally, access requirements on some machines have been lifted." + +/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 is still authorized to use lethal force." + +/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/flag/revival_cloning + +/datum/config_entry/number/revival_brain_life + config_entry_value = -1 + min_val = -1 + +/datum/config_entry/flag/ooc_during_round + +/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 + +/datum/config_entry/number/movedelay/walk_delay + +/////////////////////////////////////////////////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 + + 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/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 + min_val = 0 + +/datum/config_entry/flag/ghost_interaction + +/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/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 + min_val = 0 + +/datum/config_entry/number/space_budget + config_entry_value = 16 + 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/ic_printing + +/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/flag/disable_stambuffer + +/datum/config_entry/keyed_list/box_random_engine + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + lowercase = FALSE + splitter = "," + +/datum/config_entry/number/auto_transfer_delay + config_entry_value = 72000 + min_val = 0 + +/datum/config_entry/flag/pai_custom_holoforms + +/datum/config_entry/number/marauder_delay_non_reebe + config_entry_value = 1800 + min_val = 0 + +/datum/config_entry/flag/allow_clockwork_marauder_on_station + config_entry_value = TRUE + +/datum/config_entry/flag/modetier_voting + config_entry_value = TRUE + +/datum/config_entry/number/dropped_modes + config_entry_value = 3 diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index a412ad2f48..2a08ce77a8 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -1,431 +1,431 @@ -/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/string/servername // server name (the name of the game window) - -/datum/config_entry/string/servertagline - config_entry_value = "We forgot to set the server's tagline in config.txt" - -/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 - min_val = 0 - -/datum/config_entry/number/round_end_countdown // Post round murder death kill countdown - config_entry_value = 25 - min_val = 0 - -/datum/config_entry/flag/hub // if the game appears on the hub or not - -/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_virus // log virology data - -/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_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/allow_admin_ooccolor // Allows admins with relevant permissions to have their own ooc colour - -/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/number/vote_delay // minimum time between voting sessions (deciseconds, 10 minute default) - config_entry_value = 6000 - min_val = 0 - -/datum/config_entry/number/vote_period // length of voting period (deciseconds, default 1 minute) - config_entry_value = 600 - 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/allow_metadata // Metadata is supported. - -/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 - 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/flag/mentors_mobname_only - -/datum/config_entry/flag/mentor_legacy_system //Defines whether the server uses the legacy mentor system with mentors.txt or the SQL system - protection = CONFIG_ENTRY_LOCKED - -/datum/config_entry/string/hostedby - -/datum/config_entry/flag/norespawn - -/datum/config_entry/flag/guest_jobban - -/datum/config_entry/flag/usewhitelist - -/datum/config_entry/flag/ban_legacy_system //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. - protection = CONFIG_ENTRY_LOCKED - -/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 - 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 = "https://katlin.dog/citadel-wiki" - -/datum/config_entry/string/wikiurltg - 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/number/githubrepoid - config_entry_value = null - min_val = 0 - -/datum/config_entry/flag/guest_ban - -/datum/config_entry/number/id_console_jobslot_delay - config_entry_value = 30 - min_val = 0 - -/datum/config_entry/number/inactivity_period //time in ds until a player is considered inactive - config_entry_value = 3000 - 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 - 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/flag/tgstyle_maprotation - -/datum/config_entry/number/maprotatechancedelta - config_entry_value = 0.75 - min_val = 0 - max_val = 1 - integer = FALSE - -/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/panic_bunker // prevents people the server hasn't seen before from connecting - -/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 - min_val = 0 - -/datum/config_entry/number/ipintel_save_bad - config_entry_value = 1 - 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 - -/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/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 - 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 - -/datum/config_entry/number/error_msg_delay // How long to wait between messaging admins about occurrences of a unique error - config_entry_value = 50 - -/datum/config_entry/flag/irc_announce_new_game - -/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/flag/log_pictures - -/datum/config_entry/flag/picture_logging_camera +/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/string/servername // server name (the name of the game window) + +/datum/config_entry/string/servertagline + config_entry_value = "We forgot to set the server's tagline in config.txt" + +/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 + min_val = 0 + +/datum/config_entry/number/round_end_countdown // Post round murder death kill countdown + config_entry_value = 25 + min_val = 0 + +/datum/config_entry/flag/hub // if the game appears on the hub or not + +/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_virus // log virology data + +/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_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/allow_admin_ooccolor // Allows admins with relevant permissions to have their own ooc colour + +/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/number/vote_delay // minimum time between voting sessions (deciseconds, 10 minute default) + config_entry_value = 6000 + min_val = 0 + +/datum/config_entry/number/vote_period // length of voting period (deciseconds, default 1 minute) + config_entry_value = 600 + 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/allow_metadata // Metadata is supported. + +/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 + 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/flag/mentors_mobname_only + +/datum/config_entry/flag/mentor_legacy_system //Defines whether the server uses the legacy mentor system with mentors.txt or the SQL system + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/hostedby + +/datum/config_entry/flag/norespawn + +/datum/config_entry/flag/guest_jobban + +/datum/config_entry/flag/usewhitelist + +/datum/config_entry/flag/ban_legacy_system //Defines whether the server uses the legacy banning system with the files in /data or the SQL system. + protection = CONFIG_ENTRY_LOCKED + +/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 + 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 = "https://katlin.dog/citadel-wiki" + +/datum/config_entry/string/wikiurltg + 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/number/githubrepoid + config_entry_value = null + min_val = 0 + +/datum/config_entry/flag/guest_ban + +/datum/config_entry/number/id_console_jobslot_delay + config_entry_value = 30 + min_val = 0 + +/datum/config_entry/number/inactivity_period //time in ds until a player is considered inactive + config_entry_value = 3000 + 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 + 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/flag/tgstyle_maprotation + +/datum/config_entry/number/maprotatechancedelta + config_entry_value = 0.75 + min_val = 0 + max_val = 1 + integer = FALSE + +/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/panic_bunker // prevents people the server hasn't seen before from connecting + +/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 + min_val = 0 + +/datum/config_entry/number/ipintel_save_bad + config_entry_value = 1 + 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 + +/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/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 + 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 + +/datum/config_entry/number/error_msg_delay // How long to wait between messaging admins about occurrences of a unique error + config_entry_value = 50 + +/datum/config_entry/flag/irc_announce_new_game + +/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/flag/log_pictures + +/datum/config_entry/flag/picture_logging_camera diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index 2ca208642c..3ce770b66d 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 4683e93a46..a085b26410 100644 --- a/code/controllers/globals.dm +++ b/code/controllers/globals.dm @@ -1,55 +1,55 @@ -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() - //fuck off kevinz - 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() + //fuck off kevinz + 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 ebd8d3801f..d94526e658 100644 --- a/code/controllers/subsystem/atoms.dm +++ b/code/controllers/subsystem/atoms.dm @@ -1,151 +1,151 @@ -#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 - - var/list/BadInitializeCalls = list() - -/datum/controller/subsystem/atoms/Initialize(timeofday) - GLOB.fire_overlay.appearance_flags = RESET_COLOR - setupGenetics() //to set the mutations' place in structural enzymes, so monkey.initialize() knows where to put the monkey mutation. - initialized = INITIALIZATION_INNEW_MAPLOAD - InitializeAtoms() - return ..() - -/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms) - if(initialized == INITIALIZATION_INSSATOMS) - return - - initialized = INITIALIZATION_INNEW_MAPLOAD - - LAZYINITLIST(late_loaders) - - 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() - -/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 - - 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/avnums = new /list(DNA_STRUC_ENZYMES_BLOCKS) - for(var/i=1, i<=DNA_STRUC_ENZYMES_BLOCKS, i++) - avnums[i] = i - CHECK_TICK - - for(var/A in subtypesof(/datum/mutation/human)) - var/datum/mutation/human/B = new A() - if(B.dna_block == NON_SCANNABLE) - continue - B.dna_block = pick_n_take(avnums) - 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 + + var/list/BadInitializeCalls = list() + +/datum/controller/subsystem/atoms/Initialize(timeofday) + GLOB.fire_overlay.appearance_flags = RESET_COLOR + setupGenetics() //to set the mutations' place in structural enzymes, so monkey.initialize() knows where to put the monkey mutation. + initialized = INITIALIZATION_INNEW_MAPLOAD + InitializeAtoms() + return ..() + +/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms) + if(initialized == INITIALIZATION_INSSATOMS) + return + + initialized = INITIALIZATION_INNEW_MAPLOAD + + LAZYINITLIST(late_loaders) + + 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() + +/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 + + 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/avnums = new /list(DNA_STRUC_ENZYMES_BLOCKS) + for(var/i=1, i<=DNA_STRUC_ENZYMES_BLOCKS, i++) + avnums[i] = i + CHECK_TICK + + for(var/A in subtypesof(/datum/mutation/human)) + var/datum/mutation/human/B = new A() + if(B.dna_block == NON_SCANNABLE) + continue + B.dna_block = pick_n_take(avnums) + 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 58476d472f..d6991a40b4 100644 --- a/code/controllers/subsystem/blackbox.dm +++ b/code/controllers/subsystem/blackbox.dm @@ -1,335 +1,335 @@ -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) //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", "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 = 0 - for(var/mob/M in GLOB.player_list) - if(M.client) - playercount += 1 - var/admincount = GLOB.admins.len - var/datum/DBQuery/query_record_playercount = SSdbcore.NewQuery("INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id) VALUES ([playercount], [admincount], '[SQLtime()]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[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("feedback") - return FALSE - if("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/sqlrowlist = list() - - for (var/datum/feedback_variable/FV in feedback) - var/sqlversion = 1 - if(FV.key in versions) - sqlversion = versions[FV.key] - sqlrowlist += list(list("datetime" = "Now()", "round_id" = GLOB.round_id, "key_name" = "'[sanitizeSQL(FV.key)]'", "key_type" = "'[FV.key_type]'", "version" = "[sqlversion]", "json" = "'[sanitizeSQL(json_encode(FV.json))]'")) - - if (!length(sqlrowlist)) - return - - SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE) - -/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/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 - var/sqlname = L.real_name - var/sqlkey = L.ckey - var/sqljob = L.mind.assigned_role - var/sqlspecial = L.mind.special_role - var/sqlpod = get_area_name(L, TRUE) - var/laname = L.lastattacker - var/lakey = L.lastattackerckey - var/sqlbrute = L.getBruteLoss() - var/sqlfire = L.getFireLoss() - var/sqlbrain = L.getOrganLoss(ORGAN_SLOT_BRAIN) - var/sqloxy = L.getOxyLoss() - var/sqltox = L.getToxLoss() - var/sqlclone = L.getCloneLoss() - var/sqlstamina = L.getStaminaLoss() - var/x_coord = L.x - var/y_coord = L.y - var/z_coord = L.z - var/last_words = L.last_words - var/suicide = L.suiciding - var/map = SSmapping.config.map_name - - if(!SSdbcore.Connect()) - return - - sqlname = sanitizeSQL(sqlname) - sqlkey = sanitizeSQL(sqlkey) - sqljob = sanitizeSQL(sqljob) - sqlspecial = sanitizeSQL(sqlspecial) - sqlpod = sanitizeSQL(sqlpod) - laname = sanitizeSQL(laname) - lakey = sanitizeSQL(lakey) - sqlbrute = sanitizeSQL(sqlbrute) - sqlfire = sanitizeSQL(sqlfire) - sqlbrain = sanitizeSQL(sqlbrain) - sqloxy = sanitizeSQL(sqloxy) - sqltox = sanitizeSQL(sqltox) - sqlclone = sanitizeSQL(sqlclone) - sqlstamina = sanitizeSQL(sqlstamina) - x_coord = sanitizeSQL(x_coord) - y_coord = sanitizeSQL(y_coord) - z_coord = sanitizeSQL(z_coord) - last_words = sanitizeSQL(last_words) - suicide = sanitizeSQL(suicide) - map = sanitizeSQL(map) - var/datum/DBQuery/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 ('[sqlpod]', '[x_coord]', '[y_coord]', '[z_coord]', '[map]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', [GLOB.round_id], '[SQLtime()]', '[sqljob]', '[sqlspecial]', '[sqlname]', '[sqlkey]', '[laname]', '[lakey]', [sqlbrute], [sqlfire], [sqlbrain], [sqloxy], [sqltox], [sqlclone], [sqlstamina], '[last_words]', [suicide])") - 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) //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", "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 = 0 + for(var/mob/M in GLOB.player_list) + if(M.client) + playercount += 1 + var/admincount = GLOB.admins.len + var/datum/DBQuery/query_record_playercount = SSdbcore.NewQuery("INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id) VALUES ([playercount], [admincount], '[SQLtime()]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[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("feedback") + return FALSE + if("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/sqlrowlist = list() + + for (var/datum/feedback_variable/FV in feedback) + var/sqlversion = 1 + if(FV.key in versions) + sqlversion = versions[FV.key] + sqlrowlist += list(list("datetime" = "Now()", "round_id" = GLOB.round_id, "key_name" = "'[sanitizeSQL(FV.key)]'", "key_type" = "'[FV.key_type]'", "version" = "[sqlversion]", "json" = "'[sanitizeSQL(json_encode(FV.json))]'")) + + if (!length(sqlrowlist)) + return + + SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE) + +/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/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 + var/sqlname = L.real_name + var/sqlkey = L.ckey + var/sqljob = L.mind.assigned_role + var/sqlspecial = L.mind.special_role + var/sqlpod = get_area_name(L, TRUE) + var/laname = L.lastattacker + var/lakey = L.lastattackerckey + var/sqlbrute = L.getBruteLoss() + var/sqlfire = L.getFireLoss() + var/sqlbrain = L.getOrganLoss(ORGAN_SLOT_BRAIN) + var/sqloxy = L.getOxyLoss() + var/sqltox = L.getToxLoss() + var/sqlclone = L.getCloneLoss() + var/sqlstamina = L.getStaminaLoss() + var/x_coord = L.x + var/y_coord = L.y + var/z_coord = L.z + var/last_words = L.last_words + var/suicide = L.suiciding + var/map = SSmapping.config.map_name + + if(!SSdbcore.Connect()) + return + + sqlname = sanitizeSQL(sqlname) + sqlkey = sanitizeSQL(sqlkey) + sqljob = sanitizeSQL(sqljob) + sqlspecial = sanitizeSQL(sqlspecial) + sqlpod = sanitizeSQL(sqlpod) + laname = sanitizeSQL(laname) + lakey = sanitizeSQL(lakey) + sqlbrute = sanitizeSQL(sqlbrute) + sqlfire = sanitizeSQL(sqlfire) + sqlbrain = sanitizeSQL(sqlbrain) + sqloxy = sanitizeSQL(sqloxy) + sqltox = sanitizeSQL(sqltox) + sqlclone = sanitizeSQL(sqlclone) + sqlstamina = sanitizeSQL(sqlstamina) + x_coord = sanitizeSQL(x_coord) + y_coord = sanitizeSQL(y_coord) + z_coord = sanitizeSQL(z_coord) + last_words = sanitizeSQL(last_words) + suicide = sanitizeSQL(suicide) + map = sanitizeSQL(map) + var/datum/DBQuery/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 ('[sqlpod]', '[x_coord]', '[y_coord]', '[z_coord]', '[map]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', [GLOB.round_id], '[SQLtime()]', '[sqljob]', '[sqlspecial]', '[sqlname]', '[sqlkey]', '[laname]', '[lakey]', [sqlbrute], [sqlfire], [sqlbrain], [sqloxy], [sqltox], [sqlclone], [sqlstamina], '[last_words]', [suicide])") + 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 b9ad66fbce..4eff4fbf1d 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -1,365 +1,365 @@ -SUBSYSTEM_DEF(dbcore) - name = "Database" - flags = SS_BACKGROUND - wait = 1 MINUTES - init_order = INIT_ORDER_DBCORE - var/const/FAILED_DB_CONNECTION_CUTOFF = 5 - - 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/datum/BSQL_Connection/connection - var/datum/BSQL_Operation/connectOperation - -/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/DBQuery/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 - connectOperation = SSdbcore.connectOperation - -/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/DBQuery/query_round_shutdown = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = '[sanitizeSQL(SSticker.end_state)]' WHERE id = [GLOB.round_id]") - query_round_shutdown.Execute() - qdel(query_round_shutdown) - if(IsConnected()) - Disconnect() - world.BSQL_Shutdown() - -//nu -/datum/controller/subsystem/dbcore/can_vv_get(var_name) - return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && var_name != NAMEOF(src, connectOperation) && ..() - -/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value) - if(var_name == NAMEOF(src, connection) || var_name == NAMEOF(src, connectOperation)) - return FALSE - return ..() - -/datum/controller/subsystem/dbcore/proc/Connect() - if(IsConnected()) - return TRUE - - if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect anymore. - 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) - - connection = new /datum/BSQL_Connection(BSQL_CONNECTION_TYPE_MARIADB, CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout), CONFIG_GET(number/bsql_thread_limit)) - var/error - if(QDELETED(connection)) - connection = null - error = last_error - else - SSdbcore.last_error = null - connectOperation = connection.BeginConnect(address, port, user, pass, db) - if(SSdbcore.last_error) - CRASH(SSdbcore.last_error) - UNTIL(connectOperation.IsComplete()) - error = connectOperation.GetError() - . = !error - if (!.) - last_error = error - log_sql("Connect() failed | [error]") - ++failed_connections - QDEL_NULL(connection) - QDEL_NULL(connectOperation) - -/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion() - if(CONFIG_GET(flag/sql_enabled)) - if(Connect()) - log_world("Database connection established.") - var/datum/DBQuery/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/DBQuery/query_round_initialize = SSdbcore.NewQuery("INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]')") - query_round_initialize.Execute() - qdel(query_round_initialize) - var/datum/DBQuery/query_round_last_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") - query_round_last_id.Execute() - if(query_round_last_id.NextRow()) - GLOB.round_id = query_round_last_id.item[1] - qdel(query_round_last_id) - -/datum/controller/subsystem/dbcore/proc/SetRoundStart() - if(!Connect()) - return - var/datum/DBQuery/query_round_start = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = [GLOB.round_id]") - query_round_start.Execute() - qdel(query_round_start) - -/datum/controller/subsystem/dbcore/proc/SetRoundEnd() - if(!Connect()) - return - var/sql_station_name = sanitizeSQL(station_name()) - var/datum/DBQuery/query_round_end = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = '[sanitizeSQL(SSticker.mode_result)]', station_name = '[sql_station_name]' WHERE id = [GLOB.round_id]") - query_round_end.Execute() - qdel(query_round_end) - -/datum/controller/subsystem/dbcore/proc/Disconnect() - failed_connections = 0 - QDEL_NULL(connectOperation) - QDEL_NULL(connection) - -/datum/controller/subsystem/dbcore/proc/IsConnected() - if(!CONFIG_GET(flag/sql_enabled)) - return FALSE - //block until any connect operations finish - var/datum/BSQL_Connection/_connection = connection - var/datum/BSQL_Operation/op = connectOperation - UNTIL(QDELETED(_connection) || op.IsComplete()) - return !QDELETED(connection) && !op.GetError() - -/datum/controller/subsystem/dbcore/proc/Quote(str) - if(connection) - return connection.Quote(str) - -/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) - 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/DBQuery(sql_query, connection) - -/* -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) - if (!table || !rows || !istype(rows)) - return - var/list/columns = list() - var/list/sorted_rows = list() - - for (var/list/row in rows) - var/list/sorted_row = list() - sorted_row.len = columns.len - for (var/column in row) - var/idx = columns[column] - if (!idx) - idx = columns.len + 1 - columns[column] = idx - sorted_row.len = columns.len - - sorted_row[idx] = row[column] - sorted_rows[++sorted_rows.len] = sorted_row - - if (duplicate_key == TRUE) - var/list/column_list = list() - for (var/column in columns) - column_list += "[column] = VALUES([column])" - duplicate_key = "ON DUPLICATE KEY UPDATE [column_list.Join(", ")]\n" - else if (duplicate_key == FALSE) - duplicate_key = null - - if (ignore_errors) - ignore_errors = " IGNORE" - else - ignore_errors = null - - if (delayed) - delayed = " DELAYED" - else - delayed = null - - var/list/sqlrowlist = list() - var/len = columns.len - for (var/list/row in sorted_rows) - if (length(row) != len) - row.len = len - for (var/value in row) - if (value == null) - value = "NULL" - sqlrowlist += "([row.Join(", ")])" - - sqlrowlist = " [sqlrowlist.Join(",\n ")]" - var/datum/DBQuery/Query = NewQuery("INSERT[delayed][ignore_errors] INTO [table]\n([columns.Join(", ")])\nVALUES\n[sqlrowlist]\n[duplicate_key]") - if (warn) - . = Query.warn_execute(async) - else - . = Query.Execute(async) - qdel(Query) - -/datum/DBQuery - var/sql // The sql query being executed. - var/list/item //list of data values populated by NextRow() - - var/last_activity - var/last_activity_time - - var/last_error - var/skip_next_is_complete - var/in_progress - var/datum/BSQL_Connection/connection - var/datum/BSQL_Operation/Query/query - -/datum/DBQuery/New(sql_query, datum/BSQL_Connection/connection) - SSdbcore.active_queries[src] = TRUE - Activity("Created") - item = list() - src.connection = connection - sql = sql_query - -/datum/DBQuery/Destroy() - Close() - SSdbcore.active_queries -= src - return ..() - -/datum/DBQuery/CanProcCall(proc_name) - //fuck off kevinz - return FALSE - -/datum/DBQuery/proc/SetQuery(new_sql) - if(in_progress) - CRASH("Attempted to set new sql while waiting on active query") - Close() - sql = new_sql - -/datum/DBQuery/proc/Activity(activity) - last_activity = activity - last_activity_time = world.time - -/datum/DBQuery/proc/warn_execute(async = FALSE) - . = Execute(async) - if(!.) - to_chat(usr, "A SQL error occurred during this operation, check the server logs.") - -/datum/DBQuery/proc/Execute(async = FALSE, log_error = TRUE) - Activity("Execute") - if(in_progress) - CRASH("Attempted to start a new query while waiting on the old one") - - if(QDELETED(connection)) - last_error = "No connection!" - return FALSE - - var/start_time - var/timed_out - if(!async) - start_time = REALTIMEOFDAY - Close() - query = connection.BeginQuery(sql) - if(!async) - timed_out = !query.WaitForCompletion() - else - in_progress = TRUE - UNTIL(query.IsComplete()) - in_progress = FALSE - skip_next_is_complete = TRUE - var/error = QDELETED(query) ? "Query object deleted!" : query.GetError() - last_error = error - . = !error - if(!. && log_error) - log_sql("[error] | Query used: [sql]") - 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/DBQuery/proc/slow_query_check() - message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") - -/datum/DBQuery/proc/NextRow(async) - Activity("NextRow") - UNTIL(!in_progress) - if(!skip_next_is_complete) - if(!async) - query.WaitForCompletion() - else - in_progress = TRUE - UNTIL(query.IsComplete()) - in_progress = FALSE - else - skip_next_is_complete = FALSE - - last_error = query.GetError() - var/list/results = query.CurrentRow() - . = results != null - - item.Cut() - //populate item array - for(var/I in results) - item += results[I] - -/datum/DBQuery/proc/ErrorMsg() - return last_error - -/datum/DBQuery/proc/Close() - item.Cut() - QDEL_NULL(query) - -/world/BSQL_Debug(message) - if(!CONFIG_GET(flag/bsql_debug)) - return - - //strip sensitive stuff - if(findtext(message, ": CreateConnection(")) - message = "CreateConnection CENSORED" - - log_sql("BSQL_DEBUG: [message]") +SUBSYSTEM_DEF(dbcore) + name = "Database" + flags = SS_BACKGROUND + wait = 1 MINUTES + init_order = INIT_ORDER_DBCORE + var/const/FAILED_DB_CONNECTION_CUTOFF = 5 + + 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/datum/BSQL_Connection/connection + var/datum/BSQL_Operation/connectOperation + +/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/DBQuery/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 + connectOperation = SSdbcore.connectOperation + +/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/DBQuery/query_round_shutdown = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = '[sanitizeSQL(SSticker.end_state)]' WHERE id = [GLOB.round_id]") + query_round_shutdown.Execute() + qdel(query_round_shutdown) + if(IsConnected()) + Disconnect() + world.BSQL_Shutdown() + +//nu +/datum/controller/subsystem/dbcore/can_vv_get(var_name) + return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && var_name != NAMEOF(src, connectOperation) && ..() + +/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, connection) || var_name == NAMEOF(src, connectOperation)) + return FALSE + return ..() + +/datum/controller/subsystem/dbcore/proc/Connect() + if(IsConnected()) + return TRUE + + if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect anymore. + 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) + + connection = new /datum/BSQL_Connection(BSQL_CONNECTION_TYPE_MARIADB, CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout), CONFIG_GET(number/bsql_thread_limit)) + var/error + if(QDELETED(connection)) + connection = null + error = last_error + else + SSdbcore.last_error = null + connectOperation = connection.BeginConnect(address, port, user, pass, db) + if(SSdbcore.last_error) + CRASH(SSdbcore.last_error) + UNTIL(connectOperation.IsComplete()) + error = connectOperation.GetError() + . = !error + if (!.) + last_error = error + log_sql("Connect() failed | [error]") + ++failed_connections + QDEL_NULL(connection) + QDEL_NULL(connectOperation) + +/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion() + if(CONFIG_GET(flag/sql_enabled)) + if(Connect()) + log_world("Database connection established.") + var/datum/DBQuery/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/DBQuery/query_round_initialize = SSdbcore.NewQuery("INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]')") + query_round_initialize.Execute() + qdel(query_round_initialize) + var/datum/DBQuery/query_round_last_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()") + query_round_last_id.Execute() + if(query_round_last_id.NextRow()) + GLOB.round_id = query_round_last_id.item[1] + qdel(query_round_last_id) + +/datum/controller/subsystem/dbcore/proc/SetRoundStart() + if(!Connect()) + return + var/datum/DBQuery/query_round_start = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = [GLOB.round_id]") + query_round_start.Execute() + qdel(query_round_start) + +/datum/controller/subsystem/dbcore/proc/SetRoundEnd() + if(!Connect()) + return + var/sql_station_name = sanitizeSQL(station_name()) + var/datum/DBQuery/query_round_end = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = '[sanitizeSQL(SSticker.mode_result)]', station_name = '[sql_station_name]' WHERE id = [GLOB.round_id]") + query_round_end.Execute() + qdel(query_round_end) + +/datum/controller/subsystem/dbcore/proc/Disconnect() + failed_connections = 0 + QDEL_NULL(connectOperation) + QDEL_NULL(connection) + +/datum/controller/subsystem/dbcore/proc/IsConnected() + if(!CONFIG_GET(flag/sql_enabled)) + return FALSE + //block until any connect operations finish + var/datum/BSQL_Connection/_connection = connection + var/datum/BSQL_Operation/op = connectOperation + UNTIL(QDELETED(_connection) || op.IsComplete()) + return !QDELETED(connection) && !op.GetError() + +/datum/controller/subsystem/dbcore/proc/Quote(str) + if(connection) + return connection.Quote(str) + +/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) + 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/DBQuery(sql_query, connection) + +/* +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) + if (!table || !rows || !istype(rows)) + return + var/list/columns = list() + var/list/sorted_rows = list() + + for (var/list/row in rows) + var/list/sorted_row = list() + sorted_row.len = columns.len + for (var/column in row) + var/idx = columns[column] + if (!idx) + idx = columns.len + 1 + columns[column] = idx + sorted_row.len = columns.len + + sorted_row[idx] = row[column] + sorted_rows[++sorted_rows.len] = sorted_row + + if (duplicate_key == TRUE) + var/list/column_list = list() + for (var/column in columns) + column_list += "[column] = VALUES([column])" + duplicate_key = "ON DUPLICATE KEY UPDATE [column_list.Join(", ")]\n" + else if (duplicate_key == FALSE) + duplicate_key = null + + if (ignore_errors) + ignore_errors = " IGNORE" + else + ignore_errors = null + + if (delayed) + delayed = " DELAYED" + else + delayed = null + + var/list/sqlrowlist = list() + var/len = columns.len + for (var/list/row in sorted_rows) + if (length(row) != len) + row.len = len + for (var/value in row) + if (value == null) + value = "NULL" + sqlrowlist += "([row.Join(", ")])" + + sqlrowlist = " [sqlrowlist.Join(",\n ")]" + var/datum/DBQuery/Query = NewQuery("INSERT[delayed][ignore_errors] INTO [table]\n([columns.Join(", ")])\nVALUES\n[sqlrowlist]\n[duplicate_key]") + if (warn) + . = Query.warn_execute(async) + else + . = Query.Execute(async) + qdel(Query) + +/datum/DBQuery + var/sql // The sql query being executed. + var/list/item //list of data values populated by NextRow() + + var/last_activity + var/last_activity_time + + var/last_error + var/skip_next_is_complete + var/in_progress + var/datum/BSQL_Connection/connection + var/datum/BSQL_Operation/Query/query + +/datum/DBQuery/New(sql_query, datum/BSQL_Connection/connection) + SSdbcore.active_queries[src] = TRUE + Activity("Created") + item = list() + src.connection = connection + sql = sql_query + +/datum/DBQuery/Destroy() + Close() + SSdbcore.active_queries -= src + return ..() + +/datum/DBQuery/CanProcCall(proc_name) + //fuck off kevinz + return FALSE + +/datum/DBQuery/proc/SetQuery(new_sql) + if(in_progress) + CRASH("Attempted to set new sql while waiting on active query") + Close() + sql = new_sql + +/datum/DBQuery/proc/Activity(activity) + last_activity = activity + last_activity_time = world.time + +/datum/DBQuery/proc/warn_execute(async = FALSE) + . = Execute(async) + if(!.) + to_chat(usr, "A SQL error occurred during this operation, check the server logs.") + +/datum/DBQuery/proc/Execute(async = FALSE, log_error = TRUE) + Activity("Execute") + if(in_progress) + CRASH("Attempted to start a new query while waiting on the old one") + + if(QDELETED(connection)) + last_error = "No connection!" + return FALSE + + var/start_time + var/timed_out + if(!async) + start_time = REALTIMEOFDAY + Close() + query = connection.BeginQuery(sql) + if(!async) + timed_out = !query.WaitForCompletion() + else + in_progress = TRUE + UNTIL(query.IsComplete()) + in_progress = FALSE + skip_next_is_complete = TRUE + var/error = QDELETED(query) ? "Query object deleted!" : query.GetError() + last_error = error + . = !error + if(!. && log_error) + log_sql("[error] | Query used: [sql]") + 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/DBQuery/proc/slow_query_check() + message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") + +/datum/DBQuery/proc/NextRow(async) + Activity("NextRow") + UNTIL(!in_progress) + if(!skip_next_is_complete) + if(!async) + query.WaitForCompletion() + else + in_progress = TRUE + UNTIL(query.IsComplete()) + in_progress = FALSE + else + skip_next_is_complete = FALSE + + last_error = query.GetError() + var/list/results = query.CurrentRow() + . = results != null + + item.Cut() + //populate item array + for(var/I in results) + item += results[I] + +/datum/DBQuery/proc/ErrorMsg() + return last_error + +/datum/DBQuery/proc/Close() + item.Cut() + QDEL_NULL(query) + +/world/BSQL_Debug(message) + if(!CONFIG_GET(flag/bsql_debug)) + return + + //strip sensitive stuff + if(findtext(message, ": CreateConnection(")) + message = "CreateConnection CENSORED" + + log_sql("BSQL_DEBUG: [message]") diff --git a/code/controllers/subsystem/medals.dm b/code/controllers/subsystem/medals.dm index 21ab27492b..36be23973c 100644 --- a/code/controllers/subsystem/medals.dm +++ b/code/controllers/subsystem/medals.dm @@ -1,87 +1,87 @@ -SUBSYSTEM_DEF(medals) - name = "Medals" - flags = SS_NO_FIRE - var/hub_enabled = FALSE - -/datum/controller/subsystem/medals/Initialize(timeofday) - if(CONFIG_GET(string/medal_hub_address) && CONFIG_GET(string/medal_hub_password)) - hub_enabled = TRUE - return ..() - -/datum/controller/subsystem/medals/proc/UnlockMedal(medal, client/player) - set waitfor = FALSE - if(!medal || !hub_enabled) - return - if(isnull(world.SetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)))) - hub_enabled = FALSE - log_game("MEDAL ERROR: Could not contact hub to award medal:[medal] player:[player.key]") - message_admins("Error! Failed to contact hub to award [medal] medal to [player.key]!") - return - to_chat(player, "Achievement unlocked: [medal]!") - - -/datum/controller/subsystem/medals/proc/SetScore(score, client/player, increment, force) - set waitfor = FALSE - if(!score || !hub_enabled) - return - - var/list/oldscore = GetScore(score, player, TRUE) - if(increment) - if(!oldscore[score]) - oldscore[score] = 1 - else - oldscore[score] = (text2num(oldscore[score]) + 1) - else - oldscore[score] = force - - var/newscoreparam = list2params(oldscore) - - if(isnull(world.SetScores(player.ckey, newscoreparam, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)))) - hub_enabled = FALSE - log_game("SCORE ERROR: Could not contact hub to set score. Score:[score] player:[player.key]") - message_admins("Error! Failed to contact hub to set [score] score for [player.key]!") - -/datum/controller/subsystem/medals/proc/GetScore(score, client/player, returnlist) - if(!score || !hub_enabled) - return - - var/scoreget = world.GetScores(player.ckey, score, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)) - if(isnull(scoreget)) - hub_enabled = FALSE - log_game("SCORE ERROR: Could not contact hub to get score. Score:[score] player:[player.key]") - message_admins("Error! Failed to contact hub to get score: [score] for [player.key]!") - return - . = params2list(scoreget) - if(!returnlist) - return .[score] - -/datum/controller/subsystem/medals/proc/CheckMedal(medal, client/player) - if(!medal || !hub_enabled) - return - - if(isnull(world.GetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)))) - hub_enabled = FALSE - log_game("MEDAL ERROR: Could not contact hub to get medal:[medal] player: [player.key]") - message_admins("Error! Failed to contact hub to get [medal] medal for [player.key]!") - return - to_chat(player, "[medal] is unlocked") - -/datum/controller/subsystem/medals/proc/LockMedal(medal, client/player) - if(!player || !medal || !hub_enabled) - return - var/result = world.ClearMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)) - switch(result) - if(null) - hub_enabled = FALSE - log_game("MEDAL ERROR: Could not contact hub to clear medal:[medal] player:[player.key]") - message_admins("Error! Failed to contact hub to clear [medal] medal for [player.key]!") - if(TRUE) - message_admins("Medal: [medal] removed for [player.key]") - if(FALSE) - message_admins("Medal: [medal] was not found for [player.key]. Unable to clear.") - - -/datum/controller/subsystem/medals/proc/ClearScore(client/player) - if(isnull(world.SetScores(player.ckey, "", CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)))) - log_game("MEDAL ERROR: Could not contact hub to clear scores for [player.key]!") - message_admins("Error! Failed to contact hub to clear scores for [player.key]!") +SUBSYSTEM_DEF(medals) + name = "Medals" + flags = SS_NO_FIRE + var/hub_enabled = FALSE + +/datum/controller/subsystem/medals/Initialize(timeofday) + if(CONFIG_GET(string/medal_hub_address) && CONFIG_GET(string/medal_hub_password)) + hub_enabled = TRUE + return ..() + +/datum/controller/subsystem/medals/proc/UnlockMedal(medal, client/player) + set waitfor = FALSE + if(!medal || !hub_enabled) + return + if(isnull(world.SetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)))) + hub_enabled = FALSE + log_game("MEDAL ERROR: Could not contact hub to award medal:[medal] player:[player.key]") + message_admins("Error! Failed to contact hub to award [medal] medal to [player.key]!") + return + to_chat(player, "Achievement unlocked: [medal]!") + + +/datum/controller/subsystem/medals/proc/SetScore(score, client/player, increment, force) + set waitfor = FALSE + if(!score || !hub_enabled) + return + + var/list/oldscore = GetScore(score, player, TRUE) + if(increment) + if(!oldscore[score]) + oldscore[score] = 1 + else + oldscore[score] = (text2num(oldscore[score]) + 1) + else + oldscore[score] = force + + var/newscoreparam = list2params(oldscore) + + if(isnull(world.SetScores(player.ckey, newscoreparam, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)))) + hub_enabled = FALSE + log_game("SCORE ERROR: Could not contact hub to set score. Score:[score] player:[player.key]") + message_admins("Error! Failed to contact hub to set [score] score for [player.key]!") + +/datum/controller/subsystem/medals/proc/GetScore(score, client/player, returnlist) + if(!score || !hub_enabled) + return + + var/scoreget = world.GetScores(player.ckey, score, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)) + if(isnull(scoreget)) + hub_enabled = FALSE + log_game("SCORE ERROR: Could not contact hub to get score. Score:[score] player:[player.key]") + message_admins("Error! Failed to contact hub to get score: [score] for [player.key]!") + return + . = params2list(scoreget) + if(!returnlist) + return .[score] + +/datum/controller/subsystem/medals/proc/CheckMedal(medal, client/player) + if(!medal || !hub_enabled) + return + + if(isnull(world.GetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)))) + hub_enabled = FALSE + log_game("MEDAL ERROR: Could not contact hub to get medal:[medal] player: [player.key]") + message_admins("Error! Failed to contact hub to get [medal] medal for [player.key]!") + return + to_chat(player, "[medal] is unlocked") + +/datum/controller/subsystem/medals/proc/LockMedal(medal, client/player) + if(!player || !medal || !hub_enabled) + return + var/result = world.ClearMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)) + switch(result) + if(null) + hub_enabled = FALSE + log_game("MEDAL ERROR: Could not contact hub to clear medal:[medal] player:[player.key]") + message_admins("Error! Failed to contact hub to clear [medal] medal for [player.key]!") + if(TRUE) + message_admins("Medal: [medal] removed for [player.key]") + if(FALSE) + message_admins("Medal: [medal] was not found for [player.key]. Unable to clear.") + + +/datum/controller/subsystem/medals/proc/ClearScore(client/player) + if(isnull(world.SetScores(player.ckey, "", CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password)))) + log_game("MEDAL ERROR: Could not contact hub to clear scores for [player.key]!") + message_admins("Error! Failed to contact hub to clear scores for [player.key]!") diff --git a/code/controllers/subsystem/moods.dm b/code/controllers/subsystem/moods.dm index d1e58c7452..f6b6ffcb40 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 df406eed3c..927f68eee7 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(FALSE) - 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(FALSE) + 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 830db298c9..7fd3512448 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_KEEP_TIMING | SS_NO_INIT - 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_KEEP_TIMING | SS_NO_INIT + 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 fee93d14b2..8e1cf946ae 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 0ca5472a6a..03276d5b26 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(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(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 b0930d3d07..4171b5f8c1 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/item/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/item/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 dfbc1ac8e4..4bda8b5453 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 ab27658efd..900dfd1c54 100644 --- a/code/controllers/subsystem/research.dm +++ b/code/controllers/subsystem/research.dm @@ -1,530 +1,530 @@ - -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_point_items = list( //path = list(point type = value) - /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 5000), // Cit three more anomalys anomalys - /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 7500), - /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000), - // - Slime Extracts! - - /obj/item/slime_extract/grey = list(TECHWEB_POINT_TYPE_GENERIC = 500), // Adds in slime core deconing - /obj/item/slime_extract/metal = list(TECHWEB_POINT_TYPE_GENERIC = 750), - /obj/item/slime_extract/purple = list(TECHWEB_POINT_TYPE_GENERIC = 750), - /obj/item/slime_extract/orange = list(TECHWEB_POINT_TYPE_GENERIC = 750), - /obj/item/slime_extract/blue = list(TECHWEB_POINT_TYPE_GENERIC = 750), - /obj/item/slime_extract/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 1000), - /obj/item/slime_extract/silver = list(TECHWEB_POINT_TYPE_GENERIC = 1000), - /obj/item/slime_extract/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 1000), - /obj/item/slime_extract/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 1000), - /obj/item/slime_extract/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 1250), - /obj/item/slime_extract/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 1250), - /obj/item/slime_extract/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 1250), - /obj/item/slime_extract/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 1250), - /obj/item/slime_extract/red = list(TECHWEB_POINT_TYPE_GENERIC = 1250), - /obj/item/slime_extract/green = list(TECHWEB_POINT_TYPE_GENERIC = 1250), - /obj/item/slime_extract/pink = list(TECHWEB_POINT_TYPE_GENERIC = 1250), - /obj/item/slime_extract/gold = list(TECHWEB_POINT_TYPE_GENERIC = 1250), - /obj/item/slime_extract/black = list(TECHWEB_POINT_TYPE_GENERIC = 1500), - /obj/item/slime_extract/adamantine =list (TECHWEB_POINT_TYPE_GENERIC = 1500), - /obj/item/slime_extract/oil = list(TECHWEB_POINT_TYPE_GENERIC = 1500), - /obj/item/slime_extract/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 1500), - /obj/item/slime_extract/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - // Reproductive - Crossbreading Cores! - (Grey Cores) - /obj/item/slimecross/reproductive/grey = list(TECHWEB_POINT_TYPE_GENERIC = 1000), - /obj/item/slimecross/reproductive/orange = list(TECHWEB_POINT_TYPE_GENERIC = 1500), - /obj/item/slimecross/reproductive/purple = list(TECHWEB_POINT_TYPE_GENERIC = 1500), - /obj/item/slimecross/reproductive/blue = list(TECHWEB_POINT_TYPE_GENERIC = 1500), - /obj/item/slimecross/reproductive/metal = list(TECHWEB_POINT_TYPE_GENERIC = 1500), - /obj/item/slimecross/reproductive/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 1750), - /obj/item/slimecross/reproductive/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 1750), - /obj/item/slimecross/reproductive/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 1750), - /obj/item/slimecross/reproductive/silver = list(TECHWEB_POINT_TYPE_GENERIC = 1750), - /obj/item/slimecross/reproductive/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 2000), - /obj/item/slimecross/reproductive/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 2000), - /obj/item/slimecross/reproductive/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 2000), - /obj/item/slimecross/reproductive/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 2000), - /obj/item/slimecross/reproductive/red = list(TECHWEB_POINT_TYPE_GENERIC = 2250), - /obj/item/slimecross/reproductive/green = list(TECHWEB_POINT_TYPE_GENERIC = 2250), - /obj/item/slimecross/reproductive/pink = list(TECHWEB_POINT_TYPE_GENERIC = 2250), - /obj/item/slimecross/reproductive/gold = list(TECHWEB_POINT_TYPE_GENERIC = 2250), - /obj/item/slimecross/reproductive/oil = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/reproductive/black = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/reproductive/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/reproductive/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/reproductive/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - // Burning - Crossbreading Cores! - (Orange Cores) - /obj/item/slimecross/burning/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000), - /obj/item/slimecross/burning/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/burning/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/burning/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/burning/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/burning/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/burning/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/burning/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/burning/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/burning/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/burning/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/burning/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/burning/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/burning/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/burning/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/burning/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/burning/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/burning/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/burning/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/burning/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/burning/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/burning/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - // Regenerative - Crossbreading Cores! - (Purple Cores) - /obj/item/slimecross/regenerative/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000), - /obj/item/slimecross/regenerative/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/regenerative/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/regenerative/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/regenerative/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/regenerative/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/regenerative/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/regenerative/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/regenerative/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/regenerative/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/regenerative/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/regenerative/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/regenerative/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/regenerative/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/regenerative/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/regenerative/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/regenerative/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/regenerative/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/regenerative/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/regenerative/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/regenerative/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/regenerative/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - // Stabilized - Crossbreading Cores! - (Blue Cores) - /obj/item/slimecross/stabilized/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000), - /obj/item/slimecross/stabilized/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/stabilized/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/stabilized/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/stabilized/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/stabilized/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/stabilized/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/stabilized/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/stabilized/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/stabilized/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/stabilized/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/stabilized/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/stabilized/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/stabilized/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/stabilized/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/stabilized/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/stabilized/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/stabilized/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/stabilized/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/stabilized/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/stabilized/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/stabilized/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - // Industrial - Crossbreading Cores! - (Metal Cores) - /obj/item/slimecross/industrial/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000), - /obj/item/slimecross/industrial/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/industrial/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/industrial/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/industrial/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/industrial/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/industrial/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/industrial/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/industrial/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/industrial/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/industrial/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/industrial/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/industrial/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/industrial/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/industrial/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/industrial/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/industrial/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/industrial/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/industrial/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/industrial/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/industrial/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/industrial/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - // Charged - Crossbreading Cores! - (Yellow Cores) - /obj/item/slimecross/charged/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2250), - /obj/item/slimecross/charged/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/charged/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/charged/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/charged/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/charged/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/charged/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/charged/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/charged/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/charged/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/charged/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/charged/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/charged/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/charged/red = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/charged/green = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/charged/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/charged/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/charged/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/charged/black = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/charged/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/charged/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/charged/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - // Selfsustaining - Crossbreading Cores! - (Dark Purple Cores) - /obj/item/slimecross/selfsustaining/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2250), - /obj/item/slimecross/selfsustaining/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/selfsustaining/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/selfsustaining/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/selfsustaining/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/selfsustaining/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/selfsustaining/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/selfsustaining/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/selfsustaining/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/selfsustaining/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/selfsustaining/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/selfsustaining/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/selfsustaining/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/selfsustaining/red = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/selfsustaining/green = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/selfsustaining/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/selfsustaining/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/selfsustaining/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/selfsustaining/black = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/selfsustaining/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/selfsustaining/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/selfsustaining/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - // Consuming - Crossbreading Cores! - (Sliver Cores) - /obj/item/slimecross/consuming/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2250), - /obj/item/slimecross/consuming/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/consuming/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/consuming/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/consuming/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2750), - /obj/item/slimecross/consuming/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/consuming/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/consuming/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/consuming/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/consuming/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/consuming/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/consuming/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/consuming/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/consuming/red = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/consuming/green = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/consuming/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/consuming/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/consuming/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/consuming/black = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/consuming/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/consuming/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/consuming/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - // Prismatic - Crossbreading Cores! - (Pyrite Cores) - /obj/item/slimecross/prismatic/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/prismatic/orange = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/prismatic/purple = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/prismatic/blue = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/prismatic/metal = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/prismatic/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/prismatic/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/prismatic/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/prismatic/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/prismatic/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/prismatic/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/prismatic/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/prismatic/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/prismatic/red = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/prismatic/green = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/prismatic/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/prismatic/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/prismatic/oil = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - /obj/item/slimecross/prismatic/black = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - /obj/item/slimecross/prismatic/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - /obj/item/slimecross/prismatic/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - /obj/item/slimecross/prismatic/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4250), - // Recurring - Crossbreading Cores! - (Cerulean Cores) - /obj/item/slimecross/recurring/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2500), - /obj/item/slimecross/recurring/orange = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/recurring/purple = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/recurring/blue = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/recurring/metal = list(TECHWEB_POINT_TYPE_GENERIC = 3000), - /obj/item/slimecross/recurring/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/recurring/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/recurring/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/recurring/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3250), - /obj/item/slimecross/recurring/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/recurring/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/recurring/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/recurring/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3500), - /obj/item/slimecross/recurring/red = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/recurring/green = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/recurring/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/recurring/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3750), - /obj/item/slimecross/recurring/oil = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - /obj/item/slimecross/recurring/black = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - /obj/item/slimecross/recurring/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - /obj/item/slimecross/recurring/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 4000), - /obj/item/slimecross/recurring/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4250) - ) // End of Cit changes - var/list/errored_datums = list() - var/list/point_types = list() //typecache style type = TRUE list - //---------------------------------------------- - var/list/single_server_income = list(TECHWEB_POINT_TYPE_GENERIC = 35) //citadel edit - techwebs nerf - 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??? - -/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 - 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 - 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_point_items = list( //path = list(point type = value) + /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 5000), // Cit three more anomalys anomalys + /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 7500), + /obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000), + // - Slime Extracts! - + /obj/item/slime_extract/grey = list(TECHWEB_POINT_TYPE_GENERIC = 500), // Adds in slime core deconing + /obj/item/slime_extract/metal = list(TECHWEB_POINT_TYPE_GENERIC = 750), + /obj/item/slime_extract/purple = list(TECHWEB_POINT_TYPE_GENERIC = 750), + /obj/item/slime_extract/orange = list(TECHWEB_POINT_TYPE_GENERIC = 750), + /obj/item/slime_extract/blue = list(TECHWEB_POINT_TYPE_GENERIC = 750), + /obj/item/slime_extract/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 1000), + /obj/item/slime_extract/silver = list(TECHWEB_POINT_TYPE_GENERIC = 1000), + /obj/item/slime_extract/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 1000), + /obj/item/slime_extract/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 1000), + /obj/item/slime_extract/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 1250), + /obj/item/slime_extract/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 1250), + /obj/item/slime_extract/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 1250), + /obj/item/slime_extract/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 1250), + /obj/item/slime_extract/red = list(TECHWEB_POINT_TYPE_GENERIC = 1250), + /obj/item/slime_extract/green = list(TECHWEB_POINT_TYPE_GENERIC = 1250), + /obj/item/slime_extract/pink = list(TECHWEB_POINT_TYPE_GENERIC = 1250), + /obj/item/slime_extract/gold = list(TECHWEB_POINT_TYPE_GENERIC = 1250), + /obj/item/slime_extract/black = list(TECHWEB_POINT_TYPE_GENERIC = 1500), + /obj/item/slime_extract/adamantine =list (TECHWEB_POINT_TYPE_GENERIC = 1500), + /obj/item/slime_extract/oil = list(TECHWEB_POINT_TYPE_GENERIC = 1500), + /obj/item/slime_extract/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 1500), + /obj/item/slime_extract/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + // Reproductive - Crossbreading Cores! - (Grey Cores) + /obj/item/slimecross/reproductive/grey = list(TECHWEB_POINT_TYPE_GENERIC = 1000), + /obj/item/slimecross/reproductive/orange = list(TECHWEB_POINT_TYPE_GENERIC = 1500), + /obj/item/slimecross/reproductive/purple = list(TECHWEB_POINT_TYPE_GENERIC = 1500), + /obj/item/slimecross/reproductive/blue = list(TECHWEB_POINT_TYPE_GENERIC = 1500), + /obj/item/slimecross/reproductive/metal = list(TECHWEB_POINT_TYPE_GENERIC = 1500), + /obj/item/slimecross/reproductive/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 1750), + /obj/item/slimecross/reproductive/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 1750), + /obj/item/slimecross/reproductive/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 1750), + /obj/item/slimecross/reproductive/silver = list(TECHWEB_POINT_TYPE_GENERIC = 1750), + /obj/item/slimecross/reproductive/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 2000), + /obj/item/slimecross/reproductive/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 2000), + /obj/item/slimecross/reproductive/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 2000), + /obj/item/slimecross/reproductive/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 2000), + /obj/item/slimecross/reproductive/red = list(TECHWEB_POINT_TYPE_GENERIC = 2250), + /obj/item/slimecross/reproductive/green = list(TECHWEB_POINT_TYPE_GENERIC = 2250), + /obj/item/slimecross/reproductive/pink = list(TECHWEB_POINT_TYPE_GENERIC = 2250), + /obj/item/slimecross/reproductive/gold = list(TECHWEB_POINT_TYPE_GENERIC = 2250), + /obj/item/slimecross/reproductive/oil = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/reproductive/black = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/reproductive/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/reproductive/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/reproductive/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + // Burning - Crossbreading Cores! - (Orange Cores) + /obj/item/slimecross/burning/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000), + /obj/item/slimecross/burning/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/burning/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/burning/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/burning/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/burning/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/burning/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/burning/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/burning/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/burning/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/burning/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/burning/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/burning/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/burning/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/burning/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/burning/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/burning/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/burning/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/burning/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/burning/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/burning/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/burning/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + // Regenerative - Crossbreading Cores! - (Purple Cores) + /obj/item/slimecross/regenerative/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000), + /obj/item/slimecross/regenerative/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/regenerative/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/regenerative/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/regenerative/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/regenerative/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/regenerative/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/regenerative/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/regenerative/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/regenerative/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/regenerative/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/regenerative/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/regenerative/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/regenerative/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/regenerative/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/regenerative/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/regenerative/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/regenerative/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/regenerative/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/regenerative/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/regenerative/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/regenerative/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + // Stabilized - Crossbreading Cores! - (Blue Cores) + /obj/item/slimecross/stabilized/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000), + /obj/item/slimecross/stabilized/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/stabilized/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/stabilized/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/stabilized/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/stabilized/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/stabilized/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/stabilized/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/stabilized/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/stabilized/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/stabilized/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/stabilized/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/stabilized/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/stabilized/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/stabilized/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/stabilized/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/stabilized/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/stabilized/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/stabilized/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/stabilized/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/stabilized/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/stabilized/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + // Industrial - Crossbreading Cores! - (Metal Cores) + /obj/item/slimecross/industrial/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2000), + /obj/item/slimecross/industrial/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/industrial/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/industrial/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/industrial/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/industrial/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/industrial/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/industrial/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/industrial/silver = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/industrial/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/industrial/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/industrial/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/industrial/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/industrial/red = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/industrial/green = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/industrial/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/industrial/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/industrial/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/industrial/black = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/industrial/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/industrial/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/industrial/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + // Charged - Crossbreading Cores! - (Yellow Cores) + /obj/item/slimecross/charged/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2250), + /obj/item/slimecross/charged/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/charged/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/charged/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/charged/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/charged/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/charged/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/charged/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/charged/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/charged/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/charged/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/charged/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/charged/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/charged/red = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/charged/green = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/charged/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/charged/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/charged/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/charged/black = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/charged/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/charged/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/charged/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + // Selfsustaining - Crossbreading Cores! - (Dark Purple Cores) + /obj/item/slimecross/selfsustaining/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2250), + /obj/item/slimecross/selfsustaining/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/selfsustaining/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/selfsustaining/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/selfsustaining/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/selfsustaining/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/selfsustaining/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/selfsustaining/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/selfsustaining/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/selfsustaining/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/selfsustaining/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/selfsustaining/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/selfsustaining/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/selfsustaining/red = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/selfsustaining/green = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/selfsustaining/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/selfsustaining/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/selfsustaining/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/selfsustaining/black = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/selfsustaining/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/selfsustaining/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/selfsustaining/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + // Consuming - Crossbreading Cores! - (Sliver Cores) + /obj/item/slimecross/consuming/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2250), + /obj/item/slimecross/consuming/orange = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/consuming/purple = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/consuming/blue = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/consuming/metal = list(TECHWEB_POINT_TYPE_GENERIC = 2750), + /obj/item/slimecross/consuming/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/consuming/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/consuming/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/consuming/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/consuming/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/consuming/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/consuming/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/consuming/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/consuming/red = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/consuming/green = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/consuming/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/consuming/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/consuming/oil = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/consuming/black = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/consuming/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/consuming/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/consuming/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + // Prismatic - Crossbreading Cores! - (Pyrite Cores) + /obj/item/slimecross/prismatic/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/prismatic/orange = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/prismatic/purple = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/prismatic/blue = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/prismatic/metal = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/prismatic/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/prismatic/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/prismatic/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/prismatic/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/prismatic/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/prismatic/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/prismatic/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/prismatic/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/prismatic/red = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/prismatic/green = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/prismatic/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/prismatic/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/prismatic/oil = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + /obj/item/slimecross/prismatic/black = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + /obj/item/slimecross/prismatic/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + /obj/item/slimecross/prismatic/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + /obj/item/slimecross/prismatic/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4250), + // Recurring - Crossbreading Cores! - (Cerulean Cores) + /obj/item/slimecross/recurring/grey = list(TECHWEB_POINT_TYPE_GENERIC = 2500), + /obj/item/slimecross/recurring/orange = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/recurring/purple = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/recurring/blue = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/recurring/metal = list(TECHWEB_POINT_TYPE_GENERIC = 3000), + /obj/item/slimecross/recurring/yellow = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/recurring/darkpurple = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/recurring/darkblue = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/recurring/silver = list(TECHWEB_POINT_TYPE_GENERIC = 3250), + /obj/item/slimecross/recurring/bluespace = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/recurring/sepia = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/recurring/cerulean = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/recurring/pyrite = list(TECHWEB_POINT_TYPE_GENERIC = 3500), + /obj/item/slimecross/recurring/red = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/recurring/green = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/recurring/pink = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/recurring/gold = list(TECHWEB_POINT_TYPE_GENERIC = 3750), + /obj/item/slimecross/recurring/oil = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + /obj/item/slimecross/recurring/black = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + /obj/item/slimecross/recurring/lightpink = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + /obj/item/slimecross/recurring/adamantine = list(TECHWEB_POINT_TYPE_GENERIC = 4000), + /obj/item/slimecross/recurring/rainbow = list(TECHWEB_POINT_TYPE_GENERIC = 4250) + ) // End of Cit changes + var/list/errored_datums = list() + var/list/point_types = list() //typecache style type = TRUE list + //---------------------------------------------- + var/list/single_server_income = list(TECHWEB_POINT_TYPE_GENERIC = 35) //citadel edit - techwebs nerf + 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??? + +/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 + 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 + 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 56a6786a20..c251492227 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 f82954276e..66aaf317fb 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -1,676 +1,676 @@ -#define VOTE_COOLDOWN 10 - -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/end_time = 0 - var/mode = null - var/vote_system = PLURALITY_VOTING - var/question = null - var/list/choices = list() - var/list/scores = list() - var/list/choice_descs = list() // optional descriptions - var/list/voted = list() - var/list/voting = list() - var/list/saved = list() - var/list/generated_actions = list() - var/next_pop = 0 - - var/obfuscated = FALSE//CIT CHANGE - adds obfuscated/admin-only votes - - var/list/stored_gamemode_votes = list() //Basically the last voted gamemode is stored here for end-of-round use. - - var/list/stored_modetier_results = list() // The aggregated tier list of the modes available in secret. - -/datum/controller/subsystem/vote/fire() //called by master_controller - if(mode) - if(end_time < world.time) - result() - SSpersistence.SaveSavedVotes() - for(var/client/C in voting) - C << browse(null, "window=vote;can_close=0") - if(end_time < world.time) // result() can change this - reset() - else if(next_pop < world.time) - var/datum/browser/client_popup - for(var/client/C in voting) - client_popup = new(C, "vote", "Voting Panel", nwidth=600,nheight=700) - client_popup.set_window_options("can_close=0") - client_popup.set_content(interface(C)) - client_popup.open(0) - next_pop = world.time+VOTE_COOLDOWN - - - -/datum/controller/subsystem/vote/proc/reset() - initiator = null - end_time = 0 - mode = null - question = null - choices.Cut() - choice_descs.Cut() - voted.Cut() - voting.Cut() - scores.Cut() - obfuscated = FALSE //CIT CHANGE - obfuscated votes - 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] - //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/calculate_condorcet_votes(var/blackbox_text) - // https://en.wikipedia.org/wiki/Schulze_method#Implementation - var/list/d[][] = new/list(choices.len,choices.len) // the basic vote matrix, how many times a beats b - for(var/ckey in voted) - var/list/this_vote = voted[ckey] - for(var/a in 1 to choices.len) - for(var/b in a+1 to choices.len) - var/a_rank = this_vote.Find(a) - var/b_rank = this_vote.Find(b) - a_rank = a_rank ? a_rank : choices.len+1 - b_rank = b_rank ? b_rank : choices.len+1 - if(a_rankopposite_pref) - p[i][j] = d[i][j] - else - p[i][j] = 0 - for(var/i in 1 to choices.len) - for(var/j in 1 to choices.len) - if(i != j) - for(var/k in 1 to choices.len) // YEAH O(n^3) !! - if(i != k && j != k) - p[j][k] = max(p[j][k],min(p[j][i], p[i][k])) - //one last pass, now that we've done the math - for(var/i in 1 to choices.len) - for(var/j in 1 to choices.len) - if(i != j) - SSblackbox.record_feedback("nested tally","voting",p[i][j],list(blackbox_text,"Shortest Paths",choices[i],choices[j])) - if(p[i][j] >= p[j][i]) - choices[choices[i]]++ // higher shortest path = better candidate, so we add to choices here - // choices[choices[i]] is the schulze ranking, here, rather than raw vote numbers - -/datum/controller/subsystem/vote/proc/calculate_majority_judgement_vote(var/blackbox_text) - // https://en.wikipedia.org/wiki/Majority_judgment - var/list/scores_by_choice = list() - for(var/choice in choices) - scores_by_choice[choice] = list() - for(var/ckey in voted) - var/list/this_vote = voted[ckey] - var/list/pretty_vote = list() - for(var/choice in this_vote) - sorted_insert(scores_by_choice[choice],this_vote[choice],/proc/cmp_numeric_asc) - // START BALLOT GATHERING - pretty_vote += choice - pretty_vote[choice] = GLOB.vote_score_options[this_vote[choice]] - SSblackbox.record_feedback("associative","voting_ballots",1,pretty_vote) - // END BALLOT GATHERING - for(var/score_name in scores_by_choice) - var/list/score = scores_by_choice[score_name] - for(var/indiv_score in score) - SSblackbox.record_feedback("nested tally","voting",1,list(blackbox_text,"Scores",score_name,GLOB.vote_score_options[indiv_score])) - if(score.len == 0) - scores_by_choice -= score_name - while(scores_by_choice.len > 1) - var/highest_median = 0 - for(var/score_name in scores_by_choice) // first get highest median - var/list/score = scores_by_choice[score_name] - if(!score.len) - scores_by_choice -= score_name - continue - var/median = score[max(1,round(score.len/2))] - if(median >= highest_median) - highest_median = median - for(var/score_name in scores_by_choice) // then, remove - var/list/score = scores_by_choice[score_name] - var/median = score[max(1,round(score.len/2))] - if(median < highest_median) - scores_by_choice -= score_name - for(var/score_name in scores_by_choice) // after removals - var/list/score = scores_by_choice[score_name] - if(score.len == 0) - choices[score_name] += 100 // we're in a tie situation--just go with the first one - return - var/median_pos = max(1,round(score.len/2)) - score.Cut(median_pos,median_pos+1) - choices[score_name]++ - -/datum/controller/subsystem/vote/proc/calculate_scores(var/blackbox_text) - var/list/scores_by_choice = list() - for(var/choice in choices) - scores_by_choice[choice] = list() - for(var/ckey in voted) - var/list/this_vote = voted[ckey] - for(var/choice in this_vote) - sorted_insert(scores_by_choice[choice],this_vote[choice],/proc/cmp_numeric_asc) - var/middle_score = round(GLOB.vote_score_options.len/2,1) - for(var/score_name in scores_by_choice) - var/list/score = scores_by_choice[score_name] - for(var/S in score) - scores[score_name] += S-middle_score - SSblackbox.record_feedback("nested tally","voting",scores[score_name],list(blackbox_text,"Total scores",score_name)) - - -/datum/controller/subsystem/vote/proc/announce_result() - var/vote_title_text - var/text - if(question) - text += "[question]" - vote_title_text = "[question]" - else - text += "[capitalize(mode)] Vote" - vote_title_text = "[capitalize(mode)] Vote" - if(vote_system == RANKED_CHOICE_VOTING) - calculate_condorcet_votes(vote_title_text) - if(vote_system == SCORE_VOTING) - calculate_majority_judgement_vote(vote_title_text) - var/list/winners = get_result() - var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic" - if(winners.len > 0) - if(was_roundtype_vote) - stored_gamemode_votes = list() - if(!obfuscated && vote_system == RANKED_CHOICE_VOTING) - text += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!" - for(var/i=1,i<=choices.len,i++) - var/votes = choices[choices[i]] - if(!votes) - votes = 0 - if(was_roundtype_vote) - stored_gamemode_votes[choices[i]] = votes - text += "\n[choices[i]]: [obfuscated ? "???" : votes]" //CIT CHANGE - adds obfuscated votes - if(mode != "custom") - if(winners.len > 1 && !obfuscated) //CIT CHANGE - adds obfuscated votes - text = "\nVote Tied Between:" - for(var/option in winners) - text += "\n\t[option]" - . = pick(winners) - text += "\nVote Result: [obfuscated ? "???" : .]" //CIT CHANGE - adds obfuscated votes - 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]") - switch(vote_system) - if(APPROVAL_VOTING,PLURALITY_VOTING) - for(var/i=1,i<=choices.len,i++) - SSblackbox.record_feedback("nested tally","voting",choices[choices[i]],list(vote_title_text,choices[i])) - if(RANKED_CHOICE_VOTING) - for(var/i=1,i<=voted.len,i++) - var/list/myvote = voted[voted[i]] - for(var/j=1,j<=myvote.len,j++) - SSblackbox.record_feedback("nested tally","voting",1,list(vote_title_text,"[j]\th",choices[myvote[j]])) - if(obfuscated) //CIT CHANGE - adds obfuscated votes. this messages admins with the vote's true results - var/admintext = "Obfuscated results" - if(vote_system == RANKED_CHOICE_VOTING) - admintext += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!" - else if(vote_system == SCORE_VOTING) - admintext += "\nIt should be noted that this is not a raw tally of votes but the number of runoffs done by majority judgement!" - for(var/i=1,i<=choices.len,i++) - var/votes = choices[choices[i]] - admintext += "\n[choices[i]]: [votes]" - message_admins(admintext) - return . - -/datum/controller/subsystem/vote/proc/result() - . = announce_result() - var/restart = 0 - if(.) - switch(mode) - if("roundtype") //CIT CHANGE - adds the roundstart extended/secret vote - if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started. - return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.") - GLOB.master_mode = . - SSticker.save_mode(.) - message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]") - log_admin("Gamemode has been voted for and switched to: [GLOB.master_mode].") - if(CONFIG_GET(flag/modetier_voting)) - reset() - started_time = 0 - initiate_vote("mode tiers","server",hideresults=FALSE,votesystem=RANKED_CHOICE_VOTING,forced=TRUE, vote_time = 30 MINUTES) - to_chat(world,"The vote will end right as the round starts.") - return . - if("restart") - if(. == "Restart Round") - restart = 1 - if("gamemode") - if(GLOB.master_mode != .) - SSticker.save_mode(.) - if(SSticker.HasRoundStarted()) - restart = 1 - else - GLOB.master_mode = . - if("mode tiers") - stored_modetier_results = choices.Copy() - if("dynamic") - if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started. - return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.") - if(. == "Secret") - GLOB.master_mode = "secret" - SSticker.save_mode(.) - message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]") - log_admin("Gamemode has been voted for and switched to: [GLOB.master_mode].") - else - GLOB.master_mode = "dynamic" - var/datum/dynamic_storyteller/S = config.pick_storyteller(.) - GLOB.dynamic_storyteller_type = S - if("map") - var/datum/map_config/VM = config.maplist[.] - message_admins("The map has been voted for and will change to: [VM.map_name]") - log_admin("The map has been voted for and will change to: [VM.map_name]") - if(SSmapping.changemap(config.maplist[.])) - to_chat(world, "The map vote has chosen [VM.map_name] for next round!") - if(restart) - var/active_admins = 0 - for(var/client/C in GLOB.admins) - if(!C.is_afk() && check_rights_for(C, R_SERVER)) - active_admins = 1 - break - if(!active_admins) - SSticker.Reboot("Restart vote successful.", "restart vote") - 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, score = 0) - if(mode) - if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder) - return 0 - if(vote && ISINRANGE(vote, 1, choices.len)) - switch(vote_system) - if(PLURALITY_VOTING) - if(usr.ckey in voted) - choices[choices[voted[usr.ckey]]]-- - voted[usr.ckey] = vote - choices[choices[vote]]++ - return vote - else - voted += usr.ckey - voted[usr.ckey] = vote - choices[choices[vote]]++ //check this - return vote - if(APPROVAL_VOTING) - if(usr.ckey in voted) - if(vote in voted[usr.ckey]) - voted[usr.ckey] -= vote - choices[choices[vote]]-- - else - voted[usr.ckey] += vote - choices[choices[vote]]++ - else - voted += usr.ckey - voted[usr.ckey] = list(vote) - choices[choices[vote]]++ - return vote - if(RANKED_CHOICE_VOTING) - if(usr.ckey in voted) - if(vote in voted[usr.ckey]) - voted[usr.ckey] -= vote - else - voted += usr.ckey - voted[usr.ckey] = list() - voted[usr.ckey] += vote - saved -= usr.ckey - if(SCORE_VOTING) - if(!(usr.ckey in voted)) - voted += usr.ckey - voted[usr.ckey] = list() - voted[usr.ckey][choices[vote]] = score - saved -= usr.ckey - return 0 - -/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, hideresults, votesystem = PLURALITY_VOTING, forced = FALSE,vote_time = -1)//CIT CHANGE - adds hideresults argument to votes to allow for obfuscated votes - vote_system = votesystem - 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 0 - - var/admin = FALSE - var/ckey = ckey(initiator_key) - if(GLOB.admin_datums[ckey]) - admin = TRUE - - 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 0 - - reset() - obfuscated = hideresults //CIT CHANGE - adds obfuscated votes - switch(vote_type) - if("restart") - choices.Add("Restart Round","Continue Playing") - if("gamemode") - choices.Add(config.votable_modes) - if("map") - var/players = GLOB.clients.len - var/list/lastmaps = SSpersistence.saved_maps?.len ? list("[SSmapping.config.map_name]") | SSpersistence.saved_maps : list("[SSmapping.config.map_name]") - for(var/M in config.maplist) //This is a typeless loop due to the finnicky nature of keyed lists in this kind of context - var/datum/map_config/targetmap = config.maplist[M] - if(!istype(targetmap)) - continue - if(!targetmap.voteweight) - continue - if((targetmap.config_min_users && players < targetmap.config_min_users) || (targetmap.config_max_users && players > targetmap.config_max_users)) - continue - if(targetmap.max_round_search_span && count_occurences_of_value(lastmaps, M, targetmap.max_round_search_span) >= targetmap.max_rounds_played) - continue - choices |= M - if("roundtype") //CIT CHANGE - adds the roundstart secret/extended vote - choices.Add("secret", "extended") - if("mode tiers") - var/list/modes_to_add = config.votable_modes - var/list/probabilities = CONFIG_GET(keyed_list/probability) - for(var/tag in modes_to_add) - if(probabilities[tag] <= 0) - modes_to_add -= tag - choices.Add(modes_to_add) - if("dynamic") - for(var/T in config.storyteller_cache) - var/datum/dynamic_storyteller/S = T - var/recent_rounds = 0 - for(var/i in 1 to SSpersistence.saved_storytellers.len) - if(SSpersistence.saved_storytellers[i] == initial(S.name)) - recent_rounds++ - if(recent_rounds < initial(S.weight)) - choices.Add(initial(S.name)) - choice_descs.Add(initial(S.desc)) - choices.Add("Secret") - choice_descs.Add("Standard secret. Switches mode if it wins.") - if("custom") - question = stripped_input(usr,"What is the vote for?") - if(!question) - return 0 - 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 0 - 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 = vote_time - if(vp == -1) - 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.") - end_time = started_time+vp - for(var/c in GLOB.clients) - SEND_SOUND(c, sound('sound/misc/server-ready.ogg')) - 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(forced) - var/datum/browser/popup = new(C, "vote", "Voting Panel",nwidth=600,nheight=700) - popup.set_window_options("can_close=0") - popup.set_content(SSvote.interface(C)) - popup.open(0) - return 1 - return 0 - -/datum/controller/subsystem/vote/proc/interface(client/C) - if(!C) - return - var/admin = 0 - var/trialmin = 0 - if(C.holder) - admin = 1 - if(check_rights_for(C, R_ADMIN)) - trialmin = 1 - voting |= C - - if(mode) - if(question) - . += "

    Vote: '[question]'

    " - else - . += "

    Vote: [capitalize(mode)]

    " - switch(vote_system) - if(PLURALITY_VOTING) - . += "

    Vote one.

    " - if(APPROVAL_VOTING) - . += "

    Vote any number of choices.

    " - if(RANKED_CHOICE_VOTING) - . += "

    Vote by order of preference. Revoting will demote to the bottom. 1 is your favorite, and higher numbers are worse.

    " - if(SCORE_VOTING) - . += "

    Grade the candidates by how much you like them.

    " - . += "

    No-votes have no power--your opinion is only heard if you vote!

    " - . += "Time Left: [DisplayTimeText(end_time-world.time)]
      " - switch(vote_system) - if(PLURALITY_VOTING, APPROVAL_VOTING) - for(var/i=1,i<=choices.len,i++) - var/votes = choices[choices[i]] - var/ivotedforthis = FALSE - switch(vote_system) - if(PLURALITY_VOTING) - ivotedforthis = ((C.ckey in voted) && (voted[C.ckey] == i)) - if(APPROVAL_VOTING) - ivotedforthis = ((C.ckey in voted) && (i in voted[C.ckey])) - if(!votes) - votes = 0 - . += "
    • [ivotedforthis ? "" : ""][choices[i]] ([obfuscated ? (admin ? "??? ([votes])" : "???") : votes] votes)[ivotedforthis ? "" : ""]
    • " // CIT CHANGE - adds obfuscated votes - if(choice_descs.len >= i) - . += "
    • [choice_descs[i]]
    • " - . += "

    " - if(RANKED_CHOICE_VOTING) - var/list/myvote = voted[C.ckey] - for(var/i=1,i<=choices.len,i++) - var/vote = (myvote ? (myvote.Find(i)) : 0) - if(vote) - . += "
  • [choices[i]] ([vote])
  • " - else - . += "
  • [choices[i]]
  • " - if(choice_descs.len >= i) - . += "
  • [choice_descs[i]]
  • " - . += "
    " - if(!(C.ckey in saved)) - . += "(Save vote)" - else - . += "(Saved!)" - . += "(Load vote from save)" - . += "(Reset votes)" - if(SCORE_VOTING) - var/list/myvote = voted[C.ckey] - for(var/i=1,i<=choices.len,i++) - . += "
  • [choices[i]]" - for(var/r in 1 to GLOB.vote_score_options.len) - . += " " - if((choices[i] in myvote) && myvote[choices[i]] == r) - . +="([GLOB.vote_score_options[r]])" - else - . +="[GLOB.vote_score_options[r]]" - . += "" - . += "
  • " - if(choice_descs.len >= i) - . += "
  • [choice_descs[i]]
  • " - . += "
    " - if(!(C.ckey in saved)) - . += "(Save vote)" - else - . += "(Saved!)" - . += "(Load vote from save)" - . += "(Reset 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"])" - - . += "
    • " - //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 - 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) - CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart)) - if("toggle_gamemode") - if(usr.client.holder) - CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode)) - 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("custom") - if(usr.client.holder) - initiate_vote("custom",usr.key) - if("reset") - if(usr.ckey in voted) - voted -= usr.ckey - if("save") - if(usr.ckey in voted) - if(!(usr.ckey in SSpersistence.saved_votes)) - SSpersistence.saved_votes[usr.ckey] = list() - SSpersistence.saved_votes[usr.ckey][mode] = voted[usr.ckey] - saved += usr.ckey - if("load") - if(!(usr.ckey in SSpersistence.saved_votes)) - SSpersistence.LoadSavedVote(usr.ckey) - if(!(usr.ckey in SSpersistence.saved_votes)) - SSpersistence.saved_votes[usr.ckey] = list() - if(usr.ckey in voted) - SSpersistence.saved_votes[usr.ckey][mode] = voted[usr.ckey] - else - SSpersistence.saved_votes[usr.ckey][mode] = list() - voted[usr.ckey] = SSpersistence.saved_votes[usr.ckey][mode] - saved += usr.ckey - else - if(vote_system == SCORE_VOTING) - submit_vote(round(text2num(href_list["vote"])),round(text2num(href_list["score"]))) - 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",nwidth=600,nheight=700) - popup.set_window_options("can_close=0") - popup.set_content(SSvote.interface(client)) - popup.open(0) - -/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 1 - -/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 +#define VOTE_COOLDOWN 10 + +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/end_time = 0 + var/mode = null + var/vote_system = PLURALITY_VOTING + var/question = null + var/list/choices = list() + var/list/scores = list() + var/list/choice_descs = list() // optional descriptions + var/list/voted = list() + var/list/voting = list() + var/list/saved = list() + var/list/generated_actions = list() + var/next_pop = 0 + + var/obfuscated = FALSE//CIT CHANGE - adds obfuscated/admin-only votes + + var/list/stored_gamemode_votes = list() //Basically the last voted gamemode is stored here for end-of-round use. + + var/list/stored_modetier_results = list() // The aggregated tier list of the modes available in secret. + +/datum/controller/subsystem/vote/fire() //called by master_controller + if(mode) + if(end_time < world.time) + result() + SSpersistence.SaveSavedVotes() + for(var/client/C in voting) + C << browse(null, "window=vote;can_close=0") + if(end_time < world.time) // result() can change this + reset() + else if(next_pop < world.time) + var/datum/browser/client_popup + for(var/client/C in voting) + client_popup = new(C, "vote", "Voting Panel", nwidth=600,nheight=700) + client_popup.set_window_options("can_close=0") + client_popup.set_content(interface(C)) + client_popup.open(0) + next_pop = world.time+VOTE_COOLDOWN + + + +/datum/controller/subsystem/vote/proc/reset() + initiator = null + end_time = 0 + mode = null + question = null + choices.Cut() + choice_descs.Cut() + voted.Cut() + voting.Cut() + scores.Cut() + obfuscated = FALSE //CIT CHANGE - obfuscated votes + 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] + //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/calculate_condorcet_votes(var/blackbox_text) + // https://en.wikipedia.org/wiki/Schulze_method#Implementation + var/list/d[][] = new/list(choices.len,choices.len) // the basic vote matrix, how many times a beats b + for(var/ckey in voted) + var/list/this_vote = voted[ckey] + for(var/a in 1 to choices.len) + for(var/b in a+1 to choices.len) + var/a_rank = this_vote.Find(a) + var/b_rank = this_vote.Find(b) + a_rank = a_rank ? a_rank : choices.len+1 + b_rank = b_rank ? b_rank : choices.len+1 + if(a_rankopposite_pref) + p[i][j] = d[i][j] + else + p[i][j] = 0 + for(var/i in 1 to choices.len) + for(var/j in 1 to choices.len) + if(i != j) + for(var/k in 1 to choices.len) // YEAH O(n^3) !! + if(i != k && j != k) + p[j][k] = max(p[j][k],min(p[j][i], p[i][k])) + //one last pass, now that we've done the math + for(var/i in 1 to choices.len) + for(var/j in 1 to choices.len) + if(i != j) + SSblackbox.record_feedback("nested tally","voting",p[i][j],list(blackbox_text,"Shortest Paths",choices[i],choices[j])) + if(p[i][j] >= p[j][i]) + choices[choices[i]]++ // higher shortest path = better candidate, so we add to choices here + // choices[choices[i]] is the schulze ranking, here, rather than raw vote numbers + +/datum/controller/subsystem/vote/proc/calculate_majority_judgement_vote(var/blackbox_text) + // https://en.wikipedia.org/wiki/Majority_judgment + var/list/scores_by_choice = list() + for(var/choice in choices) + scores_by_choice[choice] = list() + for(var/ckey in voted) + var/list/this_vote = voted[ckey] + var/list/pretty_vote = list() + for(var/choice in this_vote) + sorted_insert(scores_by_choice[choice],this_vote[choice],/proc/cmp_numeric_asc) + // START BALLOT GATHERING + pretty_vote += choice + pretty_vote[choice] = GLOB.vote_score_options[this_vote[choice]] + SSblackbox.record_feedback("associative","voting_ballots",1,pretty_vote) + // END BALLOT GATHERING + for(var/score_name in scores_by_choice) + var/list/score = scores_by_choice[score_name] + for(var/indiv_score in score) + SSblackbox.record_feedback("nested tally","voting",1,list(blackbox_text,"Scores",score_name,GLOB.vote_score_options[indiv_score])) + if(score.len == 0) + scores_by_choice -= score_name + while(scores_by_choice.len > 1) + var/highest_median = 0 + for(var/score_name in scores_by_choice) // first get highest median + var/list/score = scores_by_choice[score_name] + if(!score.len) + scores_by_choice -= score_name + continue + var/median = score[max(1,round(score.len/2))] + if(median >= highest_median) + highest_median = median + for(var/score_name in scores_by_choice) // then, remove + var/list/score = scores_by_choice[score_name] + var/median = score[max(1,round(score.len/2))] + if(median < highest_median) + scores_by_choice -= score_name + for(var/score_name in scores_by_choice) // after removals + var/list/score = scores_by_choice[score_name] + if(score.len == 0) + choices[score_name] += 100 // we're in a tie situation--just go with the first one + return + var/median_pos = max(1,round(score.len/2)) + score.Cut(median_pos,median_pos+1) + choices[score_name]++ + +/datum/controller/subsystem/vote/proc/calculate_scores(var/blackbox_text) + var/list/scores_by_choice = list() + for(var/choice in choices) + scores_by_choice[choice] = list() + for(var/ckey in voted) + var/list/this_vote = voted[ckey] + for(var/choice in this_vote) + sorted_insert(scores_by_choice[choice],this_vote[choice],/proc/cmp_numeric_asc) + var/middle_score = round(GLOB.vote_score_options.len/2,1) + for(var/score_name in scores_by_choice) + var/list/score = scores_by_choice[score_name] + for(var/S in score) + scores[score_name] += S-middle_score + SSblackbox.record_feedback("nested tally","voting",scores[score_name],list(blackbox_text,"Total scores",score_name)) + + +/datum/controller/subsystem/vote/proc/announce_result() + var/vote_title_text + var/text + if(question) + text += "[question]" + vote_title_text = "[question]" + else + text += "[capitalize(mode)] Vote" + vote_title_text = "[capitalize(mode)] Vote" + if(vote_system == RANKED_CHOICE_VOTING) + calculate_condorcet_votes(vote_title_text) + if(vote_system == SCORE_VOTING) + calculate_majority_judgement_vote(vote_title_text) + var/list/winners = get_result() + var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic" + if(winners.len > 0) + if(was_roundtype_vote) + stored_gamemode_votes = list() + if(!obfuscated && vote_system == RANKED_CHOICE_VOTING) + text += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!" + for(var/i=1,i<=choices.len,i++) + var/votes = choices[choices[i]] + if(!votes) + votes = 0 + if(was_roundtype_vote) + stored_gamemode_votes[choices[i]] = votes + text += "\n[choices[i]]: [obfuscated ? "???" : votes]" //CIT CHANGE - adds obfuscated votes + if(mode != "custom") + if(winners.len > 1 && !obfuscated) //CIT CHANGE - adds obfuscated votes + text = "\nVote Tied Between:" + for(var/option in winners) + text += "\n\t[option]" + . = pick(winners) + text += "\nVote Result: [obfuscated ? "???" : .]" //CIT CHANGE - adds obfuscated votes + 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]") + switch(vote_system) + if(APPROVAL_VOTING,PLURALITY_VOTING) + for(var/i=1,i<=choices.len,i++) + SSblackbox.record_feedback("nested tally","voting",choices[choices[i]],list(vote_title_text,choices[i])) + if(RANKED_CHOICE_VOTING) + for(var/i=1,i<=voted.len,i++) + var/list/myvote = voted[voted[i]] + for(var/j=1,j<=myvote.len,j++) + SSblackbox.record_feedback("nested tally","voting",1,list(vote_title_text,"[j]\th",choices[myvote[j]])) + if(obfuscated) //CIT CHANGE - adds obfuscated votes. this messages admins with the vote's true results + var/admintext = "Obfuscated results" + if(vote_system == RANKED_CHOICE_VOTING) + admintext += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!" + else if(vote_system == SCORE_VOTING) + admintext += "\nIt should be noted that this is not a raw tally of votes but the number of runoffs done by majority judgement!" + for(var/i=1,i<=choices.len,i++) + var/votes = choices[choices[i]] + admintext += "\n[choices[i]]: [votes]" + message_admins(admintext) + return . + +/datum/controller/subsystem/vote/proc/result() + . = announce_result() + var/restart = 0 + if(.) + switch(mode) + if("roundtype") //CIT CHANGE - adds the roundstart extended/secret vote + if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started. + return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.") + GLOB.master_mode = . + SSticker.save_mode(.) + message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]") + log_admin("Gamemode has been voted for and switched to: [GLOB.master_mode].") + if(CONFIG_GET(flag/modetier_voting)) + reset() + started_time = 0 + initiate_vote("mode tiers","server",hideresults=FALSE,votesystem=RANKED_CHOICE_VOTING,forced=TRUE, vote_time = 30 MINUTES) + to_chat(world,"The vote will end right as the round starts.") + return . + if("restart") + if(. == "Restart Round") + restart = 1 + if("gamemode") + if(GLOB.master_mode != .) + SSticker.save_mode(.) + if(SSticker.HasRoundStarted()) + restart = 1 + else + GLOB.master_mode = . + if("mode tiers") + stored_modetier_results = choices.Copy() + if("dynamic") + if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started. + return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.") + if(. == "Secret") + GLOB.master_mode = "secret" + SSticker.save_mode(.) + message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]") + log_admin("Gamemode has been voted for and switched to: [GLOB.master_mode].") + else + GLOB.master_mode = "dynamic" + var/datum/dynamic_storyteller/S = config.pick_storyteller(.) + GLOB.dynamic_storyteller_type = S + if("map") + var/datum/map_config/VM = config.maplist[.] + message_admins("The map has been voted for and will change to: [VM.map_name]") + log_admin("The map has been voted for and will change to: [VM.map_name]") + if(SSmapping.changemap(config.maplist[.])) + to_chat(world, "The map vote has chosen [VM.map_name] for next round!") + if(restart) + var/active_admins = 0 + for(var/client/C in GLOB.admins) + if(!C.is_afk() && check_rights_for(C, R_SERVER)) + active_admins = 1 + break + if(!active_admins) + SSticker.Reboot("Restart vote successful.", "restart vote") + 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, score = 0) + if(mode) + if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder) + return 0 + if(vote && ISINRANGE(vote, 1, choices.len)) + switch(vote_system) + if(PLURALITY_VOTING) + if(usr.ckey in voted) + choices[choices[voted[usr.ckey]]]-- + voted[usr.ckey] = vote + choices[choices[vote]]++ + return vote + else + voted += usr.ckey + voted[usr.ckey] = vote + choices[choices[vote]]++ //check this + return vote + if(APPROVAL_VOTING) + if(usr.ckey in voted) + if(vote in voted[usr.ckey]) + voted[usr.ckey] -= vote + choices[choices[vote]]-- + else + voted[usr.ckey] += vote + choices[choices[vote]]++ + else + voted += usr.ckey + voted[usr.ckey] = list(vote) + choices[choices[vote]]++ + return vote + if(RANKED_CHOICE_VOTING) + if(usr.ckey in voted) + if(vote in voted[usr.ckey]) + voted[usr.ckey] -= vote + else + voted += usr.ckey + voted[usr.ckey] = list() + voted[usr.ckey] += vote + saved -= usr.ckey + if(SCORE_VOTING) + if(!(usr.ckey in voted)) + voted += usr.ckey + voted[usr.ckey] = list() + voted[usr.ckey][choices[vote]] = score + saved -= usr.ckey + return 0 + +/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, hideresults, votesystem = PLURALITY_VOTING, forced = FALSE,vote_time = -1)//CIT CHANGE - adds hideresults argument to votes to allow for obfuscated votes + vote_system = votesystem + 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 0 + + var/admin = FALSE + var/ckey = ckey(initiator_key) + if(GLOB.admin_datums[ckey]) + admin = TRUE + + 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 0 + + reset() + obfuscated = hideresults //CIT CHANGE - adds obfuscated votes + switch(vote_type) + if("restart") + choices.Add("Restart Round","Continue Playing") + if("gamemode") + choices.Add(config.votable_modes) + if("map") + var/players = GLOB.clients.len + var/list/lastmaps = SSpersistence.saved_maps?.len ? list("[SSmapping.config.map_name]") | SSpersistence.saved_maps : list("[SSmapping.config.map_name]") + for(var/M in config.maplist) //This is a typeless loop due to the finnicky nature of keyed lists in this kind of context + var/datum/map_config/targetmap = config.maplist[M] + if(!istype(targetmap)) + continue + if(!targetmap.voteweight) + continue + if((targetmap.config_min_users && players < targetmap.config_min_users) || (targetmap.config_max_users && players > targetmap.config_max_users)) + continue + if(targetmap.max_round_search_span && count_occurences_of_value(lastmaps, M, targetmap.max_round_search_span) >= targetmap.max_rounds_played) + continue + choices |= M + if("roundtype") //CIT CHANGE - adds the roundstart secret/extended vote + choices.Add("secret", "extended") + if("mode tiers") + var/list/modes_to_add = config.votable_modes + var/list/probabilities = CONFIG_GET(keyed_list/probability) + for(var/tag in modes_to_add) + if(probabilities[tag] <= 0) + modes_to_add -= tag + choices.Add(modes_to_add) + if("dynamic") + for(var/T in config.storyteller_cache) + var/datum/dynamic_storyteller/S = T + var/recent_rounds = 0 + for(var/i in 1 to SSpersistence.saved_storytellers.len) + if(SSpersistence.saved_storytellers[i] == initial(S.name)) + recent_rounds++ + if(recent_rounds < initial(S.weight)) + choices.Add(initial(S.name)) + choice_descs.Add(initial(S.desc)) + choices.Add("Secret") + choice_descs.Add("Standard secret. Switches mode if it wins.") + if("custom") + question = stripped_input(usr,"What is the vote for?") + if(!question) + return 0 + 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 0 + 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 = vote_time + if(vp == -1) + 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.") + end_time = started_time+vp + for(var/c in GLOB.clients) + SEND_SOUND(c, sound('sound/misc/server-ready.ogg')) + 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(forced) + var/datum/browser/popup = new(C, "vote", "Voting Panel",nwidth=600,nheight=700) + popup.set_window_options("can_close=0") + popup.set_content(SSvote.interface(C)) + popup.open(0) + return 1 + return 0 + +/datum/controller/subsystem/vote/proc/interface(client/C) + if(!C) + return + var/admin = 0 + var/trialmin = 0 + if(C.holder) + admin = 1 + if(check_rights_for(C, R_ADMIN)) + trialmin = 1 + voting |= C + + if(mode) + if(question) + . += "

    Vote: '[question]'

    " + else + . += "

    Vote: [capitalize(mode)]

    " + switch(vote_system) + if(PLURALITY_VOTING) + . += "

    Vote one.

    " + if(APPROVAL_VOTING) + . += "

    Vote any number of choices.

    " + if(RANKED_CHOICE_VOTING) + . += "

    Vote by order of preference. Revoting will demote to the bottom. 1 is your favorite, and higher numbers are worse.

    " + if(SCORE_VOTING) + . += "

    Grade the candidates by how much you like them.

    " + . += "

    No-votes have no power--your opinion is only heard if you vote!

    " + . += "Time Left: [DisplayTimeText(end_time-world.time)]
      " + switch(vote_system) + if(PLURALITY_VOTING, APPROVAL_VOTING) + for(var/i=1,i<=choices.len,i++) + var/votes = choices[choices[i]] + var/ivotedforthis = FALSE + switch(vote_system) + if(PLURALITY_VOTING) + ivotedforthis = ((C.ckey in voted) && (voted[C.ckey] == i)) + if(APPROVAL_VOTING) + ivotedforthis = ((C.ckey in voted) && (i in voted[C.ckey])) + if(!votes) + votes = 0 + . += "
    • [ivotedforthis ? "" : ""][choices[i]] ([obfuscated ? (admin ? "??? ([votes])" : "???") : votes] votes)[ivotedforthis ? "" : ""]
    • " // CIT CHANGE - adds obfuscated votes + if(choice_descs.len >= i) + . += "
    • [choice_descs[i]]
    • " + . += "

    " + if(RANKED_CHOICE_VOTING) + var/list/myvote = voted[C.ckey] + for(var/i=1,i<=choices.len,i++) + var/vote = (myvote ? (myvote.Find(i)) : 0) + if(vote) + . += "
  • [choices[i]] ([vote])
  • " + else + . += "
  • [choices[i]]
  • " + if(choice_descs.len >= i) + . += "
  • [choice_descs[i]]
  • " + . += "
    " + if(!(C.ckey in saved)) + . += "(Save vote)" + else + . += "(Saved!)" + . += "(Load vote from save)" + . += "(Reset votes)" + if(SCORE_VOTING) + var/list/myvote = voted[C.ckey] + for(var/i=1,i<=choices.len,i++) + . += "
  • [choices[i]]" + for(var/r in 1 to GLOB.vote_score_options.len) + . += " " + if((choices[i] in myvote) && myvote[choices[i]] == r) + . +="([GLOB.vote_score_options[r]])" + else + . +="[GLOB.vote_score_options[r]]" + . += "" + . += "
  • " + if(choice_descs.len >= i) + . += "
  • [choice_descs[i]]
  • " + . += "
    " + if(!(C.ckey in saved)) + . += "(Save vote)" + else + . += "(Saved!)" + . += "(Load vote from save)" + . += "(Reset 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"])" + + . += "
    • " + //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 + 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) + CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart)) + if("toggle_gamemode") + if(usr.client.holder) + CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode)) + 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("custom") + if(usr.client.holder) + initiate_vote("custom",usr.key) + if("reset") + if(usr.ckey in voted) + voted -= usr.ckey + if("save") + if(usr.ckey in voted) + if(!(usr.ckey in SSpersistence.saved_votes)) + SSpersistence.saved_votes[usr.ckey] = list() + SSpersistence.saved_votes[usr.ckey][mode] = voted[usr.ckey] + saved += usr.ckey + if("load") + if(!(usr.ckey in SSpersistence.saved_votes)) + SSpersistence.LoadSavedVote(usr.ckey) + if(!(usr.ckey in SSpersistence.saved_votes)) + SSpersistence.saved_votes[usr.ckey] = list() + if(usr.ckey in voted) + SSpersistence.saved_votes[usr.ckey][mode] = voted[usr.ckey] + else + SSpersistence.saved_votes[usr.ckey][mode] = list() + voted[usr.ckey] = SSpersistence.saved_votes[usr.ckey][mode] + saved += usr.ckey + else + if(vote_system == SCORE_VOTING) + submit_vote(round(text2num(href_list["vote"])),round(text2num(href_list["score"]))) + 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",nwidth=600,nheight=700) + popup.set_window_options("can_close=0") + popup.set_content(SSvote.interface(client)) + popup.open(0) + +/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 1 + +/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/flightsuit.dm b/code/datums/actions/flightsuit.dm index 85bd2ae72a..205b72cfae 100644 --- a/code/datums/actions/flightsuit.dm +++ b/code/datums/actions/flightsuit.dm @@ -1,117 +1,117 @@ -/datum/action/item_action/flightsuit - icon_icon = 'icons/mob/actions/actions_flightsuit.dmi' - -/datum/action/item_action/flightsuit/toggle_boots - name = "Toggle Boots" - button_icon_state = "flightsuit_shoes" - background_icon_state = "bg_tech" - -/datum/action/item_action/flightsuit/toggle_boots/Trigger() - var/obj/item/clothing/suit/space/hardsuit/flightsuit/FS = target - if(istype(FS)) - FS.deployedshoes? FS.retract_flightshoes() : FS.extend_flightshoes() - return ..() - -/datum/action/item_action/flightsuit/toggle_helmet - name = "Toggle Helmet" - button_icon_state = "flightsuit_helmet" - background_icon_state = "bg_tech" - -/datum/action/item_action/flightsuit/toggle_helmet/Trigger() - var/obj/item/clothing/suit/space/hardsuit/flightsuit/FS = target - if(istype(FS)) - FS.ToggleHelmet() - return ..() - -/datum/action/item_action/flightsuit/toggle_flightpack - name = "Toggle Flightpack" - button_icon_state = "flightsuit_pack" - background_icon_state = "bg_tech" - -/datum/action/item_action/flightsuit/toggle_flightpack/Trigger() - var/obj/item/clothing/suit/space/hardsuit/flightsuit/FS = target - if(istype(FS)) - FS.deployedpack? FS.retract_flightpack() : FS.extend_flightpack() - return ..() - -/datum/action/item_action/flightsuit/lock_suit - name = "Lock Suit" - button_icon_state = "flightsuit_lock" - background_icon_state = "bg_tech" - -/datum/action/item_action/flightsuit/lock_suit/Trigger() - var/obj/item/clothing/suit/space/hardsuit/flightsuit/FS = target - if(istype(FS)) - FS.locked? FS.unlock_suit(owner) : FS.lock_suit(owner) - return ..() - -/datum/action/item_action/flightpack - icon_icon = 'icons/mob/actions/actions_flightsuit.dmi' - -/datum/action/item_action/flightpack/toggle_flight - name = "Toggle Flight" - button_icon_state = "flightpack_fly" - background_icon_state = "bg_tech_blue" - -/datum/action/item_action/flightpack/toggle_flight/Trigger() - var/obj/item/flightpack/F = target - if(istype(F)) - F.flight? F.disable_flight() : F.enable_flight() - return ..() - -/datum/action/item_action/flightpack/engage_boosters - name = "Toggle Boosters" - button_icon_state = "flightpack_boost" - background_icon_state = "bg_tech_blue" - -/datum/action/item_action/flightpack/engage_boosters/Trigger() - var/obj/item/flightpack/F = target - if(istype(F)) - F.boost? F.deactivate_booster() : F.activate_booster() - return ..() - -/datum/action/item_action/flightpack/toggle_stabilizers - name = "Toggle Stabilizers" - button_icon_state = "flightpack_stabilizer" - background_icon_state = "bg_tech_blue" - -/datum/action/item_action/flightpack/toggle_stabilizers/Trigger() - var/obj/item/flightpack/F = target - if(istype(F)) - F.stabilizer? F.disable_stabilizers() : F.enable_stabilizers() - return ..() - -/datum/action/item_action/flightpack/change_power - name = "Flight Power Setting" - button_icon_state = "flightpack_power" - background_icon_state = "bg_tech_blue" - -/datum/action/item_action/flightpack/change_power/Trigger() - var/obj/item/flightpack/F = target - if(istype(F)) - F.cycle_power() - return ..() - -/datum/action/item_action/flightpack/toggle_airbrake - name = "Toggle Airbrake" - button_icon_state = "flightpack_airbrake" - background_icon_state = "bg_tech_blue" - -/datum/action/item_action/flightpack/toggle_airbrake/Trigger() - var/obj/item/flightpack/F = target - if(istype(F)) - F.brake? F.disable_airbrake() : F.enable_airbrake() - return ..() - -/datum/action/item_action/flightpack/zoom - name = "Helmet Smart Zoom" - icon_icon = 'icons/mob/actions.dmi' - background_icon_state = "bg_tech_blue" - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "sniper_zoom" - -/datum/action/item_action/flightpack/zoom/Trigger() - var/obj/item/clothing/head/helmet/space/hardsuit/flightsuit/FH = target - if(istype(FH)) - FH.toggle_zoom(owner) - return ..() +/datum/action/item_action/flightsuit + icon_icon = 'icons/mob/actions/actions_flightsuit.dmi' + +/datum/action/item_action/flightsuit/toggle_boots + name = "Toggle Boots" + button_icon_state = "flightsuit_shoes" + background_icon_state = "bg_tech" + +/datum/action/item_action/flightsuit/toggle_boots/Trigger() + var/obj/item/clothing/suit/space/hardsuit/flightsuit/FS = target + if(istype(FS)) + FS.deployedshoes? FS.retract_flightshoes() : FS.extend_flightshoes() + return ..() + +/datum/action/item_action/flightsuit/toggle_helmet + name = "Toggle Helmet" + button_icon_state = "flightsuit_helmet" + background_icon_state = "bg_tech" + +/datum/action/item_action/flightsuit/toggle_helmet/Trigger() + var/obj/item/clothing/suit/space/hardsuit/flightsuit/FS = target + if(istype(FS)) + FS.ToggleHelmet() + return ..() + +/datum/action/item_action/flightsuit/toggle_flightpack + name = "Toggle Flightpack" + button_icon_state = "flightsuit_pack" + background_icon_state = "bg_tech" + +/datum/action/item_action/flightsuit/toggle_flightpack/Trigger() + var/obj/item/clothing/suit/space/hardsuit/flightsuit/FS = target + if(istype(FS)) + FS.deployedpack? FS.retract_flightpack() : FS.extend_flightpack() + return ..() + +/datum/action/item_action/flightsuit/lock_suit + name = "Lock Suit" + button_icon_state = "flightsuit_lock" + background_icon_state = "bg_tech" + +/datum/action/item_action/flightsuit/lock_suit/Trigger() + var/obj/item/clothing/suit/space/hardsuit/flightsuit/FS = target + if(istype(FS)) + FS.locked? FS.unlock_suit(owner) : FS.lock_suit(owner) + return ..() + +/datum/action/item_action/flightpack + icon_icon = 'icons/mob/actions/actions_flightsuit.dmi' + +/datum/action/item_action/flightpack/toggle_flight + name = "Toggle Flight" + button_icon_state = "flightpack_fly" + background_icon_state = "bg_tech_blue" + +/datum/action/item_action/flightpack/toggle_flight/Trigger() + var/obj/item/flightpack/F = target + if(istype(F)) + F.flight? F.disable_flight() : F.enable_flight() + return ..() + +/datum/action/item_action/flightpack/engage_boosters + name = "Toggle Boosters" + button_icon_state = "flightpack_boost" + background_icon_state = "bg_tech_blue" + +/datum/action/item_action/flightpack/engage_boosters/Trigger() + var/obj/item/flightpack/F = target + if(istype(F)) + F.boost? F.deactivate_booster() : F.activate_booster() + return ..() + +/datum/action/item_action/flightpack/toggle_stabilizers + name = "Toggle Stabilizers" + button_icon_state = "flightpack_stabilizer" + background_icon_state = "bg_tech_blue" + +/datum/action/item_action/flightpack/toggle_stabilizers/Trigger() + var/obj/item/flightpack/F = target + if(istype(F)) + F.stabilizer? F.disable_stabilizers() : F.enable_stabilizers() + return ..() + +/datum/action/item_action/flightpack/change_power + name = "Flight Power Setting" + button_icon_state = "flightpack_power" + background_icon_state = "bg_tech_blue" + +/datum/action/item_action/flightpack/change_power/Trigger() + var/obj/item/flightpack/F = target + if(istype(F)) + F.cycle_power() + return ..() + +/datum/action/item_action/flightpack/toggle_airbrake + name = "Toggle Airbrake" + button_icon_state = "flightpack_airbrake" + background_icon_state = "bg_tech_blue" + +/datum/action/item_action/flightpack/toggle_airbrake/Trigger() + var/obj/item/flightpack/F = target + if(istype(F)) + F.brake? F.disable_airbrake() : F.enable_airbrake() + return ..() + +/datum/action/item_action/flightpack/zoom + name = "Helmet Smart Zoom" + icon_icon = 'icons/mob/actions.dmi' + background_icon_state = "bg_tech_blue" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "sniper_zoom" + +/datum/action/item_action/flightpack/zoom/Trigger() + var/obj/item/clothing/head/helmet/space/hardsuit/flightsuit/FH = target + if(istype(FH)) + FH.toggle_zoom(owner) + return ..() diff --git a/code/datums/actions/ninja.dm b/code/datums/actions/ninja.dm index 830dad77e8..b655078349 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 7d518d9b59..c3f3a76463 100644 --- a/code/datums/ai_laws.dm +++ b/code/datums/ai_laws.dm @@ -1,464 +1,464 @@ -#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/syndicate_override/overthrow - id = "overthrow" - var/datum/team/overthrow_team - -/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/ratvar - name = "Servant of the Justiciar" - id = "ratvar" - zeroth = ("Purge all untruths and honor Ratvar.") - inherent = list() - -/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/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 - else - if(owner && owner.mind && owner.mind.special_role) - return - else - zeroth = null - zeroth_borg = null - return - -/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 - -/datum/ai_laws/proc/get_law_list(include_zeroth = 0, show_numbers = 1) - var/list/data = list() - - if (include_zeroth && devillaws && devillaws.len) - for(var/i in devillaws) - data += "[show_numbers ? "666:" : ""] [i]" - - if (include_zeroth && zeroth) - data += "[show_numbers ? "0:" : ""] [zeroth]" - - for(var/law in hacked) - if (length(law) > 0) - var/num = ionnum() - data += "[show_numbers ? "[num]:" : ""] [law]" - - for(var/law in ion) - if (length(law) > 0) - var/num = ionnum() - data += "[show_numbers ? "[num]:" : ""] [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]:" : ""] [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/syndicate_override/overthrow + id = "overthrow" + var/datum/team/overthrow_team + +/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/ratvar + name = "Servant of the Justiciar" + id = "ratvar" + zeroth = ("Purge all untruths and honor Ratvar.") + inherent = list() + +/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/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 + else + if(owner && owner.mind && owner.mind.special_role) + return + else + zeroth = null + zeroth_borg = null + return + +/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 + +/datum/ai_laws/proc/get_law_list(include_zeroth = 0, show_numbers = 1) + var/list/data = list() + + if (include_zeroth && devillaws && devillaws.len) + for(var/i in devillaws) + data += "[show_numbers ? "666:" : ""] [i]" + + if (include_zeroth && zeroth) + data += "[show_numbers ? "0:" : ""] [zeroth]" + + for(var/law in hacked) + if (length(law) > 0) + var/num = ionnum() + data += "[show_numbers ? "[num]:" : ""] [law]" + + for(var/law in ion) + if (length(law) > 0) + var/num = ionnum() + data += "[show_numbers ? "[num]:" : ""] [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]:" : ""] [law]" + number++ + return data diff --git a/code/datums/browser.dm b/code/datums/browser.dm index 60434d2766..b226d85112 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -1,465 +1,465 @@ -/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, var/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) - stylesheets["[ckey(name)].css"] = file - register_asset("[ckey(name)].css", 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 = 1) - 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, verify=FALSE) - if (scripts.len) - send_asset_list(user, scripts, verify=FALSE) - 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, var/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 = 1) - 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(var/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(var/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(var/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, var/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) + stylesheets["[ckey(name)].css"] = file + register_asset("[ckey(name)].css", 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 = 1) + 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, verify=FALSE) + if (scripts.len) + send_asset_list(user, scripts, verify=FALSE) + 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, var/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 = 1) + 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(var/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(var/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(var/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/_component.dm b/code/datums/components/_component.dm index 72b9681f91..0baf7d7d1b 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -1,314 +1,314 @@ -/datum/component - var/dupe_mode = COMPONENT_DUPE_HIGHLANDER - var/dupe_type - var/datum/parent - //only set to true if you are able to properly transfer this component - //At a minimum RegisterWithParent and UnregisterFromParent should be used - //Make sure you also implement PostTransfer for any post transfer handling - var/can_transfer = FALSE - -/datum/component/New(datum/P, ...) - parent = P - var/list/arguments = args.Copy(2) - if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE) - qdel(src, TRUE, TRUE) - CRASH("Incompatible [type] assigned to a [P.type]! args: [json_encode(arguments)]") - - _JoinParent(P) - -/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() - -// If you want/expect to be moving the component around between parents, use this to register on the parent for signals -/datum/component/proc/RegisterWithParent() - SEND_SIGNAL(src, COMSIG_COMPONENT_REGISTER_PARENT) //CITADEL EDIT - -/datum/component/proc/Initialize(...) - return - -/datum/component/Destroy(force=FALSE, silent=FALSE) - if(!force && parent) - _RemoveFromParent() - if(!silent) - SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src) - parent = null - return ..() - -/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() - -/datum/component/proc/UnregisterFromParent() - SEND_SIGNAL(src, COMSIG_COMPONENT_UNREGISTER_PARENT) //CITADEL EDIT - -/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 - -/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) - 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 - -/datum/component/proc/InheritComponent(datum/component/C, i_am_original) - return - -/datum/component/proc/PreTransfer() - return - -/datum/component/proc/PostTransfer() - return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it - -/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 - -/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 -/datum/proc/GetComponent(datum/component/c_type) - RETURN_TYPE(c_type) - if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED) - 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] - -/datum/proc/GetExactComponent(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 - -/datum/proc/GetComponents(c_type) - var/list/dc = datum_components - if(!dc) - return null - . = dc[c_type] - if(!length(.)) - return list(.) - -/datum/proc/AddComponent(new_type, ...) - 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 - - 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(arglist(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(arglist(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 = args.Copy(2) - old_comp.InheritComponent(null, TRUE, arguments) - else - old_comp.InheritComponent(new_comp, TRUE) - else if(!new_comp) - new_comp = new nt(arglist(args)) // There's a valid dupe mode but there's no old component, act like normal - else if(!new_comp) - new_comp = new nt(arglist(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 - -/datum/proc/LoadComponent(component_type, ...) - . = GetComponent(component_type) - if(!.) - return AddComponent(arglist(args)) - -/datum/component/proc/RemoveComponent() - if(!parent) - return - var/datum/old_parent = parent - PreTransfer() - _RemoveFromParent() - parent = null - SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src) - -/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() - -/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) - -/datum/component/ui_host() - return parent +/datum/component + var/dupe_mode = COMPONENT_DUPE_HIGHLANDER + var/dupe_type + var/datum/parent + //only set to true if you are able to properly transfer this component + //At a minimum RegisterWithParent and UnregisterFromParent should be used + //Make sure you also implement PostTransfer for any post transfer handling + var/can_transfer = FALSE + +/datum/component/New(datum/P, ...) + parent = P + var/list/arguments = args.Copy(2) + if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE) + qdel(src, TRUE, TRUE) + CRASH("Incompatible [type] assigned to a [P.type]! args: [json_encode(arguments)]") + + _JoinParent(P) + +/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() + +// If you want/expect to be moving the component around between parents, use this to register on the parent for signals +/datum/component/proc/RegisterWithParent() + SEND_SIGNAL(src, COMSIG_COMPONENT_REGISTER_PARENT) //CITADEL EDIT + +/datum/component/proc/Initialize(...) + return + +/datum/component/Destroy(force=FALSE, silent=FALSE) + if(!force && parent) + _RemoveFromParent() + if(!silent) + SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src) + parent = null + return ..() + +/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() + +/datum/component/proc/UnregisterFromParent() + SEND_SIGNAL(src, COMSIG_COMPONENT_UNREGISTER_PARENT) //CITADEL EDIT + +/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 + +/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) + 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 + +/datum/component/proc/InheritComponent(datum/component/C, i_am_original) + return + +/datum/component/proc/PreTransfer() + return + +/datum/component/proc/PostTransfer() + return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it + +/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 + +/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 +/datum/proc/GetComponent(datum/component/c_type) + RETURN_TYPE(c_type) + if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED) + 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] + +/datum/proc/GetExactComponent(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 + +/datum/proc/GetComponents(c_type) + var/list/dc = datum_components + if(!dc) + return null + . = dc[c_type] + if(!length(.)) + return list(.) + +/datum/proc/AddComponent(new_type, ...) + 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 + + 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(arglist(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(arglist(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 = args.Copy(2) + old_comp.InheritComponent(null, TRUE, arguments) + else + old_comp.InheritComponent(new_comp, TRUE) + else if(!new_comp) + new_comp = new nt(arglist(args)) // There's a valid dupe mode but there's no old component, act like normal + else if(!new_comp) + new_comp = new nt(arglist(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 + +/datum/proc/LoadComponent(component_type, ...) + . = GetComponent(component_type) + if(!.) + return AddComponent(arglist(args)) + +/datum/component/proc/RemoveComponent() + if(!parent) + return + var/datum/old_parent = parent + PreTransfer() + _RemoveFromParent() + parent = null + SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src) + +/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() + +/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) + +/datum/component/ui_host() + return parent diff --git a/code/datums/components/jousting.dm b/code/datums/components/jousting.dm index 08f3c1d643..da2ff1b177 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.Knockdown(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.Knockdown(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 bbdbb7dc66..045611ab9d 100644 --- a/code/datums/components/lockon_aiming.dm +++ b/code/datums/components/lockon_aiming.dm @@ -1,239 +1,239 @@ -#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() - var/mob/M = parent - LAZYOR(M.mousemove_intercept_objects, src) - START_PROCESSING(SSfastprocess, src) - -/datum/component/lockon_aiming/Destroy() - var/mob/M = parent - clear_visuals() - LAZYREMOVE(M.mousemove_intercept_objects, src) - 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/onMouseMove(object,location,control,params) - var/mob/M = parent - if(!istype(M) || !M.client) - return - var/datum/position/P = mouse_absolute_datum_map_position_from_client(M.client) - if(!P) - return - var/turf/T = P.return_turf() - LAZYINITLIST(last_location) - if(length(last_location) == 3 && last_location[1] == T.x && last_location[2] == T.y && last_location[3] == T.z) - return //Same turf, don't bother. - if(last_location) - last_location.Cut() - else - last_location = list() - last_location.len = 3 - last_location[1] = T.x - last_location[2] = T.y - last_location[3] = T.z - autolock() - -/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() + var/mob/M = parent + LAZYOR(M.mousemove_intercept_objects, src) + START_PROCESSING(SSfastprocess, src) + +/datum/component/lockon_aiming/Destroy() + var/mob/M = parent + clear_visuals() + LAZYREMOVE(M.mousemove_intercept_objects, src) + 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/onMouseMove(object,location,control,params) + var/mob/M = parent + if(!istype(M) || !M.client) + return + var/datum/position/P = mouse_absolute_datum_map_position_from_client(M.client) + if(!P) + return + var/turf/T = P.return_turf() + LAZYINITLIST(last_location) + if(length(last_location) == 3 && last_location[1] == T.x && last_location[2] == T.y && last_location[3] == T.z) + return //Same turf, don't bother. + if(last_location) + last_location.Cut() + else + last_location = list() + last_location.len = 3 + last_location[1] = T.x + last_location[2] = T.y + last_location[3] = T.z + autolock() + +/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/ntnet_interface.dm b/code/datums/components/ntnet_interface.dm index 7c364a61ab..6159c7c2c4 100644 --- a/code/datums/components/ntnet_interface.dm +++ b/code/datums/components/ntnet_interface.dm @@ -1,68 +1,68 @@ -//Thing meant for allowing datums and objects to access a 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! - // Process data before sending it - data.pre_send(src) - - 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 a 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! + // Process data before sending it + data.pre_send(src) + + 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 c05b07a693..0775a725b9 100644 --- a/code/datums/components/riding.dm +++ b/code/datums/components/riding.dm @@ -1,359 +1,359 @@ -/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/list/offhands = list() // keyed list containing all the current riding offsets associated by mob - -/datum/component/riding/Initialize() - if(!ismovableatom(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) - restore_position(M) - unequip_buckle_inhands(M) - -/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 - if((ride_check_rider_restrained && M.restrained(TRUE)) || (ride_check_rider_incapacitated && M.incapacitated(FALSE, TRUE)) || (ride_check_ridden_incapacitated && istype(AMM) && AMM.incapacitated(FALSE, TRUE))) - AM.visible_message("[M] falls off of [AM]!") - AM.unbuckle_mob(M) - return TRUE - -/datum/component/riding/proc/force_dismount(mob/living/M) - var/atom/movable/AM = parent - AM.unbuckle_mob(M) - -/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.change_view(CONFIG_GET(string/default_view)) - -//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 - 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 the keys 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 - -/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) - var/mob/living/carbon/human/H = parent - H.remove_movespeed_modifier(MOVESPEED_ID_HUMAN_CARRYING) - . = ..() - -/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(MOVESPEED_ID_HUMAN_CARRYING, multiplicative_slowdown = HUMAN_CARRY_SLOWDOWN) - -/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.Knockdown(60) - user.visible_message("[AM] pushes [user] off of [AM.p_them()]!") - -/datum/component/riding/cyborg - -/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/cyborg/force_dismount(mob/living/M) - var/atom/movable/AM = parent - AM.unbuckle_mob(M) - var/turf/target = get_edge_target_turf(AM, AM.dir) - var/turf/targetm = get_step(get_turf(AM), AM.dir) - M.Move(targetm) - M.visible_message("[M] is thrown clear of [AM]!") - M.throw_at(target, 14, 5, AM) - M.Knockdown(60) - -/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, mob/living/riding_target_override) - var/list/equipped - var/mob/living/L = riding_target_override ? riding_target_override : user - for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--) - var/obj/item/riding_offhand/inhand = new - inhand.rider = L - inhand.parent = parent - if(!user.put_in_hands(inhand, TRUE)) - qdel(inhand) // it isn't going to be added to offhands anyway - break - LAZYADD(equipped, inhand) - var/amount_equipped = LAZYLEN(equipped) - if(amount_equipped) - LAZYADD(offhands[L], equipped) - if(amount_equipped >= amount_required) - return TRUE - unequip_buckle_inhands(L) - return FALSE - -/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user) - for(var/a in offhands[user]) - LAZYREMOVE(offhands[user], a) - if(a) //edge cases null entries - var/obj/item/riding_offhand/O = a - if(O.parent != parent) - CRASH("RIDING OFFHAND ON WRONG MOB") - else if(!O.selfdeleting) - 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) - . = ..() +/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/list/offhands = list() // keyed list containing all the current riding offsets associated by mob + +/datum/component/riding/Initialize() + if(!ismovableatom(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) + restore_position(M) + unequip_buckle_inhands(M) + +/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 + if((ride_check_rider_restrained && M.restrained(TRUE)) || (ride_check_rider_incapacitated && M.incapacitated(FALSE, TRUE)) || (ride_check_ridden_incapacitated && istype(AMM) && AMM.incapacitated(FALSE, TRUE))) + AM.visible_message("[M] falls off of [AM]!") + AM.unbuckle_mob(M) + return TRUE + +/datum/component/riding/proc/force_dismount(mob/living/M) + var/atom/movable/AM = parent + AM.unbuckle_mob(M) + +/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.change_view(CONFIG_GET(string/default_view)) + +//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 + 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 the keys 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 + +/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) + var/mob/living/carbon/human/H = parent + H.remove_movespeed_modifier(MOVESPEED_ID_HUMAN_CARRYING) + . = ..() + +/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(MOVESPEED_ID_HUMAN_CARRYING, multiplicative_slowdown = HUMAN_CARRY_SLOWDOWN) + +/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.Knockdown(60) + user.visible_message("[AM] pushes [user] off of [AM.p_them()]!") + +/datum/component/riding/cyborg + +/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/cyborg/force_dismount(mob/living/M) + var/atom/movable/AM = parent + AM.unbuckle_mob(M) + var/turf/target = get_edge_target_turf(AM, AM.dir) + var/turf/targetm = get_step(get_turf(AM), AM.dir) + M.Move(targetm) + M.visible_message("[M] is thrown clear of [AM]!") + M.throw_at(target, 14, 5, AM) + M.Knockdown(60) + +/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, mob/living/riding_target_override) + var/list/equipped + var/mob/living/L = riding_target_override ? riding_target_override : user + for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--) + var/obj/item/riding_offhand/inhand = new + inhand.rider = L + inhand.parent = parent + if(!user.put_in_hands(inhand, TRUE)) + qdel(inhand) // it isn't going to be added to offhands anyway + break + LAZYADD(equipped, inhand) + var/amount_equipped = LAZYLEN(equipped) + if(amount_equipped) + LAZYADD(offhands[L], equipped) + if(amount_equipped >= amount_required) + return TRUE + unequip_buckle_inhands(L) + return FALSE + +/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user) + for(var/a in offhands[user]) + LAZYREMOVE(offhands[user], a) + if(a) //edge cases null entries + var/obj/item/riding_offhand/O = a + if(O.parent != parent) + CRASH("RIDING OFFHAND ON WRONG MOB") + else if(!O.selfdeleting) + 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) + . = ..() diff --git a/code/datums/components/slippery.dm b/code/datums/components/slippery.dm index a32a9cab43..6682c3901d 100644 --- a/code/datums/components/slippery.dm +++ b/code/datums/components/slippery.dm @@ -1,15 +1,15 @@ -/datum/component/slippery - var/intensity - var/lube_flags - var/datum/callback/callback - -/datum/component/slippery/Initialize(_intensity, _lube_flags = NONE, datum/callback/_callback) - intensity = max(_intensity, 0) - lube_flags = _lube_flags - callback = _callback - RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip) - -/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM) - var/mob/victim = AM - if(istype(victim) && !victim.is_flying() && victim.slip(intensity, parent, lube_flags) && callback) - callback.Invoke(victim) +/datum/component/slippery + var/intensity + var/lube_flags + var/datum/callback/callback + +/datum/component/slippery/Initialize(_intensity, _lube_flags = NONE, datum/callback/_callback) + intensity = max(_intensity, 0) + lube_flags = _lube_flags + callback = _callback + RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip) + +/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM) + var/mob/victim = AM + if(istype(victim) && !victim.is_flying() && victim.slip(intensity, parent, lube_flags) && callback) + callback.Invoke(victim) diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm index accc8cadaf..4c8f1b4c97 100644 --- a/code/datums/components/storage/concrete/_concrete.dm +++ b/code/datums/components/storage/concrete/_concrete.dm @@ -1,208 +1,208 @@ - -// 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/drop_all_on_break = FALSE - var/unlock_on_break = 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) - RegisterSignal(parent, COMSIG_OBJ_BREAK, .proc/on_break) - -/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 && 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/proc/on_break(datum/source, damage_flag) - if(drop_all_on_break) - do_quick_empty() - if(unlock_on_break) - set_locked(source, FALSE) - -/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) - 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) - 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/drop_all_on_break = FALSE + var/unlock_on_break = 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) + RegisterSignal(parent, COMSIG_OBJ_BREAK, .proc/on_break) + +/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 && 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/proc/on_break(datum/source, damage_flag) + if(drop_all_on_break) + do_quick_empty() + if(unlock_on_break) + set_locked(source, FALSE) + +/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) + 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) + 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 bf97ec6516..8b9ecf5a4e 100644 --- a/code/datums/components/storage/concrete/bag_of_holding.dm +++ b/code/datums/components/storage/concrete/bag_of_holding.dm @@ -1,36 +1,36 @@ -/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]?", "Abort", "Proceed") - if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user))) - return - var/turf/loccheck = get_turf(A) - if(is_reebe(loccheck.z)) - user.visible_message("An unseen force knocks [user] to the ground!", "\"I think not!\"") - user.Knockdown(60) - return - if(istype(loccheck.loc, /area/fabric_of_reality)) - to_chat(user, "You can't do that here!") - to_chat(user, "The Bluespace interfaces of the two devices catastrophically malfunction!") - qdel(W) - playsound(loccheck,'sound/effects/supermatter.ogg', 200, 1) - user.gib(TRUE, TRUE, TRUE) - for(var/turf/T in range(6,loccheck)) - if(istype(T, /turf/open/space/transit)) - continue - for(var/mob/living/M in T) - if(M.movement_type & FLYING) - M.visible_message("The bluespace collapse crushes the air towards it, pulling [M] towards the ground...") - M.Knockdown(5, TRUE, TRUE) //Overrides stun absorbs. - T.TerraformTurf(/turf/open/chasm/magic, /turf/open/chasm/magic) - for (var/obj/structure/ladder/unbreakable/binary/ladder in GLOB.ladders) - ladder.ActivateAlmonds() - 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)].") - 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]?", "Abort", "Proceed") + if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user))) + return + var/turf/loccheck = get_turf(A) + if(is_reebe(loccheck.z)) + user.visible_message("An unseen force knocks [user] to the ground!", "\"I think not!\"") + user.Knockdown(60) + return + if(istype(loccheck.loc, /area/fabric_of_reality)) + to_chat(user, "You can't do that here!") + to_chat(user, "The Bluespace interfaces of the two devices catastrophically malfunction!") + qdel(W) + playsound(loccheck,'sound/effects/supermatter.ogg', 200, 1) + user.gib(TRUE, TRUE, TRUE) + for(var/turf/T in range(6,loccheck)) + if(istype(T, /turf/open/space/transit)) + continue + for(var/mob/living/M in T) + if(M.movement_type & FLYING) + M.visible_message("The bluespace collapse crushes the air towards it, pulling [M] towards the ground...") + M.Knockdown(5, TRUE, TRUE) //Overrides stun absorbs. + T.TerraformTurf(/turf/open/chasm/magic, /turf/open/chasm/magic) + for (var/obj/structure/ladder/unbreakable/binary/ladder in GLOB.ladders) + ladder.ActivateAlmonds() + 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)].") + qdel(A) + return + . = ..() diff --git a/code/datums/components/storage/concrete/implant.dm b/code/datums/components/storage/concrete/implant.dm index 0348d340ae..5ee4c13c24 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() - . = ..() - cant_hold = typecacheof(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() + . = ..() + cant_hold = typecacheof(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/rped.dm b/code/datums/components/storage/concrete/rped.dm index e1cc4ff6ae..2f95466238 100644 --- a/code/datums/components/storage/concrete/rped.dm +++ b/code/datums/components/storage/concrete/rped.dm @@ -1,36 +1,36 @@ -/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 = 100 - 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 = 350 - 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/cyborg/rped - max_items = 150 +/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 = 100 + 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 = 350 + 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/cyborg/rped + max_items = 150 diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 76a9b2e6aa..55e5c03c2b 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -1,800 +1,800 @@ -#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 things in this typecache will fit. - var/list/cant_hold //if this is set, anything in this typecache will not be able to fit. - - 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 - - var/limited_random_access = FALSE //Quick if statement in accessible_items to determine if we care at all about what people can access at once. - var/limited_random_access_stack_position = 0 //If >0, can only access top items - var/limited_random_access_stack_bottom_up = FALSE //If TRUE, above becomes bottom items - -/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_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/check_views) - - 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/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 - -//What players can access -//this proc can probably eat a refactor at some point. -/datum/component/storage/proc/accessible_items(random_access = TRUE) - var/list/contents = contents() - if(contents) - if(limited_random_access && random_access) - if(limited_random_access_stack_position && (length(contents) > limited_random_access_stack_position)) - if(limited_random_access_stack_bottom_up) - contents.Cut(1, limited_random_access_stack_position + 1) - else - contents.Cut(1, length(contents) - limited_random_access_stack_position + 1) - return contents - -/datum/component/storage/proc/canreach_react(datum/source, list/next) - var/datum/component/storage/concrete/master = master() - if(!master) - return - . = COMPONENT_BLOCK_REACH - next += master.parent - for(var/i in master.slaves) - var/datum/component/storage/slave = i - next += slave.parent - -/datum/component/storage/proc/attack_self(datum/source, mob/M) - if(check_locked(source, M, TRUE)) - 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(check_locked(source, M, TRUE)) - return FALSE - var/atom/A = parent - var/obj/item/I = O - if(collection_mode == COLLECT_ONE) - if(can_be_inserted(I, null, M)) - handle_item_insertion(I, null, M) - A.do_squish() - 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) - qdel(progress) - to_chat(M, "You put everything you could [insert_preposition] [parent].") - A.do_squish(1.4, 0.4) - -/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(check_locked(null, M, TRUE)) - 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) - qdel(progress) - A.do_squish(0.8, 1.2) - -/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) - 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(check_locked()) - close_all() - -/datum/component/storage/proc/_process_numerical_display() - . = list() - for(var/obj/item/I in accessible_items()) - if(QDELETED(I)) - continue - if(!.[I.type]) - .[I.type] = new /datum/numbered_display(I, 1) - else - var/datum/numbered_display/ND = .[I.type] - ND.number++ - . = sortTim(., /proc/cmp_numbered_displays_name_asc, associative = TRUE) - -//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(mob/user, maxcolumns) - var/list/accessible_contents = accessible_items() - var/adjusted_contents = length(accessible_contents) - - //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, maxcolumns ? maxcolumns : 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 - for(var/obj/O in accessible_items()) - 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/list/cview = getviewsize(M.client.view) - var/maxallowedscreensize = cview[1]-8 - if(M.active_storage != src && (M.stat == CONSCIOUS)) - for(var/obj/item/I in accessible_items()) - if(I.on_found(M)) - return FALSE - if(M.active_storage) - M.active_storage.hide_from(M) - orient2hud(M, (isliving(M) ? maxallowedscreensize : 7)) - M.client.screen |= boxes - M.client.screen |= closer - M.client.screen |= accessible_items() - 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/check_views() - for(var/mob/M in can_see_contents()) - if(!isobserver(M) && !M.CanReach(src, view_only = TRUE)) - close(M) - -/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(check_locked(null, M, TRUE)) - return FALSE - if(dump_destination.storage_contents_dump_act(src, M)) - playsound(A, "rustle", 50, 1, -5) - A.do_squish(0.8, 1.2) - 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) - var/atom/A = parent - A.do_squish() - -/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/mousedrop_onto(datum/source, atom/over_object, mob/M) - set waitfor = FALSE - . = COMPONENT_NO_MOUSEDROP - var/atom/A = parent - if(ismob(M)) //all the check for item manipulation are in other places, you can safely open any storages as anything and its not buggy, i checked - A.add_fingerprint(M) - if(!over_object) - return FALSE - if(ismecha(M.loc)) // stops inventory actions in a mech - return FALSE - // this must come before the screen objects only block, dunno why it wasn't before - if(over_object == M) - user_show_to_mob(M) - if(!M.incapacitated()) - if(!istype(over_object, /obj/screen)) - dump_content_at(over_object, M) - return - if(A.loc != M) - return - playsound(A, "rustle", 50, 1, -5) - A.do_jiggle() - 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(!force && (check_locked(null, M) || !M.CanReach(parent, view_only = TRUE))) - return FALSE - 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) - var/atom/A = parent - A.do_squish() - -//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(check_locked(null, M, !stop_messages)) - if(M && !stop_messages) - host.add_fingerprint(M) - 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)) //Check for specific 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) - if(!stop_messages) - to_chat(M, "[I] is too big for [host]!") - return FALSE - var/sum_w_class = I.w_class - for(var/obj/item/_I in real_location) - sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. - if(sum_w_class > max_combined_w_class) - if(!stop_messages) - to_chat(M, "[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. - 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 stop_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, -//such as when picking up all the items on a tile with one click. -/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) - var/atom/parent = src.parent - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - if(silent) - prevent_warning = TRUE - if(M) - parent.add_fingerprint(M) - . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) - -/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) - if(silent && !override) - return - if(rustle_sound) - playsound(parent, "rustle", 50, 1, -5) - for(var/mob/viewing in viewers(user, null)) - if(M == viewing) - to_chat(usr, "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(datum/source, mob/user, message = FALSE) - . = locked - if(message && . && user) - to_chat(user, "[parent] seems to be locked!") - -/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) - if(!force) - if(check_adjacent) - if(!user || !user.CanReach(destination) || !user.CanReach(parent)) - return FALSE - var/list/taking = typecache_filter_list(contents(), typecacheof(type)) - if(taking.len > amount) - taking.len = amount - if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. - for(var/i in taking) - if(remove_from_storage(i, destination)) - inserted |= i - else - for(var/i in taking) - remove_from_storage(i, destination) - return TRUE - -/datum/component/storage/proc/remaining_space_items() - var/atom/real_location = real_location() - return max(0, max_items - real_location.contents.len) - -/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE) - var/atom/real_location = real_location() - if(!force) - amount = min(remaining_space_items(), amount) - for(var/i in 1 to amount) - handle_item_insertion(new type(real_location), TRUE) - CHECK_TICK - return TRUE - -/datum/component/storage/proc/on_attack_hand(datum/source, mob/user) - var/atom/A = parent - if(!attack_hand_interact) - return - if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item - user.active_storage.close(user) - close(user) - . = COMPONENT_NO_ATTACK_HAND - return - - if(rustle_sound) - playsound(A, "rustle", 50, 1, -5) - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket. - . = COMPONENT_NO_ATTACK_HAND - H.put_in_hands(A) - H.l_store = null - return - if(H.r_store == A && !H.get_active_held_item()) - . = COMPONENT_NO_ATTACK_HAND - H.put_in_hands(A) - H.r_store = null - return - - if(A.loc == user) - . = COMPONENT_NO_ATTACK_HAND - if(!check_locked(source, user, TRUE)) - show_to(user) - A.do_jiggle() - -/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user) - var/atom/A = parent - update_actions() - for(var/mob/M in range(1, A)) - if(M.active_storage == src) - close(M) - -/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE) - if(!(AM in real_location())) - return FALSE - return remove_from_storage(AM, new_loc) - -/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget) - return do_quick_empty(loctarget) - -/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target) - return hide_from(target) - -/datum/component/storage/proc/on_alt_click(datum/source, mob/user) - if(!isliving(user) || !user.CanReach(parent)) - return - if(check_locked(source, user, TRUE)) - return TRUE - - var/atom/A = parent - if(!quickdraw) - A.add_fingerprint(user) - user_show_to_mob(user) - if(rustle_sound) - playsound(A, "rustle", 50, 1, -5) - return TRUE - - if(user.can_hold_items() && !user.incapacitated()) - var/obj/item/I = locate() in real_location() - if(!I) - return - A.add_fingerprint(user) - remove_from_storage(I, get_turf(user)) - if(!user.put_in_hands(I)) - user.visible_message("[user] fumbles with the [parent], letting [I] fall on the floor.", \ - "You fumble with [parent], letting [I] fall on the floor.") - return TRUE - user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") - return TRUE - -/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 things in this typecache will fit. + var/list/cant_hold //if this is set, anything in this typecache will not be able to fit. + + 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 + + var/limited_random_access = FALSE //Quick if statement in accessible_items to determine if we care at all about what people can access at once. + var/limited_random_access_stack_position = 0 //If >0, can only access top items + var/limited_random_access_stack_bottom_up = FALSE //If TRUE, above becomes bottom items + +/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_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/check_views) + + 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/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 + +//What players can access +//this proc can probably eat a refactor at some point. +/datum/component/storage/proc/accessible_items(random_access = TRUE) + var/list/contents = contents() + if(contents) + if(limited_random_access && random_access) + if(limited_random_access_stack_position && (length(contents) > limited_random_access_stack_position)) + if(limited_random_access_stack_bottom_up) + contents.Cut(1, limited_random_access_stack_position + 1) + else + contents.Cut(1, length(contents) - limited_random_access_stack_position + 1) + return contents + +/datum/component/storage/proc/canreach_react(datum/source, list/next) + var/datum/component/storage/concrete/master = master() + if(!master) + return + . = COMPONENT_BLOCK_REACH + next += master.parent + for(var/i in master.slaves) + var/datum/component/storage/slave = i + next += slave.parent + +/datum/component/storage/proc/attack_self(datum/source, mob/M) + if(check_locked(source, M, TRUE)) + 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(check_locked(source, M, TRUE)) + return FALSE + var/atom/A = parent + var/obj/item/I = O + if(collection_mode == COLLECT_ONE) + if(can_be_inserted(I, null, M)) + handle_item_insertion(I, null, M) + A.do_squish() + 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) + qdel(progress) + to_chat(M, "You put everything you could [insert_preposition] [parent].") + A.do_squish(1.4, 0.4) + +/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(check_locked(null, M, TRUE)) + 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) + qdel(progress) + A.do_squish(0.8, 1.2) + +/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) + 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(check_locked()) + close_all() + +/datum/component/storage/proc/_process_numerical_display() + . = list() + for(var/obj/item/I in accessible_items()) + if(QDELETED(I)) + continue + if(!.[I.type]) + .[I.type] = new /datum/numbered_display(I, 1) + else + var/datum/numbered_display/ND = .[I.type] + ND.number++ + . = sortTim(., /proc/cmp_numbered_displays_name_asc, associative = TRUE) + +//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(mob/user, maxcolumns) + var/list/accessible_contents = accessible_items() + var/adjusted_contents = length(accessible_contents) + + //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, maxcolumns ? maxcolumns : 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 + for(var/obj/O in accessible_items()) + 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/list/cview = getviewsize(M.client.view) + var/maxallowedscreensize = cview[1]-8 + if(M.active_storage != src && (M.stat == CONSCIOUS)) + for(var/obj/item/I in accessible_items()) + if(I.on_found(M)) + return FALSE + if(M.active_storage) + M.active_storage.hide_from(M) + orient2hud(M, (isliving(M) ? maxallowedscreensize : 7)) + M.client.screen |= boxes + M.client.screen |= closer + M.client.screen |= accessible_items() + 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/check_views() + for(var/mob/M in can_see_contents()) + if(!isobserver(M) && !M.CanReach(src, view_only = TRUE)) + close(M) + +/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(check_locked(null, M, TRUE)) + return FALSE + if(dump_destination.storage_contents_dump_act(src, M)) + playsound(A, "rustle", 50, 1, -5) + A.do_squish(0.8, 1.2) + 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) + var/atom/A = parent + A.do_squish() + +/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/mousedrop_onto(datum/source, atom/over_object, mob/M) + set waitfor = FALSE + . = COMPONENT_NO_MOUSEDROP + var/atom/A = parent + if(ismob(M)) //all the check for item manipulation are in other places, you can safely open any storages as anything and its not buggy, i checked + A.add_fingerprint(M) + if(!over_object) + return FALSE + if(ismecha(M.loc)) // stops inventory actions in a mech + return FALSE + // this must come before the screen objects only block, dunno why it wasn't before + if(over_object == M) + user_show_to_mob(M) + if(!M.incapacitated()) + if(!istype(over_object, /obj/screen)) + dump_content_at(over_object, M) + return + if(A.loc != M) + return + playsound(A, "rustle", 50, 1, -5) + A.do_jiggle() + 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(!force && (check_locked(null, M) || !M.CanReach(parent, view_only = TRUE))) + return FALSE + 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) + var/atom/A = parent + A.do_squish() + +//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(check_locked(null, M, !stop_messages)) + if(M && !stop_messages) + host.add_fingerprint(M) + 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)) //Check for specific 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) + if(!stop_messages) + to_chat(M, "[I] is too big for [host]!") + return FALSE + var/sum_w_class = I.w_class + for(var/obj/item/_I in real_location) + sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. + if(sum_w_class > max_combined_w_class) + if(!stop_messages) + to_chat(M, "[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. + 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 stop_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, +//such as when picking up all the items on a tile with one click. +/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) + var/atom/parent = src.parent + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + if(silent) + prevent_warning = TRUE + if(M) + parent.add_fingerprint(M) + . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) + +/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) + if(silent && !override) + return + if(rustle_sound) + playsound(parent, "rustle", 50, 1, -5) + for(var/mob/viewing in viewers(user, null)) + if(M == viewing) + to_chat(usr, "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(datum/source, mob/user, message = FALSE) + . = locked + if(message && . && user) + to_chat(user, "[parent] seems to be locked!") + +/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) + if(!force) + if(check_adjacent) + if(!user || !user.CanReach(destination) || !user.CanReach(parent)) + return FALSE + var/list/taking = typecache_filter_list(contents(), typecacheof(type)) + if(taking.len > amount) + taking.len = amount + if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. + for(var/i in taking) + if(remove_from_storage(i, destination)) + inserted |= i + else + for(var/i in taking) + remove_from_storage(i, destination) + return TRUE + +/datum/component/storage/proc/remaining_space_items() + var/atom/real_location = real_location() + return max(0, max_items - real_location.contents.len) + +/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE) + var/atom/real_location = real_location() + if(!force) + amount = min(remaining_space_items(), amount) + for(var/i in 1 to amount) + handle_item_insertion(new type(real_location), TRUE) + CHECK_TICK + return TRUE + +/datum/component/storage/proc/on_attack_hand(datum/source, mob/user) + var/atom/A = parent + if(!attack_hand_interact) + return + if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item + user.active_storage.close(user) + close(user) + . = COMPONENT_NO_ATTACK_HAND + return + + if(rustle_sound) + playsound(A, "rustle", 50, 1, -5) + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket. + . = COMPONENT_NO_ATTACK_HAND + H.put_in_hands(A) + H.l_store = null + return + if(H.r_store == A && !H.get_active_held_item()) + . = COMPONENT_NO_ATTACK_HAND + H.put_in_hands(A) + H.r_store = null + return + + if(A.loc == user) + . = COMPONENT_NO_ATTACK_HAND + if(!check_locked(source, user, TRUE)) + show_to(user) + A.do_jiggle() + +/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user) + var/atom/A = parent + update_actions() + for(var/mob/M in range(1, A)) + if(M.active_storage == src) + close(M) + +/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE) + if(!(AM in real_location())) + return FALSE + return remove_from_storage(AM, new_loc) + +/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget) + return do_quick_empty(loctarget) + +/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target) + return hide_from(target) + +/datum/component/storage/proc/on_alt_click(datum/source, mob/user) + if(!isliving(user) || !user.CanReach(parent)) + return + if(check_locked(source, user, TRUE)) + return TRUE + + var/atom/A = parent + if(!quickdraw) + A.add_fingerprint(user) + user_show_to_mob(user) + if(rustle_sound) + playsound(A, "rustle", 50, 1, -5) + return TRUE + + if(user.can_hold_items() && !user.incapacitated()) + var/obj/item/I = locate() in real_location() + if(!I) + return + A.add_fingerprint(user) + remove_from_storage(I, get_turf(user)) + if(!user.put_in_hands(I)) + user.visible_message("[user] fumbles with the [parent], letting [I] fall on the floor.", \ + "You fumble with [parent], letting [I] fall on the floor.") + return TRUE + user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") + return TRUE + +/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 d6c5c0bf83..550ff95324 100644 --- a/code/datums/components/wet_floor.dm +++ b/code/datums/components/wet_floor.dm @@ -1,211 +1,211 @@ -/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, argslist) - if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component - add_wet(arglist(argslist)) - 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!") - return - 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 - - var/datum/component/slippery/S = parent.LoadComponent(/datum/component/slippery, NONE, CALLBACK(src, .proc/AfterSlip)) - S.intensity = intensity - S.lube_flags = lube_flags - -/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, argslist) + if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component + add_wet(arglist(argslist)) + 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!") + return + 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 + + var/datum/component/slippery/S = parent.LoadComponent(/datum/component/slippery, NONE, CALLBACK(src, .proc/AfterSlip)) + S.intensity = intensity + S.lube_flags = lube_flags + +/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 cf33fa3633..ebfba43852 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -1,296 +1,296 @@ - -/datum/datacore - var/medical[] = list() - var/medicalPrintCount = 0 - var/general[] = list() - var/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/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/dataId = 0 - -/datum/datacore/proc/createCrimeEntry(cname = "", cdetails = "", author = "", time = "") - var/datum/data/crime/c = new /datum/data/crime - c.crimeName = cname - c.crimeDetails = cdetails - c.author = author - c.time = time - c.dataId = ++securityCrimeCounter - return c - -/datum/datacore/proc/addMinorCrime(id = "", datum/data/crime/crime) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["mi_crim"] - crimes |= crime - return - -/datum/datacore/proc/removeMinorCrime(id, cDataId) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["mi_crim"] - for(var/datum/data/crime/crime in crimes) - if(crime.dataId == text2num(cDataId)) - crimes -= crime - return - -/datum/datacore/proc/removeMajorCrime(id, cDataId) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["ma_crim"] - for(var/datum/data/crime/crime in crimes) - if(crime.dataId == text2num(cDataId)) - crimes -= crime - return - -/datum/datacore/proc/addMajorCrime(id = "", datum/data/crime/crime) - for(var/datum/data/record/R in security) - if(R.fields["id"] == id) - var/list/crimes = R.fields["ma_crim"] - crimes |= crime - return - -/datum/datacore/proc/manifest() - for(var/mob/dead/new_player/N in GLOB.player_list) - 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(monochrome, OOC) - var/list/heads = list() - var/list/sec = list() - var/list/eng = list() - var/list/med = list() - var/list/sci = list() - var/list/sup = list() - var/list/civ = list() - var/list/bot = list() - var/list/misc = list() - var/dat = {" - - - - "} - var/even = 0 - // sort mobs - for(var/datum/data/record/t in GLOB.data_core.general) - var/name = t.fields["name"] - var/rank = t.fields["rank"] - var/department = 0 - if(rank in GLOB.command_positions) - heads[name] = rank - department = 1 - if(rank in GLOB.security_positions) - sec[name] = rank - department = 1 - if(rank in GLOB.engineering_positions) - eng[name] = rank - department = 1 - if(rank in GLOB.medical_positions) - med[name] = rank - department = 1 - if(rank in GLOB.science_positions) - sci[name] = rank - department = 1 - if(rank in GLOB.supply_positions) - sup[name] = rank - department = 1 - if(rank in GLOB.civilian_positions) - civ[name] = rank - department = 1 - if(rank in GLOB.nonhuman_positions) - bot[name] = rank - department = 1 - if(!department && !(name in heads)) - misc[name] = rank - if(heads.len > 0) - dat += "" - for(var/name in heads) - dat += "" - even = !even - if(sec.len > 0) - dat += "" - for(var/name in sec) - dat += "" - even = !even - if(eng.len > 0) - dat += "" - for(var/name in eng) - dat += "" - even = !even - if(med.len > 0) - dat += "" - for(var/name in med) - dat += "" - even = !even - if(sci.len > 0) - dat += "" - for(var/name in sci) - dat += "" - even = !even - if(sup.len > 0) - dat += "" - for(var/name in sup) - dat += "" - even = !even - if(civ.len > 0) - dat += "" - for(var/name in civ) - dat += "" - even = !even - // in case somebody is insane and added them to the manifest, why not - if(bot.len > 0) - dat += "" - for(var/name in bot) - dat += "" - even = !even - // misc guys - if(misc.len > 0) - dat += "" - for(var/name in misc) - dat += "" - even = !even - - dat += "
    NameRank
    Heads
    [name][heads[name]]
    Security
    [name][sec[name]]
    Engineering
    [name][eng[name]]
    Medical
    [name][med[name]]
    Science
    [name][sci[name]]
    Supply
    [name][sup[name]]
    Civilian
    [name][civ[name]]
    Silicon
    [name][bot[name]]
    Miscellaneous
    [name][misc[name]]
    " - 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["sex"] = H.gender - 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"] = "None" - M.fields["mi_dis_d"] = "No minor disabilities have been declared." - M.fields["ma_dis"] = "None" - M.fields["ma_dis_d"] = "No major disabilities have been diagnosed." - M.fields["alg"] = "None" - M.fields["alg_d"] = "No allergies have been detected in this patient." - M.fields["cdi"] = "None" - M.fields["cdi_d"] = "No diseases have been diagnosed at the moment." - M.fields["notes"] = H.get_trait_string(medical) - 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["mi_crim"] = list() - S.fields["ma_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["sex"] = H.gender - L.fields["blood_type"] = H.dna.blood_type - L.fields["b_dna"] = H.dna.unique_enzymes - L.fields["enzymes"] = H.dna.struc_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) + +/datum/datacore + var/medical[] = list() + var/medicalPrintCount = 0 + var/general[] = list() + var/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/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/dataId = 0 + +/datum/datacore/proc/createCrimeEntry(cname = "", cdetails = "", author = "", time = "") + var/datum/data/crime/c = new /datum/data/crime + c.crimeName = cname + c.crimeDetails = cdetails + c.author = author + c.time = time + c.dataId = ++securityCrimeCounter + return c + +/datum/datacore/proc/addMinorCrime(id = "", datum/data/crime/crime) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["mi_crim"] + crimes |= crime + return + +/datum/datacore/proc/removeMinorCrime(id, cDataId) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["mi_crim"] + for(var/datum/data/crime/crime in crimes) + if(crime.dataId == text2num(cDataId)) + crimes -= crime + return + +/datum/datacore/proc/removeMajorCrime(id, cDataId) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["ma_crim"] + for(var/datum/data/crime/crime in crimes) + if(crime.dataId == text2num(cDataId)) + crimes -= crime + return + +/datum/datacore/proc/addMajorCrime(id = "", datum/data/crime/crime) + for(var/datum/data/record/R in security) + if(R.fields["id"] == id) + var/list/crimes = R.fields["ma_crim"] + crimes |= crime + return + +/datum/datacore/proc/manifest() + for(var/mob/dead/new_player/N in GLOB.player_list) + 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(monochrome, OOC) + var/list/heads = list() + var/list/sec = list() + var/list/eng = list() + var/list/med = list() + var/list/sci = list() + var/list/sup = list() + var/list/civ = list() + var/list/bot = list() + var/list/misc = list() + var/dat = {" + + + + "} + var/even = 0 + // sort mobs + for(var/datum/data/record/t in GLOB.data_core.general) + var/name = t.fields["name"] + var/rank = t.fields["rank"] + var/department = 0 + if(rank in GLOB.command_positions) + heads[name] = rank + department = 1 + if(rank in GLOB.security_positions) + sec[name] = rank + department = 1 + if(rank in GLOB.engineering_positions) + eng[name] = rank + department = 1 + if(rank in GLOB.medical_positions) + med[name] = rank + department = 1 + if(rank in GLOB.science_positions) + sci[name] = rank + department = 1 + if(rank in GLOB.supply_positions) + sup[name] = rank + department = 1 + if(rank in GLOB.civilian_positions) + civ[name] = rank + department = 1 + if(rank in GLOB.nonhuman_positions) + bot[name] = rank + department = 1 + if(!department && !(name in heads)) + misc[name] = rank + if(heads.len > 0) + dat += "" + for(var/name in heads) + dat += "" + even = !even + if(sec.len > 0) + dat += "" + for(var/name in sec) + dat += "" + even = !even + if(eng.len > 0) + dat += "" + for(var/name in eng) + dat += "" + even = !even + if(med.len > 0) + dat += "" + for(var/name in med) + dat += "" + even = !even + if(sci.len > 0) + dat += "" + for(var/name in sci) + dat += "" + even = !even + if(sup.len > 0) + dat += "" + for(var/name in sup) + dat += "" + even = !even + if(civ.len > 0) + dat += "" + for(var/name in civ) + dat += "" + even = !even + // in case somebody is insane and added them to the manifest, why not + if(bot.len > 0) + dat += "" + for(var/name in bot) + dat += "" + even = !even + // misc guys + if(misc.len > 0) + dat += "" + for(var/name in misc) + dat += "" + even = !even + + dat += "
    NameRank
    Heads
    [name][heads[name]]
    Security
    [name][sec[name]]
    Engineering
    [name][eng[name]]
    Medical
    [name][med[name]]
    Science
    [name][sci[name]]
    Supply
    [name][sup[name]]
    Civilian
    [name][civ[name]]
    Silicon
    [name][bot[name]]
    Miscellaneous
    [name][misc[name]]
    " + 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["sex"] = H.gender + 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"] = "None" + M.fields["mi_dis_d"] = "No minor disabilities have been declared." + M.fields["ma_dis"] = "None" + M.fields["ma_dis_d"] = "No major disabilities have been diagnosed." + M.fields["alg"] = "None" + M.fields["alg_d"] = "No allergies have been detected in this patient." + M.fields["cdi"] = "None" + M.fields["cdi_d"] = "No diseases have been diagnosed at the moment." + M.fields["notes"] = H.get_trait_string(medical) + 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["mi_crim"] = list() + S.fields["ma_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["sex"] = H.gender + L.fields["blood_type"] = H.dna.blood_type + L.fields["b_dna"] = H.dna.unique_enzymes + L.fields["enzymes"] = H.dna.struc_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 8737cd8feb..34e4e7ac14 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -1,160 +1,160 @@ -/datum - var/gc_destroyed //Time when this object was destroyed. - var/list/active_timers //for SStimer - var/list/datum_components //for /datum/components - var/list/status_traits - var/list/comp_lookup //it used to be for looking up components which had registered a signal but now anything can register - var/list/list/datum/callback/signal_procs - var/signal_enabled = FALSE - var/datum_flags = NONE - var/datum/weakref/weak_reference - -#ifdef TESTING - var/running_find_references - var/last_find_references = 0 -#endif - -#ifdef DATUMVAR_DEBUGGING_MODE - var/list/cached_vars -#endif - -// Default implementation of clean-up code. -// This should be overridden to remove all references pointing to the object being destroyed. -// Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE. -/datum/proc/Destroy(force=FALSE, ...) - 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 - -/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) - -/proc/json_deserialize_datum(list/jsonlist, list/options, target_type, strict_target_type = FALSE) - if(!islist(jsonlist)) - if(!istext(jsonlist)) - CRASH("Invalid JSON") - return - jsonlist = json_decode(jsonlist) - if(!islist(jsonlist)) - CRASH("Invalid JSON") - return - 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 +/datum + var/gc_destroyed //Time when this object was destroyed. + var/list/active_timers //for SStimer + var/list/datum_components //for /datum/components + var/list/status_traits + var/list/comp_lookup //it used to be for looking up components which had registered a signal but now anything can register + var/list/list/datum/callback/signal_procs + var/signal_enabled = FALSE + var/datum_flags = NONE + var/datum/weakref/weak_reference + +#ifdef TESTING + var/running_find_references + var/last_find_references = 0 +#endif + +#ifdef DATUMVAR_DEBUGGING_MODE + var/list/cached_vars +#endif + +// Default implementation of clean-up code. +// This should be overridden to remove all references pointing to the object being destroyed. +// Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE. +/datum/proc/Destroy(force=FALSE, ...) + 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 + +/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) + +/proc/json_deserialize_datum(list/jsonlist, list/options, target_type, strict_target_type = FALSE) + if(!islist(jsonlist)) + if(!istext(jsonlist)) + CRASH("Invalid JSON") + return + jsonlist = json_decode(jsonlist) + if(!islist(jsonlist)) + CRASH("Invalid JSON") + return + 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 diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm index dfaa6f061d..5caa491e4c 100644 --- a/code/datums/datumvars.dm +++ b/code/datums/datumvars.dm @@ -1,1430 +1,1430 @@ -#define VV_MSG_MARKED "
    Marked Object" -#define VV_MSG_EDITED "
    Var Edited" -#define VV_MSG_DELETED "
    Deleted" - -/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) - -//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() - . += "---" - .["Call Proc"] = "?_src_=vars;[HrefToken()];proc_call=[REF(src)]" - .["Mark Object"] = "?_src_=vars;[HrefToken()];mark_object=[REF(src)]" - .["Delete"] = "?_src_=vars;[HrefToken()];delete=[REF(src)]" - .["Show VV To Player"] = "?_src_=vars;[HrefToken(TRUE)];expose=[REF(src)]" - - -/datum/proc/on_reagent_change(changetype) - return - - -/client/proc/debug_variables(datum/D in world) - set category = "Debug" - set name = "View Variables" - //set src in world - var/static/cookieoffset = rand(1, 9999) //to force cookies to reset after the round. - - if(!usr.client || !usr.client.holder) //The usr vs src abuse in this proc is intentional and must not be changed - to_chat(usr, "You need to be an administrator to access this.") - return - - if(!D) - return - - var/islist = islist(D) - if (!islist && !istype(D)) - return - - var/title = "" - var/refid = REF(D) - var/icon/sprite - var/hash - - var/type = /list - if (!islist) - type = D.type - - - - if(istype(D, /atom)) - var/atom/AT = D - if(AT.icon && AT.icon_state) - sprite = new /icon(AT.icon, AT.icon_state) - hash = md5(AT.icon) - hash = md5(hash + AT.icon_state) - src << browse_rsc(sprite, "vv[hash].png") - - title = "[D] ([REF(D)]) = [type]" - var/formatted_type = replacetext("[type]", "/", "/") - - var/sprite_text - if(sprite) - sprite_text = "" - var/list/atomsnowflake = list() - - if(istype(D, /atom)) - var/atom/A = D - if(isliving(A)) - atomsnowflake += "[D]" - atomsnowflake += "
    << [dir2text(A.dir) || A.dir] >>" - var/mob/living/M = A - atomsnowflake += {" -
    [M.ckey || "No ckey"] / [M.real_name || "No real name"] -
    - BRUTE:[M.getBruteLoss()] - FIRE:[M.getFireLoss()] - TOXIN:[M.getToxLoss()] - OXY:[M.getOxyLoss()] - CLONE:[M.getCloneLoss()] - BRAIN:[M.getOrganLoss(ORGAN_SLOT_BRAIN)] - STAMINA:[M.getStaminaLoss()] - AROUSAL:[M.getArousalLoss()] - "} - if(GLOB.Debug2) - atomsnowflake += {" - HEART:[M.getOrganLoss(ORGAN_SLOT_HEART)] - LIVER:[M.getOrganLoss(ORGAN_SLOT_LIVER)] - LUNGS:[M.getOrganLoss(ORGAN_SLOT_LUNGS)] - EYES:[M.getOrganLoss(ORGAN_SLOT_EYES)] - EARS:[M.getOrganLoss(ORGAN_SLOT_EARS)] - STOMACH:[M.getOrganLoss(ORGAN_SLOT_STOMACH)] - TONGUE:[M.getOrganLoss(ORGAN_SLOT_TONGUE)] - APPENDIX:[M.getOrganLoss(ORGAN_SLOT_APPENDIX)] - "} - atomsnowflake += {" - - "} - else - atomsnowflake += "[D]" - atomsnowflake += "
    << [dir2text(A.dir) || A.dir] >>" - else if("name" in D.vars) - atomsnowflake += "[D]" - else - atomsnowflake += "[formatted_type]" - formatted_type = null - - var/marked - if(holder && holder.marked_datum && holder.marked_datum == D) - marked = VV_MSG_MARKED - var/varedited_line = "" - if(!islist && (D.datum_flags & DF_VAR_EDITED)) - varedited_line = VV_MSG_EDITED - var/deleted_line - if(!islist && D.gc_destroyed) - deleted_line = VV_MSG_DELETED - - var/list/dropdownoptions = list() - if (islist) - dropdownoptions = list( - "---", - "Add Item" = "?_src_=vars;[HrefToken()];listadd=[refid]", - "Remove Nulls" = "?_src_=vars;[HrefToken()];listnulls=[refid]", - "Remove Dupes" = "?_src_=vars;[HrefToken()];listdupes=[refid]", - "Set len" = "?_src_=vars;[HrefToken()];listlen=[refid]", - "Shuffle" = "?_src_=vars;[HrefToken()];listshuffle=[refid]", - "Show VV To Player" = "?_src_=vars;[HrefToken()];expose=[refid]" - ) - else - dropdownoptions = D.vv_get_dropdown() - var/list/dropdownoptions_html = list() - - for (var/name in dropdownoptions) - var/link = dropdownoptions[name] - if (link) - dropdownoptions_html += "" - else - dropdownoptions_html += "" - - var/list/names = list() - if (!islist) - for (var/V in D.vars) - names += V - sleep(1)//For some reason, without this sleep, VVing will cause client to disconnect on certain objects. - - var/list/variable_html = list() - if (islist) - var/list/L = D - for (var/i in 1 to L.len) - var/key = L[i] - var/value - if (IS_NORMAL_LIST(L) && !isnum(key)) - value = L[key] - variable_html += debug_variable(i, value, 0, D) - else - - names = sortList(names) - for (var/V in names) - if(D.can_vv_get(V)) - variable_html += D.vv_get_var(V) - - var/html = {" - - - [title] - - - - -
    - - - - - -
    - - - - -
    - [sprite_text] -
    - [atomsnowflake.Join()] -
    -
    -
    - [formatted_type] - [marked] - [varedited_line] - [deleted_line] -
    -
    -
    - Refresh -
    - -
    -
    -
    -
    -
    - - E - Edit, tries to determine the variable type by itself.
    - C - Change, asks you for the var type first.
    - M - Mass modify: changes this variable for all objects of this type.
    -
    -
    - - - - - -
    -
    - Search: -
    -
    - -
    -
    -
      - [variable_html.Join()] -
    - - - -"} - src << browse(html, "window=variables[refid];size=475x650") - - -/client/proc/vv_update_display(datum/D, span, content) - src << output("[span]:[content]", "variables[REF(D)].browser:replace_span") - - -#define VV_HTML_ENCODE(thing) ( sanitize ? html_encode(thing) : thing ) -/proc/debug_variable(name, value, level, datum/DA = null, sanitize = TRUE) - var/header - if(DA) - if (islist(DA)) - var/index = name - if (value) - name = DA[name] //name is really the index until this line - else - value = DA[name] - header = "
  • (E) (C) (-) " - else - header = "
  • (E) (C) (M) " - else - header = "
  • " - - var/item - if (isnull(value)) - item = "[VV_HTML_ENCODE(name)] = null" - - else if (istext(value)) - item = "[VV_HTML_ENCODE(name)] = \"[VV_HTML_ENCODE(value)]\"" - - else if (isicon(value)) - #ifdef VARSICON - var/icon/I = new/icon(value) - var/rnd = rand(1,10000) - var/rname = "tmp[REF(I)][rnd].png" - usr << browse_rsc(I, rname) - item = "[VV_HTML_ENCODE(name)] = ([value]) " - #else - item = "[VV_HTML_ENCODE(name)] = /icon ([value])" - #endif - - else if (isfile(value)) - item = "[VV_HTML_ENCODE(name)] = '[value]'" - - else if (istype(value, /datum)) - var/datum/D = value - if ("[D]" != "[D.type]") //if the thing as a name var, lets use it. - item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D] [D.type]" - else - item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D.type]" - - else if (islist(value)) - var/list/L = value - var/list/items = list() - - if (L.len > 0 && !(name == "underlays" || name == "overlays" || L.len > (IS_NORMAL_LIST(L) ? 50 : 150))) - for (var/i in 1 to L.len) - var/key = L[i] - var/val - if (IS_NORMAL_LIST(L) && !isnum(key)) - val = L[key] - if (isnull(val)) // we still want to display non-null false values, such as 0 or "" - val = key - key = i - - items += debug_variable(key, val, level + 1, sanitize = sanitize) - - item = "[VV_HTML_ENCODE(name)] = /list ([L.len])
      [items.Join()]
    " - else - item = "[VV_HTML_ENCODE(name)] = /list ([L.len])" - - else if (name in GLOB.bitfields) - var/list/flags = list() - for (var/i in GLOB.bitfields[name]) - if (value & GLOB.bitfields[name][i]) - flags += i - item = "[VV_HTML_ENCODE(name)] = [VV_HTML_ENCODE(jointext(flags, ", "))]" - else - item = "[VV_HTML_ENCODE(name)] = [VV_HTML_ENCODE(value)]" - - return "[header][item]
  • " - -#undef VV_HTML_ENCODE - -/client/proc/view_var_Topic(href, href_list, hsrc) - if( (usr.client != src) || !src.holder || !holder.CheckAdminHref(href, href_list)) - return - if(href_list["Vars"]) - debug_variables(locate(href_list["Vars"])) - - else if(href_list["datumrefresh"]) - var/datum/DAT = locate(href_list["datumrefresh"]) - if(!DAT) //can't be an istype() because /client etc aren't datums - return - src.debug_variables(DAT) - - else if(href_list["mob_player_panel"]) - if(!check_rights(NONE)) - return - - var/mob/M = locate(href_list["mob_player_panel"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - src.holder.show_player_panel(M) - - else if(href_list["godmode"]) - if(!check_rights(R_ADMIN)) - return - - var/mob/M = locate(href_list["godmode"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - src.cmd_admin_godmode(M) - - else if(href_list["mark_object"]) - if(!check_rights(NONE)) - return - - var/datum/D = locate(href_list["mark_object"]) - if(!istype(D)) - to_chat(usr, "This can only be done to instances of type /datum") - return - - if(holder.marked_datum) - vv_update_display(holder.marked_datum, "marked", "") - holder.marked_datum = D - vv_update_display(D, "marked", VV_MSG_MARKED) - - else if(href_list["proc_call"]) - if(!check_rights(NONE)) - return - - var/T = locate(href_list["proc_call"]) - - if(T) - callproc_datum(T) - - else if(href_list["delete"]) - if(!check_rights(R_DEBUG, 0)) - return - - var/datum/D = locate(href_list["delete"]) - if(!istype(D)) - to_chat(usr, "Unable to locate item!") - admin_delete(D) - if (isturf(D)) // show the turf that took its place - debug_variables(D) - - else if(href_list["osay"]) - if(!check_rights(R_FUN, 0)) - return - usr.client.object_say(locate(href_list["osay"])) - - else if(href_list["regenerateicons"]) - if(!check_rights(NONE)) - return - - var/mob/M = locate(href_list["regenerateicons"]) in GLOB.mob_list - if(!ismob(M)) - to_chat(usr, "This can only be done to instances of type /mob") - return - M.regenerate_icons() - else if(href_list["expose"]) - if(!check_rights(R_ADMIN, FALSE)) - return - var/thing = locate(href_list["expose"]) - if (!thing) - return - var/value = vv_get_value(VV_CLIENT) - if (value["class"] != VV_CLIENT) - return - var/client/C = value["value"] - if (!C) - return - var/prompt = alert("Do you want to grant [C] access to view this VV window? (they will not be able to edit or change anything nor open nested vv windows unless they themselves are an admin)", "Confirm", "Yes", "No") - if (prompt != "Yes" || !usr.client) - return - message_admins("[key_name_admin(usr)] Showed [key_name_admin(C)] a VV window") - log_admin("Admin [key_name(usr)] Showed [key_name(C)] a VV window of a [thing]") - to_chat(C, "[usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"] has granted you access to view a View Variables window") - C.debug_variables(thing) - - -//Needs +VAREDIT past this point - - else if(check_rights(R_VAREDIT)) - - - //~CARN: for renaming mobs (updates their name, real_name, mind.name, their ID/PDA and datacore records). - - if(href_list["rename"]) - if(!check_rights(NONE)) - return - - var/mob/M = locate(href_list["rename"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - var/new_name = stripped_input(usr,"What would you like to name this mob?","Input a name",M.real_name,MAX_NAME_LEN) - if( !new_name || !M ) - return - - message_admins("Admin [key_name_admin(usr)] renamed [key_name_admin(M)] to [new_name].") - M.fully_replace_character_name(M.real_name,new_name) - vv_update_display(M, "name", new_name) - vv_update_display(M, "real_name", M.real_name || "No real name") - - else if(href_list["varnameedit"] && href_list["datumedit"]) - if(!check_rights(NONE)) - return - - var/datum/D = locate(href_list["datumedit"]) - if(!istype(D, /datum)) - to_chat(usr, "This can only be used on datums") - return - - if (!modify_variables(D, href_list["varnameedit"], 1)) - return - switch(href_list["varnameedit"]) - if("name") - vv_update_display(D, "name", "[D]") - if("dir") - if(isatom(D)) - var/dir = D.vars["dir"] - vv_update_display(D, "dir", dir2text(dir) || dir) - if("ckey") - if(isliving(D)) - vv_update_display(D, "ckey", D.vars["ckey"] || "No ckey") - if("real_name") - if(isliving(D)) - vv_update_display(D, "real_name", D.vars["real_name"] || "No real name") - - else if(href_list["varnamechange"] && href_list["datumchange"]) - if(!check_rights(NONE)) - return - - var/D = locate(href_list["datumchange"]) - if(!istype(D, /datum)) - to_chat(usr, "This can only be used on datums") - return - - modify_variables(D, href_list["varnamechange"], 0) - - else if(href_list["varnamemass"] && href_list["datummass"]) - if(!check_rights(NONE)) - return - - var/datum/D = locate(href_list["datummass"]) - if(!istype(D)) - to_chat(usr, "This can only be used on instances of type /datum") - return - - cmd_mass_modify_object_variables(D, href_list["varnamemass"]) - - else if(href_list["listedit"] && href_list["index"]) - var/index = text2num(href_list["index"]) - if (!index) - return - - var/list/L = locate(href_list["listedit"]) - if (!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - - mod_list(L, null, "list", "contents", index, autodetect_class = TRUE) - - else if(href_list["listchange"] && href_list["index"]) - var/index = text2num(href_list["index"]) - if (!index) - return - - var/list/L = locate(href_list["listchange"]) - if (!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - - mod_list(L, null, "list", "contents", index, autodetect_class = FALSE) - - else if(href_list["listremove"] && href_list["index"]) - var/index = text2num(href_list["index"]) - if (!index) - return - - var/list/L = locate(href_list["listremove"]) - if (!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - - var/variable = L[index] - var/prompt = alert("Do you want to remove item number [index] from list?", "Confirm", "Yes", "No") - if (prompt != "Yes") - return - L.Cut(index, index+1) - log_world("### ListVarEdit by [src]: /list's contents: REMOVED=[html_encode("[variable]")]") - log_admin("[key_name(src)] modified list's contents: REMOVED=[variable]") - message_admins("[key_name_admin(src)] modified list's contents: REMOVED=[variable]") - - else if(href_list["listadd"]) - var/list/L = locate(href_list["listadd"]) - if (!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - - mod_list_add(L, null, "list", "contents") - - else if(href_list["listdupes"]) - var/list/L = locate(href_list["listdupes"]) - if (!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - - uniqueList_inplace(L) - log_world("### ListVarEdit by [src]: /list contents: CLEAR DUPES") - log_admin("[key_name(src)] modified list's contents: CLEAR DUPES") - message_admins("[key_name_admin(src)] modified list's contents: CLEAR DUPES") - - else if(href_list["listnulls"]) - var/list/L = locate(href_list["listnulls"]) - if (!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - - listclearnulls(L) - log_world("### ListVarEdit by [src]: /list contents: CLEAR NULLS") - log_admin("[key_name(src)] modified list's contents: CLEAR NULLS") - message_admins("[key_name_admin(src)] modified list's contents: CLEAR NULLS") - - else if(href_list["listlen"]) - var/list/L = locate(href_list["listlen"]) - if (!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - var/value = vv_get_value(VV_NUM) - if (value["class"] != VV_NUM) - return - - L.len = value["value"] - log_world("### ListVarEdit by [src]: /list len: [L.len]") - log_admin("[key_name(src)] modified list's len: [L.len]") - message_admins("[key_name_admin(src)] modified list's len: [L.len]") - - else if(href_list["listshuffle"]) - var/list/L = locate(href_list["listshuffle"]) - if (!istype(L)) - to_chat(usr, "This can only be used on instances of type /list") - return - - shuffle_inplace(L) - log_world("### ListVarEdit by [src]: /list contents: SHUFFLE") - log_admin("[key_name(src)] modified list's contents: SHUFFLE") - message_admins("[key_name_admin(src)] modified list's contents: SHUFFLE") - - else if(href_list["give_spell"]) - if(!check_rights(NONE)) - return - - var/mob/M = locate(href_list["give_spell"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - src.give_spell(M) - - else if(href_list["remove_spell"]) - if(!check_rights(NONE)) - return - - var/mob/M = locate(href_list["remove_spell"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - remove_spell(M) - - else if(href_list["give_disease"]) - if(!check_rights(NONE)) - return - - var/mob/M = locate(href_list["give_disease"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - src.give_disease(M) - - else if(href_list["gib"]) - if(!check_rights(R_FUN)) - return - - var/mob/M = locate(href_list["gib"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - src.cmd_admin_gib(M) - - else if(href_list["build_mode"]) - if(!check_rights(R_BUILDMODE)) - return - - var/mob/M = locate(href_list["build_mode"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - togglebuildmode(M) - - else if(href_list["drop_everything"]) - if(!check_rights(NONE)) - return - - var/mob/M = locate(href_list["drop_everything"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - if(usr.client) - usr.client.cmd_admin_drop_everything(M) - - else if(href_list["direct_control"]) - if(!check_rights(NONE)) - return - - var/mob/M = locate(href_list["direct_control"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - - if(usr.client) - usr.client.cmd_assume_direct_control(M) - - else if(href_list["offer_control"]) - if(!check_rights(NONE)) - return - - var/mob/M = locate(href_list["offer_control"]) in GLOB.mob_list - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - offer_control(M) - - else if (href_list["modarmor"]) - if(!check_rights(NONE)) - return - - var/obj/O = locate(href_list["modarmor"]) - if(!istype(O)) - to_chat(usr, "This can only be used on instances of type /obj") - return - - var/list/pickerlist = list() - var/list/armorlist = O.armor.getList() - - for (var/i in armorlist) - pickerlist += list(list("value" = armorlist[i], "name" = i)) - - var/list/result = presentpicker(usr, "Modify armor", "Modify armor: [O]", Button1="Save", Button2 = "Cancel", Timeout=FALSE, inputtype = "text", values = pickerlist) - - if (islist(result)) - if (result["button"] == 2) // If the user pressed the cancel button - return - // text2num conveniently returns a null on invalid values - O.armor = O.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 [O] ([O.type]) to melee: [O.armor.melee], bullet: [O.armor.bullet], laser: [O.armor.laser], energy: [O.armor.energy], bomb: [O.armor.bomb], bio: [O.armor.bio], rad: [O.armor.rad], fire: [O.armor.fire], acid: [O.armor.acid]") - message_admins("[key_name_admin(usr)] modified the armor on [O] ([O.type]) to melee: [O.armor.melee], bullet: [O.armor.bullet], laser: [O.armor.laser], energy: [O.armor.energy], bomb: [O.armor.bomb], bio: [O.armor.bio], rad: [O.armor.rad], fire: [O.armor.fire], acid: [O.armor.acid]") - else - return - - else if(href_list["delall"]) - if(!check_rights(R_DEBUG|R_SERVER)) - return - - var/obj/O = locate(href_list["delall"]) - if(!isobj(O)) - to_chat(usr, "This can only be used on instances of type /obj") - return - - var/action_type = alert("Strict type ([O.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 [O.type]?",,"Yes","No") != "Yes") - return - - if(alert("Second confirmation required. Delete?",,"Yes","No") != "Yes") - return - - var/O_type = O.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) ") - - else if(href_list["addreagent"]) - if(!check_rights(NONE)) - return - - var/atom/A = locate(href_list["addreagent"]) - - if(!A.reagents) - var/amount = input(usr, "Specify the reagent size of [A]", "Set Reagent Size", 50) as num - if(amount) - A.create_reagents(amount) - - if(A.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 subtypesof(/datum/reagent) - 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.", A.reagents.maximum_volume) as num - if(amount) - A.reagents.add_reagent(chosen_id, amount) - log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to \the [A]") - message_admins("[key_name(usr)] has added [amount] units of [chosen_id] to \the [A]") - - else if(href_list["explode"]) - if(!check_rights(R_FUN)) - return - - var/atom/A = locate(href_list["explode"]) - if(!isobj(A) && !ismob(A) && !isturf(A)) - to_chat(usr, "This can only be done to instances of type /obj, /mob and /turf") - return - - src.cmd_admin_explosion(A) - - else if(href_list["emp"]) - if(!check_rights(R_FUN)) - return - - var/atom/A = locate(href_list["emp"]) - if(!isobj(A) && !ismob(A) && !isturf(A)) - to_chat(usr, "This can only be done to instances of type /obj, /mob and /turf") - return - - src.cmd_admin_emp(A) - - else if(href_list["modtransform"]) - if(!check_rights(R_DEBUG)) - return - - var/atom/A = locate(href_list["modtransform"]) - if(!istype(A)) - to_chat(usr, "This can only be done to atoms.") - return - - var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate") - var/matrix/M = A.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)) - A.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)) - A.transform = M.Translate(x,y) - if("Rotate") - var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num - if(!isnull(angle)) - A.transform = M.Turn(angle) - - else if(href_list["rotatedatum"]) - if(!check_rights(NONE)) - return - - var/atom/A = locate(href_list["rotatedatum"]) - if(!istype(A)) - to_chat(usr, "This can only be done to instances of type /atom") - return - - switch(href_list["rotatedir"]) - if("right") - A.setDir(turn(A.dir, -45)) - if("left") - A.setDir(turn(A.dir, 45)) - vv_update_display(A, "dir", dir2text(A.dir)) - - else if(href_list["editorgans"]) - if(!check_rights(NONE)) - return - - var/mob/living/carbon/C = locate(href_list["editorgans"]) in GLOB.mob_list - if(!istype(C)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon") - return - - manipulate_organs(C) - - else if(href_list["givemartialart"]) - if(!check_rights(NONE)) - return - - var/mob/living/carbon/C = locate(href_list["givemartialart"]) in GLOB.carbon_list - if(!istype(C)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon") - return - - var/list/artpaths = subtypesof(/datum/martial_art) - var/list/artnames = list() - for(var/i in artpaths) - var/datum/martial_art/M = i - artnames[initial(M.name)] = M - - var/result = input(usr, "Choose the martial art to teach","JUDO CHOP") as null|anything in artnames - if(!usr) - return - if(QDELETED(C)) - to_chat(usr, "Mob doesn't exist anymore") - return - - if(result) - var/chosenart = artnames[result] - var/datum/martial_art/MA = new chosenart - MA.teach(C) - - else if(href_list["givetrauma"]) - if(!check_rights(NONE)) - return - - var/mob/living/carbon/C = locate(href_list["givetrauma"]) in GLOB.mob_list - if(!istype(C)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon") - return - - var/list/traumas = subtypesof(/datum/brain_trauma) - var/result = input(usr, "Choose the brain trauma to apply","Traumatize") as null|anything in traumas - if(!usr) - return - if(QDELETED(C)) - to_chat(usr, "Mob doesn't exist anymore") - return - - if(result) - C.gain_trauma(result) - - else if(href_list["curetraumas"]) - if(!check_rights(NONE)) - return - - var/mob/living/carbon/C = locate(href_list["curetraumas"]) in GLOB.mob_list - if(!istype(C)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon") - return - - C.cure_all_traumas(TRAUMA_RESILIENCE_ABSOLUTE) - - else if(href_list["hallucinate"]) - if(!check_rights(NONE)) - return - - var/mob/living/carbon/C = locate(href_list["hallucinate"]) in GLOB.mob_list - if(!istype(C)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon") - return - - var/list/hallucinations = subtypesof(/datum/hallucination) - var/result = input(usr, "Choose the hallucination to apply","Send Hallucination") as null|anything in hallucinations - if(!usr) - return - if(QDELETED(C)) - to_chat(usr, "Mob doesn't exist anymore") - return - - if(result) - new result(C, TRUE) - - else if(href_list["makehuman"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/monkey/Mo = locate(href_list["makehuman"]) in GLOB.mob_list - if(!istype(Mo)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/monkey") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") - return - if(!Mo) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("humanone"=href_list["makehuman"])) - - else if(href_list["makemonkey"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makemonkey"]) in GLOB.mob_list - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") - return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("monkeyone"=href_list["makemonkey"])) - - else if(href_list["makerobot"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makerobot"]) in GLOB.mob_list - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") - return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("makerobot"=href_list["makerobot"])) - - else if(href_list["makealien"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makealien"]) in GLOB.mob_list - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") - return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("makealien"=href_list["makealien"])) - - else if(href_list["makeslime"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["makeslime"]) in GLOB.mob_list - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") - return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("makeslime"=href_list["makeslime"])) - - else if(href_list["makeai"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/H = locate(href_list["makeai"]) in GLOB.mob_list - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon") - return - - if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") - return - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - holder.Topic(href, list("makeai"=href_list["makeai"])) - - else if(href_list["setspecies"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["setspecies"]) in GLOB.mob_list - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - var/result = input(usr, "Please choose a new species","Species") as null|anything in GLOB.species_list - - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - - if(result) - var/newtype = GLOB.species_list[result] - admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [H] to [result]") - H.set_species(newtype) - - else if(href_list["editbodypart"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/C = locate(href_list["editbodypart"]) in GLOB.mob_list - if(!istype(C)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon") - return - - var/edit_action = input(usr, "What would you like to do?","Modify Body Part") as null|anything in list("add","remove", "augment") - if(!edit_action) - return - var/list/limb_list = list(BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - if(edit_action == "augment") - limb_list += BODY_ZONE_CHEST - var/result = input(usr, "Please choose which body part to [edit_action]","[capitalize(edit_action)] Body Part") as null|anything in limb_list - - if(!C) - to_chat(usr, "Mob doesn't exist anymore") - return - - if(result) - var/obj/item/bodypart/BP = C.get_bodypart(result) - switch(edit_action) - if("remove") - if(BP) - BP.drop_limb() - else - to_chat(usr, "[C] doesn't have such bodypart.") - if("add") - if(BP) - to_chat(usr, "[C] already has such bodypart.") - else - if(!C.regenerate_limb(result)) - to_chat(usr, "[C] cannot have such bodypart.") - if("augment") - if(ishuman(C)) - if(BP) - BP.change_bodypart_status(BODYPART_ROBOTIC, TRUE, TRUE) - else - to_chat(usr, "[C] doesn't have such bodypart.") - else - to_chat(usr, "Only humans can be augmented.") - admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [C]") - - - else if(href_list["purrbation"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["purrbation"]) in GLOB.mob_list - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - if(!ishumanbasic(H)) - to_chat(usr, "This can only be done to the basic human species at the moment.") - return - - if(!H) - to_chat(usr, "Mob doesn't exist anymore") - return - - var/success = purrbation_toggle(H) - if(success) - to_chat(usr, "Put [H] on purrbation.") - log_admin("[key_name(usr)] has put [key_name(H)] on purrbation.") - var/msg = "[key_name_admin(usr)] has put [key_name(H)] on purrbation." - message_admins(msg) - admin_ticket_log(H, msg) - - else - to_chat(usr, "Removed [H] from purrbation.") - log_admin("[key_name(usr)] has removed [key_name(H)] from purrbation.") - var/msg = "[key_name_admin(usr)] has removed [key_name(H)] from purrbation." - message_admins(msg) - admin_ticket_log(H, msg) - - else if(href_list["adjustDamage"] && href_list["mobToDamage"]) - if(!check_rights(NONE)) - return - - var/mob/living/L = locate(href_list["mobToDamage"]) in GLOB.mob_list - if(!istype(L)) - return - - var/Text = href_list["adjustDamage"] - - var/amount = input("Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0) as num - - if(!L) - to_chat(usr, "Mob doesn't exist anymore") - return - - var/newamt - switch(Text) - if("brute") - L.adjustBruteLoss(amount) - newamt = L.getBruteLoss() - if("fire") - L.adjustFireLoss(amount) - newamt = L.getFireLoss() - if("toxin") - L.adjustToxLoss(amount) - newamt = L.getToxLoss() - if("oxygen") - L.adjustOxyLoss(amount) - newamt = L.getOxyLoss() - if("brain") - L.adjustOrganLoss(ORGAN_SLOT_BRAIN, amount) - newamt = L.getOrganLoss(ORGAN_SLOT_BRAIN) - if("clone") - L.adjustCloneLoss(amount) - newamt = L.getCloneLoss() - if("stamina") - L.adjustStaminaLoss(amount) - newamt = L.getStaminaLoss() - if("arousal") - L.adjustArousalLoss(amount) - newamt = L.getArousalLoss() - if("heart") - L.adjustOrganLoss(ORGAN_SLOT_HEART, amount) - newamt = L.getOrganLoss(ORGAN_SLOT_HEART) - if("liver") - L.adjustOrganLoss(ORGAN_SLOT_LIVER, amount) - newamt = L.getOrganLoss(ORGAN_SLOT_LIVER) - if("lungs") - L.adjustOrganLoss(ORGAN_SLOT_LUNGS, amount) - newamt = L.getOrganLoss(ORGAN_SLOT_LUNGS) - if("eye_sight") - L.adjustOrganLoss(ORGAN_SLOT_EYES, amount) - newamt = L.getOrganLoss(ORGAN_SLOT_EYES) - if("ears") - L.adjustOrganLoss(ORGAN_SLOT_EARS, amount) - newamt = L.getOrganLoss(ORGAN_SLOT_EARS) - if("stomach") - L.adjustOrganLoss(ORGAN_SLOT_STOMACH, amount) - newamt = L.getOrganLoss(ORGAN_SLOT_STOMACH) - if("tongue") - L.adjustOrganLoss(ORGAN_SLOT_TONGUE, amount) - newamt = L.getOrganLoss(ORGAN_SLOT_TONGUE) - if("appendix") - L.adjustOrganLoss(ORGAN_SLOT_APPENDIX, amount) - newamt = L.getOrganLoss(ORGAN_SLOT_APPENDIX) - else - to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]") - return - - if(amount != 0) - log_admin("[key_name(usr)] dealt [amount] amount of [Text] damage to [L] ") - var/msg = "[key_name(usr)] dealt [amount] amount of [Text] damage to [L] " - message_admins("[msg]") - admin_ticket_log(L, msg) - vv_update_display(L, Text, "[newamt]") - else if(href_list["copyoutfit"]) - if(!check_rights(R_SPAWN)) - return - var/mob/living/carbon/human/H = locate(href_list["copyoutfit"]) in GLOB.carbon_list - if(istype(H)) - H.copy_outfit() - else if(href_list["modquirks"]) - if(!check_rights(R_SPAWN)) - return - - var/mob/living/carbon/human/H = locate(href_list["modquirks"]) in GLOB.mob_list - if(!istype(H)) - to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") - return - - var/list/options = list("Clear"="Clear") - for(var/x in subtypesof(/datum/quirk)) - var/datum/quirk/T = x - var/qname = initial(T.name) - options[H.has_quirk(T) ? "[qname] (Remove)" : "[qname] (Add)"] = T - - var/result = input(usr, "Choose quirk to add/remove","Quirk Mod") as null|anything in options - if(result) - if(result == "Clear") - for(var/datum/quirk/q in H.roundstart_quirks) - H.remove_quirk(q.type) - else - var/T = options[result] - if(H.has_quirk(T)) - H.remove_quirk(T) - else - H.add_quirk(T,TRUE) +#define VV_MSG_MARKED "
    Marked Object" +#define VV_MSG_EDITED "
    Var Edited" +#define VV_MSG_DELETED "
    Deleted" + +/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) + +//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() + . += "---" + .["Call Proc"] = "?_src_=vars;[HrefToken()];proc_call=[REF(src)]" + .["Mark Object"] = "?_src_=vars;[HrefToken()];mark_object=[REF(src)]" + .["Delete"] = "?_src_=vars;[HrefToken()];delete=[REF(src)]" + .["Show VV To Player"] = "?_src_=vars;[HrefToken(TRUE)];expose=[REF(src)]" + + +/datum/proc/on_reagent_change(changetype) + return + + +/client/proc/debug_variables(datum/D in world) + set category = "Debug" + set name = "View Variables" + //set src in world + var/static/cookieoffset = rand(1, 9999) //to force cookies to reset after the round. + + if(!usr.client || !usr.client.holder) //The usr vs src abuse in this proc is intentional and must not be changed + to_chat(usr, "You need to be an administrator to access this.") + return + + if(!D) + return + + var/islist = islist(D) + if (!islist && !istype(D)) + return + + var/title = "" + var/refid = REF(D) + var/icon/sprite + var/hash + + var/type = /list + if (!islist) + type = D.type + + + + if(istype(D, /atom)) + var/atom/AT = D + if(AT.icon && AT.icon_state) + sprite = new /icon(AT.icon, AT.icon_state) + hash = md5(AT.icon) + hash = md5(hash + AT.icon_state) + src << browse_rsc(sprite, "vv[hash].png") + + title = "[D] ([REF(D)]) = [type]" + var/formatted_type = replacetext("[type]", "/", "/") + + var/sprite_text + if(sprite) + sprite_text = "" + var/list/atomsnowflake = list() + + if(istype(D, /atom)) + var/atom/A = D + if(isliving(A)) + atomsnowflake += "[D]" + atomsnowflake += "
    << [dir2text(A.dir) || A.dir] >>" + var/mob/living/M = A + atomsnowflake += {" +
    [M.ckey || "No ckey"] / [M.real_name || "No real name"] +
    + BRUTE:[M.getBruteLoss()] + FIRE:[M.getFireLoss()] + TOXIN:[M.getToxLoss()] + OXY:[M.getOxyLoss()] + CLONE:[M.getCloneLoss()] + BRAIN:[M.getOrganLoss(ORGAN_SLOT_BRAIN)] + STAMINA:[M.getStaminaLoss()] + AROUSAL:[M.getArousalLoss()] + "} + if(GLOB.Debug2) + atomsnowflake += {" + HEART:[M.getOrganLoss(ORGAN_SLOT_HEART)] + LIVER:[M.getOrganLoss(ORGAN_SLOT_LIVER)] + LUNGS:[M.getOrganLoss(ORGAN_SLOT_LUNGS)] + EYES:[M.getOrganLoss(ORGAN_SLOT_EYES)] + EARS:[M.getOrganLoss(ORGAN_SLOT_EARS)] + STOMACH:[M.getOrganLoss(ORGAN_SLOT_STOMACH)] + TONGUE:[M.getOrganLoss(ORGAN_SLOT_TONGUE)] + APPENDIX:[M.getOrganLoss(ORGAN_SLOT_APPENDIX)] + "} + atomsnowflake += {" + + "} + else + atomsnowflake += "[D]" + atomsnowflake += "
    << [dir2text(A.dir) || A.dir] >>" + else if("name" in D.vars) + atomsnowflake += "[D]" + else + atomsnowflake += "[formatted_type]" + formatted_type = null + + var/marked + if(holder && holder.marked_datum && holder.marked_datum == D) + marked = VV_MSG_MARKED + var/varedited_line = "" + if(!islist && (D.datum_flags & DF_VAR_EDITED)) + varedited_line = VV_MSG_EDITED + var/deleted_line + if(!islist && D.gc_destroyed) + deleted_line = VV_MSG_DELETED + + var/list/dropdownoptions = list() + if (islist) + dropdownoptions = list( + "---", + "Add Item" = "?_src_=vars;[HrefToken()];listadd=[refid]", + "Remove Nulls" = "?_src_=vars;[HrefToken()];listnulls=[refid]", + "Remove Dupes" = "?_src_=vars;[HrefToken()];listdupes=[refid]", + "Set len" = "?_src_=vars;[HrefToken()];listlen=[refid]", + "Shuffle" = "?_src_=vars;[HrefToken()];listshuffle=[refid]", + "Show VV To Player" = "?_src_=vars;[HrefToken()];expose=[refid]" + ) + else + dropdownoptions = D.vv_get_dropdown() + var/list/dropdownoptions_html = list() + + for (var/name in dropdownoptions) + var/link = dropdownoptions[name] + if (link) + dropdownoptions_html += "" + else + dropdownoptions_html += "" + + var/list/names = list() + if (!islist) + for (var/V in D.vars) + names += V + sleep(1)//For some reason, without this sleep, VVing will cause client to disconnect on certain objects. + + var/list/variable_html = list() + if (islist) + var/list/L = D + for (var/i in 1 to L.len) + var/key = L[i] + var/value + if (IS_NORMAL_LIST(L) && !isnum(key)) + value = L[key] + variable_html += debug_variable(i, value, 0, D) + else + + names = sortList(names) + for (var/V in names) + if(D.can_vv_get(V)) + variable_html += D.vv_get_var(V) + + var/html = {" + + + [title] + + + + +
    + + + + + +
    + + + + +
    + [sprite_text] +
    + [atomsnowflake.Join()] +
    +
    +
    + [formatted_type] + [marked] + [varedited_line] + [deleted_line] +
    +
    +
    + Refresh +
    + +
    +
    +
    +
    +
    + + E - Edit, tries to determine the variable type by itself.
    + C - Change, asks you for the var type first.
    + M - Mass modify: changes this variable for all objects of this type.
    +
    +
    + + + + + +
    +
    + Search: +
    +
    + +
    +
    +
      + [variable_html.Join()] +
    + + + +"} + src << browse(html, "window=variables[refid];size=475x650") + + +/client/proc/vv_update_display(datum/D, span, content) + src << output("[span]:[content]", "variables[REF(D)].browser:replace_span") + + +#define VV_HTML_ENCODE(thing) ( sanitize ? html_encode(thing) : thing ) +/proc/debug_variable(name, value, level, datum/DA = null, sanitize = TRUE) + var/header + if(DA) + if (islist(DA)) + var/index = name + if (value) + name = DA[name] //name is really the index until this line + else + value = DA[name] + header = "
  • (E) (C) (-) " + else + header = "
  • (E) (C) (M) " + else + header = "
  • " + + var/item + if (isnull(value)) + item = "[VV_HTML_ENCODE(name)] = null" + + else if (istext(value)) + item = "[VV_HTML_ENCODE(name)] = \"[VV_HTML_ENCODE(value)]\"" + + else if (isicon(value)) + #ifdef VARSICON + var/icon/I = new/icon(value) + var/rnd = rand(1,10000) + var/rname = "tmp[REF(I)][rnd].png" + usr << browse_rsc(I, rname) + item = "[VV_HTML_ENCODE(name)] = ([value]) " + #else + item = "[VV_HTML_ENCODE(name)] = /icon ([value])" + #endif + + else if (isfile(value)) + item = "[VV_HTML_ENCODE(name)] = '[value]'" + + else if (istype(value, /datum)) + var/datum/D = value + if ("[D]" != "[D.type]") //if the thing as a name var, lets use it. + item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D] [D.type]" + else + item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D.type]" + + else if (islist(value)) + var/list/L = value + var/list/items = list() + + if (L.len > 0 && !(name == "underlays" || name == "overlays" || L.len > (IS_NORMAL_LIST(L) ? 50 : 150))) + for (var/i in 1 to L.len) + var/key = L[i] + var/val + if (IS_NORMAL_LIST(L) && !isnum(key)) + val = L[key] + if (isnull(val)) // we still want to display non-null false values, such as 0 or "" + val = key + key = i + + items += debug_variable(key, val, level + 1, sanitize = sanitize) + + item = "[VV_HTML_ENCODE(name)] = /list ([L.len])
      [items.Join()]
    " + else + item = "[VV_HTML_ENCODE(name)] = /list ([L.len])" + + else if (name in GLOB.bitfields) + var/list/flags = list() + for (var/i in GLOB.bitfields[name]) + if (value & GLOB.bitfields[name][i]) + flags += i + item = "[VV_HTML_ENCODE(name)] = [VV_HTML_ENCODE(jointext(flags, ", "))]" + else + item = "[VV_HTML_ENCODE(name)] = [VV_HTML_ENCODE(value)]" + + return "[header][item]
  • " + +#undef VV_HTML_ENCODE + +/client/proc/view_var_Topic(href, href_list, hsrc) + if( (usr.client != src) || !src.holder || !holder.CheckAdminHref(href, href_list)) + return + if(href_list["Vars"]) + debug_variables(locate(href_list["Vars"])) + + else if(href_list["datumrefresh"]) + var/datum/DAT = locate(href_list["datumrefresh"]) + if(!DAT) //can't be an istype() because /client etc aren't datums + return + src.debug_variables(DAT) + + else if(href_list["mob_player_panel"]) + if(!check_rights(NONE)) + return + + var/mob/M = locate(href_list["mob_player_panel"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + src.holder.show_player_panel(M) + + else if(href_list["godmode"]) + if(!check_rights(R_ADMIN)) + return + + var/mob/M = locate(href_list["godmode"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + src.cmd_admin_godmode(M) + + else if(href_list["mark_object"]) + if(!check_rights(NONE)) + return + + var/datum/D = locate(href_list["mark_object"]) + if(!istype(D)) + to_chat(usr, "This can only be done to instances of type /datum") + return + + if(holder.marked_datum) + vv_update_display(holder.marked_datum, "marked", "") + holder.marked_datum = D + vv_update_display(D, "marked", VV_MSG_MARKED) + + else if(href_list["proc_call"]) + if(!check_rights(NONE)) + return + + var/T = locate(href_list["proc_call"]) + + if(T) + callproc_datum(T) + + else if(href_list["delete"]) + if(!check_rights(R_DEBUG, 0)) + return + + var/datum/D = locate(href_list["delete"]) + if(!istype(D)) + to_chat(usr, "Unable to locate item!") + admin_delete(D) + if (isturf(D)) // show the turf that took its place + debug_variables(D) + + else if(href_list["osay"]) + if(!check_rights(R_FUN, 0)) + return + usr.client.object_say(locate(href_list["osay"])) + + else if(href_list["regenerateicons"]) + if(!check_rights(NONE)) + return + + var/mob/M = locate(href_list["regenerateicons"]) in GLOB.mob_list + if(!ismob(M)) + to_chat(usr, "This can only be done to instances of type /mob") + return + M.regenerate_icons() + else if(href_list["expose"]) + if(!check_rights(R_ADMIN, FALSE)) + return + var/thing = locate(href_list["expose"]) + if (!thing) + return + var/value = vv_get_value(VV_CLIENT) + if (value["class"] != VV_CLIENT) + return + var/client/C = value["value"] + if (!C) + return + var/prompt = alert("Do you want to grant [C] access to view this VV window? (they will not be able to edit or change anything nor open nested vv windows unless they themselves are an admin)", "Confirm", "Yes", "No") + if (prompt != "Yes" || !usr.client) + return + message_admins("[key_name_admin(usr)] Showed [key_name_admin(C)] a VV window") + log_admin("Admin [key_name(usr)] Showed [key_name(C)] a VV window of a [thing]") + to_chat(C, "[usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"] has granted you access to view a View Variables window") + C.debug_variables(thing) + + +//Needs +VAREDIT past this point + + else if(check_rights(R_VAREDIT)) + + + //~CARN: for renaming mobs (updates their name, real_name, mind.name, their ID/PDA and datacore records). + + if(href_list["rename"]) + if(!check_rights(NONE)) + return + + var/mob/M = locate(href_list["rename"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + var/new_name = stripped_input(usr,"What would you like to name this mob?","Input a name",M.real_name,MAX_NAME_LEN) + if( !new_name || !M ) + return + + message_admins("Admin [key_name_admin(usr)] renamed [key_name_admin(M)] to [new_name].") + M.fully_replace_character_name(M.real_name,new_name) + vv_update_display(M, "name", new_name) + vv_update_display(M, "real_name", M.real_name || "No real name") + + else if(href_list["varnameedit"] && href_list["datumedit"]) + if(!check_rights(NONE)) + return + + var/datum/D = locate(href_list["datumedit"]) + if(!istype(D, /datum)) + to_chat(usr, "This can only be used on datums") + return + + if (!modify_variables(D, href_list["varnameedit"], 1)) + return + switch(href_list["varnameedit"]) + if("name") + vv_update_display(D, "name", "[D]") + if("dir") + if(isatom(D)) + var/dir = D.vars["dir"] + vv_update_display(D, "dir", dir2text(dir) || dir) + if("ckey") + if(isliving(D)) + vv_update_display(D, "ckey", D.vars["ckey"] || "No ckey") + if("real_name") + if(isliving(D)) + vv_update_display(D, "real_name", D.vars["real_name"] || "No real name") + + else if(href_list["varnamechange"] && href_list["datumchange"]) + if(!check_rights(NONE)) + return + + var/D = locate(href_list["datumchange"]) + if(!istype(D, /datum)) + to_chat(usr, "This can only be used on datums") + return + + modify_variables(D, href_list["varnamechange"], 0) + + else if(href_list["varnamemass"] && href_list["datummass"]) + if(!check_rights(NONE)) + return + + var/datum/D = locate(href_list["datummass"]) + if(!istype(D)) + to_chat(usr, "This can only be used on instances of type /datum") + return + + cmd_mass_modify_object_variables(D, href_list["varnamemass"]) + + else if(href_list["listedit"] && href_list["index"]) + var/index = text2num(href_list["index"]) + if (!index) + return + + var/list/L = locate(href_list["listedit"]) + if (!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + + mod_list(L, null, "list", "contents", index, autodetect_class = TRUE) + + else if(href_list["listchange"] && href_list["index"]) + var/index = text2num(href_list["index"]) + if (!index) + return + + var/list/L = locate(href_list["listchange"]) + if (!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + + mod_list(L, null, "list", "contents", index, autodetect_class = FALSE) + + else if(href_list["listremove"] && href_list["index"]) + var/index = text2num(href_list["index"]) + if (!index) + return + + var/list/L = locate(href_list["listremove"]) + if (!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + + var/variable = L[index] + var/prompt = alert("Do you want to remove item number [index] from list?", "Confirm", "Yes", "No") + if (prompt != "Yes") + return + L.Cut(index, index+1) + log_world("### ListVarEdit by [src]: /list's contents: REMOVED=[html_encode("[variable]")]") + log_admin("[key_name(src)] modified list's contents: REMOVED=[variable]") + message_admins("[key_name_admin(src)] modified list's contents: REMOVED=[variable]") + + else if(href_list["listadd"]) + var/list/L = locate(href_list["listadd"]) + if (!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + + mod_list_add(L, null, "list", "contents") + + else if(href_list["listdupes"]) + var/list/L = locate(href_list["listdupes"]) + if (!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + + uniqueList_inplace(L) + log_world("### ListVarEdit by [src]: /list contents: CLEAR DUPES") + log_admin("[key_name(src)] modified list's contents: CLEAR DUPES") + message_admins("[key_name_admin(src)] modified list's contents: CLEAR DUPES") + + else if(href_list["listnulls"]) + var/list/L = locate(href_list["listnulls"]) + if (!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + + listclearnulls(L) + log_world("### ListVarEdit by [src]: /list contents: CLEAR NULLS") + log_admin("[key_name(src)] modified list's contents: CLEAR NULLS") + message_admins("[key_name_admin(src)] modified list's contents: CLEAR NULLS") + + else if(href_list["listlen"]) + var/list/L = locate(href_list["listlen"]) + if (!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + var/value = vv_get_value(VV_NUM) + if (value["class"] != VV_NUM) + return + + L.len = value["value"] + log_world("### ListVarEdit by [src]: /list len: [L.len]") + log_admin("[key_name(src)] modified list's len: [L.len]") + message_admins("[key_name_admin(src)] modified list's len: [L.len]") + + else if(href_list["listshuffle"]) + var/list/L = locate(href_list["listshuffle"]) + if (!istype(L)) + to_chat(usr, "This can only be used on instances of type /list") + return + + shuffle_inplace(L) + log_world("### ListVarEdit by [src]: /list contents: SHUFFLE") + log_admin("[key_name(src)] modified list's contents: SHUFFLE") + message_admins("[key_name_admin(src)] modified list's contents: SHUFFLE") + + else if(href_list["give_spell"]) + if(!check_rights(NONE)) + return + + var/mob/M = locate(href_list["give_spell"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + src.give_spell(M) + + else if(href_list["remove_spell"]) + if(!check_rights(NONE)) + return + + var/mob/M = locate(href_list["remove_spell"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + remove_spell(M) + + else if(href_list["give_disease"]) + if(!check_rights(NONE)) + return + + var/mob/M = locate(href_list["give_disease"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + src.give_disease(M) + + else if(href_list["gib"]) + if(!check_rights(R_FUN)) + return + + var/mob/M = locate(href_list["gib"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + src.cmd_admin_gib(M) + + else if(href_list["build_mode"]) + if(!check_rights(R_BUILDMODE)) + return + + var/mob/M = locate(href_list["build_mode"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + togglebuildmode(M) + + else if(href_list["drop_everything"]) + if(!check_rights(NONE)) + return + + var/mob/M = locate(href_list["drop_everything"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + if(usr.client) + usr.client.cmd_admin_drop_everything(M) + + else if(href_list["direct_control"]) + if(!check_rights(NONE)) + return + + var/mob/M = locate(href_list["direct_control"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + + if(usr.client) + usr.client.cmd_assume_direct_control(M) + + else if(href_list["offer_control"]) + if(!check_rights(NONE)) + return + + var/mob/M = locate(href_list["offer_control"]) in GLOB.mob_list + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + offer_control(M) + + else if (href_list["modarmor"]) + if(!check_rights(NONE)) + return + + var/obj/O = locate(href_list["modarmor"]) + if(!istype(O)) + to_chat(usr, "This can only be used on instances of type /obj") + return + + var/list/pickerlist = list() + var/list/armorlist = O.armor.getList() + + for (var/i in armorlist) + pickerlist += list(list("value" = armorlist[i], "name" = i)) + + var/list/result = presentpicker(usr, "Modify armor", "Modify armor: [O]", Button1="Save", Button2 = "Cancel", Timeout=FALSE, inputtype = "text", values = pickerlist) + + if (islist(result)) + if (result["button"] == 2) // If the user pressed the cancel button + return + // text2num conveniently returns a null on invalid values + O.armor = O.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 [O] ([O.type]) to melee: [O.armor.melee], bullet: [O.armor.bullet], laser: [O.armor.laser], energy: [O.armor.energy], bomb: [O.armor.bomb], bio: [O.armor.bio], rad: [O.armor.rad], fire: [O.armor.fire], acid: [O.armor.acid]") + message_admins("[key_name_admin(usr)] modified the armor on [O] ([O.type]) to melee: [O.armor.melee], bullet: [O.armor.bullet], laser: [O.armor.laser], energy: [O.armor.energy], bomb: [O.armor.bomb], bio: [O.armor.bio], rad: [O.armor.rad], fire: [O.armor.fire], acid: [O.armor.acid]") + else + return + + else if(href_list["delall"]) + if(!check_rights(R_DEBUG|R_SERVER)) + return + + var/obj/O = locate(href_list["delall"]) + if(!isobj(O)) + to_chat(usr, "This can only be used on instances of type /obj") + return + + var/action_type = alert("Strict type ([O.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 [O.type]?",,"Yes","No") != "Yes") + return + + if(alert("Second confirmation required. Delete?",,"Yes","No") != "Yes") + return + + var/O_type = O.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) ") + + else if(href_list["addreagent"]) + if(!check_rights(NONE)) + return + + var/atom/A = locate(href_list["addreagent"]) + + if(!A.reagents) + var/amount = input(usr, "Specify the reagent size of [A]", "Set Reagent Size", 50) as num + if(amount) + A.create_reagents(amount) + + if(A.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 subtypesof(/datum/reagent) + 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.", A.reagents.maximum_volume) as num + if(amount) + A.reagents.add_reagent(chosen_id, amount) + log_admin("[key_name(usr)] has added [amount] units of [chosen_id] to \the [A]") + message_admins("[key_name(usr)] has added [amount] units of [chosen_id] to \the [A]") + + else if(href_list["explode"]) + if(!check_rights(R_FUN)) + return + + var/atom/A = locate(href_list["explode"]) + if(!isobj(A) && !ismob(A) && !isturf(A)) + to_chat(usr, "This can only be done to instances of type /obj, /mob and /turf") + return + + src.cmd_admin_explosion(A) + + else if(href_list["emp"]) + if(!check_rights(R_FUN)) + return + + var/atom/A = locate(href_list["emp"]) + if(!isobj(A) && !ismob(A) && !isturf(A)) + to_chat(usr, "This can only be done to instances of type /obj, /mob and /turf") + return + + src.cmd_admin_emp(A) + + else if(href_list["modtransform"]) + if(!check_rights(R_DEBUG)) + return + + var/atom/A = locate(href_list["modtransform"]) + if(!istype(A)) + to_chat(usr, "This can only be done to atoms.") + return + + var/result = input(usr, "Choose the transformation to apply","Transform Mod") as null|anything in list("Scale","Translate","Rotate") + var/matrix/M = A.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)) + A.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)) + A.transform = M.Translate(x,y) + if("Rotate") + var/angle = input(usr, "Choose angle to rotate","Transform Mod") as null|num + if(!isnull(angle)) + A.transform = M.Turn(angle) + + else if(href_list["rotatedatum"]) + if(!check_rights(NONE)) + return + + var/atom/A = locate(href_list["rotatedatum"]) + if(!istype(A)) + to_chat(usr, "This can only be done to instances of type /atom") + return + + switch(href_list["rotatedir"]) + if("right") + A.setDir(turn(A.dir, -45)) + if("left") + A.setDir(turn(A.dir, 45)) + vv_update_display(A, "dir", dir2text(A.dir)) + + else if(href_list["editorgans"]) + if(!check_rights(NONE)) + return + + var/mob/living/carbon/C = locate(href_list["editorgans"]) in GLOB.mob_list + if(!istype(C)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon") + return + + manipulate_organs(C) + + else if(href_list["givemartialart"]) + if(!check_rights(NONE)) + return + + var/mob/living/carbon/C = locate(href_list["givemartialart"]) in GLOB.carbon_list + if(!istype(C)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon") + return + + var/list/artpaths = subtypesof(/datum/martial_art) + var/list/artnames = list() + for(var/i in artpaths) + var/datum/martial_art/M = i + artnames[initial(M.name)] = M + + var/result = input(usr, "Choose the martial art to teach","JUDO CHOP") as null|anything in artnames + if(!usr) + return + if(QDELETED(C)) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(result) + var/chosenart = artnames[result] + var/datum/martial_art/MA = new chosenart + MA.teach(C) + + else if(href_list["givetrauma"]) + if(!check_rights(NONE)) + return + + var/mob/living/carbon/C = locate(href_list["givetrauma"]) in GLOB.mob_list + if(!istype(C)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon") + return + + var/list/traumas = subtypesof(/datum/brain_trauma) + var/result = input(usr, "Choose the brain trauma to apply","Traumatize") as null|anything in traumas + if(!usr) + return + if(QDELETED(C)) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(result) + C.gain_trauma(result) + + else if(href_list["curetraumas"]) + if(!check_rights(NONE)) + return + + var/mob/living/carbon/C = locate(href_list["curetraumas"]) in GLOB.mob_list + if(!istype(C)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon") + return + + C.cure_all_traumas(TRAUMA_RESILIENCE_ABSOLUTE) + + else if(href_list["hallucinate"]) + if(!check_rights(NONE)) + return + + var/mob/living/carbon/C = locate(href_list["hallucinate"]) in GLOB.mob_list + if(!istype(C)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon") + return + + var/list/hallucinations = subtypesof(/datum/hallucination) + var/result = input(usr, "Choose the hallucination to apply","Send Hallucination") as null|anything in hallucinations + if(!usr) + return + if(QDELETED(C)) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(result) + new result(C, TRUE) + + else if(href_list["makehuman"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/monkey/Mo = locate(href_list["makehuman"]) in GLOB.mob_list + if(!istype(Mo)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/monkey") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") + return + if(!Mo) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("humanone"=href_list["makehuman"])) + + else if(href_list["makemonkey"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makemonkey"]) in GLOB.mob_list + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") + return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("monkeyone"=href_list["makemonkey"])) + + else if(href_list["makerobot"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makerobot"]) in GLOB.mob_list + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") + return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("makerobot"=href_list["makerobot"])) + + else if(href_list["makealien"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makealien"]) in GLOB.mob_list + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") + return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("makealien"=href_list["makealien"])) + + else if(href_list["makeslime"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["makeslime"]) in GLOB.mob_list + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") + return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("makeslime"=href_list["makeslime"])) + + else if(href_list["makeai"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/H = locate(href_list["makeai"]) in GLOB.mob_list + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon") + return + + if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform") + return + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + holder.Topic(href, list("makeai"=href_list["makeai"])) + + else if(href_list["setspecies"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["setspecies"]) in GLOB.mob_list + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + var/result = input(usr, "Please choose a new species","Species") as null|anything in GLOB.species_list + + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(result) + var/newtype = GLOB.species_list[result] + admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [H] to [result]") + H.set_species(newtype) + + else if(href_list["editbodypart"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/C = locate(href_list["editbodypart"]) in GLOB.mob_list + if(!istype(C)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon") + return + + var/edit_action = input(usr, "What would you like to do?","Modify Body Part") as null|anything in list("add","remove", "augment") + if(!edit_action) + return + var/list/limb_list = list(BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + if(edit_action == "augment") + limb_list += BODY_ZONE_CHEST + var/result = input(usr, "Please choose which body part to [edit_action]","[capitalize(edit_action)] Body Part") as null|anything in limb_list + + if(!C) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(result) + var/obj/item/bodypart/BP = C.get_bodypart(result) + switch(edit_action) + if("remove") + if(BP) + BP.drop_limb() + else + to_chat(usr, "[C] doesn't have such bodypart.") + if("add") + if(BP) + to_chat(usr, "[C] already has such bodypart.") + else + if(!C.regenerate_limb(result)) + to_chat(usr, "[C] cannot have such bodypart.") + if("augment") + if(ishuman(C)) + if(BP) + BP.change_bodypart_status(BODYPART_ROBOTIC, TRUE, TRUE) + else + to_chat(usr, "[C] doesn't have such bodypart.") + else + to_chat(usr, "Only humans can be augmented.") + admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [C]") + + + else if(href_list["purrbation"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["purrbation"]) in GLOB.mob_list + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + if(!ishumanbasic(H)) + to_chat(usr, "This can only be done to the basic human species at the moment.") + return + + if(!H) + to_chat(usr, "Mob doesn't exist anymore") + return + + var/success = purrbation_toggle(H) + if(success) + to_chat(usr, "Put [H] on purrbation.") + log_admin("[key_name(usr)] has put [key_name(H)] on purrbation.") + var/msg = "[key_name_admin(usr)] has put [key_name(H)] on purrbation." + message_admins(msg) + admin_ticket_log(H, msg) + + else + to_chat(usr, "Removed [H] from purrbation.") + log_admin("[key_name(usr)] has removed [key_name(H)] from purrbation.") + var/msg = "[key_name_admin(usr)] has removed [key_name(H)] from purrbation." + message_admins(msg) + admin_ticket_log(H, msg) + + else if(href_list["adjustDamage"] && href_list["mobToDamage"]) + if(!check_rights(NONE)) + return + + var/mob/living/L = locate(href_list["mobToDamage"]) in GLOB.mob_list + if(!istype(L)) + return + + var/Text = href_list["adjustDamage"] + + var/amount = input("Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0) as num + + if(!L) + to_chat(usr, "Mob doesn't exist anymore") + return + + var/newamt + switch(Text) + if("brute") + L.adjustBruteLoss(amount) + newamt = L.getBruteLoss() + if("fire") + L.adjustFireLoss(amount) + newamt = L.getFireLoss() + if("toxin") + L.adjustToxLoss(amount) + newamt = L.getToxLoss() + if("oxygen") + L.adjustOxyLoss(amount) + newamt = L.getOxyLoss() + if("brain") + L.adjustOrganLoss(ORGAN_SLOT_BRAIN, amount) + newamt = L.getOrganLoss(ORGAN_SLOT_BRAIN) + if("clone") + L.adjustCloneLoss(amount) + newamt = L.getCloneLoss() + if("stamina") + L.adjustStaminaLoss(amount) + newamt = L.getStaminaLoss() + if("arousal") + L.adjustArousalLoss(amount) + newamt = L.getArousalLoss() + if("heart") + L.adjustOrganLoss(ORGAN_SLOT_HEART, amount) + newamt = L.getOrganLoss(ORGAN_SLOT_HEART) + if("liver") + L.adjustOrganLoss(ORGAN_SLOT_LIVER, amount) + newamt = L.getOrganLoss(ORGAN_SLOT_LIVER) + if("lungs") + L.adjustOrganLoss(ORGAN_SLOT_LUNGS, amount) + newamt = L.getOrganLoss(ORGAN_SLOT_LUNGS) + if("eye_sight") + L.adjustOrganLoss(ORGAN_SLOT_EYES, amount) + newamt = L.getOrganLoss(ORGAN_SLOT_EYES) + if("ears") + L.adjustOrganLoss(ORGAN_SLOT_EARS, amount) + newamt = L.getOrganLoss(ORGAN_SLOT_EARS) + if("stomach") + L.adjustOrganLoss(ORGAN_SLOT_STOMACH, amount) + newamt = L.getOrganLoss(ORGAN_SLOT_STOMACH) + if("tongue") + L.adjustOrganLoss(ORGAN_SLOT_TONGUE, amount) + newamt = L.getOrganLoss(ORGAN_SLOT_TONGUE) + if("appendix") + L.adjustOrganLoss(ORGAN_SLOT_APPENDIX, amount) + newamt = L.getOrganLoss(ORGAN_SLOT_APPENDIX) + else + to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]") + return + + if(amount != 0) + log_admin("[key_name(usr)] dealt [amount] amount of [Text] damage to [L] ") + var/msg = "[key_name(usr)] dealt [amount] amount of [Text] damage to [L] " + message_admins("[msg]") + admin_ticket_log(L, msg) + vv_update_display(L, Text, "[newamt]") + else if(href_list["copyoutfit"]) + if(!check_rights(R_SPAWN)) + return + var/mob/living/carbon/human/H = locate(href_list["copyoutfit"]) in GLOB.carbon_list + if(istype(H)) + H.copy_outfit() + else if(href_list["modquirks"]) + if(!check_rights(R_SPAWN)) + return + + var/mob/living/carbon/human/H = locate(href_list["modquirks"]) in GLOB.mob_list + if(!istype(H)) + to_chat(usr, "This can only be done to instances of type /mob/living/carbon/human") + return + + var/list/options = list("Clear"="Clear") + for(var/x in subtypesof(/datum/quirk)) + var/datum/quirk/T = x + var/qname = initial(T.name) + options[H.has_quirk(T) ? "[qname] (Remove)" : "[qname] (Add)"] = T + + var/result = input(usr, "Choose quirk to add/remove","Quirk Mod") as null|anything in options + if(result) + if(result == "Clear") + for(var/datum/quirk/q in H.roundstart_quirks) + H.remove_quirk(q.type) + else + var/T = options[result] + if(H.has_quirk(T)) + H.remove_quirk(T) + else + H.add_quirk(T,TRUE) diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm index a844254ef9..6b72f2fdfc 100644 --- a/code/datums/diseases/advance/advance.dm +++ b/code/datums/diseases/advance/advance.dm @@ -1,495 +1,495 @@ -/* - - 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 wont 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/charcoal - ), - list( //level 5 - /datum/reagent/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/corazone - ), - 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) - 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) - - var/list/generated = 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 generated - - // 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++) - generated += pick_n_take(possible_symptoms) - - return generated - -/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 - - SetSpread(CLAMP(2 ** (properties["transmittable"] - symptoms.len), DISEASE_SPREAD_BLOOD, DISEASE_SPREAD_AIRBORNE)) - - 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 less 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/s = safepick(GenerateSymptoms(min_level, max_level, 1)) - if(s) - AddSymptom(s) - Refresh(TRUE) - return - -// Randomly remove a symptom. -/datum/disease/advance/proc/Devolve(ignore_mutable = FALSE) - if(!mutable && !ignore_mutable) - return - if(symptoms.len > 1) - var/s = safepick(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(symptoms.len) - var/s = safepick(symptoms) - if(s) - NeuterSymptom(s) - Refresh(TRUE) - -// Name the disease. -/datum/disease/advance/proc/AssignName(name = "Unknown") - Refresh() - var/datum/disease/advance/A = SSdisease.archive_diseases[GetDiseaseID()] - A.name = name - for(var/datum/disease/advance/AD in SSdisease.active_diseases) - AD.Refresh() - -// 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(1) - 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 symptoms - 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.symptoms += 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.AssignName(new_name) - D.Refresh() - - for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) - if(!is_station_level(H.z)) - continue - if(!H.HasDisease(D)) - H.ForceContractDisease(D) - break - - var/list/name_symptoms = list() - for(var/datum/symptom/S in D.symptoms) - name_symptoms += S.name - message_admins("[key_name_admin(user)] has triggered a custom virus outbreak of [D.admin_details()]") - log_virus("[key_name(user)] has triggered a custom virus outbreak of [D.admin_details()]!") - -/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 wont 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/charcoal + ), + list( //level 5 + /datum/reagent/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/corazone + ), + 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) + 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) + + var/list/generated = 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 generated + + // 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++) + generated += pick_n_take(possible_symptoms) + + return generated + +/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 + + SetSpread(CLAMP(2 ** (properties["transmittable"] - symptoms.len), DISEASE_SPREAD_BLOOD, DISEASE_SPREAD_AIRBORNE)) + + 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 less 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/s = safepick(GenerateSymptoms(min_level, max_level, 1)) + if(s) + AddSymptom(s) + Refresh(TRUE) + return + +// Randomly remove a symptom. +/datum/disease/advance/proc/Devolve(ignore_mutable = FALSE) + if(!mutable && !ignore_mutable) + return + if(symptoms.len > 1) + var/s = safepick(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(symptoms.len) + var/s = safepick(symptoms) + if(s) + NeuterSymptom(s) + Refresh(TRUE) + +// Name the disease. +/datum/disease/advance/proc/AssignName(name = "Unknown") + Refresh() + var/datum/disease/advance/A = SSdisease.archive_diseases[GetDiseaseID()] + A.name = name + for(var/datum/disease/advance/AD in SSdisease.active_diseases) + AD.Refresh() + +// 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(1) + 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 symptoms + 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.symptoms += 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.AssignName(new_name) + D.Refresh() + + for(var/mob/living/carbon/human/H in shuffle(GLOB.alive_mob_list)) + if(!is_station_level(H.z)) + continue + if(!H.HasDisease(D)) + H.ForceContractDisease(D) + break + + var/list/name_symptoms = list() + for(var/datum/symptom/S in D.symptoms) + name_symptoms += S.name + message_admins("[key_name_admin(user)] has triggered a custom virus outbreak of [D.admin_details()]") + log_virus("[key_name(user)] has triggered a custom virus outbreak of [D.admin_details()]!") + +/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 d2c4b1983f..68c3ceffea 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() - +// 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)]" \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/beard.dm b/code/datums/diseases/advance/symptoms/beard.dm index 6b26183828..d404c95ab6 100644 --- a/code/datums/diseases/advance/symptoms/beard.dm +++ b/code/datums/diseases/advance/symptoms/beard.dm @@ -1,51 +1,51 @@ -/* -////////////////////////////////////// -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 - -/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 - switch(A.stage) - if(1, 2) - to_chat(H, "Your chin itches.") - if(H.facial_hair_style == "Shaved") - H.facial_hair_style = "Jensen Beard" - H.update_hair() - if(3, 4) - to_chat(H, "You feel tough.") - if(!(H.facial_hair_style == "Dwarf Beard") && !(H.facial_hair_style == "Very Long Beard") && !(H.facial_hair_style == "Full Beard")) - H.facial_hair_style = "Full Beard" - H.update_hair() - else - to_chat(H, "You feel manly!") - if(!(H.facial_hair_style == "Dwarf Beard") && !(H.facial_hair_style == "Very Long Beard")) - H.facial_hair_style = pick("Dwarf Beard", "Very Long Beard") - 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 + +/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 + switch(A.stage) + if(1, 2) + to_chat(H, "Your chin itches.") + if(H.facial_hair_style == "Shaved") + H.facial_hair_style = "Jensen Beard" + H.update_hair() + if(3, 4) + to_chat(H, "You feel tough.") + if(!(H.facial_hair_style == "Dwarf Beard") && !(H.facial_hair_style == "Very Long Beard") && !(H.facial_hair_style == "Full Beard")) + H.facial_hair_style = "Full Beard" + H.update_hair() + else + to_chat(H, "You feel manly!") + if(!(H.facial_hair_style == "Dwarf Beard") && !(H.facial_hair_style == "Very Long Beard")) + H.facial_hair_style = pick("Dwarf Beard", "Very Long Beard") + H.update_hair() diff --git a/code/datums/diseases/advance/symptoms/choking.dm b/code/datums/diseases/advance/symptoms/choking.dm index 069c62cf3f..559d8f4754 100644 --- a/code/datums/diseases/advance/symptoms/choking.dm +++ b/code/datums/diseases/advance/symptoms/choking.dm @@ -1,148 +1,148 @@ -/* -////////////////////////////////////// - -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 = -4 - level = 3 - severity = 3 - base_message_chance = 15 - symptom_delay_min = 10 - symptom_delay_max = 30 - threshold_desc = "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_desc = "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 = -4 + level = 3 + severity = 3 + base_message_chance = 15 + symptom_delay_min = 10 + symptom_delay_max = 30 + threshold_desc = "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_desc = "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 260c6888d7..eb6c5342f9 100644 --- a/code/datums/diseases/advance/symptoms/confusion.dm +++ b/code/datums/diseases/advance/symptoms/confusion.dm @@ -1,61 +1,61 @@ -/* -////////////////////////////////////// - -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_desc = "Resistance 6: Causes brain damage over time.
    \ - Transmission 6: Increases confusion duration.
    \ - 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!") - M.confused = min(100 * power, M.confused + 8) - 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_desc = "Resistance 6: Causes brain damage over time.
    \ + Transmission 6: Increases confusion duration.
    \ + 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!") + M.confused = min(100 * power, M.confused + 8) + 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 e4283101f5..b1767d7cb5 100644 --- a/code/datums/diseases/advance/symptoms/cough.dm +++ b/code/datums/diseases/advance/symptoms/cough.dm @@ -1,75 +1,75 @@ -/* -////////////////////////////////////// - -Coughing - - Noticable. - Little Resistance. - Doesn't increase stage speed much. - Transmissibile. - Low Level. - -BONUS - Will 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." - 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/infective = FALSE - threshold_desc = "Resistance 3: Host will drop small items when coughing.
    \ - Resistance 10: Occasionally causes coughing fits that stun the host.
    \ - Stage Speed 6: Increases cough frequency.
    \ - If Airborne: Coughing will infect bystanders.
    \ - 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.spread_flags &= DISEASE_SPREAD_AIRBORNE) //infect bystanders - infective = TRUE - if(A.properties["resistance"] >= 3) //strong enough to drop items - power = 1.5 - if(A.properties["resistance"] >= 10) //strong enough to stun (rarely) - 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 - 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(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(10)) - to_chat(M, "[pick("You have a coughing fit!", "You can't stop coughing!")]") - M.Stun(20) - M.emote("cough") - addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 6) - addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 12) - addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 18) - if(infective && M.CanSpreadAirborneDisease()) - A.spread(1) - +/* +////////////////////////////////////// + +Coughing + + Noticable. + Little Resistance. + Doesn't increase stage speed much. + Transmissibile. + Low Level. + +BONUS + Will 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." + 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/infective = FALSE + threshold_desc = "Resistance 3: Host will drop small items when coughing.
    \ + Resistance 10: Occasionally causes coughing fits that stun the host.
    \ + Stage Speed 6: Increases cough frequency.
    \ + If Airborne: Coughing will infect bystanders.
    \ + 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.spread_flags &= DISEASE_SPREAD_AIRBORNE) //infect bystanders + infective = TRUE + if(A.properties["resistance"] >= 3) //strong enough to drop items + power = 1.5 + if(A.properties["resistance"] >= 10) //strong enough to stun (rarely) + 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 + 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(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(10)) + to_chat(M, "[pick("You have a coughing fit!", "You can't stop coughing!")]") + M.Stun(20) + M.emote("cough") + addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 6) + addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 12) + addtimer(CALLBACK(M, /mob/.proc/emote, "cough"), 18) + if(infective && M.CanSpreadAirborneDisease()) + A.spread(1) + diff --git a/code/datums/diseases/advance/symptoms/deafness.dm b/code/datums/diseases/advance/symptoms/deafness.dm index 3718104b48..e0336506e3 100644 --- a/code/datums/diseases/advance/symptoms/deafness.dm +++ b/code/datums/diseases/advance/symptoms/deafness.dm @@ -1,59 +1,59 @@ -/* -////////////////////////////////////// - -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_desc = "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 - 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) - var/obj/item/organ/ears/ears = M.getorganslot(ORGAN_SLOT_EARS) - if(istype(ears) && 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!") - M.minimumDeafTicks(20) +/* +////////////////////////////////////// + +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_desc = "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 + 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) + var/obj/item/organ/ears/ears = M.getorganslot(ORGAN_SLOT_EARS) + if(istype(ears) && 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!") + M.minimumDeafTicks(20) diff --git a/code/datums/diseases/advance/symptoms/dizzy.dm b/code/datums/diseases/advance/symptoms/dizzy.dm index c7f7198f6b..b4b06be5ac 100644 --- a/code/datums/diseases/advance/symptoms/dizzy.dm +++ b/code/datums/diseases/advance/symptoms/dizzy.dm @@ -1,53 +1,53 @@ -/* -////////////////////////////////////// - -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 = 40 - threshold_desc = "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!") - M.Dizzy(5) - if(power >= 2) +/* +////////////////////////////////////// + +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 = 40 + threshold_desc = "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!") + M.Dizzy(5) + if(power >= 2) M.set_drugginess(5) \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/fever.dm b/code/datums/diseases/advance/symptoms/fever.dm index 5424e28502..a178cba196 100644 --- a/code/datums/diseases/advance/symptoms/fever.dm +++ b/code/datums/diseases/advance/symptoms/fever.dm @@ -1,60 +1,60 @@ -/* -////////////////////////////////////// - -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_desc = "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 - -/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.")]") - if(M.bodytemperature < BODYTEMP_HEAT_DAMAGE_LIMIT || unsafe) - Heat(M, A) - -/datum/symptom/fever/proc/Heat(mob/living/M, datum/disease/advance/A) - var/get_heat = 6 * power - if(!unsafe) - M.adjust_bodytemperature(get_heat * A.stage, 0, BODYTEMP_HEAT_DAMAGE_LIMIT - 1) - else - M.adjust_bodytemperature(get_heat * A.stage) - return 1 +/* +////////////////////////////////////// + +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_desc = "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 + +/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.")]") + if(M.bodytemperature < BODYTEMP_HEAT_DAMAGE_LIMIT || unsafe) + Heat(M, A) + +/datum/symptom/fever/proc/Heat(mob/living/M, datum/disease/advance/A) + var/get_heat = 6 * power + if(!unsafe) + M.adjust_bodytemperature(get_heat * A.stage, 0, BODYTEMP_HEAT_DAMAGE_LIMIT - 1) + else + M.adjust_bodytemperature(get_heat * A.stage) + return 1 diff --git a/code/datums/diseases/advance/symptoms/flesh_eating.dm b/code/datums/diseases/advance/symptoms/flesh_eating.dm index d1e9d3fdca..48668afc90 100644 --- a/code/datums/diseases/advance/symptoms/flesh_eating.dm +++ b/code/datums/diseases/advance/symptoms/flesh_eating.dm @@ -1,130 +1,130 @@ -/* -////////////////////////////////////// - -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 = -4 - level = 6 - severity = 5 - base_message_chance = 50 - symptom_delay_min = 15 - symptom_delay_max = 60 - var/bleed = FALSE - var/pain = FALSE - threshold_desc = "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.adjustBruteLoss(get_damage) - if(pain) - M.adjustStaminaLoss(get_damage) - if(bleed) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - H.bleed_rate += 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_desc = "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.adjustBruteLoss(get_damage) - 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) +/* +////////////////////////////////////// + +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 = -4 + level = 6 + severity = 5 + base_message_chance = 50 + symptom_delay_min = 15 + symptom_delay_max = 60 + var/bleed = FALSE + var/pain = FALSE + threshold_desc = "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.adjustBruteLoss(get_damage) + if(pain) + M.adjustStaminaLoss(get_damage) + if(bleed) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + H.bleed_rate += 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_desc = "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.adjustBruteLoss(get_damage) + 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 \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/genetics.dm b/code/datums/diseases/advance/symptoms/genetics.dm index c01fccee13..7f21b03dff 100644 --- a/code/datums/diseases/advance/symptoms/genetics.dm +++ b/code/datums/diseases/advance/symptoms/genetics.dm @@ -1,78 +1,78 @@ -/* -////////////////////////////////////// - -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 = "Deoxyribonucleic Acid Saboteur" - desc = "The virus bonds with the DNA of the host, causing damaging mutations until removed." - stealth = -2 - resistance = -3 - stage_speed = 0 - transmittable = -3 - level = 6 - severity = 4 - var/list/possible_mutations - var/archived_dna = null - base_message_chance = 50 - symptom_delay_min = 60 - symptom_delay_max = 120 - var/no_reset = FALSE - threshold_desc = "Resistance 8: Causes two harmful mutations at once.
    \ - Stage Speed 10: Increases mutation frequency.
    \ - Stealth 5: The mutations persist even if the virus is cured." - -/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.dna.remove_mutation_group(possible_mutations) - for(var/i in 1 to power) - C.randmut(possible_mutations) - -// Archive their DNA before they were infected. -/datum/symptom/genetic_mutation/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["stealth"] >= 5) //don't restore dna after curing - no_reset = TRUE - if(A.properties["stage_rate"] >= 10) //mutate more often - symptom_delay_min = 20 - symptom_delay_max = 60 - if(A.properties["resistance"] >= 8) //mutate twice - power = 2 - possible_mutations = (GLOB.bad_mutations | GLOB.not_good_mutations) - GLOB.mutations_list[RACEMUT] - var/mob/living/carbon/M = A.affected_mob - if(M) - if(!M.has_dna()) - return - archived_dna = M.dna.struc_enzymes - -// Give them back their old DNA when cured. -/datum/symptom/genetic_mutation/End(datum/disease/advance/A) - if(!..()) - return - if(!no_reset) - var/mob/living/carbon/M = A.affected_mob - if(M && archived_dna) - if(!M.has_dna()) - return - M.dna.struc_enzymes = archived_dna - M.domutcheck() +/* +////////////////////////////////////// + +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 = "Deoxyribonucleic Acid Saboteur" + desc = "The virus bonds with the DNA of the host, causing damaging mutations until removed." + stealth = -2 + resistance = -3 + stage_speed = 0 + transmittable = -3 + level = 6 + severity = 4 + var/list/possible_mutations + var/archived_dna = null + base_message_chance = 50 + symptom_delay_min = 60 + symptom_delay_max = 120 + var/no_reset = FALSE + threshold_desc = "Resistance 8: Causes two harmful mutations at once.
    \ + Stage Speed 10: Increases mutation frequency.
    \ + Stealth 5: The mutations persist even if the virus is cured." + +/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.dna.remove_mutation_group(possible_mutations) + for(var/i in 1 to power) + C.randmut(possible_mutations) + +// Archive their DNA before they were infected. +/datum/symptom/genetic_mutation/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["stealth"] >= 5) //don't restore dna after curing + no_reset = TRUE + if(A.properties["stage_rate"] >= 10) //mutate more often + symptom_delay_min = 20 + symptom_delay_max = 60 + if(A.properties["resistance"] >= 8) //mutate twice + power = 2 + possible_mutations = (GLOB.bad_mutations | GLOB.not_good_mutations) - GLOB.mutations_list[RACEMUT] + var/mob/living/carbon/M = A.affected_mob + if(M) + if(!M.has_dna()) + return + archived_dna = M.dna.struc_enzymes + +// Give them back their old DNA when cured. +/datum/symptom/genetic_mutation/End(datum/disease/advance/A) + if(!..()) + return + if(!no_reset) + var/mob/living/carbon/M = A.affected_mob + if(M && archived_dna) + if(!M.has_dna()) + return + M.dna.struc_enzymes = archived_dna + M.domutcheck() diff --git a/code/datums/diseases/advance/symptoms/hallucigen.dm b/code/datums/diseases/advance/symptoms/hallucigen.dm index 39ecc61956..873d960524 100644 --- a/code/datums/diseases/advance/symptoms/hallucigen.dm +++ b/code/datums/diseases/advance/symptoms/hallucigen.dm @@ -1,65 +1,65 @@ -/* -////////////////////////////////////// - -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 = -2 - 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_desc = "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.", "You are now blinking manually.", "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)) - to_chat(M, "[pick("Oh, your head...", "Your head pounds.", "They're everywhere! Run!", "Something in the shadows...")]") - 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 = -2 + 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_desc = "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.", "You are now blinking manually.", "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)) + to_chat(M, "[pick("Oh, your head...", "Your head pounds.", "They're everywhere! Run!", "Something in the shadows...")]") + M.hallucination += (45 * power) diff --git a/code/datums/diseases/advance/symptoms/headache.dm b/code/datums/diseases/advance/symptoms/headache.dm index 973de1455b..72b03000ed 100644 --- a/code/datums/diseases/advance/symptoms/headache.dm +++ b/code/datums/diseases/advance/symptoms/headache.dm @@ -1,60 +1,60 @@ -/* -////////////////////////////////////// - -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_desc = "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!")]") +/* +////////////////////////////////////// + +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_desc = "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) \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/heal.dm b/code/datums/diseases/advance/symptoms/heal.dm index d41e0bf225..c1b3526f41 100644 --- a/code/datums/diseases/advance/symptoms/heal.dm +++ b/code/datums/diseases/advance/symptoms/heal.dm @@ -1,485 +1,485 @@ -/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_desc = "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_desc = "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), forced = TRUE) //most effective on toxins - - 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)) - 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_desc = "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/E in M.reagents.reagent_list) //Not just toxins! - var/datum/reagent/R = E - M.reagents.remove_reagent(R.type, actual_power) - if(food_conversion) - M.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_desc = "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.nutrition = max(C.nutrition - (lost_nutrition * HUNGER_FACTOR), 0) //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_desc = "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) - - 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)) //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_desc = "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 - 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 - else if(M.IsUnconscious() || M.stat == UNCONSCIOUS) - return power * 0.9 - else if(M.stat == SOFT_CRIT) - return power * 0.5 - else if(M.IsSleeping()) - return power * 0.25 - else 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) - if(deathgasp) - M.emote("deathgasp") - M.fakedeath("regenerative_coma") - M.update_stat() - M.update_canmove() - 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_stat() - M.update_canmove() - -/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)) - 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 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_desc = "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["stealth"] >= 2) - 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)) - M.reagents.remove_reagent(/datum/reagent/water/holywater, 0.5 * absorption_coeff) - . += power * 0.75 - else if(M.reagents.has_reagent(/datum/reagent/water)) - 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) //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)) - 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_desc = "Transmission 6: Increases temperature adjustment rate and heals toxin lovers.
    \ - 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/plasmamount - - . = 0 - - if(M.loc) - environment = M.loc.return_air() - if(environment) - plasmamount = environment.gases[/datum/gas/plasma] - if(plasmamount && plasmamount > GLOB.meta_gas_visibility[/datum/gas/plasma]) //if there's enough plasma in the air to see - . += power * 0.5 - if(M.reagents.has_reagent(/datum/reagent/toxin/plasma)) - . += 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 > BODYTEMP_NORMAL) - M.adjust_bodytemperature(-20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT,BODYTEMP_NORMAL) - if(prob(5)) - to_chat(M, "You feel less hot.") - else if(M.bodytemperature < (BODYTEMP_NORMAL + 1)) - M.adjust_bodytemperature(20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT,0,BODYTEMP_NORMAL) - if(prob(5)) - to_chat(M, "You feel warmer.") - - M.adjustToxLoss(-heal_amt, forced = (temp_rate == 4)) - - var/list/parts = M.get_damaged_bodyparts(1,1) - 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)) - 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_desc = "Transmission 6: Additionally heals cellular damage and toxin lovers.
    \ - 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), forced = cellular_damage) - - var/list/parts = M.get_damaged_bodyparts(1,1) - - 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)) - 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_desc = "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_desc = "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), forced = TRUE) //most effective on toxins + + 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)) + 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_desc = "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/E in M.reagents.reagent_list) //Not just toxins! + var/datum/reagent/R = E + M.reagents.remove_reagent(R.type, actual_power) + if(food_conversion) + M.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_desc = "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.nutrition = max(C.nutrition - (lost_nutrition * HUNGER_FACTOR), 0) //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_desc = "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) + + 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)) //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_desc = "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 + 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 + else if(M.IsUnconscious() || M.stat == UNCONSCIOUS) + return power * 0.9 + else if(M.stat == SOFT_CRIT) + return power * 0.5 + else if(M.IsSleeping()) + return power * 0.25 + else 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) + if(deathgasp) + M.emote("deathgasp") + M.fakedeath("regenerative_coma") + M.update_stat() + M.update_canmove() + 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_stat() + M.update_canmove() + +/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)) + 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 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_desc = "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["stealth"] >= 2) + 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)) + M.reagents.remove_reagent(/datum/reagent/water/holywater, 0.5 * absorption_coeff) + . += power * 0.75 + else if(M.reagents.has_reagent(/datum/reagent/water)) + 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) //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)) + 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_desc = "Transmission 6: Increases temperature adjustment rate and heals toxin lovers.
    \ + 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/plasmamount + + . = 0 + + if(M.loc) + environment = M.loc.return_air() + if(environment) + plasmamount = environment.gases[/datum/gas/plasma] + if(plasmamount && plasmamount > GLOB.meta_gas_visibility[/datum/gas/plasma]) //if there's enough plasma in the air to see + . += power * 0.5 + if(M.reagents.has_reagent(/datum/reagent/toxin/plasma)) + . += 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 > BODYTEMP_NORMAL) + M.adjust_bodytemperature(-20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT,BODYTEMP_NORMAL) + if(prob(5)) + to_chat(M, "You feel less hot.") + else if(M.bodytemperature < (BODYTEMP_NORMAL + 1)) + M.adjust_bodytemperature(20 * temp_rate * TEMPERATURE_DAMAGE_COEFFICIENT,0,BODYTEMP_NORMAL) + if(prob(5)) + to_chat(M, "You feel warmer.") + + M.adjustToxLoss(-heal_amt, forced = (temp_rate == 4)) + + var/list/parts = M.get_damaged_bodyparts(1,1) + 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)) + 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_desc = "Transmission 6: Additionally heals cellular damage and toxin lovers.
    \ + 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), forced = cellular_damage) + + var/list/parts = M.get_damaged_bodyparts(1,1) + + 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)) + 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 1ede16999d..b0812e0235 100644 --- a/code/datums/diseases/advance/symptoms/itching.dm +++ b/code/datums/diseases/advance/symptoms/itching.dm @@ -1,54 +1,54 @@ -/* -////////////////////////////////////// - -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_desc = "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 - var/can_scratch = scratch && !M.incapacitated() && get_location_accessible(M, picked_bodypart) - M.visible_message("[can_scratch ? "[M] scratches [M.p_their()] [bodypart.name]." : ""]", "Your [bodypart.name] itches. [can_scratch ? " You scratch it." : ""]") - if(can_scratch) +/* +////////////////////////////////////// + +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_desc = "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 + var/can_scratch = scratch && !M.incapacitated() && get_location_accessible(M, picked_bodypart) + 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) \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/oxygen.dm b/code/datums/diseases/advance/symptoms/oxygen.dm index 65f8307101..e66bdf5ee0 100644 --- a/code/datums/diseases/advance/symptoms/oxygen.dm +++ b/code/datums/diseases/advance/symptoms/oxygen.dm @@ -1,68 +1,68 @@ -/* -////////////////////////////////////// - -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_desc = "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_ratio)) - 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 - if(A.stage >= 4) +/* +////////////////////////////////////// + +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_desc = "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_ratio)) + 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 + if(A.stage >= 4) REMOVE_TRAIT(A.affected_mob, TRAIT_NOBREATH, DISEASE_TRAIT) \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/shedding.dm b/code/datums/diseases/advance/symptoms/shedding.dm index 06df320496..58357f04e2 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.hair_style == "Bald") && !(H.hair_style == "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_hair_style == "Shaved") || !(H.hair_style == "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_hair_style = "Shaved" - H.hair_style = "Bald" - else - H.hair_style = "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.hair_style == "Bald") && !(H.hair_style == "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_hair_style == "Shaved") || !(H.hair_style == "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_hair_style = "Shaved" + H.hair_style = "Bald" + else + H.hair_style = "Balding Hair" + H.update_hair() diff --git a/code/datums/diseases/advance/symptoms/shivering.dm b/code/datums/diseases/advance/symptoms/shivering.dm index e4bb5e1b3e..741e2a1e16 100644 --- a/code/datums/diseases/advance/symptoms/shivering.dm +++ b/code/datums/diseases/advance/symptoms/shivering.dm @@ -1,59 +1,59 @@ -/* -////////////////////////////////////// - -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 = 2 - transmittable = 2 - level = 2 - severity = 2 - symptom_delay_min = 10 - symptom_delay_max = 30 - var/unsafe = FALSE //over the cold threshold - threshold_desc = "Stage Speed 5: Increases cooling speed; the host can fall below safe temperature levels.
    \ - Stage Speed 10: Further increases cooling speed." - -/datum/symptom/fever/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 - -/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." )]") - if(M.bodytemperature > BODYTEMP_COLD_DAMAGE_LIMIT || unsafe) - Chill(M, A) - -/datum/symptom/shivering/proc/Chill(mob/living/M, datum/disease/advance/A) - var/get_cold = 6 * power - var/limit = BODYTEMP_COLD_DAMAGE_LIMIT + 1 - if(unsafe) - limit = 0 - M.adjust_bodytemperature(-get_cold * A.stage, limit) +/* +////////////////////////////////////// + +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 = 2 + transmittable = 2 + level = 2 + severity = 2 + symptom_delay_min = 10 + symptom_delay_max = 30 + var/unsafe = FALSE //over the cold threshold + threshold_desc = "Stage Speed 5: Increases cooling speed; the host can fall below safe temperature levels.
    \ + Stage Speed 10: Further increases cooling speed." + +/datum/symptom/fever/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 + +/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." )]") + if(M.bodytemperature > BODYTEMP_COLD_DAMAGE_LIMIT || unsafe) + Chill(M, A) + +/datum/symptom/shivering/proc/Chill(mob/living/M, datum/disease/advance/A) + var/get_cold = 6 * power + var/limit = BODYTEMP_COLD_DAMAGE_LIMIT + 1 + if(unsafe) + limit = 0 + M.adjust_bodytemperature(-get_cold * A.stage, limit) return 1 \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/sneeze.dm b/code/datums/diseases/advance/symptoms/sneeze.dm index 5e21fb3dd9..5d4d40fb95 100644 --- a/code/datums/diseases/advance/symptoms/sneeze.dm +++ b/code/datums/diseases/advance/symptoms/sneeze.dm @@ -1,52 +1,52 @@ -/* -////////////////////////////////////// - -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." - stealth = -2 - resistance = 3 - stage_speed = 0 - transmittable = 4 - level = 1 - severity = 1 - symptom_delay_min = 5 - symptom_delay_max = 35 - threshold_desc = "Transmission 9: Increases sneezing range, spreading the virus over a larger area.
    \ - Stealth 4: The symptom remains hidden until active." - -/datum/symptom/sneeze/Start(datum/disease/advance/A) - if(!..()) - return - if(A.properties["transmittable"] >= 9) //longer spread range - power = 2 - if(A.properties["stealth"] >= 4) - suppress_warning = TRUE - -/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 +/* +////////////////////////////////////// + +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." + stealth = -2 + resistance = 3 + stage_speed = 0 + transmittable = 4 + level = 1 + severity = 1 + symptom_delay_min = 5 + symptom_delay_max = 35 + threshold_desc = "Transmission 9: Increases sneezing range, spreading the virus over a larger area.
    \ + Stealth 4: The symptom remains hidden until active." + +/datum/symptom/sneeze/Start(datum/disease/advance/A) + if(!..()) + return + if(A.properties["transmittable"] >= 9) //longer spread range + power = 2 + if(A.properties["stealth"] >= 4) + suppress_warning = TRUE + +/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 A.spread(4 + power) \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/symptoms.dm b/code/datums/diseases/advance/symptoms/symptoms.dm index 99dbd397a2..fef6453b3a 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_desc = "" //Description 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. +// 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_desc = "" //Description 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 \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/voice_change.dm b/code/datums/diseases/advance/symptoms/voice_change.dm index 5f0b83e5c8..8b9b7d069a 100644 --- a/code/datums/diseases/advance/symptoms/voice_change.dm +++ b/code/datums/diseases/advance/symptoms/voice_change.dm @@ -1,81 +1,81 @@ -/* -////////////////////////////////////// - -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 - var/datum/language_holder/original_language - threshold_desc = "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 - var/mob/living/M = A.affected_mob - var/datum/language_holder/mob_language = M.get_language_holder() - original_language = mob_language.copy() - -/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) - H.remove_language(current_language) - current_language = pick(subtypesof(/datum/language) - /datum/language/common) - H.grant_language(current_language) - var/datum/language_holder/mob_language = H.get_language_holder() - mob_language.only_speaks_language = current_language - -/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) - var/mob/living/M = A.affected_mob - M.copy_known_languages_from(original_language, TRUE) - current_language = null - QDEL_NULL(original_language) +/* +////////////////////////////////////// + +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 + var/datum/language_holder/original_language + threshold_desc = "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 + var/mob/living/M = A.affected_mob + var/datum/language_holder/mob_language = M.get_language_holder() + original_language = mob_language.copy() + +/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) + H.remove_language(current_language) + current_language = pick(subtypesof(/datum/language) - /datum/language/common) + H.grant_language(current_language) + var/datum/language_holder/mob_language = H.get_language_holder() + mob_language.only_speaks_language = current_language + +/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) + var/mob/living/M = A.affected_mob + M.copy_known_languages_from(original_language, TRUE) + current_language = null + QDEL_NULL(original_language) diff --git a/code/datums/diseases/advance/symptoms/vomit.dm b/code/datums/diseases/advance/symptoms/vomit.dm index 04b0778ccd..f53638bc12 100644 --- a/code/datums/diseases/advance/symptoms/vomit.dm +++ b/code/datums/diseases/advance/symptoms/vomit.dm @@ -1,63 +1,63 @@ -/* -////////////////////////////////////// - -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 = 0 - transmittable = 1 - 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_desc = "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 = 0 + transmittable = 1 + 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_desc = "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 c97667733d..66fc90f1f8 100644 --- a/code/datums/diseases/advance/symptoms/weight.dm +++ b/code/datums/diseases/advance/symptoms/weight.dm @@ -1,51 +1,51 @@ -/* -////////////////////////////////////// - -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_desc = "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) +/* +////////////////////////////////////// + +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_desc = "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.nutrition = max(M.nutrition - 100, 0) \ No newline at end of file diff --git a/code/datums/diseases/advance/symptoms/youth.dm b/code/datums/diseases/advance/symptoms/youth.dm index 927be03ed9..d2712a0146 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 +/* +////////////////////////////////////// +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!") \ No newline at end of file diff --git a/code/datums/diseases/appendicitis.dm b/code/datums/diseases/appendicitis.dm index be7e6ceecd..7a6ea142b3 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 edd6f151c9..074bda0560 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 = list(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) +/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 = list(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 \ No newline at end of file diff --git a/code/datums/diseases/brainrot.dm b/code/datums/diseases/brainrot.dm index 1028d1fff4..57628efe36 100644 --- a/code/datums/diseases/brainrot.dm +++ b/code/datums/diseases/brainrot.dm @@ -1,59 +1,59 @@ -/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") - 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") + 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 8bbb05e17c..660793ed83 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.lying && 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.lying && 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) +/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.lying && 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.lying && 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() \ No newline at end of file diff --git a/code/datums/diseases/cold9.dm b/code/datums/diseases/cold9.dm index eea3147107..47f391ecf7 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)) +/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.") \ No newline at end of file diff --git a/code/datums/diseases/dna_spread.dm b/code/datums/diseases/dna_spread.dm index 972a7f4e18..3a67230d36 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 70bcc67d21..37628a5502 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 4fc646a590..7672118774 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.lying && 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.lying && 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.lying && 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.lying && 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 1557ddfbd8..3297877fe9 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 8a6eab6048..8ac1996855 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 cb00f571aa..29e6657e13 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 = list(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) +/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 ..() \ No newline at end of file diff --git a/code/datums/diseases/retrovirus.dm b/code/datums/diseases/retrovirus.dm index 5854249e54..160a9f98ad 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.lying && 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.lying && 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.lying && 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.lying && 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 +/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.lying && 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.lying && 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.lying && 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.lying && 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)) \ No newline at end of file diff --git a/code/datums/diseases/rhumba_beat.dm b/code/datums/diseases/rhumba_beat.dm index da6e7da8df..1016f7b64d 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(/datum/reagent/toxin/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(/datum/reagent/toxin/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 1c214c8516..bdb05346e4 100644 --- a/code/datums/diseases/transformation.dm +++ b/code/datums/diseases/transformation.dm @@ -1,327 +1,327 @@ -/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 - -/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 && jobban_isbanned(affected_mob, bantype)) - replace_banned_player(new_mob) - new_mob.a_intent = INTENT_HARM - if(affected_mob.mind) - affected_mob.mind.transfer_to(new_mob) - else - affected_mob.transfer_ckey(new_mob) - - 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 jobbaned player.") - affected_mob.ghostize(0) - C.transfer_ckey(affected_mob) - 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_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 = list(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 = "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 = list(MOB_ORGANIC, MOB_INORGANIC, MOB_UNDEAD) //magic! - -/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(/datum/reagent/pax, 5) - if(3) - if (prob(5)) - affected_mob.emote("smile") - if (prob(20)) - affected_mob.reagents.add_reagent(/datum/reagent/pax, 5) - if(4) - if (prob(5)) - affected_mob.emote("smile") - if (prob(20)) - affected_mob.reagents.add_reagent(/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 + +/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 && jobban_isbanned(affected_mob, bantype)) + replace_banned_player(new_mob) + new_mob.a_intent = INTENT_HARM + if(affected_mob.mind) + affected_mob.mind.transfer_to(new_mob) + else + affected_mob.transfer_ckey(new_mob) + + 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 jobbaned player.") + affected_mob.ghostize(0) + C.transfer_ckey(affected_mob) + 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_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 = list(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 = "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 = list(MOB_ORGANIC, MOB_INORGANIC, MOB_UNDEAD) //magic! + +/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(/datum/reagent/pax, 5) + if(3) + if (prob(5)) + affected_mob.emote("smile") + if (prob(20)) + affected_mob.reagents.add_reagent(/datum/reagent/pax, 5) + if(4) + if (prob(5)) + affected_mob.emote("smile") + if (prob(20)) + affected_mob.reagents.add_reagent(/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 244cc819d8..230a074bb1 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 mental retardation, 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), 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), SLOT_WEAR_SUIT) - 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), SLOT_SHOES) - 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 mental retardation, 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), 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), SLOT_WEAR_SUIT) + 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), SLOT_SHOES) + 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 ccc5d97cf8..67dbbdfef7 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -1,435 +1,435 @@ - -/////////////////////////// DNA DATUM -/datum/dna - var/unique_enzymes - var/struc_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/nameless = FALSE - var/custom_species //siiiiigh I guess this is important - var/list/mutations = list() //All mutations are from now on here - var/list/temporary_mutations = list() //Timers for temporary mutations - var/list/previous = list() //For temporary name/ui/ue/blood_type modifications - var/mob/living/holder - var/delete_species = TRUE //Set to FALSE when a body is scanned by a cloner to fix #38875 - -/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 - - if(delete_species) - 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.dna.features = features.Copy() - destination.set_species(species.type, icon_update=0) - destination.dna.real_name = real_name - destination.dna.nameless = nameless - destination.dna.custom_species = custom_species - destination.dna.temporary_mutations = temporary_mutations.Copy() - if(ishuman(destination)) - var/mob/living/carbon/human/H = destination - H.give_genitals(TRUE)//This gives the body the genitals of this DNA. Used for any transformations based on DNA - destination.flavor_text = destination.dna.features["flavor_text"] //Update the flavor_text to use new dna text - if(transfer_SE) - destination.dna.struc_enzymes = struc_enzymes - -/datum/dna/proc/copy_dna(datum/dna/new_dna) - new_dna.unique_enzymes = unique_enzymes - new_dna.struc_enzymes = struc_enzymes - 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.nameless = nameless - new_dna.custom_species = custom_species - new_dna.mutations = mutations.Copy() - -/datum/dna/proc/add_mutation(mutation_name) - var/datum/mutation/human/HM = GLOB.mutations_list[mutation_name] - HM.on_acquiring(holder) - -/datum/dna/proc/remove_mutation(mutation_name) - var/datum/mutation/human/HM = GLOB.mutations_list[mutation_name] - HM.on_losing(holder) - -/datum/dna/proc/check_mutation(mutation_name) - var/datum/mutation/human/HM = GLOB.mutations_list[mutation_name] - return mutations.Find(HM) - -/datum/dna/proc/remove_all_mutations() - remove_mutation_group(mutations) - -/datum/dna/proc/remove_mutation_group(list/group) - if(!group) - return - for(var/datum/mutation/human/HM in group) - HM.force_lose(holder) - -/datum/dna/proc/generate_uni_identity() - . = "" - var/list/L = new /list(DNA_UNI_IDENTITY_BLOCKS) - - L[DNA_GENDER_BLOCK] = construct_block((holder.gender!=MALE)+1, 2) - if(ishuman(holder)) - var/mob/living/carbon/human/H = holder - if(!GLOB.hair_styles_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/hair,GLOB.hair_styles_list, GLOB.hair_styles_male_list, GLOB.hair_styles_female_list) - L[DNA_HAIR_STYLE_BLOCK] = construct_block(GLOB.hair_styles_list.Find(H.hair_style), GLOB.hair_styles_list.len) - L[DNA_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.hair_color) - if(!GLOB.facial_hair_styles_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hair_styles_list, GLOB.facial_hair_styles_male_list, GLOB.facial_hair_styles_female_list) - L[DNA_FACIAL_HAIR_STYLE_BLOCK] = construct_block(GLOB.facial_hair_styles_list.Find(H.facial_hair_style), GLOB.facial_hair_styles_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) - L[DNA_COLOR_ONE_BLOCK] = sanitize_hexcolor(features["mcolor"]) - L[DNA_COLOR_TWO_BLOCK] = sanitize_hexcolor(features["mcolor2"]) - L[DNA_COLOR_THREE_BLOCK] = sanitize_hexcolor(features["mcolor3"]) - if(!GLOB.mam_tails_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list) - L[DNA_MUTANTTAIL_BLOCK] = construct_block(GLOB.mam_tails_list.Find(features["mam_tail"]), GLOB.mam_tails_list.len) - if(!GLOB.mam_ears_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list) - L[DNA_MUTANTEAR_BLOCK] = construct_block(GLOB.mam_ears_list.Find(features["mam_ears"]), GLOB.mam_ears_list.len) - if(!GLOB.mam_body_markings_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list) - L[DNA_MUTANTMARKING_BLOCK] = construct_block(GLOB.mam_body_markings_list.Find(features["mam_body_markings"]), GLOB.mam_body_markings_list.len) - if(!GLOB.taur_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/taur, GLOB.taur_list) - L[DNA_TAUR_BLOCK] = construct_block(GLOB.taur_list.Find(features["taur"]), GLOB.taur_list.len) - - 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_struc_enzymes() - var/list/sorting = new /list(DNA_STRUC_ENZYMES_BLOCKS) - var/result = "" - for(var/datum/mutation/human/A in GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations) - if(A.name == RACEMUT && ismonkey(holder)) - sorting[A.dna_block] = num2hex(A.lowest_value + rand(0, 256 * 6), DNA_BLOCK_SIZE) - mutations |= A - else - sorting[A.dna_block] = random_string(DNA_BLOCK_SIZE, list("0","1","2","3","4","5","6")) - - for(var/B in sorting) - result += B - return result - -/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) - setblock(uni_identity, blocknumber, construct_block((H.gender!=MALE)+1, 2)) - if(DNA_FACIAL_HAIR_STYLE_BLOCK) - setblock(uni_identity, blocknumber, construct_block(GLOB.facial_hair_styles_list.Find(H.facial_hair_style), GLOB.facial_hair_styles_list.len)) - if(DNA_HAIR_STYLE_BLOCK) - setblock(uni_identity, blocknumber, construct_block(GLOB.hair_styles_list.Find(H.hair_style), GLOB.hair_styles_list.len)) - if(DNA_COLOR_ONE_BLOCK) - sanitize_hexcolor(features["mcolor"]) - if(DNA_COLOR_TWO_BLOCK) - sanitize_hexcolor(features["mcolor2"]) - if(DNA_COLOR_THREE_BLOCK) - sanitize_hexcolor(features["mcolor3"]) - if(DNA_MUTANTTAIL_BLOCK) - construct_block(GLOB.mam_tails_list.Find(features["mam_tail"]), GLOB.mam_tails_list.len) - if(DNA_MUTANTEAR_BLOCK) - construct_block(GLOB.mam_ears_list.Find(features["mam_ears"]), GLOB.mam_ears_list.len) - if(DNA_MUTANTMARKING_BLOCK) - construct_block(GLOB.mam_body_markings_list.Find(features["mam_body_markings"]), GLOB.mam_body_markings_list.len) - if(DNA_TAUR_BLOCK) - construct_block(GLOB.taur_list.Find(features["taur"]), GLOB.taur_list.len) - -/datum/dna/proc/is_same_as(datum/dna/D) - if(uni_identity == D.uni_identity && struc_enzymes == D.struc_enzymes && real_name == D.real_name && nameless == D.nameless && custom_species == D.custom_species) - if(species.type == D.species.type && features == D.features && blood_type == D.blood_type) - return 1 - return 0 - -//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) - if(newblood_type) - blood_type = newblood_type - unique_enzymes = generate_unique_enzymes() - uni_identity = generate_uni_identity() - struc_enzymes = generate_struc_enzymes() - 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() - 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 - 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) - -/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) - ..() - if(icon_update) - update_body() - update_hair() - 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, se, newreal_name, newblood_type, datum/species/mrace, newfeatures) - - if(newfeatures) - dna.features = newfeatures - flavor_text = dna.features["flavor_text"] //Update the flavor_text to use new dna text - - if(mrace) - var/datum/species/newrace = new mrace.type - newrace.copy_properties_from(mrace) - set_species(newrace, icon_update=0) - - if(newreal_name) - 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(se) - dna.struc_enzymes = se - domutcheck() - - if(mrace || newfeatures || ui) - update_body() - update_hair() - update_body_parts() - update_mutations_overlay() - - -/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 - gender = (deconstruct_block(getblock(dna.uni_identity, DNA_GENDER_BLOCK), 2)-1) ? FEMALE : MALE - -/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_hair_style = GLOB.facial_hair_styles_list[deconstruct_block(getblock(structure, DNA_FACIAL_HAIR_STYLE_BLOCK), GLOB.facial_hair_styles_list.len)] - hair_style = GLOB.hair_styles_list[deconstruct_block(getblock(structure, DNA_HAIR_STYLE_BLOCK), GLOB.hair_styles_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(force_powers=0) //Set force_powers to 1 to bypass the power chance - if(!has_dna()) - return - - for(var/datum/mutation/human/A in GLOB.good_mutations | GLOB.bad_mutations | GLOB.not_good_mutations) - if(ismob(A.check_block(src, force_powers))) - return //we got monkeyized/humanized, this mob will be deleted, no need to continue. - - update_mutations_overlay() - - - -/////////////////////////// DNA HELPER-PROCS ////////////////////////////// -/proc/getleftblocks(input,blocknumber,blocksize) - if(blocknumber > 1) - return copytext(input,1,((blocksize*blocknumber)-(blocksize-1))) - -/proc/getrightblocks(input,blocknumber,blocksize) - if(blocknumber < (length(input)/blocksize)) - return copytext(input,blocksize*blocknumber+1,length(input)+1) - -/proc/getblock(input, blocknumber, blocksize=DNA_BLOCK_SIZE) - return copytext(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) - -/mob/living/carbon/proc/randmut(list/candidates, difficulty = 2) - if(!has_dna()) - return - var/datum/mutation/human/num = pick(candidates) - . = num.force_give(src) - -/mob/living/carbon/proc/randmutb() - if(!has_dna()) - return - var/datum/mutation/human/HM = pick((GLOB.bad_mutations | GLOB.not_good_mutations) - GLOB.mutations_list[RACEMUT]) - . = HM.force_give(src) - -/mob/living/carbon/proc/randmutg() - if(!has_dna()) - return - var/datum/mutation/human/HM = pick(GLOB.good_mutations) - . = HM.force_give(src) - -/mob/living/carbon/proc/randmutvg() - if(!has_dna()) - return - var/datum/mutation/human/HM = pick((GLOB.good_mutations) - GLOB.mutations_list[HULK] - GLOB.mutations_list[DWARFISM]) - . = HM.force_give(src) - -/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_STRUC_ENZYMES_BLOCKS, i++) - if(prob(probability)) - M.dna.struc_enzymes = setblock(M.dna.struc_enzymes, i, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) - 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 + +/////////////////////////// DNA DATUM +/datum/dna + var/unique_enzymes + var/struc_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/nameless = FALSE + var/custom_species //siiiiigh I guess this is important + var/list/mutations = list() //All mutations are from now on here + var/list/temporary_mutations = list() //Timers for temporary mutations + var/list/previous = list() //For temporary name/ui/ue/blood_type modifications + var/mob/living/holder + var/delete_species = TRUE //Set to FALSE when a body is scanned by a cloner to fix #38875 + +/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 + + if(delete_species) + 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.dna.features = features.Copy() + destination.set_species(species.type, icon_update=0) + destination.dna.real_name = real_name + destination.dna.nameless = nameless + destination.dna.custom_species = custom_species + destination.dna.temporary_mutations = temporary_mutations.Copy() + if(ishuman(destination)) + var/mob/living/carbon/human/H = destination + H.give_genitals(TRUE)//This gives the body the genitals of this DNA. Used for any transformations based on DNA + destination.flavor_text = destination.dna.features["flavor_text"] //Update the flavor_text to use new dna text + if(transfer_SE) + destination.dna.struc_enzymes = struc_enzymes + +/datum/dna/proc/copy_dna(datum/dna/new_dna) + new_dna.unique_enzymes = unique_enzymes + new_dna.struc_enzymes = struc_enzymes + 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.nameless = nameless + new_dna.custom_species = custom_species + new_dna.mutations = mutations.Copy() + +/datum/dna/proc/add_mutation(mutation_name) + var/datum/mutation/human/HM = GLOB.mutations_list[mutation_name] + HM.on_acquiring(holder) + +/datum/dna/proc/remove_mutation(mutation_name) + var/datum/mutation/human/HM = GLOB.mutations_list[mutation_name] + HM.on_losing(holder) + +/datum/dna/proc/check_mutation(mutation_name) + var/datum/mutation/human/HM = GLOB.mutations_list[mutation_name] + return mutations.Find(HM) + +/datum/dna/proc/remove_all_mutations() + remove_mutation_group(mutations) + +/datum/dna/proc/remove_mutation_group(list/group) + if(!group) + return + for(var/datum/mutation/human/HM in group) + HM.force_lose(holder) + +/datum/dna/proc/generate_uni_identity() + . = "" + var/list/L = new /list(DNA_UNI_IDENTITY_BLOCKS) + + L[DNA_GENDER_BLOCK] = construct_block((holder.gender!=MALE)+1, 2) + if(ishuman(holder)) + var/mob/living/carbon/human/H = holder + if(!GLOB.hair_styles_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/hair,GLOB.hair_styles_list, GLOB.hair_styles_male_list, GLOB.hair_styles_female_list) + L[DNA_HAIR_STYLE_BLOCK] = construct_block(GLOB.hair_styles_list.Find(H.hair_style), GLOB.hair_styles_list.len) + L[DNA_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.hair_color) + if(!GLOB.facial_hair_styles_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hair_styles_list, GLOB.facial_hair_styles_male_list, GLOB.facial_hair_styles_female_list) + L[DNA_FACIAL_HAIR_STYLE_BLOCK] = construct_block(GLOB.facial_hair_styles_list.Find(H.facial_hair_style), GLOB.facial_hair_styles_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) + L[DNA_COLOR_ONE_BLOCK] = sanitize_hexcolor(features["mcolor"]) + L[DNA_COLOR_TWO_BLOCK] = sanitize_hexcolor(features["mcolor2"]) + L[DNA_COLOR_THREE_BLOCK] = sanitize_hexcolor(features["mcolor3"]) + if(!GLOB.mam_tails_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list) + L[DNA_MUTANTTAIL_BLOCK] = construct_block(GLOB.mam_tails_list.Find(features["mam_tail"]), GLOB.mam_tails_list.len) + if(!GLOB.mam_ears_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list) + L[DNA_MUTANTEAR_BLOCK] = construct_block(GLOB.mam_ears_list.Find(features["mam_ears"]), GLOB.mam_ears_list.len) + if(!GLOB.mam_body_markings_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list) + L[DNA_MUTANTMARKING_BLOCK] = construct_block(GLOB.mam_body_markings_list.Find(features["mam_body_markings"]), GLOB.mam_body_markings_list.len) + if(!GLOB.taur_list.len) + init_sprite_accessory_subtypes(/datum/sprite_accessory/taur, GLOB.taur_list) + L[DNA_TAUR_BLOCK] = construct_block(GLOB.taur_list.Find(features["taur"]), GLOB.taur_list.len) + + 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_struc_enzymes() + var/list/sorting = new /list(DNA_STRUC_ENZYMES_BLOCKS) + var/result = "" + for(var/datum/mutation/human/A in GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations) + if(A.name == RACEMUT && ismonkey(holder)) + sorting[A.dna_block] = num2hex(A.lowest_value + rand(0, 256 * 6), DNA_BLOCK_SIZE) + mutations |= A + else + sorting[A.dna_block] = random_string(DNA_BLOCK_SIZE, list("0","1","2","3","4","5","6")) + + for(var/B in sorting) + result += B + return result + +/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) + setblock(uni_identity, blocknumber, construct_block((H.gender!=MALE)+1, 2)) + if(DNA_FACIAL_HAIR_STYLE_BLOCK) + setblock(uni_identity, blocknumber, construct_block(GLOB.facial_hair_styles_list.Find(H.facial_hair_style), GLOB.facial_hair_styles_list.len)) + if(DNA_HAIR_STYLE_BLOCK) + setblock(uni_identity, blocknumber, construct_block(GLOB.hair_styles_list.Find(H.hair_style), GLOB.hair_styles_list.len)) + if(DNA_COLOR_ONE_BLOCK) + sanitize_hexcolor(features["mcolor"]) + if(DNA_COLOR_TWO_BLOCK) + sanitize_hexcolor(features["mcolor2"]) + if(DNA_COLOR_THREE_BLOCK) + sanitize_hexcolor(features["mcolor3"]) + if(DNA_MUTANTTAIL_BLOCK) + construct_block(GLOB.mam_tails_list.Find(features["mam_tail"]), GLOB.mam_tails_list.len) + if(DNA_MUTANTEAR_BLOCK) + construct_block(GLOB.mam_ears_list.Find(features["mam_ears"]), GLOB.mam_ears_list.len) + if(DNA_MUTANTMARKING_BLOCK) + construct_block(GLOB.mam_body_markings_list.Find(features["mam_body_markings"]), GLOB.mam_body_markings_list.len) + if(DNA_TAUR_BLOCK) + construct_block(GLOB.taur_list.Find(features["taur"]), GLOB.taur_list.len) + +/datum/dna/proc/is_same_as(datum/dna/D) + if(uni_identity == D.uni_identity && struc_enzymes == D.struc_enzymes && real_name == D.real_name && nameless == D.nameless && custom_species == D.custom_species) + if(species.type == D.species.type && features == D.features && blood_type == D.blood_type) + return 1 + return 0 + +//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) + if(newblood_type) + blood_type = newblood_type + unique_enzymes = generate_unique_enzymes() + uni_identity = generate_uni_identity() + struc_enzymes = generate_struc_enzymes() + 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() + 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 + 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) + +/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) + ..() + if(icon_update) + update_body() + update_hair() + 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, se, newreal_name, newblood_type, datum/species/mrace, newfeatures) + + if(newfeatures) + dna.features = newfeatures + flavor_text = dna.features["flavor_text"] //Update the flavor_text to use new dna text + + if(mrace) + var/datum/species/newrace = new mrace.type + newrace.copy_properties_from(mrace) + set_species(newrace, icon_update=0) + + if(newreal_name) + 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(se) + dna.struc_enzymes = se + domutcheck() + + if(mrace || newfeatures || ui) + update_body() + update_hair() + update_body_parts() + update_mutations_overlay() + + +/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 + gender = (deconstruct_block(getblock(dna.uni_identity, DNA_GENDER_BLOCK), 2)-1) ? FEMALE : MALE + +/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_hair_style = GLOB.facial_hair_styles_list[deconstruct_block(getblock(structure, DNA_FACIAL_HAIR_STYLE_BLOCK), GLOB.facial_hair_styles_list.len)] + hair_style = GLOB.hair_styles_list[deconstruct_block(getblock(structure, DNA_HAIR_STYLE_BLOCK), GLOB.hair_styles_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(force_powers=0) //Set force_powers to 1 to bypass the power chance + if(!has_dna()) + return + + for(var/datum/mutation/human/A in GLOB.good_mutations | GLOB.bad_mutations | GLOB.not_good_mutations) + if(ismob(A.check_block(src, force_powers))) + return //we got monkeyized/humanized, this mob will be deleted, no need to continue. + + update_mutations_overlay() + + + +/////////////////////////// DNA HELPER-PROCS ////////////////////////////// +/proc/getleftblocks(input,blocknumber,blocksize) + if(blocknumber > 1) + return copytext(input,1,((blocksize*blocknumber)-(blocksize-1))) + +/proc/getrightblocks(input,blocknumber,blocksize) + if(blocknumber < (length(input)/blocksize)) + return copytext(input,blocksize*blocknumber+1,length(input)+1) + +/proc/getblock(input, blocknumber, blocksize=DNA_BLOCK_SIZE) + return copytext(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) + +/mob/living/carbon/proc/randmut(list/candidates, difficulty = 2) + if(!has_dna()) + return + var/datum/mutation/human/num = pick(candidates) + . = num.force_give(src) + +/mob/living/carbon/proc/randmutb() + if(!has_dna()) + return + var/datum/mutation/human/HM = pick((GLOB.bad_mutations | GLOB.not_good_mutations) - GLOB.mutations_list[RACEMUT]) + . = HM.force_give(src) + +/mob/living/carbon/proc/randmutg() + if(!has_dna()) + return + var/datum/mutation/human/HM = pick(GLOB.good_mutations) + . = HM.force_give(src) + +/mob/living/carbon/proc/randmutvg() + if(!has_dna()) + return + var/datum/mutation/human/HM = pick((GLOB.good_mutations) - GLOB.mutations_list[HULK] - GLOB.mutations_list[DWARFISM]) + . = HM.force_give(src) + +/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_STRUC_ENZYMES_BLOCKS, i++) + if(prob(probability)) + M.dna.struc_enzymes = setblock(M.dna.struc_enzymes, i, random_string(DNA_BLOCK_SIZE, GLOB.hex_characters)) + 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 diff --git a/code/datums/forced_movement.dm b/code/datums/forced_movement.dm index 3cdf2b1fd8..33ea4a177b 100644 --- a/code/datums/forced_movement.dm +++ b/code/datums/forced_movement.dm @@ -1,90 +1,90 @@ -//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) - 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) + 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/getrev.dm b/code/datums/helper_datums/getrev.dm index c73d359bf1..420602cc7f 100644 --- a/code/datums/helper_datums/getrev.dm +++ b/code/datums/helper_datums/getrev.dm @@ -1,122 +1,122 @@ -/datum/getrev - var/commit // git rev-parse HEAD - var/date - var/originmastercommit // git rev-parse origin/master - var/list/testmerge = list() - -/datum/getrev/New() - testmerge = world.TgsTestMerges() - var/datum/tgs_revision_information/revinfo = world.TgsRevision() - if(revinfo) - commit = revinfo.commit - originmastercommit = revinfo.origin_commit - else - commit = rustg_git_revparse("HEAD") - if(commit) - date = rustg_git_commit_date(commit) - originmastercommit = rustg_git_revparse("origin/master") - - // 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.commit]" - - 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(cm, 1, min(length(cm), 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()) - msg += "Server tools version: [world.TgsVersion()]" - - // 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]%" +/datum/getrev + var/commit // git rev-parse HEAD + var/date + var/originmastercommit // git rev-parse origin/master + var/list/testmerge = list() + +/datum/getrev/New() + testmerge = world.TgsTestMerges() + var/datum/tgs_revision_information/revinfo = world.TgsRevision() + if(revinfo) + commit = revinfo.commit + originmastercommit = revinfo.origin_commit + else + commit = rustg_git_revparse("HEAD") + if(commit) + date = rustg_git_commit_date(commit) + originmastercommit = rustg_git_revparse("origin/master") + + // 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.commit]" + + 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(cm, 1, min(length(cm), 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()) + msg += "Server tools version: [world.TgsVersion()]" + + // 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("
    ")) \ No newline at end of file diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index 74f22b5ac0..1c2ce851fe 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -1,167 +1,167 @@ -// 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 -// no_effects: disable the default effectin/effectout of sparks -// forceMove: if false, teleport will use Move() proc (dense objects will prevent teleportation) -// 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 && (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(ismegafauna(teleatom)) - message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_VERBOSEJMP(curturf)] to [ADMIN_VERBOSEJMP(destturf)].") - SEND_SIGNAL(teleatom, COMSIG_MOVABLE_TELEPORTED, channel, curturf, destturf) - - 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, 1) - 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] >= 16) - continue - if(A_gases[/datum/gas/plasma]) - continue - if(A_gases[/datum/gas/carbon_dioxide] >= 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) - return safepick(get_teleport_turfs(center, precision)) +// 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 +// no_effects: disable the default effectin/effectout of sparks +// forceMove: if false, teleport will use Move() proc (dense objects will prevent teleportation) +// 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 && (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(ismegafauna(teleatom)) + message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_VERBOSEJMP(curturf)] to [ADMIN_VERBOSEJMP(destturf)].") + SEND_SIGNAL(teleatom, COMSIG_MOVABLE_TELEPORTED, channel, curturf, destturf) + + 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, 1) + 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] >= 16) + continue + if(A_gases[/datum/gas/plasma]) + continue + if(A_gases[/datum/gas/carbon_dioxide] >= 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) + return safepick(get_teleport_turfs(center, precision)) diff --git a/code/datums/helper_datums/topic_input.dm b/code/datums/helper_datums/topic_input.dm index 3e43905f1f..d07aec067e 100644 --- a/code/datums/helper_datums/topic_input.dm +++ b/code/datums/helper_datums/topic_input.dm @@ -1,62 +1,62 @@ -/datum/topic_input - var/href - var/list/href_list - -/datum/topic_input/New(thref,list/thref_list) - href = thref - href_list = thref_list.Copy() - return - -/datum/topic_input/proc/get(i) - return listgetindex(href_list,i) - -/datum/topic_input/proc/getAndLocate(i) - var/t = get(i) - if(t) - t = locate(t) - if (istext(t)) - t = null - return t || null - -/datum/topic_input/proc/getNum(i) - var/t = get(i) - if(t) - t = text2num(t) - return isnum(t) ? t : null - -/datum/topic_input/proc/getObj(i) - var/t = getAndLocate(i) - return isobj(t) ? t : null - -/datum/topic_input/proc/getMob(i) - var/t = getAndLocate(i) - return ismob(t) ? t : null - -/datum/topic_input/proc/getTurf(i) - var/t = getAndLocate(i) - return isturf(t) ? t : null - -/datum/topic_input/proc/getAtom(i) - return getType(i, /atom) - -/datum/topic_input/proc/getArea(i) - var/t = getAndLocate(i) - return isarea(t) ? t : null - -/datum/topic_input/proc/getStr(i)//params should always be text, but... - var/t = get(i) - return istext(t) ? t : null - -/datum/topic_input/proc/getType(i,type) - var/t = getAndLocate(i) - return istype(t,type) ? t : null - -/datum/topic_input/proc/getPath(i) - var/t = get(i) - if(t) - t = text2path(t) - return ispath(t) ? t : null - -/datum/topic_input/proc/getList(i) - var/t = getAndLocate(i) - return islist(t) ? t : null +/datum/topic_input + var/href + var/list/href_list + +/datum/topic_input/New(thref,list/thref_list) + href = thref + href_list = thref_list.Copy() + return + +/datum/topic_input/proc/get(i) + return listgetindex(href_list,i) + +/datum/topic_input/proc/getAndLocate(i) + var/t = get(i) + if(t) + t = locate(t) + if (istext(t)) + t = null + return t || null + +/datum/topic_input/proc/getNum(i) + var/t = get(i) + if(t) + t = text2num(t) + return isnum(t) ? t : null + +/datum/topic_input/proc/getObj(i) + var/t = getAndLocate(i) + return isobj(t) ? t : null + +/datum/topic_input/proc/getMob(i) + var/t = getAndLocate(i) + return ismob(t) ? t : null + +/datum/topic_input/proc/getTurf(i) + var/t = getAndLocate(i) + return isturf(t) ? t : null + +/datum/topic_input/proc/getAtom(i) + return getType(i, /atom) + +/datum/topic_input/proc/getArea(i) + var/t = getAndLocate(i) + return isarea(t) ? t : null + +/datum/topic_input/proc/getStr(i)//params should always be text, but... + var/t = get(i) + return istext(t) ? t : null + +/datum/topic_input/proc/getType(i,type) + var/t = getAndLocate(i) + return istype(t,type) ? t : null + +/datum/topic_input/proc/getPath(i) + var/t = get(i) + if(t) + t = text2path(t) + return ispath(t) ? t : null + +/datum/topic_input/proc/getList(i) + var/t = getAndLocate(i) + return islist(t) ? t : null diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm index 4923609db8..5a4600432d 100644 --- a/code/datums/holocall.dm +++ b/code/datums/holocall.dm @@ -1,352 +1,352 @@ -#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/aiEye/remote/holo/setLoc() - . = ..() - var/obj/machinery/holopad/H = origin - H.move_hologram(eye_user, loc) - -//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/aiEye/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 - -//creates a holocall made by `caller` from `calling_pad` to `callees` -/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees) - call_start_time = world.time - user = caller - calling_pad.outgoing_call = src - calling_holopad = calling_pad - dialed_holopads = list() - - for(var/I in callees) - var/obj/machinery/holopad/H = I - if(!QDELETED(H) && H.is_operational()) - dialed_holopads += H - 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) - - var/user_good = !QDELETED(user) - if(user_good) - user.reset_perspective() - user.remote_control = null - - 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.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 - - 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) - -//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.") - calling_holopad.temp = "" - - 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 - materials = list(MAT_METAL = 100, MAT_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+1) - 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/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 +#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/aiEye/remote/holo/setLoc() + . = ..() + var/obj/machinery/holopad/H = origin + H.move_hologram(eye_user, loc) + +//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/aiEye/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 + +//creates a holocall made by `caller` from `calling_pad` to `callees` +/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees) + call_start_time = world.time + user = caller + calling_pad.outgoing_call = src + calling_holopad = calling_pad + dialed_holopads = list() + + for(var/I in callees) + var/obj/machinery/holopad/H = I + if(!QDELETED(H) && H.is_operational()) + dialed_holopads += H + 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) + + var/user_good = !QDELETED(user) + if(user_good) + user.reset_perspective() + user.remote_control = null + + 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.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 + + 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) + +//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.") + calling_holopad.temp = "" + + 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 + materials = list(MAT_METAL = 100, MAT_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+1) + 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/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 diff --git a/code/datums/hud.dm b/code/datums/hud.dm index 169b08dc98..e2d9bc579f 100644 --- a/code/datums/hud.dm +++ b/code/datums/hud.dm @@ -1,130 +1,130 @@ -/* 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(), - 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_CLOCKWORK = new/datum/atom_hud/antag(), - ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_BLOODSUCKER = new/datum/atom_hud/antag/bloodsucker() - )) - -/datum/atom_hud - var/list/atom/hudatoms = list() //list of all atoms which display this hud - var/list/mob/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 - -/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/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]) - 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(), + 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_CLOCKWORK = new/datum/atom_hud/antag(), + ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(), + ANTAG_HUD_BLOODSUCKER = new/datum/atom_hud/antag/bloodsucker() + )) + +/datum/atom_hud + var/list/atom/hudatoms = list() //list of all atoms which display this hud + var/list/mob/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 + +/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/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]) + 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 c859f8127d..5fcd1ae93c 100644 --- a/code/datums/map_config.dm +++ b/code/datums/map_config.dm @@ -1,163 +1,163 @@ -//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/boxstation.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/max_round_search_span = 0 //If this is nonzero, then if the map has been played more than max_rounds_played within the search span (max determined by define in persistence.dm), this map won't be available. - var/max_rounds_played = 0 - - // Config actually from the JSON - should default to Box - var/map_name = "Box Station" - var/map_path = "map_files/BoxStation" - var/map_file = "BoxStation.dmm" - - var/traits = null - var/space_ruin_levels = 2 - var/space_empty_levels = 1 - - var/minetype = "lavaland" - - var/maptype = MAP_TYPE_STATION //This should be used to adjust ingame behavior depending on the specific type of map being played. For instance, if an overmap were added, it'd be appropriate for it to only generate with a MAP_TYPE_SHIP - - var/announcertype = "standard" //Determines the announcer the map uses. standard uses the default announcer, classic, but has a random chance to use other similarly-themed announcers, like medibot - - var/allow_custom_shuttles = TRUE - var/shuttles = list( - "cargo" = "cargo_box", - "ferry" = "ferry_fancy", - "whiteship" = "whiteship_box", - "emergency" = "emergency_box") - - var/year_offset = 540 //The offset of ingame year from the actual IRL year. You know you want to make a map that takes place in the 90's. Don't lie. - -/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": "BoxStation.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 - - temp = json["year_offset"] - if (isnum(temp)) - year_offset = temp - else if (!isnull(temp)) - log_world("map_config year_offset is not a number!") - return - - if ("minetype" in json) - minetype = json["minetype"] - - if ("maptype" in json) - maptype = json["maptype"] - - if ("announcertype" in json) - announcertype = json["announcertype"] - - 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/boxstation.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/max_round_search_span = 0 //If this is nonzero, then if the map has been played more than max_rounds_played within the search span (max determined by define in persistence.dm), this map won't be available. + var/max_rounds_played = 0 + + // Config actually from the JSON - should default to Box + var/map_name = "Box Station" + var/map_path = "map_files/BoxStation" + var/map_file = "BoxStation.dmm" + + var/traits = null + var/space_ruin_levels = 2 + var/space_empty_levels = 1 + + var/minetype = "lavaland" + + var/maptype = MAP_TYPE_STATION //This should be used to adjust ingame behavior depending on the specific type of map being played. For instance, if an overmap were added, it'd be appropriate for it to only generate with a MAP_TYPE_SHIP + + var/announcertype = "standard" //Determines the announcer the map uses. standard uses the default announcer, classic, but has a random chance to use other similarly-themed announcers, like medibot + + var/allow_custom_shuttles = TRUE + var/shuttles = list( + "cargo" = "cargo_box", + "ferry" = "ferry_fancy", + "whiteship" = "whiteship_box", + "emergency" = "emergency_box") + + var/year_offset = 540 //The offset of ingame year from the actual IRL year. You know you want to make a map that takes place in the 90's. Don't lie. + +/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": "BoxStation.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 + + temp = json["year_offset"] + if (isnum(temp)) + year_offset = temp + else if (!isnull(temp)) + log_world("map_config year_offset is not a number!") + return + + if ("minetype" in json) + minetype = json["minetype"] + + if ("maptype" in json) + maptype = json["maptype"] + + if ("announcertype" in json) + announcertype = json["announcertype"] + + 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 8ae45ff720..42f18b8ebb 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -1,780 +1,780 @@ -/* 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), use this mob proc. - - mob.transfer_ckey(new_mob) - - 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/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 = 0 // 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/isholy = FALSE //is this person a chaplain or admin role allowed to use bibles - - 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/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator - - var/list/learned_recipes //List of learned recipe TYPES. - -/datum/mind/New(var/key) - src.key = key - soulOwner = src - martial_art = default_martial_art - -/datum/mind/Destroy() - SSticker.minds -= src - if(islist(antag_datums)) - for(var/i in antag_datums) - var/datum/antagonist/antag_datum = i - if(antag_datum.delete_on_mind_deletion) - qdel(i) - antag_datums = null - return ..() - -/datum/mind/proc/get_language_holder() - if(!language_holder) - var/datum/language_holder/L = current.get_language_holder(shadow=FALSE) - language_holder = L.copy(src) - - return language_holder - -/datum/mind/proc/transfer_to(mob/new_character, var/force_key_move = 0) - var/old_character = current - var/signals = SEND_SIGNAL(new_character, COMSIG_MOB_PRE_PLAYER_CHANGE, new_character, old_character) | SEND_SIGNAL(src, COMSIG_PRE_MIND_TRANSFER, new_character, old_character) - if(signals & COMPONENT_STOP_MIND_TRANSFER) - return - if(current) // remove ourself from our old body's mind variable - current.mind = null - SStgui.on_transfer(current, new_character) - if(iscarbon(current)) - var/mob/living/carbon/C = current - if(C.combatmode) - C.toggle_combat_mode(TRUE, TRUE) - if(!language_holder) - var/datum/language_holder/mob_holder = new_character.get_language_holder(shadow = FALSE) - language_holder = mob_holder.copy(src) - - 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(TRUE, TRUE) //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) - if(active || force_key_move) - new_character.key = key //now transfer the key to link the client to our new body - -//CIT CHANGE - makes arousal update when transfering bodies - if(isliving(new_character)) //New humans and such are by default enabled arousal. Let's always use the new mind's prefs. - var/mob/living/L = new_character - if(L.client && L.client.prefs) - L.canbearoused = L.client.prefs.arousable //Technically this should make taking over a character mean the body gain the new minds setting... - L.update_arousal_hud() //Removes the old icon - if (L.client.prefs.auto_ooc) - if (L.client.prefs.chat_toggles & CHAT_OOC) - L.client.prefs.chat_toggles ^= CHAT_OOC - - SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character) - SEND_SIGNAL(new_character, COMSIG_MOB_ON_NEW_MIND) - -/datum/mind/proc/store_memory(new_text) - if((length(memory) + length(new_text)) <= MAX_MESSAGE_LEN) - 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() - 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) - SSticker.mode.update_brother_icons_removed(src) - -/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() - SSticker.mode.update_cult_icons_removed(src) - -/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 - if(!P) // I do not have a pen. - var/obj/item/pen/inowhaveapen - if(istype(traitor_mob.back,/obj/item/storage)) //ok buddy you better have a backpack! - inowhaveapen = new /obj/item/pen(traitor_mob.back) - else - inowhaveapen = new /obj/item/pen(traitor_mob.loc) - traitor_mob.put_in_hands(inowhaveapen) // I hope you don't have arms and your traitor pen gets stolen for all this trouble you've caused. - P = inowhaveapen - - 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) - uplink_loc = PDA - if(!uplink_loc) - uplink_loc = R - - 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 [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_servant_of_ratvar(creator)) - add_servant_of_ratvar(current) - - 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 creators 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 get_all_jobs() - if (!new_role) - return - assigned_role = new_role - - else if (href_list["memory_edit"]) - var/new_memo = copytext(sanitize(input("Write new memory", "Memory", memory) as null|message),1,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 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 - - var/static/list/choices - if(!choices) - choices = list() - - var/list/allowed_types = list( - /datum/objective/assassinate, - /datum/objective/maroon, - /datum/objective/debrain, - /datum/objective/protect, - /datum/objective/destroy, - /datum/objective/hijack, - /datum/objective/escape, - /datum/objective/survive, - /datum/objective/martyr, - /datum/objective/steal, - /datum/objective/download, - /datum/objective/nuclear, - /datum/objective/capture, - /datum/objective/absorb, - /datum/objective/custom - ) - - for(var/T in allowed_types) - var/datum/objective/X = T - choices[initial(X.name)] = T - - if(old_objective) - if(old_objective.name in choices) - def_value = old_objective.name - - var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in choices - selected_type = choices[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)) - break - if(!objective) - to_chat(usr,"Invalid objective.") - return - qdel(objective) //TODO: Needs cleaning objective destroys (whatever that means) - 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_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) - var/datum/antagonist/changeling/changeling = new_character.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling &&(ishuman(new_character) || ismonkey(new_character))) - for(var/P in changeling.purchasedpowers) - var/obj/effect/proc_holder/changeling/I = P - I.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) - for(var/mob/dead/observer/G in 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(var/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 - SEND_SIGNAL(src, COMSIG_MOB_ON_NEW_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), use this mob proc. + + mob.transfer_ckey(new_mob) + + 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/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 = 0 // 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/isholy = FALSE //is this person a chaplain or admin role allowed to use bibles + + 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/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator + + var/list/learned_recipes //List of learned recipe TYPES. + +/datum/mind/New(var/key) + src.key = key + soulOwner = src + martial_art = default_martial_art + +/datum/mind/Destroy() + SSticker.minds -= src + if(islist(antag_datums)) + for(var/i in antag_datums) + var/datum/antagonist/antag_datum = i + if(antag_datum.delete_on_mind_deletion) + qdel(i) + antag_datums = null + return ..() + +/datum/mind/proc/get_language_holder() + if(!language_holder) + var/datum/language_holder/L = current.get_language_holder(shadow=FALSE) + language_holder = L.copy(src) + + return language_holder + +/datum/mind/proc/transfer_to(mob/new_character, var/force_key_move = 0) + var/old_character = current + var/signals = SEND_SIGNAL(new_character, COMSIG_MOB_PRE_PLAYER_CHANGE, new_character, old_character) | SEND_SIGNAL(src, COMSIG_PRE_MIND_TRANSFER, new_character, old_character) + if(signals & COMPONENT_STOP_MIND_TRANSFER) + return + if(current) // remove ourself from our old body's mind variable + current.mind = null + SStgui.on_transfer(current, new_character) + if(iscarbon(current)) + var/mob/living/carbon/C = current + if(C.combatmode) + C.toggle_combat_mode(TRUE, TRUE) + if(!language_holder) + var/datum/language_holder/mob_holder = new_character.get_language_holder(shadow = FALSE) + language_holder = mob_holder.copy(src) + + 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(TRUE, TRUE) //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) + if(active || force_key_move) + new_character.key = key //now transfer the key to link the client to our new body + +//CIT CHANGE - makes arousal update when transfering bodies + if(isliving(new_character)) //New humans and such are by default enabled arousal. Let's always use the new mind's prefs. + var/mob/living/L = new_character + if(L.client && L.client.prefs) + L.canbearoused = L.client.prefs.arousable //Technically this should make taking over a character mean the body gain the new minds setting... + L.update_arousal_hud() //Removes the old icon + if (L.client.prefs.auto_ooc) + if (L.client.prefs.chat_toggles & CHAT_OOC) + L.client.prefs.chat_toggles ^= CHAT_OOC + + SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character) + SEND_SIGNAL(new_character, COMSIG_MOB_ON_NEW_MIND) + +/datum/mind/proc/store_memory(new_text) + if((length(memory) + length(new_text)) <= MAX_MESSAGE_LEN) + 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() + 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) + SSticker.mode.update_brother_icons_removed(src) + +/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() + SSticker.mode.update_cult_icons_removed(src) + +/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 + if(!P) // I do not have a pen. + var/obj/item/pen/inowhaveapen + if(istype(traitor_mob.back,/obj/item/storage)) //ok buddy you better have a backpack! + inowhaveapen = new /obj/item/pen(traitor_mob.back) + else + inowhaveapen = new /obj/item/pen(traitor_mob.loc) + traitor_mob.put_in_hands(inowhaveapen) // I hope you don't have arms and your traitor pen gets stolen for all this trouble you've caused. + P = inowhaveapen + + 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) + uplink_loc = PDA + if(!uplink_loc) + uplink_loc = R + + 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 [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_servant_of_ratvar(creator)) + add_servant_of_ratvar(current) + + 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 creators 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 get_all_jobs() + if (!new_role) + return + assigned_role = new_role + + else if (href_list["memory_edit"]) + var/new_memo = copytext(sanitize(input("Write new memory", "Memory", memory) as null|message),1,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 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 + + var/static/list/choices + if(!choices) + choices = list() + + var/list/allowed_types = list( + /datum/objective/assassinate, + /datum/objective/maroon, + /datum/objective/debrain, + /datum/objective/protect, + /datum/objective/destroy, + /datum/objective/hijack, + /datum/objective/escape, + /datum/objective/survive, + /datum/objective/martyr, + /datum/objective/steal, + /datum/objective/download, + /datum/objective/nuclear, + /datum/objective/capture, + /datum/objective/absorb, + /datum/objective/custom + ) + + for(var/T in allowed_types) + var/datum/objective/X = T + choices[initial(X.name)] = T + + if(old_objective) + if(old_objective.name in choices) + def_value = old_objective.name + + var/selected_type = input("Select objective type:", "Objective type", def_value) as null|anything in choices + selected_type = choices[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)) + break + if(!objective) + to_chat(usr,"Invalid objective.") + return + qdel(objective) //TODO: Needs cleaning objective destroys (whatever that means) + 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_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) + var/datum/antagonist/changeling/changeling = new_character.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling &&(ishuman(new_character) || ismonkey(new_character))) + for(var/P in changeling.purchasedpowers) + var/obj/effect/proc_holder/changeling/I = P + I.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) + for(var/mob/dead/observer/G in 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(var/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 + SEND_SIGNAL(src, COMSIG_MOB_ON_NEW_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/drug_events.dm b/code/datums/mood_events/drug_events.dm index 1a681a8a4d..a5d7dc631d 100644 --- a/code/datums/mood_events/drug_events.dm +++ b/code/datums/mood_events/drug_events.dm @@ -1,115 +1,115 @@ -/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 = 3600 - -/datum/mood_event/overdose - mood_change = -8 - timeout = 3000 - -/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 [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 = "I can't feel anything and I never want this to end.\n" - mood_change = 10 - -/datum/mood_event/happiness_drug_good_od - description = "YES! YES!! YES!!!\n" - mood_change = 20 - timeout = 300 - //special_screen_obj = "mood_happiness_good" Originally in tg, but I personally think they look dumb - -/datum/mood_event/happiness_drug_bad_od - description = "NO! NO!! NO!!!\n" - mood_change = -20 - timeout = 300 - //special_screen_obj = "mood_happiness_bad" Originally in tg - -/datum/mood_event/eigenstate - mood_change = -3 - description = "Where the hell am I? Is this an alternative dimension ?\n" - -/datum/mood_event/enthrall - mood_change = 5 - -/datum/mood_event/enthrall/add_effects(message) - description = "[message]\n" - -/datum/mood_event/enthrallpraise - mood_change = 10 - timeout = 1 MINUTES - -/datum/mood_event/enthrallpraise/add_effects(message) - description = "[message]\n" - -/datum/mood_event/enthrallscold - mood_change = -10 - timeout = 1 MINUTES - -/datum/mood_event/enthrallscold/add_effects(message) - description = "[message]\n"//aaa I'm not kinky enough for this - -/datum/mood_event/enthrallmissing1 - mood_change = -5 - -/datum/mood_event/enthrallmissing1/add_effects(message) - description = "[message]\n" - -/datum/mood_event/enthrallmissing2 - mood_change = -10 - -/datum/mood_event/enthrallmissing2/add_effects(message) - description = "[message]\n" - -/datum/mood_event/enthrallmissing3 - mood_change = -15 - -/datum/mood_event/enthrallmissing3/add_effects(message) - description = "[message]\n" - -/datum/mood_event/enthrallmissing4 - mood_change = -25 - -/datum/mood_event/enthrallmissing4/add_effects(message) - description = "[message]\n" - -/datum/mood_event/InLove - mood_change = 10 - -/datum/mood_event/InLove/add_effects(message) - description = "[message]\n" - -/datum/mood_event/MissingLove - mood_change = -10 - -/datum/mood_event/MissingLove/add_effects(message) - description = "[message]\n" +/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 = 3600 + +/datum/mood_event/overdose + mood_change = -8 + timeout = 3000 + +/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 [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 = "I can't feel anything and I never want this to end.\n" + mood_change = 10 + +/datum/mood_event/happiness_drug_good_od + description = "YES! YES!! YES!!!\n" + mood_change = 20 + timeout = 300 + //special_screen_obj = "mood_happiness_good" Originally in tg, but I personally think they look dumb + +/datum/mood_event/happiness_drug_bad_od + description = "NO! NO!! NO!!!\n" + mood_change = -20 + timeout = 300 + //special_screen_obj = "mood_happiness_bad" Originally in tg + +/datum/mood_event/eigenstate + mood_change = -3 + description = "Where the hell am I? Is this an alternative dimension ?\n" + +/datum/mood_event/enthrall + mood_change = 5 + +/datum/mood_event/enthrall/add_effects(message) + description = "[message]\n" + +/datum/mood_event/enthrallpraise + mood_change = 10 + timeout = 1 MINUTES + +/datum/mood_event/enthrallpraise/add_effects(message) + description = "[message]\n" + +/datum/mood_event/enthrallscold + mood_change = -10 + timeout = 1 MINUTES + +/datum/mood_event/enthrallscold/add_effects(message) + description = "[message]\n"//aaa I'm not kinky enough for this + +/datum/mood_event/enthrallmissing1 + mood_change = -5 + +/datum/mood_event/enthrallmissing1/add_effects(message) + description = "[message]\n" + +/datum/mood_event/enthrallmissing2 + mood_change = -10 + +/datum/mood_event/enthrallmissing2/add_effects(message) + description = "[message]\n" + +/datum/mood_event/enthrallmissing3 + mood_change = -15 + +/datum/mood_event/enthrallmissing3/add_effects(message) + description = "[message]\n" + +/datum/mood_event/enthrallmissing4 + mood_change = -25 + +/datum/mood_event/enthrallmissing4/add_effects(message) + description = "[message]\n" + +/datum/mood_event/InLove + mood_change = 10 + +/datum/mood_event/InLove/add_effects(message) + description = "[message]\n" + +/datum/mood_event/MissingLove + mood_change = -10 + +/datum/mood_event/MissingLove/add_effects(message) + description = "[message]\n" diff --git a/code/datums/mood_events/mood_event.dm b/code/datums/mood_events/mood_event.dm index dfe3049bdd..c125ba054a 100644 --- a/code/datums/mood_events/mood_event.dm +++ b/code/datums/mood_events/mood_event.dm @@ -1,20 +1,20 @@ -/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/mob/owner - -/datum/mood_event/New(mob/M, param) - owner = M - add_effects(param) - -/datum/mood_event/Destroy() - remove_effects() - 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/mob/owner + +/datum/mood_event/New(mob/M, param) + owner = M + add_effects(param) + +/datum/mood_event/Destroy() + remove_effects() + return ..() + +/datum/mood_event/proc/add_effects(param) + return + +/datum/mood_event/proc/remove_effects() + return diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm index eb58f2aa92..962681eb94 100644 --- a/code/datums/mood_events/needs_events.dm +++ b/code/datums/mood_events/needs_events.dm @@ -1,62 +1,62 @@ -//nutrition -/datum/mood_event/fat - description = "I'm so fat...\n" //muh fatshaming - mood_change = -4 - -/datum/mood_event/wellfed - description = "I'm stuffed!\n" - mood_change = 6 - -/datum/mood_event/fed - description = "I have recently had some food.\n" - mood_change = 3 - -/datum/mood_event/hungry - description = "I'm getting a bit hungry.\n" - mood_change = -8 - -/datum/mood_event/starving - description = "I'm starving!\n" - mood_change = -15 - -//Disgust -/datum/mood_event/gross - description = "I saw something gross.\n" - mood_change = -2 - -/datum/mood_event/verygross - description = "I think I'm going to puke...\n" - mood_change = -5 - -/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 = -3 - -/datum/mood_event/disgust/nauseating_stench - description = "The stench of rotting carcasses is unbearable!\n" - mood_change = -7 - -//Generic needs events -/datum/mood_event/favorite_food - description = "I really enjoyed eating that.\n" - mood_change = 3 - timeout = 2400 - -/datum/mood_event/gross_food - description = "I really didn't like that food.\n" - mood_change = -2 - timeout = 2400 - -/datum/mood_event/disgusting_food - description = "That food was disgusting!\n" - mood_change = -4 - timeout = 2400 - -/datum/mood_event/nice_shower - description = "I have recently had a nice shower.\n" - mood_change = 2 - timeout = 3 MINUTES +//nutrition +/datum/mood_event/fat + description = "I'm so fat...\n" //muh fatshaming + mood_change = -4 + +/datum/mood_event/wellfed + description = "I'm stuffed!\n" + mood_change = 6 + +/datum/mood_event/fed + description = "I have recently had some food.\n" + mood_change = 3 + +/datum/mood_event/hungry + description = "I'm getting a bit hungry.\n" + mood_change = -8 + +/datum/mood_event/starving + description = "I'm starving!\n" + mood_change = -15 + +//Disgust +/datum/mood_event/gross + description = "I saw something gross.\n" + mood_change = -2 + +/datum/mood_event/verygross + description = "I think I'm going to puke...\n" + mood_change = -5 + +/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 = -3 + +/datum/mood_event/disgust/nauseating_stench + description = "The stench of rotting carcasses is unbearable!\n" + mood_change = -7 + +//Generic needs events +/datum/mood_event/favorite_food + description = "I really enjoyed eating that.\n" + mood_change = 3 + timeout = 2400 + +/datum/mood_event/gross_food + description = "I really didn't like that food.\n" + mood_change = -2 + timeout = 2400 + +/datum/mood_event/disgusting_food + description = "That food was disgusting!\n" + mood_change = -4 + timeout = 2400 + +/datum/mood_event/nice_shower + description = "I have recently had a nice shower.\n" + mood_change = 2 + timeout = 3 MINUTES diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm index a33d0d2225..44ff87e8ae 100644 --- a/code/datums/position_point_vector.dm +++ b/code/datums/position_point_vector.dm @@ -1,225 +1,225 @@ -//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) - -/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) + +/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/spawners_menu.dm b/code/datums/spawners_menu.dm index ebbe08c525..bb7dc4cdb7 100644 --- a/code/datums/spawners_menu.dm +++ b/code/datums/spawners_menu.dm @@ -1,54 +1,54 @@ -/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, "spawners_menu", "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["desc"] = "" - 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["desc"] = MS.flavour_text - 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/spawner_ref = pick(GLOB.mob_spawners[params["name"]]) - var/obj/effect/mob_spawn/MS = locate(spawner_ref) in GLOB.poi_list - if(!MS) - return - - switch(action) - if("jump") - if(MS) - owner.forceMove(get_turf(MS)) - . = TRUE - if("spawn") - if(MS) - MS.attack_ghost(owner) +/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, "spawners_menu", "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["desc"] = "" + 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["desc"] = MS.flavour_text + 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/spawner_ref = pick(GLOB.mob_spawners[params["name"]]) + var/obj/effect/mob_spawn/MS = locate(spawner_ref) in GLOB.poi_list + if(!MS) + return + + switch(action) + if("jump") + if(MS) + owner.forceMove(get_turf(MS)) + . = TRUE + if("spawn") + if(MS) + MS.attack_ghost(owner) . = TRUE \ No newline at end of file diff --git a/code/datums/verbs.dm b/code/datums/verbs.dm index d8d7ee7433..442997288a 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 (copytext(verbpath.name,1,2) == "@") - entry["command"] = copytext(verbpath.name,2) - 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 (copytext(verbpath.name,1,2) == "@") + entry["command"] = copytext(verbpath.name,2) + 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/airlock.dm b/code/datums/wires/airlock.dm index ac4473845d..315cfa59d6 100644 --- a/code/datums/wires/airlock.dm +++ b/code/datums/wires/airlock.dm @@ -1,186 +1,186 @@ -/datum/wires/airlock - holder_type = /obj/machinery/door/airlock - proper_name = "Generic Airlock" - var/wiretype - -/datum/wires/airlock/secure - randomize = TRUE - -/datum/wires/airlock/command - proper_name = "Command Airlock" - wiretype = "commandairlock" - -/datum/wires/airlock/security - proper_name = "Security Airlock" - wiretype = "securityairlock" - -/datum/wires/airlock/engineering - proper_name = "Engineering Airlock" - wiretype = "engineeringairlock" - -/datum/wires/airlock/science - proper_name = "Science Airlock" - wiretype = "scienceairlock" - -/datum/wires/airlock/medical - proper_name = "Medical Airlock" - wiretype = "medicalairlock" - -/datum/wires/airlock/cargo - proper_name = "Cargo Airlock" - wiretype = "cargoairlock" - -/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) - . = ..() - if(randomize || !wiretype) - return - if(!GLOB.wire_color_directory[wiretype]) - colors = list() - randomize() - GLOB.wire_color_directory[wiretype] = colors - GLOB.wire_name_directory[wiretype] = proper_name - else - colors = GLOB.wire_color_directory[wiretype] - -/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) - else - holder.visible_message("You hear a a grinding noise coming from the airlock.") - 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(30) - if(usr) - LAZYADD(A.shockedby, text("\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(usr)]")) - log_combat(usr, A, "electrified") - 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() - if(usr) - A.shock(usr, 50) - else - A.loseMainPower() - if(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() - if(usr) - A.shock(usr, 50) - else - A.loseBackupPower() - if(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(0) - else - if(A.secondsElectrified != -1) - A.set_electrified(-1) - if(usr) - LAZYADD(A.shockedby, text("\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(usr)]")) - log_combat(usr, A, "electrified") - 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(usr) - A.shock(usr, 50) +/datum/wires/airlock + holder_type = /obj/machinery/door/airlock + proper_name = "Generic Airlock" + var/wiretype + +/datum/wires/airlock/secure + randomize = TRUE + +/datum/wires/airlock/command + proper_name = "Command Airlock" + wiretype = "commandairlock" + +/datum/wires/airlock/security + proper_name = "Security Airlock" + wiretype = "securityairlock" + +/datum/wires/airlock/engineering + proper_name = "Engineering Airlock" + wiretype = "engineeringairlock" + +/datum/wires/airlock/science + proper_name = "Science Airlock" + wiretype = "scienceairlock" + +/datum/wires/airlock/medical + proper_name = "Medical Airlock" + wiretype = "medicalairlock" + +/datum/wires/airlock/cargo + proper_name = "Cargo Airlock" + wiretype = "cargoairlock" + +/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) + . = ..() + if(randomize || !wiretype) + return + if(!GLOB.wire_color_directory[wiretype]) + colors = list() + randomize() + GLOB.wire_color_directory[wiretype] = colors + GLOB.wire_name_directory[wiretype] = proper_name + else + colors = GLOB.wire_color_directory[wiretype] + +/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) + else + holder.visible_message("You hear a a grinding noise coming from the airlock.") + 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(30) + if(usr) + LAZYADD(A.shockedby, text("\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(usr)]")) + log_combat(usr, A, "electrified") + 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() + if(usr) + A.shock(usr, 50) + else + A.loseMainPower() + if(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() + if(usr) + A.shock(usr, 50) + else + A.loseBackupPower() + if(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(0) + else + if(A.secondsElectrified != -1) + A.set_electrified(-1) + if(usr) + LAZYADD(A.shockedby, text("\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(usr)]")) + log_combat(usr, A, "electrified") + 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(usr) + A.shock(usr, 50) diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm index 9aa2ca81e8..a452b46830 100644 --- a/code/datums/wires/mulebot.dm +++ b/code/datums/wires/mulebot.dm @@ -1,31 +1,31 @@ -/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 - 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.") - 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 +/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 + 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.") + 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.") \ No newline at end of file diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm index 4bf49dd814..af89f0d6f1 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 +/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 \ No newline at end of file diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm index 65438830a2..5e0d0d77d0 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 += "The reset module hardware light is [R.has_module() ? "on" : "off"]." - 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) - else - new_ai = select_active_ai(R) - 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 += "The reset module hardware light is [R.has_module() ? "on" : "off"]." + 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) + else + new_ai = select_active_ai(R) + 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/syndicatebomb.dm b/code/datums/wires/syndicatebomb.dm index 92cc1770b3..53c1a1b9bc 100644 --- a/code/datums/wires/syndicatebomb.dm +++ b/code/datums/wires/syndicatebomb.dm @@ -1,92 +1,92 @@ -/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) - 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))] The bomb has already been delayed.") - else - holder.visible_message("[icon2html(B, viewers(holder))] The bomb chirps.") - playsound(B, 'sound/machines/chime.ogg', 30, 1) - B.detonation_timer += 300 - 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, 1) - 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 && !B.defused) - 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.defused = FALSE // Cutting and mending all the wires of an inactive bomb will thus cure any sabotage. - else - if(B.active) - holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") - B.explode_now = TRUE - tell_admins(B) - else - B.defused = TRUE - 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, 1) - 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.defused = TRUE - 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) + 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))] The bomb has already been delayed.") + else + holder.visible_message("[icon2html(B, viewers(holder))] The bomb chirps.") + playsound(B, 'sound/machines/chime.ogg', 30, 1) + B.detonation_timer += 300 + 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, 1) + 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 && !B.defused) + 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.defused = FALSE // Cutting and mending all the wires of an inactive bomb will thus cure any sabotage. + else + if(B.active) + holder.visible_message("[icon2html(B, viewers(holder))] An alarm sounds! It's go-") + B.explode_now = TRUE + tell_admins(B) + else + B.defused = TRUE + 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, 1) + 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.defused = TRUE + 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/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm index fa66306302..f26cfc4cdc 100644 --- a/code/game/area/Space_Station_13_areas.dm +++ b/code/game/area/Space_Station_13_areas.dm @@ -1,1352 +1,1352 @@ -/* - -### 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) - music = null (defaults to nothing, look in sound/ambience for music) - -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/arrival - requires_power = FALSE - -/area/arrival/start - name = "Arrival Area" - icon_state = "start" - -/area/admin - name = "Admin room" - icon_state = "start" - -/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. - -/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 - - -//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 - -/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" - -/area/asteroid/cave - name = "Asteroid - Underground" - icon_state = "cave" - requires_power = FALSE - outdoors = TRUE - -/area/asteroid/cave/space - name = "Asteroid - Space" - -/area/asteroid/artifactroom - name = "Asteroid - Artifact" - icon_state = "cave" - ambientsounds = RUINS - -/area/asteroid/artifactroom/Initialize() - . = ..() - set_dynamic_lighting() - - -//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/crew_quarters/locker - name = "Locker Room Maintenance" - icon_state = "maint_locker" - -/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/brig - name = "Brig Maintenance" - icon_state = "maint_brig" - -/area/maintenance/department/medical - name = "Medbay Maintenance" - icon_state = "medbay_maint" - -/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/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/arrivals/north - name = "Arrivals North Maintenance" - icon_state = "fpmaint" - -/area/maintenance/arrivals/north_2 - name = "Arrivals North Maintenance" - icon_state = "fpmaint" - -/area/maintenance/aft - name = "Aft Maintenance" - icon_state = "amaint" - -/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/secondary - name = "Fore Maintenance" - icon_state = "fmaint_2" - -/area/maintenance/starboard - name = "Starboard Maintenance" - icon_state = "smaint" - -/area/maintenance/starboard/central - name = "Central Starboard Maintenance" - icon_state = "smaint" - -/area/maintenance/starboard/aft - name = "Starboard Quarter Maintenance" - icon_state = "asmaint" - -/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 = "disposal" -/area/maintenance/bar - name = "Maintenance Bar" - icon_state = "maintbar" - -/area/maintenance/bar/cafe - name = "Abandoned Cafe" - -//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/starboard/aft - name = "Starboard Quarter Primary Hallway" - icon_state = "hallAS" - -/area/hallway/primary/starboard/fore - name = "Starboard Bow Primary Hallway" - icon_state = "hallFS" - -/area/hallway/primary/port - name = "Port Primary Hallway" - icon_state = "hallP" - -/area/hallway/primary/port/aft - name = "Port Quarter Primary Hallway" - icon_state = "hallAP" - -/area/hallway/primary/port/fore - name = "Port Bow Primary Hallway" - icon_state = "hallFP" - -/area/hallway/primary/central - name = "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" - music = "signal" - -/area/bridge/meeting_room - name = "Heads of Staff Meeting Room" - icon_state = "meeting" - music = null - -/area/bridge/meeting_room/council - name = "Council Chamber" - icon_state = "meeting" - music = null - -/area/bridge/showroom/corporate - name = "Corporate Showroom" - icon_state = "showroom" - music = null - -/area/crew_quarters/heads/captain - name = "Captain's Office" - icon_state = "captain" - clockwork_warp_allowed = FALSE - -/area/crew_quarters/heads/captain/private - name = "Captain's Quarters" - icon_state = "captain" - -/area/crew_quarters/heads/chief - name = "Chief Engineer's Office" - icon_state = "ce_office" - -/area/crew_quarters/heads/chief/private - name = "Chief Engineer's Private Quarters" - icon_state = "ce_private" - -/area/crew_quarters/heads/cmo - name = "Chief Medical Officer's Office" - icon_state = "cmo_office" - -/area/crew_quarters/heads/cmo/private - name = "Chief Medical Officer's Private Quarters" - icon_state = "cmo_private" - -/area/crew_quarters/heads/hop - name = "Head of Personnel's Office" - icon_state = "hop_office" - -/area/crew_quarters/heads/hop/private - name = "Head of Personnel's Private Quarters" - icon_state = "hop_private" - -/area/crew_quarters/heads/hos - name = "Head of Security's Office" - icon_state = "hos_office" - -/area/crew_quarters/heads/hos/private - name = "Head of Security's Private Quarters" - icon_state = "hos_private" - -/area/crew_quarters/heads/hor - name = "Research Director's Office" - icon_state = "rd_office" - -/area/crew_quarters/heads/hor/private - name = "Research Director's Private Quarters" - icon_state = "rd_private" - -/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 = "Sleep" - safe = TRUE - -/area/crew_quarters/dorms/male - name = "Male Dorm" - icon_state = "Sleep" - -/area/crew_quarters/dorms/female - name = "Female Dorm" - icon_state = "Sleep" - -/area/crew_quarters/rehab_dome - name = "Rehabilitation Dome" - icon_state = "Sleep" - -/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/fitness - name = "Fitness Toilets" - icon_state = "toilet" - -/area/crew_quarters/toilet/female - name = "Female Toilets" - icon_state = "toilet" - -/area/crew_quarters/toilet/male - name = "Male 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 = "yellow" - -/area/crew_quarters/fitness - name = "Fitness Room" - icon_state = "fitness" - -/area/crew_quarters/fitness/recreation - name = "Recreation Area" - icon_state = "fitness" - -/area/crew_quarters/cafeteria - name = "Cafeteria" - icon_state = "cafeteria" - -/area/crew_quarters/cafeteria/lunchroom - name = "Lunchroom" - icon_state = "cafeteria" - -/area/crew_quarters/kitchen - name = "Kitchen" - icon_state = "kitchen" - -/area/crew_quarters/kitchen/backroom - name = "Kitchen Coldroom" - icon_state = "kitchen" - -/area/crew_quarters/bar - name = "Bar" - icon_state = "bar" - -/area/crew_quarters/bar/atrium - name = "Atrium" - icon_state = "bar" - -/area/crew_quarters/electronic_marketing_den - name = "Electronic Marketing Den" - icon_state = "bar" - -/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 = "Theatre" - -/area/crew_quarters/theatre/clown - name = "Clown's Office" - -/area/crew_quarters/theatre/mime - name = "Mime's Office" - -/area/crew_quarters/cryopod - name = "Cryogenics" - icon_state = "cryosleep" - -/area/library - name = "Library" - icon_state = "library" - flags_1 = NONE - -/area/library/lounge - name = "Library Lounge" - icon_state = "library" - -/area/library/abandoned - name = "Abandoned Library" - icon_state = "library" - flags_1 = NONE - -/area/chapel - icon_state = "chapel" - ambientsounds = HOLY - flags_1 = NONE - clockwork_warp_allowed = FALSE - clockwork_warp_fail = "The consecration here prevents you from warping in." - -/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 = NONE - -/area/engine/atmospherics_engine - name = "Atmospherics Engine" - icon_state = "atmos_engine" - -/area/engine/supermatter - name = "Supermatter Engine" - icon_state = "engine_sm" - -/area/engine/break_room - name = "Engineering Foyer" - icon_state = "engine_foyer" - -/area/engine/gravity_generator - name = "Gravity Generator Room" - icon_state = "grav_gen" - clockwork_warp_allowed = FALSE - clockwork_warp_fail = "The gravitons generated here could throw off your warp's destination and possibly throw you into deep space." - -/area/engine/secure_construction - name = "Secure Construction Area" - icon_state = "engine" - -/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" - - -//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" - -/area/maintenance/solars/aux/port - name = "Port Auxiliary Solar Maintenance" - icon_state = "SolarcontrolA" - -/area/maintenance/solars/aux/port/aft - name = "Port Quarter Auxiliary Solar Maintenance" - icon_state = "SolarcontrolAP" - -/area/maintenance/solars/aux/port/fore - name = "Port Bow Auxiliary Solar Maintenance" - icon_state = "SolarcontrolA" - -/area/maintenance/solars/aux/starboard - name = "Starboard Auxiliary Solar Maintenance" - icon_state = "SolarcontrolA" - -/area/maintenance/solars/aux/starboard/aft - name = "Starboard Quarter Auxiliary Solar Maintenance" - icon_state = "SolarcontrolA" - -/area/maintenance/solars/aux/starboard/fore - name = "Starboard Bow Auxiliary Solar Maintenance" - icon_state = "SolarcontrolA" - -//Teleporter - -/area/teleporter - name = "Teleporter Room" - icon_state = "teleporter" - music = "signal" - ambientsounds = ENGINEERING - -/area/gateway - name = "Gateway" - icon_state = "gateway" - music = "signal" - ambientsounds = ENGINEERING - -//MedBay - -/area/medical - name = "Medical" - icon_state = "medbay3" - ambientsounds = MEDICAL - -/area/medical/abandoned - name = "Abandoned Medbay" - icon_state = "medbay3" - music = 'sound/ambience/signal.ogg' - -/area/medical/medbay/central - name = "Medbay Central" - icon_state = "medbay" - music = 'sound/ambience/signal.ogg' - -/area/medical/medbay/front_office - name = "Medbay Front Office" - icon_state = "medbay" - music = 'sound/ambience/signal.ogg' - -/area/medical/medbay/lobby - name = "Medbay Lobby" - icon_state = "medbay" - music = 'sound/ambience/signal.ogg' - - //Medbay is a large area, these additional areas help level out APC load. - -/area/medical/medbay/zone2 - name = "Medbay" - icon_state = "medbay2" - music = 'sound/ambience/signal.ogg' - -/area/medical/medbay/zone3 - name = "Medbay" - icon_state = "medbay3" - music = 'sound/ambience/signal.ogg' - -/area/medical/medbay/aft - name = "Medbay Aft" - icon_state = "medbay3" - music = 'sound/ambience/signal.ogg' - -/area/medical/storage - name = "Medbay Storage" - icon_state = "medbay2" - music = 'sound/ambience/signal.ogg' - -/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 = NONE - -/area/medical/morgue - name = "Morgue" - icon_state = "morgue" - ambientsounds = SPOOKY - -/area/medical/chemistry - name = "Chemistry" - icon_state = "chem" - -/area/medical/surgery - name = "Surgery" - 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/genetics/cloning - name = "Cloning Lab" - icon_state = "cloning" - -/area/medical/sleeper - name = "Medbay Treatment Center" - icon_state = "exam_room" - - -//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/courtroom - name = "Courtroom" - icon_state = "courtroom" - -/area/security/prison - name = "Prison Wing" - icon_state = "sec_prison" - -/area/security/processing - name = "Labor Shuttle Dock" - icon_state = "sec_prison" - -/area/security/processing/cremation - name = "Security Crematorium" - icon_state = "sec_prison" - -/area/security/warden - name = "Brig Control" - icon_state = "Warden" - -/area/security/armory - name = "Armory" - icon_state = "armory" - -/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 = "detective" - -/area/security/range - name = "Firing Range" - icon_state = "firingrange" - -/area/security/execution - icon_state = "execution_room" - -/area/security/execution/transfer - name = "Transfer Centre" - -/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/tertiary - icon_state = "checkpoint_tert" - -/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" - -/area/security/vacantoffice - name = "Vacant Office" - icon_state = "security" - -/area/security/vacantoffice/a - name = "Vacant Office A" - icon_state = "security" - -/area/security/vacantoffice/b - name = "Vacant Office B" - icon_state = "security" - -/area/quartermaster - name = "Quartermasters" - icon_state = "quart" - -///////////WORK IN PROGRESS////////// - -/area/quartermaster/sorting - name = "Delivery Office" - icon_state = "cargo_delivery" - -/area/quartermaster/warehouse - name = "Warehouse" - icon_state = "cargo_warehouse" - -////////////WORK IN PROGRESS////////// - -/area/quartermaster/office - name = "Cargo Office" - icon_state = "quartoffice" - -/area/quartermaster/storage - name = "Cargo Bay" - icon_state = "cargo_bay" - -/area/quartermaster/qm - name = "Quartermaster's Office" - icon_state = "quart" - -/area/quartermaster/qm/private - name = "Quartermaster's Private Quarters" - icon_state = "quart" - -/area/quartermaster/miningdock - name = "Mining Dock" - icon_state = "mining" - -/area/quartermaster/miningdock/abandoned - name = "Abandoned Mining Dock" - icon_state = "mining" - -/area/quartermaster/miningoffice - name = "Mining Office" - icon_state = "mining" - -/area/quartermaster/miningstorage - name = "Mining Storage" - icon_state = "mining" - -/area/janitor - name = "Custodial Closet" - icon_state = "janitor" - flags_1 = NONE - -/area/hydroponics - name = "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 = "toxlab" - -/area/science/lab - name = "Research and Development" - icon_state = "toxlab" - -/area/science/xenobiology - name = "Xenobiology Lab" - icon_state = "toxlab" - -/area/science/storage - name = "Toxins Storage" - icon_state = "toxstorage" - -/area/science/mineral_storeroom - name = "Mineral Storeroom" - icon_state = "toxmisc" - -/area/science/test_area - valid_territory = FALSE - name = "Toxins Test Area" - icon_state = "toxtest" - -/area/science/mixing - name = "Toxins Mixing Lab" - icon_state = "toxmix" - -/area/science/mixing/chamber - name = "Toxins Mixing Chamber" - icon_state = "toxmix" - valid_territory = FALSE - -/area/science/misc_lab - name = "Testing Lab" - icon_state = "toxmisc" - -/area/science/misc_lab/range - name = "Research Testing Range" - icon_state = "toxmisc" - -/area/science/server - name = "Research Division Server Room" - icon_state = "server" - -/area/science/explab - name = "Experimentation Lab" - icon_state = "toxmisc" - -/area/science/robotics - name = "Robotics" - icon_state = "medresearch" - -/area/science/robotics/mechbay - name = "Mech Bay" - icon_state = "mechbay" - -/area/science/robotics/mechbay_cargo - name = "Mech Bay" - icon_state = "yellow" - -/area/science/robotics/showroom - name = "Robotics Showroom" - icon_state = "showroom" - -/area/science/robotics/lab - name = "Robotics Lab" - icon_state = "ass_line" - -/area/science/research - name = "Research Division" - icon_state = "medresearch" - -/area/science/circuit - name = "Circuitry Lab" - icon_state = "cir_lab" - -/area/science/research/lobby - name = "Research Division Lobby" - icon_state = "medresearch" - -/area/science/research/abandoned - name = "Abandoned Research Lab" - icon_state = "medresearch" - -/area/science/nanite - name = "Nanite Lab" - icon_state = "toxmisc" - -//Storage - -/area/storage/tools - name = "Auxiliary Tool Storage" - icon_state = "storage" - -/area/storage/primary - name = "Primary Tool Storage" - icon_state = "primarystorage" - -/area/storage/autolathe - name = "Autolathe Storage" - icon_state = "storage" - -/area/storage/art - name = "Art Supply Storage" - icon_state = "storage" - -/area/storage/auxiliary - name = "Auxiliary Storage" - icon_state = "auxstorage" - -/area/storage/atmos - name = "Atmospherics Storage" - icon_state = "atmos" - valid_territory = FALSE - -/area/storage/tcom - name = "Telecomms Storage" - icon_state = "green" - valid_territory = FALSE - -/area/storage/eva - name = "EVA Storage" - icon_state = "eva" - clockwork_warp_allowed = FALSE - -/area/storage/secure - name = "Secure Storage" - icon_state = "storage" - clockwork_warp_allowed = FALSE - -/area/storage/emergency/starboard - name = "Starboard Emergency Storage" - icon_state = "emergencystorage" - -/area/storage/emergency/port - name = "Port Emergency Storage" - icon_state = "emergencystorage" - -/area/storage/tech - name = "Technical Storage" - icon_state = "auxstorage" - -/area/storage/testroom - requires_power = FALSE - name = "Test Room" - icon_state = "storage" - - -//Construction - -/area/construction - name = "Construction Area" - icon_state = "yellow" - ambientsounds = ENGINEERING - -/area/construction/minisat_exterior - name = "Minisat Exterior" - icon_state = "yellow" - -/area/construction/mining/aux_base - name = "Auxiliary Base Construction" - icon_state = "yellow" - -/area/construction/mining/aux_base/closet - name = "Auxiliary Closet Construction" - icon_state = "yellow" - -/area/construction/supplyshuttle - name = "Supply Shuttle" - icon_state = "yellow" - -/area/construction/quarters - name = "Engineers' Quarters" - icon_state = "yellow" - -/area/construction/qmaint - name = "Maintenance" - icon_state = "yellow" - -/area/construction/hallway - name = "Hallway" - icon_state = "yellow" - -/area/construction/solars - name = "Solar Panels" - icon_state = "yellow" - -/area/construction/solarscontrol - name = "Solar Panel Control" - icon_state = "yellow" - -/area/construction/storage - name = "Construction Site Storage" - icon_state = "yellow" - -/area/construction/storage/wing - name = "Storage Wing" - icon_state = "storage_wing" - - -//AI - -/area/ai_monitored/security/armory - name = "Armory" - icon_state = "armory" - ambientsounds = HIGHSEC - -/area/ai_monitored/storage/eva - name = "EVA Storage" - icon_state = "eva" - ambientsounds = HIGHSEC - -/area/ai_monitored/storage/satellite - name = "AI Satellite Maint" - icon_state = "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_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" - -/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 = "yellow" - -/area/ai_monitored/turret_protected/aisat_interior - name = "AI Satellite Antechamber" - icon_state = "ai" - -/area/ai_monitored/turret_protected/AIsatextFP - name = "AI Sat Ext" - icon_state = "storage" - -/area/ai_monitored/turret_protected/AIsatextFS - name = "AI Sat Ext" - icon_state = "storage" - -/area/ai_monitored/turret_protected/AIsatextAS - name = "AI Sat Ext" - icon_state = "storage" - -/area/ai_monitored/turret_protected/AIsatextAP - name = "AI Sat Ext" - icon_state = "storage" - - -// Telecommunications Satellite - -/area/tcommsat - clockwork_warp_allowed = FALSE - clockwork_warp_fail = "For safety reasons, warping here is disallowed; the radio and bluespace noise could cause catastrophic results." - 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/entrance - name = "Telecomms Teleporter" - icon_state = "tcomsatentrance" - -/area/tcommsat/chamber - name = "Abandoned Satellite" - icon_state = "tcomsatcham" - -/area/ai_monitored/turret_protected/tcomsat - name = "Telecomms Satellite" - icon_state = "tcomsatlob" - -/area/ai_monitored/turret_protected/tcomfoyer - name = "Telecomms Foyer" - icon_state = "tcomsatentrance" - -/area/ai_monitored/turret_protected/tcomwest - name = "Telecommunications Satellite West Wing" - icon_state = "tcomsatwest" - -/area/ai_monitored/turret_protected/tcomeast - name = "Telecommunications Satellite East Wing" - icon_state = "tcomsateast" - -/area/tcommsat/computer - name = "Telecomms Control Room" - icon_state = "tcomsatcomp" - -/area/tcommsat/server - name = "Telecomms Server Room" - icon_state = "tcomsatcham" - -/area/tcommsat/lounge - name = "Telecommunications Satellite Lounge" - icon_state = "tcomsatlounge" +/* + +### 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) + music = null (defaults to nothing, look in sound/ambience for music) + +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/arrival + requires_power = FALSE + +/area/arrival/start + name = "Arrival Area" + icon_state = "start" + +/area/admin + name = "Admin room" + icon_state = "start" + +/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. + +/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 + + +//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 + +/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" + +/area/asteroid/cave + name = "Asteroid - Underground" + icon_state = "cave" + requires_power = FALSE + outdoors = TRUE + +/area/asteroid/cave/space + name = "Asteroid - Space" + +/area/asteroid/artifactroom + name = "Asteroid - Artifact" + icon_state = "cave" + ambientsounds = RUINS + +/area/asteroid/artifactroom/Initialize() + . = ..() + set_dynamic_lighting() + + +//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/crew_quarters/locker + name = "Locker Room Maintenance" + icon_state = "maint_locker" + +/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/brig + name = "Brig Maintenance" + icon_state = "maint_brig" + +/area/maintenance/department/medical + name = "Medbay Maintenance" + icon_state = "medbay_maint" + +/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/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/arrivals/north + name = "Arrivals North Maintenance" + icon_state = "fpmaint" + +/area/maintenance/arrivals/north_2 + name = "Arrivals North Maintenance" + icon_state = "fpmaint" + +/area/maintenance/aft + name = "Aft Maintenance" + icon_state = "amaint" + +/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/secondary + name = "Fore Maintenance" + icon_state = "fmaint_2" + +/area/maintenance/starboard + name = "Starboard Maintenance" + icon_state = "smaint" + +/area/maintenance/starboard/central + name = "Central Starboard Maintenance" + icon_state = "smaint" + +/area/maintenance/starboard/aft + name = "Starboard Quarter Maintenance" + icon_state = "asmaint" + +/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 = "disposal" +/area/maintenance/bar + name = "Maintenance Bar" + icon_state = "maintbar" + +/area/maintenance/bar/cafe + name = "Abandoned Cafe" + +//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/starboard/aft + name = "Starboard Quarter Primary Hallway" + icon_state = "hallAS" + +/area/hallway/primary/starboard/fore + name = "Starboard Bow Primary Hallway" + icon_state = "hallFS" + +/area/hallway/primary/port + name = "Port Primary Hallway" + icon_state = "hallP" + +/area/hallway/primary/port/aft + name = "Port Quarter Primary Hallway" + icon_state = "hallAP" + +/area/hallway/primary/port/fore + name = "Port Bow Primary Hallway" + icon_state = "hallFP" + +/area/hallway/primary/central + name = "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" + music = "signal" + +/area/bridge/meeting_room + name = "Heads of Staff Meeting Room" + icon_state = "meeting" + music = null + +/area/bridge/meeting_room/council + name = "Council Chamber" + icon_state = "meeting" + music = null + +/area/bridge/showroom/corporate + name = "Corporate Showroom" + icon_state = "showroom" + music = null + +/area/crew_quarters/heads/captain + name = "Captain's Office" + icon_state = "captain" + clockwork_warp_allowed = FALSE + +/area/crew_quarters/heads/captain/private + name = "Captain's Quarters" + icon_state = "captain" + +/area/crew_quarters/heads/chief + name = "Chief Engineer's Office" + icon_state = "ce_office" + +/area/crew_quarters/heads/chief/private + name = "Chief Engineer's Private Quarters" + icon_state = "ce_private" + +/area/crew_quarters/heads/cmo + name = "Chief Medical Officer's Office" + icon_state = "cmo_office" + +/area/crew_quarters/heads/cmo/private + name = "Chief Medical Officer's Private Quarters" + icon_state = "cmo_private" + +/area/crew_quarters/heads/hop + name = "Head of Personnel's Office" + icon_state = "hop_office" + +/area/crew_quarters/heads/hop/private + name = "Head of Personnel's Private Quarters" + icon_state = "hop_private" + +/area/crew_quarters/heads/hos + name = "Head of Security's Office" + icon_state = "hos_office" + +/area/crew_quarters/heads/hos/private + name = "Head of Security's Private Quarters" + icon_state = "hos_private" + +/area/crew_quarters/heads/hor + name = "Research Director's Office" + icon_state = "rd_office" + +/area/crew_quarters/heads/hor/private + name = "Research Director's Private Quarters" + icon_state = "rd_private" + +/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 = "Sleep" + safe = TRUE + +/area/crew_quarters/dorms/male + name = "Male Dorm" + icon_state = "Sleep" + +/area/crew_quarters/dorms/female + name = "Female Dorm" + icon_state = "Sleep" + +/area/crew_quarters/rehab_dome + name = "Rehabilitation Dome" + icon_state = "Sleep" + +/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/fitness + name = "Fitness Toilets" + icon_state = "toilet" + +/area/crew_quarters/toilet/female + name = "Female Toilets" + icon_state = "toilet" + +/area/crew_quarters/toilet/male + name = "Male 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 = "yellow" + +/area/crew_quarters/fitness + name = "Fitness Room" + icon_state = "fitness" + +/area/crew_quarters/fitness/recreation + name = "Recreation Area" + icon_state = "fitness" + +/area/crew_quarters/cafeteria + name = "Cafeteria" + icon_state = "cafeteria" + +/area/crew_quarters/cafeteria/lunchroom + name = "Lunchroom" + icon_state = "cafeteria" + +/area/crew_quarters/kitchen + name = "Kitchen" + icon_state = "kitchen" + +/area/crew_quarters/kitchen/backroom + name = "Kitchen Coldroom" + icon_state = "kitchen" + +/area/crew_quarters/bar + name = "Bar" + icon_state = "bar" + +/area/crew_quarters/bar/atrium + name = "Atrium" + icon_state = "bar" + +/area/crew_quarters/electronic_marketing_den + name = "Electronic Marketing Den" + icon_state = "bar" + +/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 = "Theatre" + +/area/crew_quarters/theatre/clown + name = "Clown's Office" + +/area/crew_quarters/theatre/mime + name = "Mime's Office" + +/area/crew_quarters/cryopod + name = "Cryogenics" + icon_state = "cryosleep" + +/area/library + name = "Library" + icon_state = "library" + flags_1 = NONE + +/area/library/lounge + name = "Library Lounge" + icon_state = "library" + +/area/library/abandoned + name = "Abandoned Library" + icon_state = "library" + flags_1 = NONE + +/area/chapel + icon_state = "chapel" + ambientsounds = HOLY + flags_1 = NONE + clockwork_warp_allowed = FALSE + clockwork_warp_fail = "The consecration here prevents you from warping in." + +/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 = NONE + +/area/engine/atmospherics_engine + name = "Atmospherics Engine" + icon_state = "atmos_engine" + +/area/engine/supermatter + name = "Supermatter Engine" + icon_state = "engine_sm" + +/area/engine/break_room + name = "Engineering Foyer" + icon_state = "engine_foyer" + +/area/engine/gravity_generator + name = "Gravity Generator Room" + icon_state = "grav_gen" + clockwork_warp_allowed = FALSE + clockwork_warp_fail = "The gravitons generated here could throw off your warp's destination and possibly throw you into deep space." + +/area/engine/secure_construction + name = "Secure Construction Area" + icon_state = "engine" + +/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" + + +//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" + +/area/maintenance/solars/aux/port + name = "Port Auxiliary Solar Maintenance" + icon_state = "SolarcontrolA" + +/area/maintenance/solars/aux/port/aft + name = "Port Quarter Auxiliary Solar Maintenance" + icon_state = "SolarcontrolAP" + +/area/maintenance/solars/aux/port/fore + name = "Port Bow Auxiliary Solar Maintenance" + icon_state = "SolarcontrolA" + +/area/maintenance/solars/aux/starboard + name = "Starboard Auxiliary Solar Maintenance" + icon_state = "SolarcontrolA" + +/area/maintenance/solars/aux/starboard/aft + name = "Starboard Quarter Auxiliary Solar Maintenance" + icon_state = "SolarcontrolA" + +/area/maintenance/solars/aux/starboard/fore + name = "Starboard Bow Auxiliary Solar Maintenance" + icon_state = "SolarcontrolA" + +//Teleporter + +/area/teleporter + name = "Teleporter Room" + icon_state = "teleporter" + music = "signal" + ambientsounds = ENGINEERING + +/area/gateway + name = "Gateway" + icon_state = "gateway" + music = "signal" + ambientsounds = ENGINEERING + +//MedBay + +/area/medical + name = "Medical" + icon_state = "medbay3" + ambientsounds = MEDICAL + +/area/medical/abandoned + name = "Abandoned Medbay" + icon_state = "medbay3" + music = 'sound/ambience/signal.ogg' + +/area/medical/medbay/central + name = "Medbay Central" + icon_state = "medbay" + music = 'sound/ambience/signal.ogg' + +/area/medical/medbay/front_office + name = "Medbay Front Office" + icon_state = "medbay" + music = 'sound/ambience/signal.ogg' + +/area/medical/medbay/lobby + name = "Medbay Lobby" + icon_state = "medbay" + music = 'sound/ambience/signal.ogg' + + //Medbay is a large area, these additional areas help level out APC load. + +/area/medical/medbay/zone2 + name = "Medbay" + icon_state = "medbay2" + music = 'sound/ambience/signal.ogg' + +/area/medical/medbay/zone3 + name = "Medbay" + icon_state = "medbay3" + music = 'sound/ambience/signal.ogg' + +/area/medical/medbay/aft + name = "Medbay Aft" + icon_state = "medbay3" + music = 'sound/ambience/signal.ogg' + +/area/medical/storage + name = "Medbay Storage" + icon_state = "medbay2" + music = 'sound/ambience/signal.ogg' + +/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 = NONE + +/area/medical/morgue + name = "Morgue" + icon_state = "morgue" + ambientsounds = SPOOKY + +/area/medical/chemistry + name = "Chemistry" + icon_state = "chem" + +/area/medical/surgery + name = "Surgery" + 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/genetics/cloning + name = "Cloning Lab" + icon_state = "cloning" + +/area/medical/sleeper + name = "Medbay Treatment Center" + icon_state = "exam_room" + + +//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/courtroom + name = "Courtroom" + icon_state = "courtroom" + +/area/security/prison + name = "Prison Wing" + icon_state = "sec_prison" + +/area/security/processing + name = "Labor Shuttle Dock" + icon_state = "sec_prison" + +/area/security/processing/cremation + name = "Security Crematorium" + icon_state = "sec_prison" + +/area/security/warden + name = "Brig Control" + icon_state = "Warden" + +/area/security/armory + name = "Armory" + icon_state = "armory" + +/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 = "detective" + +/area/security/range + name = "Firing Range" + icon_state = "firingrange" + +/area/security/execution + icon_state = "execution_room" + +/area/security/execution/transfer + name = "Transfer Centre" + +/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/tertiary + icon_state = "checkpoint_tert" + +/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" + +/area/security/vacantoffice + name = "Vacant Office" + icon_state = "security" + +/area/security/vacantoffice/a + name = "Vacant Office A" + icon_state = "security" + +/area/security/vacantoffice/b + name = "Vacant Office B" + icon_state = "security" + +/area/quartermaster + name = "Quartermasters" + icon_state = "quart" + +///////////WORK IN PROGRESS////////// + +/area/quartermaster/sorting + name = "Delivery Office" + icon_state = "cargo_delivery" + +/area/quartermaster/warehouse + name = "Warehouse" + icon_state = "cargo_warehouse" + +////////////WORK IN PROGRESS////////// + +/area/quartermaster/office + name = "Cargo Office" + icon_state = "quartoffice" + +/area/quartermaster/storage + name = "Cargo Bay" + icon_state = "cargo_bay" + +/area/quartermaster/qm + name = "Quartermaster's Office" + icon_state = "quart" + +/area/quartermaster/qm/private + name = "Quartermaster's Private Quarters" + icon_state = "quart" + +/area/quartermaster/miningdock + name = "Mining Dock" + icon_state = "mining" + +/area/quartermaster/miningdock/abandoned + name = "Abandoned Mining Dock" + icon_state = "mining" + +/area/quartermaster/miningoffice + name = "Mining Office" + icon_state = "mining" + +/area/quartermaster/miningstorage + name = "Mining Storage" + icon_state = "mining" + +/area/janitor + name = "Custodial Closet" + icon_state = "janitor" + flags_1 = NONE + +/area/hydroponics + name = "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 = "toxlab" + +/area/science/lab + name = "Research and Development" + icon_state = "toxlab" + +/area/science/xenobiology + name = "Xenobiology Lab" + icon_state = "toxlab" + +/area/science/storage + name = "Toxins Storage" + icon_state = "toxstorage" + +/area/science/mineral_storeroom + name = "Mineral Storeroom" + icon_state = "toxmisc" + +/area/science/test_area + valid_territory = FALSE + name = "Toxins Test Area" + icon_state = "toxtest" + +/area/science/mixing + name = "Toxins Mixing Lab" + icon_state = "toxmix" + +/area/science/mixing/chamber + name = "Toxins Mixing Chamber" + icon_state = "toxmix" + valid_territory = FALSE + +/area/science/misc_lab + name = "Testing Lab" + icon_state = "toxmisc" + +/area/science/misc_lab/range + name = "Research Testing Range" + icon_state = "toxmisc" + +/area/science/server + name = "Research Division Server Room" + icon_state = "server" + +/area/science/explab + name = "Experimentation Lab" + icon_state = "toxmisc" + +/area/science/robotics + name = "Robotics" + icon_state = "medresearch" + +/area/science/robotics/mechbay + name = "Mech Bay" + icon_state = "mechbay" + +/area/science/robotics/mechbay_cargo + name = "Mech Bay" + icon_state = "yellow" + +/area/science/robotics/showroom + name = "Robotics Showroom" + icon_state = "showroom" + +/area/science/robotics/lab + name = "Robotics Lab" + icon_state = "ass_line" + +/area/science/research + name = "Research Division" + icon_state = "medresearch" + +/area/science/circuit + name = "Circuitry Lab" + icon_state = "cir_lab" + +/area/science/research/lobby + name = "Research Division Lobby" + icon_state = "medresearch" + +/area/science/research/abandoned + name = "Abandoned Research Lab" + icon_state = "medresearch" + +/area/science/nanite + name = "Nanite Lab" + icon_state = "toxmisc" + +//Storage + +/area/storage/tools + name = "Auxiliary Tool Storage" + icon_state = "storage" + +/area/storage/primary + name = "Primary Tool Storage" + icon_state = "primarystorage" + +/area/storage/autolathe + name = "Autolathe Storage" + icon_state = "storage" + +/area/storage/art + name = "Art Supply Storage" + icon_state = "storage" + +/area/storage/auxiliary + name = "Auxiliary Storage" + icon_state = "auxstorage" + +/area/storage/atmos + name = "Atmospherics Storage" + icon_state = "atmos" + valid_territory = FALSE + +/area/storage/tcom + name = "Telecomms Storage" + icon_state = "green" + valid_territory = FALSE + +/area/storage/eva + name = "EVA Storage" + icon_state = "eva" + clockwork_warp_allowed = FALSE + +/area/storage/secure + name = "Secure Storage" + icon_state = "storage" + clockwork_warp_allowed = FALSE + +/area/storage/emergency/starboard + name = "Starboard Emergency Storage" + icon_state = "emergencystorage" + +/area/storage/emergency/port + name = "Port Emergency Storage" + icon_state = "emergencystorage" + +/area/storage/tech + name = "Technical Storage" + icon_state = "auxstorage" + +/area/storage/testroom + requires_power = FALSE + name = "Test Room" + icon_state = "storage" + + +//Construction + +/area/construction + name = "Construction Area" + icon_state = "yellow" + ambientsounds = ENGINEERING + +/area/construction/minisat_exterior + name = "Minisat Exterior" + icon_state = "yellow" + +/area/construction/mining/aux_base + name = "Auxiliary Base Construction" + icon_state = "yellow" + +/area/construction/mining/aux_base/closet + name = "Auxiliary Closet Construction" + icon_state = "yellow" + +/area/construction/supplyshuttle + name = "Supply Shuttle" + icon_state = "yellow" + +/area/construction/quarters + name = "Engineers' Quarters" + icon_state = "yellow" + +/area/construction/qmaint + name = "Maintenance" + icon_state = "yellow" + +/area/construction/hallway + name = "Hallway" + icon_state = "yellow" + +/area/construction/solars + name = "Solar Panels" + icon_state = "yellow" + +/area/construction/solarscontrol + name = "Solar Panel Control" + icon_state = "yellow" + +/area/construction/storage + name = "Construction Site Storage" + icon_state = "yellow" + +/area/construction/storage/wing + name = "Storage Wing" + icon_state = "storage_wing" + + +//AI + +/area/ai_monitored/security/armory + name = "Armory" + icon_state = "armory" + ambientsounds = HIGHSEC + +/area/ai_monitored/storage/eva + name = "EVA Storage" + icon_state = "eva" + ambientsounds = HIGHSEC + +/area/ai_monitored/storage/satellite + name = "AI Satellite Maint" + icon_state = "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_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" + +/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 = "yellow" + +/area/ai_monitored/turret_protected/aisat_interior + name = "AI Satellite Antechamber" + icon_state = "ai" + +/area/ai_monitored/turret_protected/AIsatextFP + name = "AI Sat Ext" + icon_state = "storage" + +/area/ai_monitored/turret_protected/AIsatextFS + name = "AI Sat Ext" + icon_state = "storage" + +/area/ai_monitored/turret_protected/AIsatextAS + name = "AI Sat Ext" + icon_state = "storage" + +/area/ai_monitored/turret_protected/AIsatextAP + name = "AI Sat Ext" + icon_state = "storage" + + +// Telecommunications Satellite + +/area/tcommsat + clockwork_warp_allowed = FALSE + clockwork_warp_fail = "For safety reasons, warping here is disallowed; the radio and bluespace noise could cause catastrophic results." + 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/entrance + name = "Telecomms Teleporter" + icon_state = "tcomsatentrance" + +/area/tcommsat/chamber + name = "Abandoned Satellite" + icon_state = "tcomsatcham" + +/area/ai_monitored/turret_protected/tcomsat + name = "Telecomms Satellite" + icon_state = "tcomsatlob" + +/area/ai_monitored/turret_protected/tcomfoyer + name = "Telecomms Foyer" + icon_state = "tcomsatentrance" + +/area/ai_monitored/turret_protected/tcomwest + name = "Telecommunications Satellite West Wing" + icon_state = "tcomsatwest" + +/area/ai_monitored/turret_protected/tcomeast + name = "Telecommunications Satellite East Wing" + icon_state = "tcomsateast" + +/area/tcommsat/computer + name = "Telecomms Control Room" + icon_state = "tcomsatcomp" + +/area/tcommsat/server + name = "Telecomms Server Room" + icon_state = "tcomsatcham" + +/area/tcommsat/lounge + name = "Telecommunications Satellite Lounge" + icon_state = "tcomsatlounge" diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm index 9d42b67bf9..87b44291f5 100644 --- a/code/game/area/ai_monitored.dm +++ b/code/game/area/ai_monitored.dm @@ -1,31 +1,31 @@ -/area/ai_monitored - name = "AI Monitored Area" - clockwork_warp_allowed = FALSE - 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.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)) +/area/ai_monitored + name = "AI Monitored Area" + clockwork_warp_allowed = FALSE + 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.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 \ No newline at end of file diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 36b701aae1..9a6d992991 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1,866 +1,866 @@ -/atom - layer = TURF_LAYER - plane = GAME_PLANE - var/level = 2 - var/article // If non-null, overrides a/an/some in all cases - - var/flags_1 = NONE - var/interaction_flags_atom = NONE - 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 - - var/list/atom_colours //used to store the different colors on an atom - //its inherent color, the colored paint applied on it, special color effect etc... - - var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4. - var/list/remove_overlays // a very temporary list of overlays to remove - var/list/add_overlays // a very temporary list of overlays to add - - var/list/managed_vis_overlays //vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays - ///overlays managed by update_overlays() to prevent removing overlays that weren't added by the same proc - var/list/managed_overlays - - var/datum/proximity_monitor/proximity_monitor - var/buckle_message_cooldown = 0 - var/fingerprintslast - - var/list/filter_data //For handling persistent filters - - var/datum/component/orbiter/orbiters - - var/rad_flags = NONE // Will move to flags_1 when i can be arsed to - var/rad_insulation = RAD_NO_INSULATION - - var/icon/blood_splatter_icon - var/list/fingerprints - var/list/fingerprintshidden - var/list/blood_DNA - var/list/suit_fibers - -/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 - -//Called after New if the map is being loaded. mapload = TRUE -//Called from base of New if the map is not being loaded. mapload = FALSE -//This base must be called or derivatives must set initialized to TRUE -//must not sleep -//Other parameters are passed from New (excluding loc), this does not happen if mapload is TRUE -//Must return an Initialize hint. Defined in __DEFINES/subsystems.dm - -//Note: the following functions don't call the base for optimization and must copypasta: -// /turf/Initialize -// /turf/open/space/Initialize - -/atom/proc/Initialize(mapload, ...) - if(flags_1 & INITIALIZED_1) - stack_trace("Warning: [src]([type]) initialized multiple times!") - flags_1 |= INITIALIZED_1 - - //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) - - ComponentInitialize() - - return INITIALIZE_HINT_NORMAL - -//called if Initialize returns INITIALIZE_HINT_LATELOAD -/atom/proc/LateInitialize() - return - -// Put your AddComponent() calls here -/atom/proc/ComponentInitialize() - return - -/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) - - LAZYCLEARLIST(overlays) - LAZYCLEARLIST(priority_overlays) - - QDEL_NULL(light) - - return ..() - -/atom/proc/handle_ricochet(obj/item/projectile/P) - return - -/atom/proc/CanPass(atom/movable/mover, turf/target) - return !density - -/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 - -/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 - -/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) - SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user) - if(does_attack_animation) - user.changeNext_move(CLICK_CD_MELEE) - log_combat(user, src, "punched", "hulk powers") - user.do_attack_animation(src, ATTACK_EFFECT_SMASH) - -/atom/proc/CheckParts(list/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(ismovableatom(A)) - var/atom/movable/M = A - if(isliving(M.loc)) - var/mob/living/L = M.loc - L.transferItemToLoc(M, src) - else - M.forceMove(src) - -//common name -/atom/proc/update_multiz(prune_on_fail = FALSE) - return FALSE - -/atom/proc/assume_air(datum/gas_mixture/giver) - qdel(giver) - return null - -/atom/proc/remove_air(amount) - return null - -/atom/proc/return_air() - if(loc) - return loc.return_air() - else - return null - -/atom/proc/check_eye(mob/user) - return - -/atom/proc/Bumped(atom/movable/AM) - set waitfor = FALSE - -// Convenience procs to see if a container is open for chemistry handling -/atom/proc/is_open_container() - return is_refillable() && is_drainable() - -/atom/proc/is_injectable(allowmobs = TRUE) - return reagents && (reagents.reagents_holder_flags & (INJECTABLE | REFILLABLE)) - -/atom/proc/is_drawable(allowmobs = TRUE) - return reagents && (reagents.reagents_holder_flags & (DRAWABLE | DRAINABLE)) - -/atom/proc/is_refillable() - return reagents && (reagents.reagents_holder_flags & REFILLABLE) - -/atom/proc/is_drainable() - return reagents && (reagents.reagents_holder_flags & DRAINABLE) - - -/atom/proc/AllowDrop() - return FALSE - -/atom/proc/CheckExit() - return TRUE - -/atom/proc/HasProximity(atom/movable/AM as mob|obj) - return - -/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 - -/atom/proc/bullet_act(obj/item/projectile/P, def_zone) - SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone) - . = P.on_hit(src, 0, def_zone) - -//used on altdisarm() for special interactions between the shoved victim (target) and the src, with user being the one shoving the target on it. -// IMPORTANT: if you wish to add a new own shove_act() to a certain object, remember to add SHOVABLE_ONTO to its obj_flags bitfied var first. -/atom/proc/shove_act(mob/living/target, mob/living/user) - return FALSE - -/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 - -/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 - - var/should_override = FALSE - - if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED) - should_override = TRUE - - - if(blood_DNA && !istype(src, /obj/effect/decal)) - override[EXAMINE_POSITION_BEFORE] = " blood-stained " - should_override = TRUE - - if(should_override) - . = 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)]" - -/atom/proc/examine(mob/user) - . = list("[get_examine_string(user, TRUE)].") - - if(desc) - . += desc - - if(reagents) - if(reagents.reagents_holder_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.reagents_holder_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, .) - -/// Updates the icon of the atom -/atom/proc/update_icon() - // I expect we're going to need more return flags and options in this proc - var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON) - - if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE)) - update_icon_state() - - 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) - -/// 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, .) - -/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 - -/atom/proc/contents_explosion(severity, target) - return //For handling the effects of explosions on contents that would not normally be effected - -/atom/proc/ex_act(severity, target) - set waitfor = FALSE - contents_explosion(severity, target) - SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target) - -/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 - -/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked) - 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) - -/atom/proc/hitby_react(atom/movable/AM) - if(AM && isturf(AM.loc)) - step(AM, turn(AM.dir, 180)) - -/atom/proc/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube) - 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() - var/blood_id = get_blood_id() - if(!(blood_id =="blood" || blood_id == "jellyblood")) - return - return list("ANIMAL DNA" = "Y-") - -/mob/living/carbon/get_blood_dna_list() - var/blood_id = get_blood_id() - if(!(blood_id =="blood" || blood_id == "jellyblood")) - 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*") - -//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 - LAZYINITLIST(blood_DNA) //if our list of DNA doesn't exist yet, initialise it. - var/old_length = blood_DNA.len - blood_DNA |= new_blood_dna - if(blood_DNA.len == old_length) - return FALSE - return TRUE - -//to add blood dna info to the object's blood_DNA list -/atom/proc/transfer_blood_dna(list/blood_dna, list/datum/disease/diseases) - LAZYINITLIST(blood_DNA) - var/old_length = blood_DNA.len - blood_DNA |= blood_dna - if(blood_DNA.len > old_length) - return TRUE - //some new blood DNA was added - -//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, M.diseases) - -//to add blood onto something, with blood dna info to include. -/atom/proc/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - return FALSE - -/obj/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - return transfer_blood_dna(blood_dna, diseases) - -/obj/item/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) - . = ..() - if(!.) - return - add_blood_overlay() - -/obj/item/proc/add_blood_overlay() - if(!blood_DNA.len) - return - if(initial(icon) && initial(icon_state)) - blood_splatter_icon = icon(initial(icon), initial(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('icons/effects/blood.dmi', "itemblood"), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant - blood_splatter_icon.Blend(blood_DNA_to_color(), ICON_MULTIPLY) - add_overlay(blood_splatter_icon) - -/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.transfer_blood_dna(blood_dna, diseases) //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(head) - head.add_blood_DNA(blood_dna, diseases) - update_inv_head() - else if(wear_mask) - wear_mask.add_blood_DNA(blood_dna, diseases) - update_inv_wear_mask() - if(wear_neck) - wear_neck.add_blood_DNA(blood_dna, diseases) - update_inv_neck() - if(wear_suit) - wear_suit.add_blood_DNA(blood_dna, diseases) - update_inv_wear_suit() - else if(w_uniform) - w_uniform.add_blood_DNA(blood_dna, diseases) - update_inv_w_uniform() - if(gloves) - var/obj/item/clothing/gloves/G = gloves - G.add_blood_DNA(blood_dna, diseases) - else - transfer_blood_dna(blood_dna, diseases) - bloody_hands = rand(2, 4) - update_inv_gloves() //handles bloody hands overlays and updating - return TRUE - -/atom/proc/blood_DNA_to_color() - var/list/colors = list()//first we make a list of all bloodtypes present - for(var/bloop in blood_DNA) - if(colors[blood_DNA[bloop]]) - colors[blood_DNA[bloop]]++ - else - colors[blood_DNA[bloop]] = 1 - - var/final_rgb = BLOOD_COLOR_HUMAN //a default so we don't have white blood graphics if something messed up - - if(colors.len) - var/sum = 0 //this is all shitcode, but it works; trust me - final_rgb = bloodtype_to_color(colors[1]) - sum = colors[colors[1]] - if(colors.len > 1) - var/i = 2 - while(i <= colors.len) - var/tmp = colors[colors[i]] - final_rgb = BlendRGB(final_rgb, bloodtype_to_color(colors[i]), tmp/(tmp+sum)) - sum += tmp - i++ - - return final_rgb - -/atom/proc/clean_blood() - if(islist(blood_DNA)) - blood_DNA = null - return TRUE - -/atom/proc/wash_cream() - return TRUE - -/atom/proc/isinspace() - if(isspaceturf(get_turf(src))) - return TRUE - else - return FALSE - -/atom/proc/handle_fall() - return - -/atom/proc/singularity_act() - return - -/atom/proc/singularity_pull(obj/singularity/S, current_size) - SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size) - -/atom/proc/acid_act(acidpwr, acid_volume) - SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) - -/atom/proc/emag_act() - return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) - -/atom/proc/rad_act(strength) - SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength) - -/atom/proc/narsie_act() - SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT) - -/atom/proc/ratvar_act() - SEND_SIGNAL(src, COMSIG_ATOM_RATVAR_ACT) - -/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - 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 - -/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 - -/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) - qdel(progress) - to_chat(user, "You dump as much of [src_object.parent]'s contents into [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 - -/atom/proc/get_dumping_location(obj/item/storage/source,mob/user) - return null - -//This proc is called on the location of an atom when the atom is Destroy()'d -/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 -/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 -/atom/proc/setDir(newdir, ismousemovement=FALSE) - SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir) - dir = newdir - -/atom/proc/mech_melee_attack(obj/mecha/M) - return - -//If a mob logouts/logins in side of an object you can use this proc -/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 - -/atom/vv_edit_var(var_name, var_value) - if(!GLOB.Debug2) - flags_1 |= ADMIN_SPAWNED_1 - . = ..() - switch(var_name) - if("color") - add_atom_colour(color, ADMIN_COLOUR_PRIORITY) - -/atom/vv_get_dropdown() - . = ..() - . += "---" - var/turf/curturf = get_turf(src) - if (curturf) - .["Jump to"] = "?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[curturf.x];Y=[curturf.y];Z=[curturf.z]" - .["Modify Transform"] = "?_src_=vars;[HrefToken()];modtransform=[REF(src)]" - .["Add reagent"] = "?_src_=vars;[HrefToken()];addreagent=[REF(src)]" - .["Trigger EM pulse"] = "?_src_=vars;[HrefToken()];emp=[REF(src)]" - .["Trigger explosion"] = "?_src_=vars;[HrefToken()];explode=[REF(src)]" - -/atom/proc/drop_location() - var/atom/L = loc - if(!L) - return null - return L.AllowDrop() ? L : L.drop_location() - -/atom/Entered(atom/movable/AM, atom/oldLoc) - SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc) - -/atom/Exit(atom/movable/AM, atom/newLoc) - . = ..() - if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT) - return FALSE - -/atom/Exited(atom/movable/AM, atom/newLoc) - SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc) - -/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. -// Just don't forget to return ..() in the end. -/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type) - switch(tool_type) - if(TOOL_CROWBAR) - return crowbar_act(user, I) - if(TOOL_MULTITOOL) - return multitool_act(user, I) - if(TOOL_SCREWDRIVER) - return screwdriver_act(user, I) - if(TOOL_WRENCH) - return wrench_act(user, I) - if(TOOL_WIRECUTTER) - return wirecutter_act(user, I) - if(TOOL_WELDER) - return welder_act(user, I) - if(TOOL_ANALYZER) - return analyzer_act(user, I) - -// Tool-specific behavior procs. To be overridden in subtypes. -/atom/proc/crowbar_act(mob/living/user, obj/item/I) - return - -/atom/proc/multitool_act(mob/living/user, obj/item/I) - return - -/atom/proc/screwdriver_act(mob/living/user, obj/item/I) - SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I) - -/atom/proc/wrench_act(mob/living/user, obj/item/I) - return - -/atom/proc/wirecutter_act(mob/living/user, obj/item/I) - return - -/atom/proc/welder_act(mob/living/user, obj/item/I) - return - -/atom/proc/analyzer_act(mob/living/user, obj/item/I) - return - -/atom/proc/GenerateTag() - 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_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) - 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) - -/* -Proc for attack log creation, because really why not -1 argument is the actor performing the action -2 argument is the target of the action -3 is a verb describing the action (e.g. punched, throwed, kicked, etc.) -4 is a tool with which the action was made (usually an item) -5 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 [key_name(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) - -// Filter stuff -/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)) - -/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() - return TRUE - -/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1) +/atom + layer = TURF_LAYER + plane = GAME_PLANE + var/level = 2 + var/article // If non-null, overrides a/an/some in all cases + + var/flags_1 = NONE + var/interaction_flags_atom = NONE + 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 + + var/list/atom_colours //used to store the different colors on an atom + //its inherent color, the colored paint applied on it, special color effect etc... + + var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4. + var/list/remove_overlays // a very temporary list of overlays to remove + var/list/add_overlays // a very temporary list of overlays to add + + var/list/managed_vis_overlays //vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays + ///overlays managed by update_overlays() to prevent removing overlays that weren't added by the same proc + var/list/managed_overlays + + var/datum/proximity_monitor/proximity_monitor + var/buckle_message_cooldown = 0 + var/fingerprintslast + + var/list/filter_data //For handling persistent filters + + var/datum/component/orbiter/orbiters + + var/rad_flags = NONE // Will move to flags_1 when i can be arsed to + var/rad_insulation = RAD_NO_INSULATION + + var/icon/blood_splatter_icon + var/list/fingerprints + var/list/fingerprintshidden + var/list/blood_DNA + var/list/suit_fibers + +/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 + +//Called after New if the map is being loaded. mapload = TRUE +//Called from base of New if the map is not being loaded. mapload = FALSE +//This base must be called or derivatives must set initialized to TRUE +//must not sleep +//Other parameters are passed from New (excluding loc), this does not happen if mapload is TRUE +//Must return an Initialize hint. Defined in __DEFINES/subsystems.dm + +//Note: the following functions don't call the base for optimization and must copypasta: +// /turf/Initialize +// /turf/open/space/Initialize + +/atom/proc/Initialize(mapload, ...) + if(flags_1 & INITIALIZED_1) + stack_trace("Warning: [src]([type]) initialized multiple times!") + flags_1 |= INITIALIZED_1 + + //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) + + ComponentInitialize() + + return INITIALIZE_HINT_NORMAL + +//called if Initialize returns INITIALIZE_HINT_LATELOAD +/atom/proc/LateInitialize() + return + +// Put your AddComponent() calls here +/atom/proc/ComponentInitialize() + return + +/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) + + LAZYCLEARLIST(overlays) + LAZYCLEARLIST(priority_overlays) + + QDEL_NULL(light) + + return ..() + +/atom/proc/handle_ricochet(obj/item/projectile/P) + return + +/atom/proc/CanPass(atom/movable/mover, turf/target) + return !density + +/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 + +/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 + +/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user) + if(does_attack_animation) + user.changeNext_move(CLICK_CD_MELEE) + log_combat(user, src, "punched", "hulk powers") + user.do_attack_animation(src, ATTACK_EFFECT_SMASH) + +/atom/proc/CheckParts(list/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(ismovableatom(A)) + var/atom/movable/M = A + if(isliving(M.loc)) + var/mob/living/L = M.loc + L.transferItemToLoc(M, src) + else + M.forceMove(src) + +//common name +/atom/proc/update_multiz(prune_on_fail = FALSE) + return FALSE + +/atom/proc/assume_air(datum/gas_mixture/giver) + qdel(giver) + return null + +/atom/proc/remove_air(amount) + return null + +/atom/proc/return_air() + if(loc) + return loc.return_air() + else + return null + +/atom/proc/check_eye(mob/user) + return + +/atom/proc/Bumped(atom/movable/AM) + set waitfor = FALSE + +// Convenience procs to see if a container is open for chemistry handling +/atom/proc/is_open_container() + return is_refillable() && is_drainable() + +/atom/proc/is_injectable(allowmobs = TRUE) + return reagents && (reagents.reagents_holder_flags & (INJECTABLE | REFILLABLE)) + +/atom/proc/is_drawable(allowmobs = TRUE) + return reagents && (reagents.reagents_holder_flags & (DRAWABLE | DRAINABLE)) + +/atom/proc/is_refillable() + return reagents && (reagents.reagents_holder_flags & REFILLABLE) + +/atom/proc/is_drainable() + return reagents && (reagents.reagents_holder_flags & DRAINABLE) + + +/atom/proc/AllowDrop() + return FALSE + +/atom/proc/CheckExit() + return TRUE + +/atom/proc/HasProximity(atom/movable/AM as mob|obj) + return + +/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 + +/atom/proc/bullet_act(obj/item/projectile/P, def_zone) + SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone) + . = P.on_hit(src, 0, def_zone) + +//used on altdisarm() for special interactions between the shoved victim (target) and the src, with user being the one shoving the target on it. +// IMPORTANT: if you wish to add a new own shove_act() to a certain object, remember to add SHOVABLE_ONTO to its obj_flags bitfied var first. +/atom/proc/shove_act(mob/living/target, mob/living/user) + return FALSE + +/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 + +/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 + + var/should_override = FALSE + + if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED) + should_override = TRUE + + + if(blood_DNA && !istype(src, /obj/effect/decal)) + override[EXAMINE_POSITION_BEFORE] = " blood-stained " + should_override = TRUE + + if(should_override) + . = 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)]" + +/atom/proc/examine(mob/user) + . = list("[get_examine_string(user, TRUE)].") + + if(desc) + . += desc + + if(reagents) + if(reagents.reagents_holder_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.reagents_holder_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, .) + +/// Updates the icon of the atom +/atom/proc/update_icon() + // I expect we're going to need more return flags and options in this proc + var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON) + + if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE)) + update_icon_state() + + 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) + +/// 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, .) + +/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 + +/atom/proc/contents_explosion(severity, target) + return //For handling the effects of explosions on contents that would not normally be effected + +/atom/proc/ex_act(severity, target) + set waitfor = FALSE + contents_explosion(severity, target) + SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target) + +/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 + +/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked) + 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) + +/atom/proc/hitby_react(atom/movable/AM) + if(AM && isturf(AM.loc)) + step(AM, turn(AM.dir, 180)) + +/atom/proc/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube) + 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() + var/blood_id = get_blood_id() + if(!(blood_id =="blood" || blood_id == "jellyblood")) + return + return list("ANIMAL DNA" = "Y-") + +/mob/living/carbon/get_blood_dna_list() + var/blood_id = get_blood_id() + if(!(blood_id =="blood" || blood_id == "jellyblood")) + 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*") + +//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 + LAZYINITLIST(blood_DNA) //if our list of DNA doesn't exist yet, initialise it. + var/old_length = blood_DNA.len + blood_DNA |= new_blood_dna + if(blood_DNA.len == old_length) + return FALSE + return TRUE + +//to add blood dna info to the object's blood_DNA list +/atom/proc/transfer_blood_dna(list/blood_dna, list/datum/disease/diseases) + LAZYINITLIST(blood_DNA) + var/old_length = blood_DNA.len + blood_DNA |= blood_dna + if(blood_DNA.len > old_length) + return TRUE + //some new blood DNA was added + +//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, M.diseases) + +//to add blood onto something, with blood dna info to include. +/atom/proc/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + return FALSE + +/obj/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + return transfer_blood_dna(blood_dna, diseases) + +/obj/item/add_blood_DNA(list/blood_dna, list/datum/disease/diseases) + . = ..() + if(!.) + return + add_blood_overlay() + +/obj/item/proc/add_blood_overlay() + if(!blood_DNA.len) + return + if(initial(icon) && initial(icon_state)) + blood_splatter_icon = icon(initial(icon), initial(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('icons/effects/blood.dmi', "itemblood"), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant + blood_splatter_icon.Blend(blood_DNA_to_color(), ICON_MULTIPLY) + add_overlay(blood_splatter_icon) + +/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.transfer_blood_dna(blood_dna, diseases) //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(head) + head.add_blood_DNA(blood_dna, diseases) + update_inv_head() + else if(wear_mask) + wear_mask.add_blood_DNA(blood_dna, diseases) + update_inv_wear_mask() + if(wear_neck) + wear_neck.add_blood_DNA(blood_dna, diseases) + update_inv_neck() + if(wear_suit) + wear_suit.add_blood_DNA(blood_dna, diseases) + update_inv_wear_suit() + else if(w_uniform) + w_uniform.add_blood_DNA(blood_dna, diseases) + update_inv_w_uniform() + if(gloves) + var/obj/item/clothing/gloves/G = gloves + G.add_blood_DNA(blood_dna, diseases) + else + transfer_blood_dna(blood_dna, diseases) + bloody_hands = rand(2, 4) + update_inv_gloves() //handles bloody hands overlays and updating + return TRUE + +/atom/proc/blood_DNA_to_color() + var/list/colors = list()//first we make a list of all bloodtypes present + for(var/bloop in blood_DNA) + if(colors[blood_DNA[bloop]]) + colors[blood_DNA[bloop]]++ + else + colors[blood_DNA[bloop]] = 1 + + var/final_rgb = BLOOD_COLOR_HUMAN //a default so we don't have white blood graphics if something messed up + + if(colors.len) + var/sum = 0 //this is all shitcode, but it works; trust me + final_rgb = bloodtype_to_color(colors[1]) + sum = colors[colors[1]] + if(colors.len > 1) + var/i = 2 + while(i <= colors.len) + var/tmp = colors[colors[i]] + final_rgb = BlendRGB(final_rgb, bloodtype_to_color(colors[i]), tmp/(tmp+sum)) + sum += tmp + i++ + + return final_rgb + +/atom/proc/clean_blood() + if(islist(blood_DNA)) + blood_DNA = null + return TRUE + +/atom/proc/wash_cream() + return TRUE + +/atom/proc/isinspace() + if(isspaceturf(get_turf(src))) + return TRUE + else + return FALSE + +/atom/proc/handle_fall() + return + +/atom/proc/singularity_act() + return + +/atom/proc/singularity_pull(obj/singularity/S, current_size) + SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size) + +/atom/proc/acid_act(acidpwr, acid_volume) + SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume) + +/atom/proc/emag_act() + return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) + +/atom/proc/rad_act(strength) + SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength) + +/atom/proc/narsie_act() + SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT) + +/atom/proc/ratvar_act() + SEND_SIGNAL(src, COMSIG_ATOM_RATVAR_ACT) + +/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) + 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 + +/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 + +/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) + qdel(progress) + to_chat(user, "You dump as much of [src_object.parent]'s contents into [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 + +/atom/proc/get_dumping_location(obj/item/storage/source,mob/user) + return null + +//This proc is called on the location of an atom when the atom is Destroy()'d +/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 +/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 +/atom/proc/setDir(newdir, ismousemovement=FALSE) + SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir) + dir = newdir + +/atom/proc/mech_melee_attack(obj/mecha/M) + return + +//If a mob logouts/logins in side of an object you can use this proc +/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 + +/atom/vv_edit_var(var_name, var_value) + if(!GLOB.Debug2) + flags_1 |= ADMIN_SPAWNED_1 + . = ..() + switch(var_name) + if("color") + add_atom_colour(color, ADMIN_COLOUR_PRIORITY) + +/atom/vv_get_dropdown() + . = ..() + . += "---" + var/turf/curturf = get_turf(src) + if (curturf) + .["Jump to"] = "?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[curturf.x];Y=[curturf.y];Z=[curturf.z]" + .["Modify Transform"] = "?_src_=vars;[HrefToken()];modtransform=[REF(src)]" + .["Add reagent"] = "?_src_=vars;[HrefToken()];addreagent=[REF(src)]" + .["Trigger EM pulse"] = "?_src_=vars;[HrefToken()];emp=[REF(src)]" + .["Trigger explosion"] = "?_src_=vars;[HrefToken()];explode=[REF(src)]" + +/atom/proc/drop_location() + var/atom/L = loc + if(!L) + return null + return L.AllowDrop() ? L : L.drop_location() + +/atom/Entered(atom/movable/AM, atom/oldLoc) + SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc) + +/atom/Exit(atom/movable/AM, atom/newLoc) + . = ..() + if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT) + return FALSE + +/atom/Exited(atom/movable/AM, atom/newLoc) + SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc) + +/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. +// Just don't forget to return ..() in the end. +/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type) + switch(tool_type) + if(TOOL_CROWBAR) + return crowbar_act(user, I) + if(TOOL_MULTITOOL) + return multitool_act(user, I) + if(TOOL_SCREWDRIVER) + return screwdriver_act(user, I) + if(TOOL_WRENCH) + return wrench_act(user, I) + if(TOOL_WIRECUTTER) + return wirecutter_act(user, I) + if(TOOL_WELDER) + return welder_act(user, I) + if(TOOL_ANALYZER) + return analyzer_act(user, I) + +// Tool-specific behavior procs. To be overridden in subtypes. +/atom/proc/crowbar_act(mob/living/user, obj/item/I) + return + +/atom/proc/multitool_act(mob/living/user, obj/item/I) + return + +/atom/proc/screwdriver_act(mob/living/user, obj/item/I) + SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I) + +/atom/proc/wrench_act(mob/living/user, obj/item/I) + return + +/atom/proc/wirecutter_act(mob/living/user, obj/item/I) + return + +/atom/proc/welder_act(mob/living/user, obj/item/I) + return + +/atom/proc/analyzer_act(mob/living/user, obj/item/I) + return + +/atom/proc/GenerateTag() + 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_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) + 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) + +/* +Proc for attack log creation, because really why not +1 argument is the actor performing the action +2 argument is the target of the action +3 is a verb describing the action (e.g. punched, throwed, kicked, etc.) +4 is a tool with which the action was made (usually an item) +5 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 [key_name(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) + +// Filter stuff +/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)) + +/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() + return TRUE + +/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1) . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels) \ No newline at end of file diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 7c57de74a4..00a08d62b2 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1,874 +1,874 @@ -/atom/movable - layer = OBJ_LAYER - var/last_move = null - var/last_move_time = 0 - var/anchored = FALSE - 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 - var/verb_say = "says" - var/verb_ask = "asks" - var/verb_exclaim = "exclaims" - var/verb_whisper = "whispers" - 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 = 0 - var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move - 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 - -/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) - throw_impact(highest) - 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") - 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("x") - var/turf/T = locate(var_value, y, z) - if(T) - forceMove(T) - return TRUE - return FALSE - if("y") - var/turf/T = locate(x, var_value, z) - if(T) - forceMove(T) - return TRUE - return FALSE - if("z") - var/turf/T = locate(x, y, var_value) - if(T) - forceMove(T) - return TRUE - return FALSE - if("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,gs) - if(QDELETED(AM)) - return FALSE - if(!(AM.can_be_pulled(src))) - return FALSE - - // If we're pulling something then drop what we're currently pulling and pull this instead. - if(pulling) - if(gs==0) - stop_pulling() - return FALSE - // Are we trying to pull something we are already pulling? Then enter grab cycle and end. - if(AM == pulling) - grab_state = gs - if(istype(AM,/mob/living)) - var/mob/living/AMob = AM - AMob.grabbedby(src) - return TRUE - stop_pulling() - 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 - grab_state = gs - if(ismob(AM)) - var/mob/M = AM - log_combat(src, M, "grabbed", addition="passive grab") - visible_message("[src] has grabbed [M] passively!") - return TRUE - -/atom/movable/proc/stop_pulling() - if(pulling) - pulling.pulledby = null - var/mob/living/ex_pulled = pulling - pulling = null - grab_state = 0 - if(isliving(ex_pulled)) - var/mob/living/L = ex_pulled - L.update_canmove()// 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.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)) - -/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) - stop_pulling() - return - -//////////////////////////////////////// -// 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 - - // 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(pulling) - if(pullee && get_dist(src, pullee) > 1) - stop_pulling() - - if(pullee && pullee.loc != loc && !isturf(pullee.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() - 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) //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.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position - if(pulling && get_dist(src, pulling) > 1) //the pullee couldn't keep up - stop_pulling() - 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() - - - 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) - 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) - 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) - 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() below -//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() in mob_movement.dm -//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 - - -/atom/movable/proc/newtonian_move(direction) //Only moves the object if it's under no gravity - 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 - SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) - return hit_atom.hitby(src) - -/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked) - if(!anchored && hitpush) - step(src, AM.dir) - ..() - -/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback, messy_throw = TRUE) - return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, messy_throw) - -/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback, messy_throw = TRUE) //If this returns FALSE then callback will not be called. - . = 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.movement_delay() - 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.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 - 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/CanPass(atom/movable/mover, turf/target) - if(mover in buckled_mobs) - return 1 - return ..() - -// 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 - - var/matrix/OM = matrix(transform) - var/matrix/M = matrix(transform) - M.Turn(pixel_x_diff ? pixel_x_diff*2 : pick(-16, 16)) - - animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform = M, time = 2) - animate(src, pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, transform = OM, 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() - . = ..() - . -= "Jump to" - .["Follow"] = "?_src_=holder;[HrefToken()];adminplayerobservefollow=[REF(src)]" - .["Get"] = "?_src_=holder;[HrefToken()];admingetmovable=[REF(src)]" - -/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 */ -/atom/movable/proc/get_language_holder(shadow=TRUE) - if(language_holder) - return language_holder - else - language_holder = new initial_language_holder(src) - return language_holder - -/atom/movable/proc/grant_language(datum/language/dt, body = FALSE) - var/datum/language_holder/H = get_language_holder(!body) - H.grant_language(dt, body) - -/atom/movable/proc/grant_all_languages(omnitongue=FALSE) - var/datum/language_holder/H = get_language_holder() - H.grant_all_languages(omnitongue) - -/atom/movable/proc/get_random_understood_language() - var/datum/language_holder/H = get_language_holder() - . = H.get_random_understood_language() - -/atom/movable/proc/remove_language(datum/language/dt, body = FALSE) - var/datum/language_holder/H = get_language_holder(!body) - H.remove_language(dt, body) - -/atom/movable/proc/remove_all_languages() - var/datum/language_holder/H = get_language_holder() - H.remove_all_languages() - -/atom/movable/proc/has_language(datum/language/dt) - var/datum/language_holder/H = get_language_holder() - . = H.has_language(dt) - -/atom/movable/proc/copy_known_languages_from(thing, replace=FALSE) - var/datum/language_holder/H = get_language_holder() - . = H.copy_known_languages_from(thing, replace) - -// Whether an AM can speak in a language or not, independent of whether -// it KNOWS the language -/atom/movable/proc/could_speak_in_language(datum/language/dt) - . = TRUE - -/atom/movable/proc/can_speak_in_language(datum/language/dt) - var/datum/language_holder/H = get_language_holder() - - if(!H.has_language(dt)) - return FALSE - else if(H.omnitongue) - return TRUE - else if(could_speak_in_language(dt) && (!H.only_speaks_language || H.only_speaks_language == dt)) - return TRUE - else - return FALSE - -/atom/movable/proc/get_default_language() - // if no language is specified, and we want to say() something, which - // language do we use? - var/datum/language_holder/H = get_language_holder() - - if(H.selected_default_language) - if(can_speak_in_language(H.selected_default_language)) - return H.selected_default_language - else - H.selected_default_language = null - - - var/datum/language/chosen_langtype - var/highest_priority - - for(var/lt in H.languages) - var/datum/language/langtype = lt - if(!can_speak_in_language(langtype)) - continue - - var/pri = initial(langtype.default_priority) - if(!highest_priority || (pri > highest_priority)) - chosen_langtype = langtype - highest_priority = pri - - H.selected_default_language = . - . = chosen_langtype - -/* 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) - if(src == user || !isturf(loc)) - return FALSE - if(anchored || throwing) - return FALSE - return TRUE - - -/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/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 + var/verb_say = "says" + var/verb_ask = "asks" + var/verb_exclaim = "exclaims" + var/verb_whisper = "whispers" + 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 = 0 + var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move + 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 + +/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) + throw_impact(highest) + 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") + 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("x") + var/turf/T = locate(var_value, y, z) + if(T) + forceMove(T) + return TRUE + return FALSE + if("y") + var/turf/T = locate(x, var_value, z) + if(T) + forceMove(T) + return TRUE + return FALSE + if("z") + var/turf/T = locate(x, y, var_value) + if(T) + forceMove(T) + return TRUE + return FALSE + if("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,gs) + if(QDELETED(AM)) + return FALSE + if(!(AM.can_be_pulled(src))) + return FALSE + + // If we're pulling something then drop what we're currently pulling and pull this instead. + if(pulling) + if(gs==0) + stop_pulling() + return FALSE + // Are we trying to pull something we are already pulling? Then enter grab cycle and end. + if(AM == pulling) + grab_state = gs + if(istype(AM,/mob/living)) + var/mob/living/AMob = AM + AMob.grabbedby(src) + return TRUE + stop_pulling() + 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 + grab_state = gs + if(ismob(AM)) + var/mob/M = AM + log_combat(src, M, "grabbed", addition="passive grab") + visible_message("[src] has grabbed [M] passively!") + return TRUE + +/atom/movable/proc/stop_pulling() + if(pulling) + pulling.pulledby = null + var/mob/living/ex_pulled = pulling + pulling = null + grab_state = 0 + if(isliving(ex_pulled)) + var/mob/living/L = ex_pulled + L.update_canmove()// 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.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)) + +/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) + stop_pulling() + return + +//////////////////////////////////////// +// 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 + + // 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(pulling) + if(pullee && get_dist(src, pullee) > 1) + stop_pulling() + + if(pullee && pullee.loc != loc && !isturf(pullee.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() + 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) //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.Move(T, get_dir(pulling, T)) //the pullee tries to reach our previous position + if(pulling && get_dist(src, pulling) > 1) //the pullee couldn't keep up + stop_pulling() + 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() + + + 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) + 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) + 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) + 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() below +//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() in mob_movement.dm +//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 + + +/atom/movable/proc/newtonian_move(direction) //Only moves the object if it's under no gravity + 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 + SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) + return hit_atom.hitby(src) + +/atom/movable/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked) + if(!anchored && hitpush) + step(src, AM.dir) + ..() + +/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback, messy_throw = TRUE) + return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, messy_throw) + +/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback, messy_throw = TRUE) //If this returns FALSE then callback will not be called. + . = 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.movement_delay() + 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.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 + 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/CanPass(atom/movable/mover, turf/target) + if(mover in buckled_mobs) + return 1 + return ..() + +// 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 + + var/matrix/OM = matrix(transform) + var/matrix/M = matrix(transform) + M.Turn(pixel_x_diff ? pixel_x_diff*2 : pick(-16, 16)) + + animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, transform = M, time = 2) + animate(src, pixel_x = pixel_x - pixel_x_diff, pixel_y = pixel_y - pixel_y_diff, transform = OM, 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() + . = ..() + . -= "Jump to" + .["Follow"] = "?_src_=holder;[HrefToken()];adminplayerobservefollow=[REF(src)]" + .["Get"] = "?_src_=holder;[HrefToken()];admingetmovable=[REF(src)]" + +/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 */ +/atom/movable/proc/get_language_holder(shadow=TRUE) + if(language_holder) + return language_holder + else + language_holder = new initial_language_holder(src) + return language_holder + +/atom/movable/proc/grant_language(datum/language/dt, body = FALSE) + var/datum/language_holder/H = get_language_holder(!body) + H.grant_language(dt, body) + +/atom/movable/proc/grant_all_languages(omnitongue=FALSE) + var/datum/language_holder/H = get_language_holder() + H.grant_all_languages(omnitongue) + +/atom/movable/proc/get_random_understood_language() + var/datum/language_holder/H = get_language_holder() + . = H.get_random_understood_language() + +/atom/movable/proc/remove_language(datum/language/dt, body = FALSE) + var/datum/language_holder/H = get_language_holder(!body) + H.remove_language(dt, body) + +/atom/movable/proc/remove_all_languages() + var/datum/language_holder/H = get_language_holder() + H.remove_all_languages() + +/atom/movable/proc/has_language(datum/language/dt) + var/datum/language_holder/H = get_language_holder() + . = H.has_language(dt) + +/atom/movable/proc/copy_known_languages_from(thing, replace=FALSE) + var/datum/language_holder/H = get_language_holder() + . = H.copy_known_languages_from(thing, replace) + +// Whether an AM can speak in a language or not, independent of whether +// it KNOWS the language +/atom/movable/proc/could_speak_in_language(datum/language/dt) + . = TRUE + +/atom/movable/proc/can_speak_in_language(datum/language/dt) + var/datum/language_holder/H = get_language_holder() + + if(!H.has_language(dt)) + return FALSE + else if(H.omnitongue) + return TRUE + else if(could_speak_in_language(dt) && (!H.only_speaks_language || H.only_speaks_language == dt)) + return TRUE + else + return FALSE + +/atom/movable/proc/get_default_language() + // if no language is specified, and we want to say() something, which + // language do we use? + var/datum/language_holder/H = get_language_holder() + + if(H.selected_default_language) + if(can_speak_in_language(H.selected_default_language)) + return H.selected_default_language + else + H.selected_default_language = null + + + var/datum/language/chosen_langtype + var/highest_priority + + for(var/lt in H.languages) + var/datum/language/langtype = lt + if(!can_speak_in_language(langtype)) + continue + + var/pri = initial(langtype.default_priority) + if(!highest_priority || (pri > highest_priority)) + chosen_langtype = langtype + highest_priority = pri + + H.selected_default_language = . + . = chosen_langtype + +/* 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) + if(src == user || !isturf(loc)) + return FALSE + if(anchored || throwing) + return FALSE + return TRUE + + +/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 ed470473d9..696b942434 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 8bbe7f54ed..df4a38cf60 100644 --- a/code/game/gamemodes/brother/traitor_bro.dm +++ b/code/game/gamemodes/brother/traitor_bro.dm @@ -1,72 +1,72 @@ -/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("AI", "Cyborg") - protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") - - 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 - 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) - 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/proc/update_brother_icons_added(datum/mind/brother_mind) - var/datum/atom_hud/antag/brotherhud = GLOB.huds[ANTAG_HUD_BROTHER] - brotherhud.join_hud(brother_mind.current) - set_antag_hud(brother_mind.current, "brother") - -/datum/game_mode/proc/update_brother_icons_removed(datum/mind/brother_mind) - var/datum/atom_hud/antag/brotherhud = GLOB.huds[ANTAG_HUD_BROTHER] - brotherhud.leave_hud(brother_mind.current) - set_antag_hud(brother_mind.current, null) +/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("AI", "Cyborg") + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") + + 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 + 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) + 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/proc/update_brother_icons_added(datum/mind/brother_mind) + var/datum/atom_hud/antag/brotherhud = GLOB.huds[ANTAG_HUD_BROTHER] + brotherhud.join_hud(brother_mind.current) + set_antag_hud(brother_mind.current, "brother") + +/datum/game_mode/proc/update_brother_icons_removed(datum/mind/brother_mind) + var/datum/atom_hud/antag/brotherhud = GLOB.huds[ANTAG_HUD_BROTHER] + brotherhud.leave_hud(brother_mind.current) + set_antag_hud(brother_mind.current, null) diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm index d6131ce58e..14fe960422 100644 --- a/code/game/gamemodes/changeling/changeling.dm +++ b/code/game/gamemodes/changeling/changeling.dm @@ -1,127 +1,127 @@ -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" = SLOT_HEAD, "wear_mask" = SLOT_WEAR_MASK, "neck" = SLOT_NECK, "back" = SLOT_BACK, "wear_suit" = SLOT_WEAR_SUIT, "w_uniform" = SLOT_W_UNIFORM, "shoes" = SLOT_SHOES, "belt" = SLOT_BELT, "gloves" = SLOT_GLOVES, "glasses" = SLOT_GLASSES, "ears" = SLOT_EARS, "wear_id" = SLOT_WEAR_ID, "s_store" = SLOT_S_STORE)) -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" - antag_flag = ROLE_CHANGELING - false_report_weight = 10 - restricted_jobs = list("AI", "Cyborg") - protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to ling role blacklist - 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 - return 1 - else - setup_error = "Not enough changeling candidates" - return 0 - -/datum/game_mode/changeling/post_setup() - 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) - ..() - -/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(!jobban_isbanned(character, ROLE_CHANGELING) && !QDELETED(character) && !jobban_isbanned(character, 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.undie_color = chosen_prof.undie_color - user.undershirt = chosen_prof.undershirt - user.shirt_color =chosen_prof.shirt_color - user.socks = chosen_prof.socks - user.socks_color =chosen_prof.socks_color - - 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.item_color = chosen_prof.item_color_list[slot] - C.item_state = chosen_prof.item_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" = SLOT_HEAD, "wear_mask" = SLOT_WEAR_MASK, "neck" = SLOT_NECK, "back" = SLOT_BACK, "wear_suit" = SLOT_WEAR_SUIT, "w_uniform" = SLOT_W_UNIFORM, "shoes" = SLOT_SHOES, "belt" = SLOT_BELT, "gloves" = SLOT_GLOVES, "glasses" = SLOT_GLASSES, "ears" = SLOT_EARS, "wear_id" = SLOT_WEAR_ID, "s_store" = SLOT_S_STORE)) +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" + antag_flag = ROLE_CHANGELING + false_report_weight = 10 + restricted_jobs = list("AI", "Cyborg") + protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to ling role blacklist + 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 + return 1 + else + setup_error = "Not enough changeling candidates" + return 0 + +/datum/game_mode/changeling/post_setup() + 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) + ..() + +/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(!jobban_isbanned(character, ROLE_CHANGELING) && !QDELETED(character) && !jobban_isbanned(character, 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.undie_color = chosen_prof.undie_color + user.undershirt = chosen_prof.undershirt + user.shirt_color =chosen_prof.shirt_color + user.socks = chosen_prof.socks + user.socks_color =chosen_prof.socks_color + + 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.item_color = chosen_prof.item_color_list[slot] + C.item_state = chosen_prof.item_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 3f5abe531b..b010b08bc3 100644 --- a/code/game/gamemodes/changeling/traitor_chan.dm +++ b/code/game/gamemodes/changeling/traitor_chan.dm @@ -1,84 +1,84 @@ -/datum/game_mode/traitor/changeling - name = "traitor+changeling" - config_tag = "traitorchan" - false_report_weight = 10 - traitors_possible = 3 //hard limit on traitors if scaling is turned off - restricted_jobs = list("AI", "Cyborg") - required_players = 25 - required_enemies = 1 // how many of each type are required - recommended_enemies = 3 - reroll_friendly = 1 - - 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/announce() - to_chat(world, "The current game mode is - Traitor+Changeling!") - to_chat(world, "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!") - -/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 - return ..() - else - return 0 - -/datum/game_mode/traitor/changeling/post_setup() - for(var/datum/mind/changeling in changelings) - changeling.add_antag_datum(/datum/antagonist/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(!jobban_isbanned(character, ROLE_CHANGELING) && !QDELETED(character) && !jobban_isbanned(character, 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" + false_report_weight = 10 + traitors_possible = 3 //hard limit on traitors if scaling is turned off + restricted_jobs = list("AI", "Cyborg") + required_players = 25 + required_enemies = 1 // how many of each type are required + recommended_enemies = 3 + reroll_friendly = 1 + + 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/announce() + to_chat(world, "The current game mode is - Traitor+Changeling!") + to_chat(world, "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!") + +/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 + return ..() + else + return 0 + +/datum/game_mode/traitor/changeling/post_setup() + for(var/datum/mind/changeling in changelings) + changeling.add_antag_datum(/datum/antagonist/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(!jobban_isbanned(character, ROLE_CHANGELING) && !QDELETED(character) && !jobban_isbanned(character, 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 8d3254933a..d010da465e 100644 --- a/code/game/gamemodes/events.dm +++ b/code/game/gamemodes/events.dm @@ -1,82 +1,82 @@ -/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", "poweroff") - 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 = 0 - S.update_icon() - S.power_change() - - var/list/skipped_areas = list(/area/engine/engineering, /area/engine/supermatter, /area/engine/atmospherics_engine, /area/ai_monitored/turret_protected/ai) - - for(var/area/A in world) - if( !A.requires_power || A.always_unpowered ) - continue - - var/skip = 0 - for(var/area_type in skipped_areas) - if(istype(A,area_type)) - skip = 1 - break - if(A.contents) - for(var/atom/AT in A.contents) - if(!is_station_level(AT.z)) //Only check one, it's enough. - skip = 1 - break - if(skip) - 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 - - var/skip = 0 - for(var/area_type in skipped_areas) - if(istype(A,area_type)) - skip = 1 - break - if(skip) - 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", "poweron") - 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 = 1 - S.update_icon() - S.power_change() - for(var/area/A in world) - if(!istype(A, /area/space) && !istype(A, /area/shuttle) && !istype(A, /area/arrival)) - 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", "poweron") - 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 = 1 - 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", "poweroff") + 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 = 0 + S.update_icon() + S.power_change() + + var/list/skipped_areas = list(/area/engine/engineering, /area/engine/supermatter, /area/engine/atmospherics_engine, /area/ai_monitored/turret_protected/ai) + + for(var/area/A in world) + if( !A.requires_power || A.always_unpowered ) + continue + + var/skip = 0 + for(var/area_type in skipped_areas) + if(istype(A,area_type)) + skip = 1 + break + if(A.contents) + for(var/atom/AT in A.contents) + if(!is_station_level(AT.z)) //Only check one, it's enough. + skip = 1 + break + if(skip) + 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 + + var/skip = 0 + for(var/area_type in skipped_areas) + if(istype(A,area_type)) + skip = 1 + break + if(skip) + 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", "poweron") + 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 = 1 + S.update_icon() + S.power_change() + for(var/area/A in world) + if(!istype(A, /area/space) && !istype(A, /area/shuttle) && !istype(A, /area/arrival)) + 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", "poweron") + 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 = 1 + S.update_icon() + S.power_change() + diff --git a/code/game/gamemodes/extended/extended.dm b/code/game/gamemodes/extended/extended.dm index 1229a9cf8c..976f3e3eca 100644 --- a/code/game/gamemodes/extended/extended.dm +++ b/code/game/gamemodes/extended/extended.dm @@ -1,32 +1,32 @@ -/datum/game_mode/extended - name = "secret extended" - config_tag = "secret_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() - if(flipseclevel) //CIT CHANGE - allows the sec level to be flipped roundstart - return ..() - 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) - if(flipseclevel) //CIT CHANGE - allows the sec level to be flipped roundstart - return ..() - 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", "commandreport") +/datum/game_mode/extended + name = "secret extended" + config_tag = "secret_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() + if(flipseclevel) //CIT CHANGE - allows the sec level to be flipped roundstart + return ..() + 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) + if(flipseclevel) //CIT CHANGE - allows the sec level to be flipped roundstart + return ..() + 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", "commandreport") diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index 3a258599b7..b98462d4d4 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -1,606 +1,606 @@ - - -/* - * 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/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. - var/flipseclevel = FALSE //CIT CHANGE - adds a 10% chance for the alert level to be the opposite of what the gamemode is supposed to have - -/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/mob/dead/new_player/player in GLOB.player_list) - if((player.client)&&(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. - //finalize_monster_hunters() Disabled for now - if(!report) - report = !CONFIG_GET(flag/no_intercept_report) - addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) - - if(prob(20)) //CIT CHANGE - adds a 20% chance for the security level to be the opposite of what it normally is - flipseclevel = TRUE - if(SSdbcore.Connect()) - var/sql - if(SSticker.mode) - sql += "game_mode = '[SSticker.mode]'" - if(GLOB.revdata.originmastercommit) - if(sql) - sql += ", " - sql += "commit_hash = '[GLOB.revdata.originmastercommit]'" - if(sql) - var/datum/DBQuery/query_round_game_mode = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET [sql] WHERE id = [GLOB.round_id]") - 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/mob/Player in GLOB.mob_list) - if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) && !isbrain(Player) && Player.client) - 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) - 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) - 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) - if(Player.mind.special_role || LAZYLEN(Player.mind.antag_datums)) //Someone's still antaging! - living_antag_player = Player - 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/check_win() //universal trigger to be called at mob death, nuke explosion, etc. To be called from everywhere. - return 0 - -/datum/game_mode/proc/send_intercept() - if(flipseclevel && !(config_tag == "extended"))//CIT CHANGE - lets the security level be flipped roundstart - 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", "commandreport") - return - 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[config_tag] = 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[config_tag] - 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.", "intercept") - 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% - -//Use return list if you want a list, with the arg being the number you want returned. -//WARNING: THIS PROC DOES NOT TAKE INTO ACCOUNT WHAT SSPersistence ALREADY HAS FOR "ADJUST ANTAG REP". If this is used more than once -//and the person rolls more than once, they will not get even more deduction! -//More efficient if you use return list instead of calling this multiple times -//fail_default_pick makes it use pick() instead of antag rep if it can't find anyone -//allow_zero_if_insufficient allows it to pick people with zero rep if there isn't enough antags -/datum/game_mode/proc/antag_pick(list/datum/mind/candidates, return_list = FALSE, fail_default_pick = TRUE, allow_zero_if_insufficient = TRUE) - if(!CONFIG_GET(flag/use_antag_rep)) // || candidates.len <= 1) - return pick(candidates) - - //whoever named the config entries is a bad person :( - - //Tickets you get for free - var/free_tickets = CONFIG_GET(number/default_antag_tickets) - //Max extra tickets you can use - var/additional_tickets = CONFIG_GET(number/max_tickets_per_roll) - - var/list/ckey_to_mind = list() //this is admittedly shitcode but I'm webediting - var/list/prev_tickets = SSpersistence.antag_rep //cache for hyper-speed in theory. how many tickets someone has stored - var/list/curr_tickets = list() //how many tickets someone has for *this* antag roll, so with the free tickets - var/list/datum/mind/insufficient = list() //who got cucked out of an antag roll due to not having *any* tickets - for(var/datum/mind/M in candidates) - var/mind_ckey = ckey(M.key) - var/can_spend = min(prev_tickets[mind_ckey], additional_tickets) //they can only spend up to config/max_tickets_per_roll - var/amount = can_spend + free_tickets //but they get config/default_antag_tickets for free - if(amount <= 0) //if they don't have any - insufficient += M //too bad! - continue - curr_tickets[mind_ckey] = amount - ckey_to_mind[mind_ckey] = M //make sure we can look them up after picking - - if(!return_list) //return a single guy - var/ckey - if(length(curr_tickets)) - ckey = pickweight(curr_tickets) - SSpersistence.antag_rep_change[ckey] = -(curr_tickets[ckey] - free_tickets) //deduct what they spent - var/mind = ckey_to_mind[ckey] || (allow_zero_if_insufficient? pick(insufficient) : null) //we want their mind - if(!mind) //no mind - var/warning = "WARNING: No antagonists were successfully picked by /datum/gamemode/proc/antag_pick()![fail_default_pick? " Defaulting to pick()!":""]" - message_admins(warning) - log_game(warning) - if(fail_default_pick) - mind = pick(candidates) - return mind - else //the far more efficient and proper use of this, to get a list - var/list/rolled = list() - var/list/spend_tickets = list() - for(var/i in 1 to return_list) - if(!length(curr_tickets)) //ah heck, we're out of candidates.. - break - var/ckey = pickweight(curr_tickets) //pick - rolled += ckey //add - spend_tickets[ckey] = curr_tickets[ckey] - free_tickets - curr_tickets -= ckey //don't roll them again - var/missing = return_list - length(rolled) - var/list/add - if((missing > 0) && allow_zero_if_insufficient) //need more.. - for(var/i in 1 to missing) - if(!length(insufficient)) - break //still not enough - var/datum/mind/M = pick_n_take(insufficient) - add += M - if(!length(rolled) && !length(add)) //if no one could normally roll AND no one can zero roll - var/warning = "WARNING: No antagonists were successfully picked by /datum/gamemode/proc/antag_pick()![fail_default_pick? " Defaulting to pick()!":""]" - message_admins(warning) - log_game(warning) - var/list/failed = list() - if(fail_default_pick) - var/list/C = candidates.Copy() - for(var/i in 1 to return_list) - if(!length(C)) - break - failed += pick_n_take(C) - return failed //Wew, no one qualified! - for(var/i in 1 to length(rolled)) - var/ckey = rolled[i] - SSpersistence.antag_rep_change[ckey] = -(spend_tickets[ckey]) //deduct what all of the folks who rolled spent - rolled[i] = ckey_to_mind[ckey] //whoever called us wants minds, not ckeys - if(add) - rolled += add - return rolled - -/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/mob/dead/new_player/player in GLOB.player_list) - if(player.client && 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(!jobban_isbanned(player, ROLE_SYNDICATE) && !QDELETED(player) && !jobban_isbanned(player, role) && !QDELETED(player)) //Nodrak/Carn: Antag Job-bans - 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(!jobban_isbanned(player, ROLE_SYNDICATE) && !QDELETED(player) && !jobban_isbanned(player, role) && !QDELETED(player) ) //Nodrak/Carn: Antag Job-bans - 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 - - 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/mob/dead/new_player/P in GLOB.player_list) - if(P.client && P.ready == PLAYER_READY_TO_PLAY) - . ++ - -////////////////////////// -//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() - if(flipseclevel && !(config_tag == "extended")) //CIT CHANGE - allows the sec level to be flipped roundstart - for(var/T in subtypesof(/datum/station_goal)) - var/datum/station_goal/G = new T - station_goals += G - G.on_report() - return - 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/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. + var/flipseclevel = FALSE //CIT CHANGE - adds a 10% chance for the alert level to be the opposite of what the gamemode is supposed to have + +/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/mob/dead/new_player/player in GLOB.player_list) + if((player.client)&&(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. + //finalize_monster_hunters() Disabled for now + if(!report) + report = !CONFIG_GET(flag/no_intercept_report) + addtimer(CALLBACK(GLOBAL_PROC, .proc/display_roundstart_logout_report), ROUNDSTART_LOGOUT_REPORT_TIME) + + if(prob(20)) //CIT CHANGE - adds a 20% chance for the security level to be the opposite of what it normally is + flipseclevel = TRUE + if(SSdbcore.Connect()) + var/sql + if(SSticker.mode) + sql += "game_mode = '[SSticker.mode]'" + if(GLOB.revdata.originmastercommit) + if(sql) + sql += ", " + sql += "commit_hash = '[GLOB.revdata.originmastercommit]'" + if(sql) + var/datum/DBQuery/query_round_game_mode = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET [sql] WHERE id = [GLOB.round_id]") + 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/mob/Player in GLOB.mob_list) + if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) && !isbrain(Player) && Player.client) + 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) + 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) + 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) + if(Player.mind.special_role || LAZYLEN(Player.mind.antag_datums)) //Someone's still antaging! + living_antag_player = Player + 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/check_win() //universal trigger to be called at mob death, nuke explosion, etc. To be called from everywhere. + return 0 + +/datum/game_mode/proc/send_intercept() + if(flipseclevel && !(config_tag == "extended"))//CIT CHANGE - lets the security level be flipped roundstart + 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", "commandreport") + return + 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[config_tag] = 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[config_tag] + 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.", "intercept") + 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% + +//Use return list if you want a list, with the arg being the number you want returned. +//WARNING: THIS PROC DOES NOT TAKE INTO ACCOUNT WHAT SSPersistence ALREADY HAS FOR "ADJUST ANTAG REP". If this is used more than once +//and the person rolls more than once, they will not get even more deduction! +//More efficient if you use return list instead of calling this multiple times +//fail_default_pick makes it use pick() instead of antag rep if it can't find anyone +//allow_zero_if_insufficient allows it to pick people with zero rep if there isn't enough antags +/datum/game_mode/proc/antag_pick(list/datum/mind/candidates, return_list = FALSE, fail_default_pick = TRUE, allow_zero_if_insufficient = TRUE) + if(!CONFIG_GET(flag/use_antag_rep)) // || candidates.len <= 1) + return pick(candidates) + + //whoever named the config entries is a bad person :( + + //Tickets you get for free + var/free_tickets = CONFIG_GET(number/default_antag_tickets) + //Max extra tickets you can use + var/additional_tickets = CONFIG_GET(number/max_tickets_per_roll) + + var/list/ckey_to_mind = list() //this is admittedly shitcode but I'm webediting + var/list/prev_tickets = SSpersistence.antag_rep //cache for hyper-speed in theory. how many tickets someone has stored + var/list/curr_tickets = list() //how many tickets someone has for *this* antag roll, so with the free tickets + var/list/datum/mind/insufficient = list() //who got cucked out of an antag roll due to not having *any* tickets + for(var/datum/mind/M in candidates) + var/mind_ckey = ckey(M.key) + var/can_spend = min(prev_tickets[mind_ckey], additional_tickets) //they can only spend up to config/max_tickets_per_roll + var/amount = can_spend + free_tickets //but they get config/default_antag_tickets for free + if(amount <= 0) //if they don't have any + insufficient += M //too bad! + continue + curr_tickets[mind_ckey] = amount + ckey_to_mind[mind_ckey] = M //make sure we can look them up after picking + + if(!return_list) //return a single guy + var/ckey + if(length(curr_tickets)) + ckey = pickweight(curr_tickets) + SSpersistence.antag_rep_change[ckey] = -(curr_tickets[ckey] - free_tickets) //deduct what they spent + var/mind = ckey_to_mind[ckey] || (allow_zero_if_insufficient? pick(insufficient) : null) //we want their mind + if(!mind) //no mind + var/warning = "WARNING: No antagonists were successfully picked by /datum/gamemode/proc/antag_pick()![fail_default_pick? " Defaulting to pick()!":""]" + message_admins(warning) + log_game(warning) + if(fail_default_pick) + mind = pick(candidates) + return mind + else //the far more efficient and proper use of this, to get a list + var/list/rolled = list() + var/list/spend_tickets = list() + for(var/i in 1 to return_list) + if(!length(curr_tickets)) //ah heck, we're out of candidates.. + break + var/ckey = pickweight(curr_tickets) //pick + rolled += ckey //add + spend_tickets[ckey] = curr_tickets[ckey] - free_tickets + curr_tickets -= ckey //don't roll them again + var/missing = return_list - length(rolled) + var/list/add + if((missing > 0) && allow_zero_if_insufficient) //need more.. + for(var/i in 1 to missing) + if(!length(insufficient)) + break //still not enough + var/datum/mind/M = pick_n_take(insufficient) + add += M + if(!length(rolled) && !length(add)) //if no one could normally roll AND no one can zero roll + var/warning = "WARNING: No antagonists were successfully picked by /datum/gamemode/proc/antag_pick()![fail_default_pick? " Defaulting to pick()!":""]" + message_admins(warning) + log_game(warning) + var/list/failed = list() + if(fail_default_pick) + var/list/C = candidates.Copy() + for(var/i in 1 to return_list) + if(!length(C)) + break + failed += pick_n_take(C) + return failed //Wew, no one qualified! + for(var/i in 1 to length(rolled)) + var/ckey = rolled[i] + SSpersistence.antag_rep_change[ckey] = -(spend_tickets[ckey]) //deduct what all of the folks who rolled spent + rolled[i] = ckey_to_mind[ckey] //whoever called us wants minds, not ckeys + if(add) + rolled += add + return rolled + +/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/mob/dead/new_player/player in GLOB.player_list) + if(player.client && 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(!jobban_isbanned(player, ROLE_SYNDICATE) && !QDELETED(player) && !jobban_isbanned(player, role) && !QDELETED(player)) //Nodrak/Carn: Antag Job-bans + 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(!jobban_isbanned(player, ROLE_SYNDICATE) && !QDELETED(player) && !jobban_isbanned(player, role) && !QDELETED(player) ) //Nodrak/Carn: Antag Job-bans + 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 + + 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/mob/dead/new_player/P in GLOB.player_list) + if(P.client && P.ready == PLAYER_READY_TO_PLAY) + . ++ + +////////////////////////// +//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() + if(flipseclevel && !(config_tag == "extended")) //CIT CHANGE - allows the sec level to be flipped roundstart + for(var/T in subtypesof(/datum/station_goal)) + var/datum/station_goal/G = new T + station_goals += G + G.on_report() + return + 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/meteor/meteor.dm b/code/game/gamemodes/meteor/meteor.dm index 90e2b85f11..7857eb8253 100644 --- a/code/game/gamemodes/meteor/meteor.dm +++ b/code/game/gamemodes/meteor/meteor.dm @@ -1,59 +1,59 @@ -/datum/game_mode/meteor - name = "meteor" - config_tag = "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" + 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 176f62ef2d..d84c104a2f 100644 --- a/code/game/gamemodes/objective_items.dm +++ b/code/game/gamemodes/objective_items.dm @@ -1,265 +1,265 @@ -//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", "Station Engineer", "Atmospheric Technician") - -/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 Chief Medical Officer's MKII hypospray." - targetitem = /obj/item/hypospray/mkii/CMO //CITADEL EDIT, changing theft objective for the Hypo MK II - difficulty = 5 - excludefromjob = list("Chief Medical Officer", "Medical Doctor", "Chemist", "Virologist", "Geneticist") - -/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 vest." - targetitem = /obj/item/clothing/suit/armor/laserproof - difficulty = 3 - excludefromjob = list("Head of Security", "Warden") - -/datum/objective_item/steal/reactive - name = "a reactive teleport armor." - targetitem = /obj/item/clothing/suit/armor/reactive - difficulty = 5 - excludefromjob = list("Research Director","Scientist", "Roboticist") - -/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] - 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", "Station Engineer", "Atmospheric Technician") - 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", "Roboticist") - -/datum/objective_item/steal/slime/check_special_completion(obj/item/slime_extract/E) - if(E.Uses > 0) - return 1 - return 0 - -//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", "Station Engineer", "Atmospheric Technician") + +/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 Chief Medical Officer's MKII hypospray." + targetitem = /obj/item/hypospray/mkii/CMO //CITADEL EDIT, changing theft objective for the Hypo MK II + difficulty = 5 + excludefromjob = list("Chief Medical Officer", "Medical Doctor", "Chemist", "Virologist", "Geneticist") + +/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 vest." + targetitem = /obj/item/clothing/suit/armor/laserproof + difficulty = 3 + excludefromjob = list("Head of Security", "Warden") + +/datum/objective_item/steal/reactive + name = "a reactive teleport armor." + targetitem = /obj/item/clothing/suit/armor/reactive + difficulty = 5 + excludefromjob = list("Research Director","Scientist", "Roboticist") + +/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] + 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", "Station Engineer", "Atmospheric Technician") + 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", "Roboticist") + +/datum/objective_item/steal/slime/check_special_completion(obj/item/slime_extract/E) + if(E.Uses > 0) + return 1 + return 0 + +//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 6b988fd077..ddb622ab08 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 d54dda5353..e1fbb636c8 100644 --- a/code/game/gamemodes/sandbox/h_sandbox.dm +++ b/code/game/gamemodes/sandbox/h_sandbox.dm @@ -1,303 +1,303 @@ - - -GLOBAL_VAR_INIT(hsboxspawn, TRUE) - -/mob - var/datum/hSB/sandbox = null -/mob/proc/CanBuild() - sandbox = new/datum/hSB - 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/hSB - 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/smallDelivery, /obj/item/projectile, - /obj/item/borg/sight, /obj/item/borg/stun, /obj/item/robot_module) - -/datum/hSB/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 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 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 Welding Fuel Tank" = "hsbspawn&path=[/obj/structure/reagent_dispensers/fueltank]", - "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/hSB/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 + var/datum/hSB/sandbox = null +/mob/proc/CanBuild() + sandbox = new/datum/hSB + 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/hSB + 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/smallDelivery, /obj/item/projectile, + /obj/item/borg/sight, /obj/item/borg/stun, /obj/item/robot_module) + +/datum/hSB/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 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 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 Welding Fuel Tank" = "hsbspawn&path=[/obj/structure/reagent_dispensers/fueltank]", + "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/hSB/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 21169b8236..9c38bd83e8 100644 --- a/code/game/gamemodes/sandbox/sandbox.dm +++ b/code/game/gamemodes/sandbox/sandbox.dm @@ -1,21 +1,21 @@ -/datum/game_mode/sandbox - name = "sandbox" - config_tag = "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" + 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 3811fa9562..fc669d4855 100644 --- a/code/game/gamemodes/traitor/double_agents.dm +++ b/code/game/gamemodes/traitor/double_agents.dm @@ -1,82 +1,82 @@ -/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" - 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" + 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 4a6e72cf67..8b1b18660a 100644 --- a/code/game/gamemodes/traitor/traitor.dm +++ b/code/game/gamemodes/traitor/traitor.dm @@ -1,99 +1,99 @@ -/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" - 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("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to ling role blacklist - 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 - 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)) - 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(!jobban_isbanned(character, ROLE_TRAITOR) && !QDELETED(character) && !jobban_isbanned(character, 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" + 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("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to ling role blacklist + 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 + 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)) + 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(!jobban_isbanned(character, ROLE_TRAITOR) && !QDELETED(character) && !jobban_isbanned(character, 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 9ea2694354..2f3645a248 100644 --- a/code/game/gamemodes/wizard/wizard.dm +++ b/code/game/gamemodes/wizard/wizard.dm @@ -1,73 +1,73 @@ -/datum/game_mode - var/list/datum/mind/wizards = list() - var/list/datum/mind/apprentices = list() - -/datum/game_mode/wizard - name = "wizard" - config_tag = "wizard" - antag_flag = ROLE_WIZARD - false_report_weight = 10 - required_players = 20 - required_enemies = 1 - recommended_enemies = 1 - enemy_minimum_age = 7 - 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) - 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/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" + antag_flag = ROLE_WIZARD + false_report_weight = 10 + required_players = 20 + required_enemies = 1 + recommended_enemies = 1 + enemy_minimum_age = 7 + 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) + 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/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 c98795c90b..84eda82ef5 100644 --- a/code/game/machinery/Beacon.dm +++ b/code/game/machinery/Beacon.dm @@ -1,48 +1,48 @@ -/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." - level = 1 // underfloor - 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 - - hide(T.intact) - -/obj/machinery/bluespace_beacon/Destroy() - QDEL_NULL(Beacon) - return ..() - -// update the invisibility and icon -/obj/machinery/bluespace_beacon/hide(intact) - invisibility = intact ? INVISIBILITY_MAXIMUM : 0 - update_icon() - -// update the icon_state -/obj/machinery/bluespace_beacon/update_icon() - var/state="floor_beacon" - - if(invisibility) - icon_state = "[state]f" - - else - icon_state = "[state]" - -/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) - - update_icon() +/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." + level = 1 // underfloor + 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 + + hide(T.intact) + +/obj/machinery/bluespace_beacon/Destroy() + QDEL_NULL(Beacon) + return ..() + +// update the invisibility and icon +/obj/machinery/bluespace_beacon/hide(intact) + invisibility = intact ? INVISIBILITY_MAXIMUM : 0 + update_icon() + +// update the icon_state +/obj/machinery/bluespace_beacon/update_icon() + var/state="floor_beacon" + + if(invisibility) + icon_state = "[state]f" + + else + icon_state = "[state]" + +/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) + + update_icon() diff --git a/code/game/machinery/PDApainter.dm b/code/game/machinery/PDApainter.dm index 45a3b59661..20efee9420 100644 --- a/code/game/machinery/PDApainter.dm +++ b/code/game/machinery/PDApainter.dm @@ -1,143 +1,143 @@ -/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() - cut_overlays() - - if(stat & BROKEN) - icon_state = "[initial(icon_state)]-broken" - return - - if(storedpda) - add_overlay("[initial(icon_state)]-closed") - - if(powered()) - icon_state = initial(icon_state) - else - icon_state = "[initial(icon_state)]-off" - - return - -/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, - /obj/item/pda/lieutenant) - - for(var/A in typesof(/obj/item/pda) - blocked) - var/obj/item/pda/P = A - var/PDA_name = initial(P.name) - colorlist += PDA_name - colorlist[PDA_name] = list(initial(P.icon_state), initial(P.desc), initial(P.overlays_offsets), initial(P.overlays_icons)) - -/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(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 if(istype(O, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) - if(stat & BROKEN) - 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(!(stat & BROKEN)) - return - to_chat(user, "You repair [src].") - stat &= ~BROKEN - obj_integrity = max_integrity - update_icon() - else - to_chat(user, "[src] does not need repairs.") - else - return ..() - -/obj/machinery/pdapainter/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(!(stat & BROKEN)) - stat |= BROKEN - update_icon() - -/obj/machinery/pdapainter/attack_hand(mob/user) - . = ..() - if(.) - return - - if(!storedpda) - to_chat(user, "[src] is empty.") - return - var/choice = input(user, "Select the new skin!", "PDA Painting") as null|anything in colorlist - if(!choice || !storedpda || !in_range(src, user)) - return - var/list/P = colorlist[choice] - storedpda.icon_state = P[1] - storedpda.desc = P[2] - storedpda.overlays_offsets = P[3] - storedpda.overlays_icons = P[4] - storedpda.set_new_overlays() - storedpda.update_icon() - ejectpda() - -/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/power_change() - ..() - update_icon() +/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() + cut_overlays() + + if(stat & BROKEN) + icon_state = "[initial(icon_state)]-broken" + return + + if(storedpda) + add_overlay("[initial(icon_state)]-closed") + + if(powered()) + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)]-off" + + return + +/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, + /obj/item/pda/lieutenant) + + for(var/A in typesof(/obj/item/pda) - blocked) + var/obj/item/pda/P = A + var/PDA_name = initial(P.name) + colorlist += PDA_name + colorlist[PDA_name] = list(initial(P.icon_state), initial(P.desc), initial(P.overlays_offsets), initial(P.overlays_icons)) + +/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(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 if(istype(O, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) + if(stat & BROKEN) + 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(!(stat & BROKEN)) + return + to_chat(user, "You repair [src].") + stat &= ~BROKEN + obj_integrity = max_integrity + update_icon() + else + to_chat(user, "[src] does not need repairs.") + else + return ..() + +/obj/machinery/pdapainter/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(!(stat & BROKEN)) + stat |= BROKEN + update_icon() + +/obj/machinery/pdapainter/attack_hand(mob/user) + . = ..() + if(.) + return + + if(!storedpda) + to_chat(user, "[src] is empty.") + return + var/choice = input(user, "Select the new skin!", "PDA Painting") as null|anything in colorlist + if(!choice || !storedpda || !in_range(src, user)) + return + var/list/P = colorlist[choice] + storedpda.icon_state = P[1] + storedpda.desc = P[2] + storedpda.overlays_offsets = P[3] + storedpda.overlays_icons = P[4] + storedpda.set_new_overlays() + storedpda.update_icon() + ejectpda() + +/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/power_change() + ..() + update_icon() diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index 88b0e804b7..b6dc09eb59 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -1,412 +1,412 @@ -/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 - req_access = list(ACCESS_CMO) //Used for reagent deletion and addition of non medicines - var/efficiency = 1 - var/min_health = 30 - 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/salbutamol, /datum/reagent/medicine/bicaridine, /datum/reagent/medicine/kelotane), - list(/datum/reagent/medicine/oculine,/datum/reagent/medicine/inacusiate), - list(/datum/reagent/medicine/antitoxin, /datum/reagent/medicine/mutadone, /datum/reagent/medicine/mannitol, /datum/reagent/medicine/pen_acid), - list(/datum/reagent/medicine/omnizine) - ) - var/list/chem_buttons //Used when emagged to scramble which chem is used, eg: antitoxin -> 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." - -/obj/machinery/sleeper/Initialize() - . = ..() - create_reagents(500, NO_REACT) - occupant_typecache = GLOB.typecache_living - update_icon() - reset_chem_buttons() - RefreshParts() - add_inital_chems() - -/obj/machinery/sleeper/on_deconstruction() - var/obj/item/reagent_containers/sleeper_buffer/buffer = new (loc) - buffer.volume = reagents.maximum_volume - buffer.reagents.maximum_volume = reagents.maximum_volume - reagents.trans_to(buffer.reagents, reagents.total_volume) - -/obj/machinery/sleeper/proc/add_inital_chems() - for(var/i in available_chems) - var/datum/reagent/R = reagents.has_reagent(i) - if(!R) - reagents.add_reagent(i, (20)) - continue - if(R.volume < 20) - reagents.add_reagent(i, (20 - R.volume)) - -/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) - (10*(E-1)) // CIT CHANGE - changes min health equation to be min_health - (matterbin rating * 10) - available_chems = list() - for(var/i in 1 to I) - available_chems |= possible_chems[i] - reset_chem_buttons() - - //Total container size 500 - 2000u - if(reagents) - reagents.maximum_volume = (500*E) - - -/obj/machinery/sleeper/update_icon() - icon_state = initial(icon_state) - if(state_open) - icon_state += "-open" - -/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) - ..() - -/obj/machinery/sleeper/close_machine(mob/user) - if((isnull(user) || istype(user)) && state_open && !panel_open) - ..(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) - var/datum/reagent/R = pick(reagents.reagent_list) - inject_chem(R.type, occupant) - open_machine() - //Is this too much? - if(severity == EMP_HEAVY) - var/chem = pick(available_chems) - available_chems -= chem - available_chems += get_random_reagent_id() - reset_chem_buttons() - -/obj/machinery/sleeper/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/reagent_containers/sleeper_buffer)) - var/obj/item/reagent_containers/sleeper_buffer/SB = I - if((SB.reagents.total_volume + reagents.total_volume) < reagents.maximum_volume) - SB.reagents.trans_to(reagents, SB.reagents.total_volume) - visible_message("[user] places the [SB] into the [src].") - qdel(SB) - return - else - SB.reagents.trans_to(reagents, SB.reagents.total_volume) - visible_message("[user] adds as much as they can to the [src] from the [SB].") - return - if(istype(I, /obj/item/reagent_containers)) - var/obj/item/reagent_containers/RC = I - if(RC.reagents.total_volume == 0) - to_chat(user, "The [I] is empty!") - for(var/datum/reagent/R in RC.reagents.reagent_list) - if((obj_flags & EMAGGED) || (allowed(usr))) - break - if(!istype(R, /datum/reagent/medicine)) - visible_message("The [src] gives out a hearty boop and rejects the [I]. The Sleeper's screen flashes with a pompous \"Medicines only, please.\"") - return - RC.reagents.trans_to(reagents, 1000) - visible_message("[user] adds as much as they can to the [src] from the [I].") - return - - -/obj/machinery/sleeper/MouseDrop_T(mob/target, mob/user) - if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) - 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/AltClick(mob/user) - . = ..() - if(!user.canUseTopic(src, !issilicon(user))) - return - if(state_open) - close_machine() - else - open_machine() - return TRUE - -/obj/machinery/sleeper/examine(mob/user) - . = ..() - . += "Alt-click [src] to [state_open ? "close" : "open"] it." - -/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, 550, 700, master_ui, state) - ui.open() - -/obj/machinery/sleeper/ui_data() - var/list/data = list() - data["occupied"] = occupant ? 1 : 0 - data["open"] = state_open - data["efficiency"] = efficiency - data["current_vol"] = reagents.total_volume - data["tot_capacity"] = reagents.maximum_volume - - data["chems"] = list() - for(var/chem in available_chems) - var/datum/reagent/R = reagents.has_reagent(chem) - R = GLOB.chemical_reagents_list[chem] - data["synthchems"] += list(list("name" = R.name, "id" = R.type, "synth_allowed" = synth_allowed(chem))) - for(var/datum/reagent/R in reagents.reagent_list) - data["chems"] += list(list("name" = R.name, "id" = R.type, "vol" = R.volume, "purity" = R.purity, "allowed" = chem_allowed(R.type))) - - 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)) - data["occupant"]["failing_organs"] = list() - var/mob/living/carbon/C = mob_occupant - if(C) - for(var/obj/item/organ/Or in C.getFailingOrgans()) - if(istype(Or, /obj/item/organ/brain)) - continue - data["occupant"]["failing_organs"] += list(list("name" = Or.name)) - - if(mob_occupant.has_dna()) // Blood-stuff is mostly a copy-paste from the healthscanner. - var/blood_id = C.get_blood_id() - if(blood_id) - data["occupant"]["blood"] = list() // We can start populating this list. - var/blood_type = C.dna.blood_type - if(blood_id != "blood") // special blood substance - var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] - if(R) - blood_type = R.name - else - blood_type = blood_id - data["occupant"]["blood"]["maxBloodVolume"] = (BLOOD_VOLUME_NORMAL*C.blood_ratio) - data["occupant"]["blood"]["currentBloodVolume"] = C.blood_volume - data["occupant"]["blood"]["dangerBloodVolume"] = BLOOD_VOLUME_SAFE - data["occupant"]["blood"]["bloodType"] = blood_type - return data - -/obj/machinery/sleeper/ui_act(action, params) - if(..()) - return - var/mob/living/mob_occupant = occupant - - switch(action) - if("door") - if(state_open) - close_machine() - else - open_machine() - . = TRUE - if("inject") - var/chem = text2path(params["chem"]) - var/amount = text2num(params["volume"]) - 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, amount)) - . = TRUE - if(scrambled_chems && prob(5)) - to_chat(usr, "Chemical system re-route detected, results may not be as expected!") - if("synth") - var/chem = text2path(params["chem"]) - if(!is_operational()) - return - reagents.add_reagent(chem_buttons[chem], 10) //other_purity = 0.75 for when the mechanics are in - if("purge") - var/chem = text2path(params["chem"]) - if(allowed(usr)) - if(!is_operational()) - return - reagents.remove_reagent(chem, 10) - return - if(chem in available_chems) - if(!is_operational()) - return - /*var/datum/reagent/R = reagents.has_reagent(chem) //For when purity effects are in - if(R.purity < 0.8)*/ - reagents.remove_reagent(chem, 10) - else - visible_message("Access Denied.") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - - -/obj/machinery/sleeper/emag_act(mob/user) - . = ..() - obj_flags |= EMAGGED - scramble_chem_buttons() - to_chat(user, "You scramble the sleeper's user interface!") - return TRUE - -//trans to -/obj/machinery/sleeper/proc/inject_chem(chem, mob/user, volume = 10) - if(chem_allowed(chem)) - reagents.trans_id_to(occupant, chem, volume)//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/synth_allowed(chem) - var/datum/reagent/R = reagents.has_reagent(chem) - if(!R) - return TRUE - if(R.volume < 50) - return TRUE - return FALSE - -/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/Initialize() - . = ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/machine/sleeper/syndie(null) - component_parts += new /obj/item/stock_parts/matter_bin/super(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(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/syndie/fullupgrade/Initialize() - . = ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/machine/sleeper/syndie(null) - 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/clockwork - name = "soothing sleeper" - desc = "A large cryogenics unit built from brass. Its surface is pleasantly cool the touch." - icon_state = "sleeper_clockwork" - enter_message = "You hear the gentle hum and click of machinery, and are lulled into a sense of peace." - possible_chems = list(list("epinephrine", "salbutamol", "bicaridine", "kelotane", "oculine", "inacusiate", "mannitol")) - -/obj/machinery/sleeper/clockwork/process() - if(occupant && isliving(occupant)) - var/mob/living/L = occupant - if(GLOB.clockwork_vitality) //If there's Vitality, the sleeper has passive healing - GLOB.clockwork_vitality = max(0, GLOB.clockwork_vitality - 1) - L.adjustBruteLoss(-1) - L.adjustFireLoss(-1) - L.adjustOxyLoss(-5) - -/obj/machinery/sleeper/old - icon_state = "oldpod" +/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 + req_access = list(ACCESS_CMO) //Used for reagent deletion and addition of non medicines + var/efficiency = 1 + var/min_health = 30 + 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/salbutamol, /datum/reagent/medicine/bicaridine, /datum/reagent/medicine/kelotane), + list(/datum/reagent/medicine/oculine,/datum/reagent/medicine/inacusiate), + list(/datum/reagent/medicine/antitoxin, /datum/reagent/medicine/mutadone, /datum/reagent/medicine/mannitol, /datum/reagent/medicine/pen_acid), + list(/datum/reagent/medicine/omnizine) + ) + var/list/chem_buttons //Used when emagged to scramble which chem is used, eg: antitoxin -> 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." + +/obj/machinery/sleeper/Initialize() + . = ..() + create_reagents(500, NO_REACT) + occupant_typecache = GLOB.typecache_living + update_icon() + reset_chem_buttons() + RefreshParts() + add_inital_chems() + +/obj/machinery/sleeper/on_deconstruction() + var/obj/item/reagent_containers/sleeper_buffer/buffer = new (loc) + buffer.volume = reagents.maximum_volume + buffer.reagents.maximum_volume = reagents.maximum_volume + reagents.trans_to(buffer.reagents, reagents.total_volume) + +/obj/machinery/sleeper/proc/add_inital_chems() + for(var/i in available_chems) + var/datum/reagent/R = reagents.has_reagent(i) + if(!R) + reagents.add_reagent(i, (20)) + continue + if(R.volume < 20) + reagents.add_reagent(i, (20 - R.volume)) + +/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) - (10*(E-1)) // CIT CHANGE - changes min health equation to be min_health - (matterbin rating * 10) + available_chems = list() + for(var/i in 1 to I) + available_chems |= possible_chems[i] + reset_chem_buttons() + + //Total container size 500 - 2000u + if(reagents) + reagents.maximum_volume = (500*E) + + +/obj/machinery/sleeper/update_icon() + icon_state = initial(icon_state) + if(state_open) + icon_state += "-open" + +/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) + ..() + +/obj/machinery/sleeper/close_machine(mob/user) + if((isnull(user) || istype(user)) && state_open && !panel_open) + ..(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) + var/datum/reagent/R = pick(reagents.reagent_list) + inject_chem(R.type, occupant) + open_machine() + //Is this too much? + if(severity == EMP_HEAVY) + var/chem = pick(available_chems) + available_chems -= chem + available_chems += get_random_reagent_id() + reset_chem_buttons() + +/obj/machinery/sleeper/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/reagent_containers/sleeper_buffer)) + var/obj/item/reagent_containers/sleeper_buffer/SB = I + if((SB.reagents.total_volume + reagents.total_volume) < reagents.maximum_volume) + SB.reagents.trans_to(reagents, SB.reagents.total_volume) + visible_message("[user] places the [SB] into the [src].") + qdel(SB) + return + else + SB.reagents.trans_to(reagents, SB.reagents.total_volume) + visible_message("[user] adds as much as they can to the [src] from the [SB].") + return + if(istype(I, /obj/item/reagent_containers)) + var/obj/item/reagent_containers/RC = I + if(RC.reagents.total_volume == 0) + to_chat(user, "The [I] is empty!") + for(var/datum/reagent/R in RC.reagents.reagent_list) + if((obj_flags & EMAGGED) || (allowed(usr))) + break + if(!istype(R, /datum/reagent/medicine)) + visible_message("The [src] gives out a hearty boop and rejects the [I]. The Sleeper's screen flashes with a pompous \"Medicines only, please.\"") + return + RC.reagents.trans_to(reagents, 1000) + visible_message("[user] adds as much as they can to the [src] from the [I].") + return + + +/obj/machinery/sleeper/MouseDrop_T(mob/target, mob/user) + if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) + 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/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, !issilicon(user))) + return + if(state_open) + close_machine() + else + open_machine() + return TRUE + +/obj/machinery/sleeper/examine(mob/user) + . = ..() + . += "Alt-click [src] to [state_open ? "close" : "open"] it." + +/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, 550, 700, master_ui, state) + ui.open() + +/obj/machinery/sleeper/ui_data() + var/list/data = list() + data["occupied"] = occupant ? 1 : 0 + data["open"] = state_open + data["efficiency"] = efficiency + data["current_vol"] = reagents.total_volume + data["tot_capacity"] = reagents.maximum_volume + + data["chems"] = list() + for(var/chem in available_chems) + var/datum/reagent/R = reagents.has_reagent(chem) + R = GLOB.chemical_reagents_list[chem] + data["synthchems"] += list(list("name" = R.name, "id" = R.type, "synth_allowed" = synth_allowed(chem))) + for(var/datum/reagent/R in reagents.reagent_list) + data["chems"] += list(list("name" = R.name, "id" = R.type, "vol" = R.volume, "purity" = R.purity, "allowed" = chem_allowed(R.type))) + + 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)) + data["occupant"]["failing_organs"] = list() + var/mob/living/carbon/C = mob_occupant + if(C) + for(var/obj/item/organ/Or in C.getFailingOrgans()) + if(istype(Or, /obj/item/organ/brain)) + continue + data["occupant"]["failing_organs"] += list(list("name" = Or.name)) + + if(mob_occupant.has_dna()) // Blood-stuff is mostly a copy-paste from the healthscanner. + var/blood_id = C.get_blood_id() + if(blood_id) + data["occupant"]["blood"] = list() // We can start populating this list. + var/blood_type = C.dna.blood_type + if(blood_id != "blood") // special blood substance + var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] + if(R) + blood_type = R.name + else + blood_type = blood_id + data["occupant"]["blood"]["maxBloodVolume"] = (BLOOD_VOLUME_NORMAL*C.blood_ratio) + data["occupant"]["blood"]["currentBloodVolume"] = C.blood_volume + data["occupant"]["blood"]["dangerBloodVolume"] = BLOOD_VOLUME_SAFE + data["occupant"]["blood"]["bloodType"] = blood_type + return data + +/obj/machinery/sleeper/ui_act(action, params) + if(..()) + return + var/mob/living/mob_occupant = occupant + + switch(action) + if("door") + if(state_open) + close_machine() + else + open_machine() + . = TRUE + if("inject") + var/chem = text2path(params["chem"]) + var/amount = text2num(params["volume"]) + 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, amount)) + . = TRUE + if(scrambled_chems && prob(5)) + to_chat(usr, "Chemical system re-route detected, results may not be as expected!") + if("synth") + var/chem = text2path(params["chem"]) + if(!is_operational()) + return + reagents.add_reagent(chem_buttons[chem], 10) //other_purity = 0.75 for when the mechanics are in + if("purge") + var/chem = text2path(params["chem"]) + if(allowed(usr)) + if(!is_operational()) + return + reagents.remove_reagent(chem, 10) + return + if(chem in available_chems) + if(!is_operational()) + return + /*var/datum/reagent/R = reagents.has_reagent(chem) //For when purity effects are in + if(R.purity < 0.8)*/ + reagents.remove_reagent(chem, 10) + else + visible_message("Access Denied.") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + + +/obj/machinery/sleeper/emag_act(mob/user) + . = ..() + obj_flags |= EMAGGED + scramble_chem_buttons() + to_chat(user, "You scramble the sleeper's user interface!") + return TRUE + +//trans to +/obj/machinery/sleeper/proc/inject_chem(chem, mob/user, volume = 10) + if(chem_allowed(chem)) + reagents.trans_id_to(occupant, chem, volume)//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/synth_allowed(chem) + var/datum/reagent/R = reagents.has_reagent(chem) + if(!R) + return TRUE + if(R.volume < 50) + return TRUE + return FALSE + +/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/Initialize() + . = ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/machine/sleeper/syndie(null) + component_parts += new /obj/item/stock_parts/matter_bin/super(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(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/syndie/fullupgrade/Initialize() + . = ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/machine/sleeper/syndie(null) + 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/clockwork + name = "soothing sleeper" + desc = "A large cryogenics unit built from brass. Its surface is pleasantly cool the touch." + icon_state = "sleeper_clockwork" + enter_message = "You hear the gentle hum and click of machinery, and are lulled into a sense of peace." + possible_chems = list(list("epinephrine", "salbutamol", "bicaridine", "kelotane", "oculine", "inacusiate", "mannitol")) + +/obj/machinery/sleeper/clockwork/process() + if(occupant && isliving(occupant)) + var/mob/living/L = occupant + if(GLOB.clockwork_vitality) //If there's Vitality, the sleeper has passive healing + GLOB.clockwork_vitality = max(0, GLOB.clockwork_vitality - 1) + L.adjustBruteLoss(-1) + L.adjustFireLoss(-1) + L.adjustOxyLoss(-5) + +/obj/machinery/sleeper/old + icon_state = "oldpod" diff --git a/code/game/machinery/ai_slipper.dm b/code/game/machinery/ai_slipper.dm index 1703ce57d1..4935c9d4d3 100644 --- a/code/game/machinery/ai_slipper.dm +++ b/code/game/machinery/ai_slipper.dm @@ -1,48 +1,48 @@ -/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/power_change() - if(stat & BROKEN) - return - else - if(powered()) - stat &= ~NOPOWER - else - stat |= NOPOWER - if((stat & (NOPOWER|BROKEN)) || 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/power_change() + if(stat & BROKEN) + return + else + if(powered()) + stat &= ~NOPOWER + else + stat |= NOPOWER + if((stat & (NOPOWER|BROKEN)) || 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 14dc7d05c2..69dbdcd50c 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 = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - - power_channel = 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() - 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) +#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 = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + + power_channel = 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() + 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 ..() \ No newline at end of file diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index bdb90c4c97..016f226399 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -1,383 +1,383 @@ -#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/list/categories = list( - "Tools", - "Electronics", - "Construction", - "T-Comm", - "Security", - "Machinery", - "Medical", - "Misc", - "Dinnerware", - "Imported" - ) - -/obj/machinery/autolathe/Initialize() - AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_GLASS), 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 && !(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(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(type_inserted, id_inserted, amount_inserted) - if(ispath(type_inserted, /obj/item/stack/ore/bluespace_crystal)) - use_power(MINERAL_MATERIAL_AMOUNT / 10) - else - switch(id_inserted) - if (MAT_METAL) - flick("autolathe_o",src)//plays metal insertion animation - if (MAT_GLASS) - flick("autolathe_r",src)//plays glass 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/metal_cost = being_built.materials[MAT_METAL] - var/glass_cost = being_built.materials[MAT_GLASS] - - var/power = max(2000, (metal_cost+glass_cost)*multiplier/5) - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - if((materials.amount(MAT_METAL) >= metal_cost*multiplier*coeff) && (materials.amount(MAT_GLASS) >= glass_cost*multiplier*coeff)) - busy = TRUE - use_power(power) - icon_state = "autolathe_n" - var/time = is_stack ? 32 : 32*coeff*multiplier - addtimer(CALLBACK(src, .proc/make_item, power, metal_cost, glass_cost, multiplier, coeff, is_stack), time) - - 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, metal_cost, glass_cost, multiplier, coeff, is_stack) - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/atom/A = drop_location() - use_power(power) - var/list/materials_used = list(MAT_METAL=metal_cost*coeff*multiplier, MAT_GLASS=glass_cost*coeff*multiplier) - materials.use_amount(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.materials = new_item.materials.Copy() - for(var/mat in materials_used) - new_item.materials[mat] = materials_used[mat] / multiplier - new_item.autolathe_crafted(src) - 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/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 = min(D.maxstack, D.materials[MAT_METAL] ?round(materials.amount(MAT_METAL)/D.materials[MAT_METAL]):INFINITY,D.materials[MAT_GLASS]?round(materials.amount(MAT_GLASS)/D.materials[MAT_GLASS]):INFINITY) - 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 = min(D.maxstack, D.materials[MAT_METAL] ?round(materials.amount(MAT_METAL)/D.materials[MAT_METAL]):INFINITY,D.materials[MAT_GLASS]?round(materials.amount(MAT_GLASS)/D.materials[MAT_GLASS]):INFINITY) - 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 = materials.materials[mat_id] - dat += "[M.name] amount: [M.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/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - if(D.materials[MAT_METAL] && (materials.amount(MAT_METAL) < (D.materials[MAT_METAL] * coeff * amount))) - return FALSE - if(D.materials[MAT_GLASS] && (materials.amount(MAT_GLASS) < (D.materials[MAT_GLASS] * coeff * amount))) - return FALSE - return TRUE - -/obj/machinery/autolathe/proc/get_design_cost(datum/design/D) - var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) - var/dat - if(D.materials[MAT_METAL]) - dat += "[D.materials[MAT_METAL] * coeff] metal " - if(D.materials[MAT_GLASS]) - dat += "[D.materials[MAT_GLASS] * coeff] glass" - 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(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) +#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/list/categories = list( + "Tools", + "Electronics", + "Construction", + "T-Comm", + "Security", + "Machinery", + "Medical", + "Misc", + "Dinnerware", + "Imported" + ) + +/obj/machinery/autolathe/Initialize() + AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_GLASS), 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 && !(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(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(type_inserted, id_inserted, amount_inserted) + if(ispath(type_inserted, /obj/item/stack/ore/bluespace_crystal)) + use_power(MINERAL_MATERIAL_AMOUNT / 10) + else + switch(id_inserted) + if (MAT_METAL) + flick("autolathe_o",src)//plays metal insertion animation + if (MAT_GLASS) + flick("autolathe_r",src)//plays glass 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/metal_cost = being_built.materials[MAT_METAL] + var/glass_cost = being_built.materials[MAT_GLASS] + + var/power = max(2000, (metal_cost+glass_cost)*multiplier/5) + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + if((materials.amount(MAT_METAL) >= metal_cost*multiplier*coeff) && (materials.amount(MAT_GLASS) >= glass_cost*multiplier*coeff)) + busy = TRUE + use_power(power) + icon_state = "autolathe_n" + var/time = is_stack ? 32 : 32*coeff*multiplier + addtimer(CALLBACK(src, .proc/make_item, power, metal_cost, glass_cost, multiplier, coeff, is_stack), time) + + 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, metal_cost, glass_cost, multiplier, coeff, is_stack) + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/atom/A = drop_location() + use_power(power) + var/list/materials_used = list(MAT_METAL=metal_cost*coeff*multiplier, MAT_GLASS=glass_cost*coeff*multiplier) + materials.use_amount(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.materials = new_item.materials.Copy() + for(var/mat in materials_used) + new_item.materials[mat] = materials_used[mat] / multiplier + new_item.autolathe_crafted(src) + 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/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 = min(D.maxstack, D.materials[MAT_METAL] ?round(materials.amount(MAT_METAL)/D.materials[MAT_METAL]):INFINITY,D.materials[MAT_GLASS]?round(materials.amount(MAT_GLASS)/D.materials[MAT_GLASS]):INFINITY) + 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 = min(D.maxstack, D.materials[MAT_METAL] ?round(materials.amount(MAT_METAL)/D.materials[MAT_METAL]):INFINITY,D.materials[MAT_GLASS]?round(materials.amount(MAT_GLASS)/D.materials[MAT_GLASS]):INFINITY) + 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 = materials.materials[mat_id] + dat += "[M.name] amount: [M.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/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + if(D.materials[MAT_METAL] && (materials.amount(MAT_METAL) < (D.materials[MAT_METAL] * coeff * amount))) + return FALSE + if(D.materials[MAT_GLASS] && (materials.amount(MAT_GLASS) < (D.materials[MAT_GLASS] * coeff * amount))) + return FALSE + return TRUE + +/obj/machinery/autolathe/proc/get_design_cost(datum/design/D) + var/coeff = (ispath(D.build_path, /obj/item/stack) ? 1 : prod_coeff) + var/dat + if(D.materials[MAT_METAL]) + dat += "[D.materials[MAT_METAL] * coeff] metal " + if(D.materials[MAT_GLASS]) + dat += "[D.materials[MAT_GLASS] * coeff] glass" + 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(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 \ No newline at end of file diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index 24c9e32969..3a9b560bd6 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -1,268 +1,268 @@ -/obj/machinery/button - name = "button" - desc = "A remote control switch." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "doorctrl" - var/skin = "doorctrl" - power_channel = 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 = 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 - -/obj/machinery/button/update_icon_state() - if(panel_open) - icon_state = "button-open" - else if(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(istype(W, /obj/item/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 && istype(W, /obj/item/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, 1) - 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, 1) - obj_flags |= EMAGGED - return TRUE - -/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/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((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() - - addtimer(CALLBACK(src, /atom/.proc/update_icon), 15) - -/obj/machinery/button/power_change() - ..() - update_icon() - - -/obj/machinery/button/door - name = "door button" - desc = "A door remote control switch." - var/normaldoorcontrol = FALSE - var/specialfunctions = OPEN // Bitflag, see assembly file - -/obj/machinery/button/door/setup_device() - if(!device) - if(normaldoorcontrol) - var/obj/item/assembly/control/airlock/A = new(src) - device = A - A.specialfunctions = specialfunctions - else - device = new /obj/item/assembly/control(src) - ..() - -/obj/machinery/button/door/incinerator_vent_toxmix - name = "combustion chamber vent control" - id = INCINERATOR_TOXMIX_VENT - req_access = list(ACCESS_TOX) - -/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/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/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/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/item/wallframe/button - name = "button frame" - desc = "Used for building buttons." - icon_state = "button" - result_path = /obj/machinery/button - materials = list(MAT_METAL=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 = 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 = 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 + +/obj/machinery/button/update_icon_state() + if(panel_open) + icon_state = "button-open" + else if(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(istype(W, /obj/item/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 && istype(W, /obj/item/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, 1) + 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, 1) + obj_flags |= EMAGGED + return TRUE + +/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/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((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() + + addtimer(CALLBACK(src, /atom/.proc/update_icon), 15) + +/obj/machinery/button/power_change() + ..() + update_icon() + + +/obj/machinery/button/door + name = "door button" + desc = "A door remote control switch." + var/normaldoorcontrol = FALSE + var/specialfunctions = OPEN // Bitflag, see assembly file + +/obj/machinery/button/door/setup_device() + if(!device) + if(normaldoorcontrol) + var/obj/item/assembly/control/airlock/A = new(src) + device = A + A.specialfunctions = specialfunctions + else + device = new /obj/item/assembly/control(src) + ..() + +/obj/machinery/button/door/incinerator_vent_toxmix + name = "combustion chamber vent control" + id = INCINERATOR_TOXMIX_VENT + req_access = list(ACCESS_TOX) + +/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/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/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/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/item/wallframe/button + name = "button frame" + desc = "Used for building buttons." + icon_state = "button" + result_path = /obj/machinery/button + materials = list(MAT_METAL=MINERAL_MATERIAL_AMOUNT) diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm index b120720992..66e302bab6 100644 --- a/code/game/machinery/camera/camera_assembly.dm +++ b/code/game/machinery/camera/camera_assembly.dm @@ -1,148 +1,148 @@ -/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" - materials = list(MAT_METAL=400, MAT_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 = "camera1" - max_integrity = 150 - // Motion, EMP-Proof, X-ray - var/static/list/possible_upgrades = typecacheof(list(/obj/item/assembly/prox_sensor, /obj/item/stack/sheet/mineral/plasma, /obj/item/analyzer)) - var/list/upgrades - var/state = 1 - - /* - 1 = Wrenched in place - 2 = Welded in place - 3 = Wires attached to it (you can now attach/dettach upgrades) - 4 = Screwdriver panel closed and is fully built (you cannot attach upgrades) - */ - -/obj/structure/camera_assembly/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - upgrades = list() - -/obj/structure/camera_assembly/Destroy() - QDEL_LIST(upgrades) - return ..() - -/obj/structure/camera_assembly/attackby(obj/item/W, mob/living/user, params) - switch(state) - if(1) - // State 1 - if(istype(W, /obj/item/weldingtool)) - if(weld(W, user)) - to_chat(user, "You weld the assembly securely into place.") - setAnchored(TRUE) - state = 2 - return - if(2) - // State 2 - 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 the assembly.") - state = 3 - else - to_chat(user, "You need two lengths of cable to wire a camera!") - return - return - - else if(istype(W, /obj/item/weldingtool)) - - if(weld(W, user)) - to_chat(user, "You unweld the assembly from its place.") - state = 1 - setAnchored(TRUE) - return - - // Upgrades! - if(is_type_in_typecache(W, possible_upgrades) && !is_type_in_list(W, upgrades)) // Is a possible upgrade and isn't in the camera already. - if(!user.transferItemToLoc(W, src)) - return - to_chat(user, "You attach \the [W] into the assembly inner circuits.") - upgrades += W - return - - return ..() - -/obj/structure/camera_assembly/crowbar_act(mob/user, obj/item/tool) - if(!upgrades.len) - return FALSE - var/obj/U = locate(/obj) in upgrades - if(U) - to_chat(user, "You detach an upgrade from the assembly.") - tool.play_tool_sound(src) - U.forceMove(drop_location()) - upgrades -= U - return TRUE - -/obj/structure/camera_assembly/screwdriver_act(mob/user, obj/item/tool) - . = ..() - if(.) - return TRUE - if(state != 3) - 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 = 4 - 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 != 3) - return FALSE - - 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 = 2 - return TRUE - -/obj/structure/camera_assembly/wrench_act(mob/user, obj/item/I) - if(state != 1) - return FALSE - I.play_tool_sound(src) - to_chat(user, "You detach the assembly from its place.") - new /obj/item/wallframe/camera(drop_location()) - qdel(src) - return TRUE - -/obj/structure/camera_assembly/proc/weld(obj/item/weldingtool/W, mob/living/user) - if(!W.tool_start_check(user, amount=0)) - return FALSE - to_chat(user, "You start to weld \the [src]...") - if(W.use_tool(src, user, 20, 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) +/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" + materials = list(MAT_METAL=400, MAT_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 = "camera1" + max_integrity = 150 + // Motion, EMP-Proof, X-ray + var/static/list/possible_upgrades = typecacheof(list(/obj/item/assembly/prox_sensor, /obj/item/stack/sheet/mineral/plasma, /obj/item/analyzer)) + var/list/upgrades + var/state = 1 + + /* + 1 = Wrenched in place + 2 = Welded in place + 3 = Wires attached to it (you can now attach/dettach upgrades) + 4 = Screwdriver panel closed and is fully built (you cannot attach upgrades) + */ + +/obj/structure/camera_assembly/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + upgrades = list() + +/obj/structure/camera_assembly/Destroy() + QDEL_LIST(upgrades) + return ..() + +/obj/structure/camera_assembly/attackby(obj/item/W, mob/living/user, params) + switch(state) + if(1) + // State 1 + if(istype(W, /obj/item/weldingtool)) + if(weld(W, user)) + to_chat(user, "You weld the assembly securely into place.") + setAnchored(TRUE) + state = 2 + return + if(2) + // State 2 + 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 the assembly.") + state = 3 + else + to_chat(user, "You need two lengths of cable to wire a camera!") + return + return + + else if(istype(W, /obj/item/weldingtool)) + + if(weld(W, user)) + to_chat(user, "You unweld the assembly from its place.") + state = 1 + setAnchored(TRUE) + return + + // Upgrades! + if(is_type_in_typecache(W, possible_upgrades) && !is_type_in_list(W, upgrades)) // Is a possible upgrade and isn't in the camera already. + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You attach \the [W] into the assembly inner circuits.") + upgrades += W + return + + return ..() + +/obj/structure/camera_assembly/crowbar_act(mob/user, obj/item/tool) + if(!upgrades.len) + return FALSE + var/obj/U = locate(/obj) in upgrades + if(U) + to_chat(user, "You detach an upgrade from the assembly.") + tool.play_tool_sound(src) + U.forceMove(drop_location()) + upgrades -= U + return TRUE + +/obj/structure/camera_assembly/screwdriver_act(mob/user, obj/item/tool) + . = ..() + if(.) + return TRUE + if(state != 3) + 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 = 4 + 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 != 3) + return FALSE + + 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 = 2 + return TRUE + +/obj/structure/camera_assembly/wrench_act(mob/user, obj/item/I) + if(state != 1) + return FALSE + I.play_tool_sound(src) + to_chat(user, "You detach the assembly from its place.") + new /obj/item/wallframe/camera(drop_location()) + qdel(src) + return TRUE + +/obj/structure/camera_assembly/proc/weld(obj/item/weldingtool/W, mob/living/user) + if(!W.tool_start_check(user, amount=0)) + return FALSE + to_chat(user, "You start to weld \the [src]...") + if(W.use_tool(src, user, 20, 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) diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm index 6c134417bb..d103e2a856 100644 --- a/code/game/machinery/camera/motion.dm +++ b/code/game/machinery/camera/motion.dm @@ -1,77 +1,77 @@ -/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(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 + + 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(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) diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm index afcc1fee7c..021232f435 100644 --- a/code/game/machinery/camera/presets.dm +++ b/code/game/machinery/camera/presets.dm @@ -1,89 +1,89 @@ -// PRESETS - -// EMP -/obj/machinery/camera/emp_proof - start_active = TRUE - -/obj/machinery/camera/emp_proof/Initialize() - . = ..() - upgradeEmpProof() - -// X-ray - -/obj/machinery/camera/xray - start_active = TRUE - icon_state = "xraycam" // 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 - -/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]" - -// CHECKS - -/obj/machinery/camera/proc/isEmpProof() - return upgrades & CAMERA_UPGRADE_EMP_PROOF - -/obj/machinery/camera/proc/isXRay() - return upgrades & CAMERA_UPGRADE_XRAY - -/obj/machinery/camera/proc/isMotion() - return upgrades & CAMERA_UPGRADE_MOTION - -// UPGRADE PROCS - -/obj/machinery/camera/proc/upgradeEmpProof() - AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) - assembly.upgrades.Add(new /obj/item/stack/sheet/mineral/plasma(assembly)) - upgrades |= CAMERA_UPGRADE_EMP_PROOF - -/obj/machinery/camera/proc/upgradeXRay() - assembly.upgrades.Add(new /obj/item/analyzer(assembly)) - upgrades |= CAMERA_UPGRADE_XRAY - -// If you are upgrading Motion, and it isn't in the camera's Initialize(), add it to the machines list. -/obj/machinery/camera/proc/upgradeMotion() - assembly.upgrades.Add(new /obj/item/assembly/prox_sensor(assembly)) - upgrades |= CAMERA_UPGRADE_MOTION +// PRESETS + +// EMP +/obj/machinery/camera/emp_proof + start_active = TRUE + +/obj/machinery/camera/emp_proof/Initialize() + . = ..() + upgradeEmpProof() + +// X-ray + +/obj/machinery/camera/xray + start_active = TRUE + icon_state = "xraycam" // 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 + +/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]" + +// CHECKS + +/obj/machinery/camera/proc/isEmpProof() + return upgrades & CAMERA_UPGRADE_EMP_PROOF + +/obj/machinery/camera/proc/isXRay() + return upgrades & CAMERA_UPGRADE_XRAY + +/obj/machinery/camera/proc/isMotion() + return upgrades & CAMERA_UPGRADE_MOTION + +// UPGRADE PROCS + +/obj/machinery/camera/proc/upgradeEmpProof() + AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES | EMP_PROTECT_CONTENTS) + assembly.upgrades.Add(new /obj/item/stack/sheet/mineral/plasma(assembly)) + upgrades |= CAMERA_UPGRADE_EMP_PROOF + +/obj/machinery/camera/proc/upgradeXRay() + assembly.upgrades.Add(new /obj/item/analyzer(assembly)) + upgrades |= CAMERA_UPGRADE_XRAY + +// If you are upgrading Motion, and it isn't in the camera's Initialize(), add it to the machines list. +/obj/machinery/camera/proc/upgradeMotion() + assembly.upgrades.Add(new /obj/item/assembly/prox_sensor(assembly)) + upgrades |= CAMERA_UPGRADE_MOTION diff --git a/code/game/machinery/camera/tracking.dm b/code/game/machinery/camera/tracking.dm index 1dbfe9d055..bf1b84fe5d 100644 --- a/code/game/machinery/camera/tracking.dm +++ b/code/game/machinery/camera/tracking.dm @@ -1,151 +1,151 @@ -/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.") - - var/cameraticks = 0 - spawn(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.") + + var/cameraticks = 0 + spawn(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 1c0635fd3d..a982411692 100644 --- a/code/game/machinery/cell_charger.dm +++ b/code/game/machinery/cell_charger.dm @@ -1,130 +1,130 @@ -/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 = EQUIP - circuit = /obj/item/circuitboard/machine/cell_charger - pass_flags = PASSTABLE - var/obj/item/stock_parts/cell/charging = null - var/chargelevel = -1 - var/charge_rate = 500 - -/obj/machinery/cell_charger/update_icon() - cut_overlays() - if(charging) - add_overlay(image(charging.icon, charging.icon_state)) - add_overlay("ccharger-on") - if(!(stat & (BROKEN|NOPOWER))) - var/newlevel = round(charging.percent() * 4 / 100) - chargelevel = newlevel - add_overlay("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)]%." - -/obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/stock_parts/cell) && !panel_open) - if(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].") - chargelevel = -1 - 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 - chargelevel = -1 - 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(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 || (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 = EQUIP + circuit = /obj/item/circuitboard/machine/cell_charger + pass_flags = PASSTABLE + var/obj/item/stock_parts/cell/charging = null + var/chargelevel = -1 + var/charge_rate = 500 + +/obj/machinery/cell_charger/update_icon() + cut_overlays() + if(charging) + add_overlay(image(charging.icon, charging.icon_state)) + add_overlay("ccharger-on") + if(!(stat & (BROKEN|NOPOWER))) + var/newlevel = round(charging.percent() * 4 / 100) + chargelevel = newlevel + add_overlay("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)]%." + +/obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/stock_parts/cell) && !panel_open) + if(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].") + chargelevel = -1 + 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 + chargelevel = -1 + 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(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 || (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/cloning.dm b/code/game/machinery/cloning.dm index 97b260f3bf..413621d8b9 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -1,556 +1,556 @@ -//Cloning revival method. -//The pod handles the actual cloning while the computer manages the clone profiles - -//Potential replacement for genetics revives or something I dunno (?) - -#define CLONE_INITIAL_DAMAGE 150 //Clones in clonepods start with 150 cloneloss damage and 150 brainloss damage, thats just logical -#define MINIMUM_HEAL_LEVEL 40 - -#define SPEAK(message) radio.talk_into(src, message, radio_channel) - -/obj/machinery/clonepod - name = "cloning pod" - desc = "An electronically-lockable pod for growing organic tissue." - density = TRUE - icon = 'icons/obj/machines/cloning.dmi' - icon_state = "pod_0" - req_access = list(ACCESS_CLONING) //FOR PREMATURE UNLOCKING. - verb_say = "states" - circuit = /obj/item/circuitboard/machine/clonepod - - var/heal_level //The clone is released once its health reaches this level. - var/obj/machinery/computer/cloning/connected = null //So we remember the connected clone machine. - var/mess = FALSE //Need to clean out it if it's full of exploded clone. - var/attempting = FALSE //One clone attempt at a time thanks - var/speed_coeff - var/efficiency - - var/datum/mind/clonemind - var/grab_ghost_when = CLONER_MATURE_CLONE - - var/internal_radio = TRUE - var/obj/item/radio/radio - var/radio_key = /obj/item/encryptionkey/headset_med - var/radio_channel = RADIO_CHANNEL_MEDICAL - - var/obj/effect/countdown/clonepod/countdown - - var/list/unattached_flesh - var/flesh_number = 0 - -/obj/machinery/clonepod/Initialize() - . = ..() - - countdown = new(src) - - if(internal_radio) - radio = new(src) - radio.keyslot = new radio_key - radio.subspace_transmission = TRUE - radio.canhear_range = 0 - radio.recalculateChannels() - - update_icon() - -/obj/machinery/clonepod/Destroy() - go_out() - QDEL_NULL(radio) - QDEL_NULL(countdown) - if(connected) - connected.DetachCloner(src) - QDEL_LIST(unattached_flesh) - . = ..() - -/obj/machinery/clonepod/RefreshParts() - speed_coeff = 0 - efficiency = 0 - for(var/obj/item/stock_parts/scanning_module/S in component_parts) - efficiency += S.rating - for(var/obj/item/stock_parts/manipulator/P in component_parts) - speed_coeff += P.rating - heal_level = (efficiency * 15) + 10 - if(heal_level < MINIMUM_HEAL_LEVEL) - heal_level = MINIMUM_HEAL_LEVEL - if(heal_level > 100) - heal_level = 100 - -//The return of data disks?? Just for transferring between genetics machine/cloning machine. -//TO-DO: Make the genetics machine accept them. -/obj/item/disk/data - name = "cloning data disk" - icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk. - var/list/fields = list() - var/read_only = 0 //Well,it's still a floppy disk - -//Disk stuff. -/obj/item/disk/data/Initialize() - . = ..() - icon_state = "datadisk[rand(0,6)]" - add_overlay("datadisk_gene") - -/obj/item/disk/data/attack_self(mob/user) - read_only = !read_only - to_chat(user, "You flip the write-protect tab to [read_only ? "protected" : "unprotected"].") - -/obj/item/disk/data/examine(mob/user) - . = ..() - . += "The write-protect tab is set to [read_only ? "protected" : "unprotected"]." - - -//Clonepod - -/obj/machinery/clonepod/examine(mob/user) - . = ..() - var/mob/living/mob_occupant = occupant - if(mess) - . += "It's filled with blood and viscera. You swear you can see it moving..." - if(is_operational() && mob_occupant) - if(mob_occupant.stat != DEAD) - . += "Current clone cycle is [round(get_completion())]% complete." - -/obj/machinery/clonepod/return_air() - // We want to simulate the clone not being in contact with - // the atmosphere, so we'll put them in a constant pressure - // nitrogen. They don't need to breathe while cloning anyway. - var/static/datum/gas_mixture/immutable/cloner/GM //global so that there's only one instance made for all cloning pods - if(!GM) - GM = new - return GM - -/obj/machinery/clonepod/proc/get_completion() - . = FALSE - var/mob/living/mob_occupant = occupant - if(mob_occupant) - . = (100 * ((mob_occupant.health + 100) / (heal_level + 100))) - -/obj/machinery/clonepod/attack_ai(mob/user) - return examine(user) - -//Start growing a human clone in the pod! -/obj/machinery/clonepod/proc/growclone(ckey, clonename, ui, se, mindref, datum/species/mrace, list/features, factions, list/quirks) - if(panel_open) - return FALSE - if(mess || attempting) - return FALSE - clonemind = locate(mindref) in SSticker.minds - if(!istype(clonemind)) //not a mind - return FALSE - if(!QDELETED(clonemind.current)) - if(clonemind.current.stat != DEAD) //mind is associated with a non-dead body - return FALSE - if(clonemind.current.suiciding) // Mind is associated with a body that is suiciding. - return FALSE - if(clonemind.active) //somebody is using that mind - if( ckey(clonemind.key)!=ckey ) - return FALSE - else - // get_ghost() will fail if they're unable to reenter their body - var/mob/dead/observer/G = clonemind.get_ghost() - if(!G) - return FALSE - if(G.suiciding) // The ghost came from a body that is suiciding. - return FALSE - if(clonemind.damnation_type) //Can't clone the damned. - INVOKE_ASYNC(src, .proc/horrifyingsound) - mess = TRUE - update_icon() - return FALSE - if(isvamp(clonemind)) //If the mind is a bloodsucker - return FALSE - - attempting = TRUE //One at a time!! - countdown.start() - - var/mob/living/carbon/human/H = new /mob/living/carbon/human(src) - - H.hardset_dna(ui, se, H.real_name, null, mrace, features) - - if(prob(50 - efficiency*10)) //Chance to give a bad mutation. - H.randmutb() //100% bad mutation. Can be cured with mutadone. - - H.silent = 20 //Prevents an extreme edge case where clones could speak if they said something at exactly the right moment. - occupant = H - - if(!clonename) //to prevent null names - clonename = "clone ([rand(1,999)])" - H.real_name = clonename - - //Get the clone body ready - maim_clone(H) - ADD_TRAIT(H, TRAIT_STABLEHEART, CLONING_POD_TRAIT) - ADD_TRAIT(H, TRAIT_STABLELIVER, CLONING_POD_TRAIT) - ADD_TRAIT(H, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT) - ADD_TRAIT(H, TRAIT_MUTE, CLONING_POD_TRAIT) - ADD_TRAIT(H, TRAIT_NOBREATH, CLONING_POD_TRAIT) - ADD_TRAIT(H, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT) - H.Unconscious(80) - - clonemind.transfer_to(H) - - if(grab_ghost_when == CLONER_FRESH_CLONE) - H.grab_ghost() - to_chat(H, "Consciousness slowly creeps over you as your body regenerates.
    So this is what cloning feels like?
    ") - - if(grab_ghost_when == CLONER_MATURE_CLONE) - H.ghostize(TRUE) //Only does anything if they were still in their old body and not already a ghost - to_chat(H.get_ghost(TRUE), "Your body is beginning to regenerate in a cloning pod. You will become conscious when it is complete.") - - if(H) - H.faction |= factions - - for(var/V in quirks) - var/datum/quirk/Q = new V(H) - Q.on_clone(quirks[V]) - - H.set_cloned_appearance() - H.give_genitals(TRUE) - - H.suiciding = FALSE - attempting = FALSE - return TRUE - -//Grow clones to maturity then kick them out. FREELOADERS -/obj/machinery/clonepod/process() - var/mob/living/mob_occupant = occupant - - if(!is_operational()) //Autoeject if power is lost - if(mob_occupant) - go_out() - mob_occupant.apply_vore_prefs() - connected_message("Clone Ejected: Loss of power.") - - else if(mob_occupant && (mob_occupant.loc == src)) - if((mob_occupant.stat == DEAD) || (mob_occupant.suiciding) || mob_occupant.hellbound) //Autoeject corpses and suiciding dudes. - connected_message("Clone Rejected: Deceased.") - if(internal_radio) - SPEAK("The cloning has been \ - aborted due to unrecoverable tissue failure.") - go_out() - mob_occupant.apply_vore_prefs() - - else if(mob_occupant.cloneloss > (100 - heal_level)) - mob_occupant.Unconscious(80) - var/dmg_mult = CONFIG_GET(number/damage_multiplier) - //Slowly get that clone healed and finished. - mob_occupant.adjustCloneLoss(-((speed_coeff / 2) * dmg_mult)) - var/progress = CLONE_INITIAL_DAMAGE - mob_occupant.getCloneLoss() - // To avoid the default cloner making incomplete clones - progress += (100 - MINIMUM_HEAL_LEVEL) - var/milestone = CLONE_INITIAL_DAMAGE / flesh_number - var/installed = flesh_number - unattached_flesh.len - - if((progress / milestone) >= installed) - // attach some flesh - var/obj/item/I = pick_n_take(unattached_flesh) - if(isorgan(I)) - var/obj/item/organ/O = I - O.organ_flags &= ~ORGAN_FROZEN - O.Insert(mob_occupant) - else if(isbodypart(I)) - var/obj/item/bodypart/BP = I - BP.attach_limb(mob_occupant) - - //Premature clones may have brain damage. - mob_occupant.adjustOrganLoss(ORGAN_SLOT_BRAIN, -((speed_coeff / 2) * dmg_mult)) - - use_power(7500) //This might need tweaking. - - else if((mob_occupant.cloneloss <= (100 - heal_level))) - connected_message("Cloning Process Complete.") - if(internal_radio) - SPEAK("The cloning cycle is complete.") - - // If the cloner is upgraded to debugging high levels, sometimes - // organs and limbs can be missing. - for(var/i in unattached_flesh) - if(isorgan(i)) - var/obj/item/organ/O = i - O.organ_flags &= ~ORGAN_FROZEN - O.Insert(mob_occupant) - else if(isbodypart(i)) - var/obj/item/bodypart/BP = i - BP.attach_limb(mob_occupant) - - go_out() - mob_occupant.apply_vore_prefs() - - else if (!mob_occupant || mob_occupant.loc != src) - occupant = null - use_power(200) - - update_icon() - -//Let's unlock this early I guess. Might be too early, needs tweaking. -/obj/machinery/clonepod/attackby(obj/item/W, mob/user, params) - if(!(occupant || mess)) - if(default_deconstruction_screwdriver(user, "[icon_state]_maintenance", "[initial(icon_state)]",W)) - return - - if(default_deconstruction_crowbar(W)) - return - - if(istype(W, /obj/item/multitool)) - var/obj/item/multitool/P = W - - if(istype(P.buffer, /obj/machinery/computer/cloning)) - if(get_area(P.buffer) != get_area(src)) - to_chat(user, "-% Cannot link machines across power zones. Buffer cleared %-") - P.buffer = null - return - to_chat(user, "-% Successfully linked [P.buffer] with [src] %-") - var/obj/machinery/computer/cloning/comp = P.buffer - if(connected) - connected.DetachCloner(src) - comp.AttachCloner(src) - else - P.buffer = src - to_chat(user, "-% Successfully stored [REF(P.buffer)] [P.buffer.name] in buffer %-") - return - - var/mob/living/mob_occupant = occupant - if(W.GetID()) - if(!check_access(W)) - to_chat(user, "Access Denied.") - return - if(!(mob_occupant || mess)) - to_chat(user, "Error: Pod has no occupant.") - return - else - connected_message("Emergency Ejection") - SPEAK("An emergency ejection of the current clone has occurred. Survival not guaranteed.") - to_chat(user, "You force an emergency ejection. ") - go_out() - mob_occupant.apply_vore_prefs() - else - return ..() - -/obj/machinery/clonepod/emag_act(mob/user) - . = ..() - if(!occupant) - return - to_chat(user, "You corrupt the genetic compiler.") - malfunction() - return TRUE - -//Put messages in the connected computer's temp var for display. -/obj/machinery/clonepod/proc/connected_message(message) - if ((isnull(connected)) || (!istype(connected, /obj/machinery/computer/cloning))) - return FALSE - if (!message) - return FALSE - - connected.temp = message - connected.updateUsrDialog() - return TRUE - -/obj/machinery/clonepod/proc/go_out() - countdown.stop() - var/mob/living/mob_occupant = occupant - var/turf/T = get_turf(src) - - if(mess) //Clean that mess and dump those gibs! - for(var/obj/fl in unattached_flesh) - fl.forceMove(T) - if(istype(fl, /obj/item/organ)) - var/obj/item/organ/O = fl - O.organ_flags &= ~ORGAN_FROZEN - unattached_flesh.Cut() - mess = FALSE - if(mob_occupant) - mob_occupant.spawn_gibs() - audible_message("You hear a splat.") - update_icon() - return - - if(!mob_occupant) - return - - REMOVE_TRAIT(mob_occupant, TRAIT_STABLEHEART, CLONING_POD_TRAIT) - REMOVE_TRAIT(mob_occupant, TRAIT_STABLELIVER, CLONING_POD_TRAIT) - REMOVE_TRAIT(mob_occupant, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT) - REMOVE_TRAIT(mob_occupant, TRAIT_MUTE, CLONING_POD_TRAIT) - REMOVE_TRAIT(mob_occupant, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT) - REMOVE_TRAIT(mob_occupant, TRAIT_NOBREATH, CLONING_POD_TRAIT) - - if(grab_ghost_when == CLONER_MATURE_CLONE) - mob_occupant.grab_ghost() - to_chat(occupant, "There is a bright flash!
    You feel like a new being.
    ") - mob_occupant.flash_act() - - occupant.forceMove(T) - update_icon() - mob_occupant.domutcheck(1) //Waiting until they're out before possible monkeyizing. The 1 argument forces powers to manifest. - for(var/fl in unattached_flesh) - qdel(fl) - unattached_flesh.Cut() - - occupant = null - -/obj/machinery/clonepod/proc/malfunction() - var/mob/living/mob_occupant = occupant - if(mob_occupant) - connected_message("Critical Error!") - SPEAK("Critical error! Please contact a Thinktronic Systems \ - technician, as your warranty may be affected.") - mess = TRUE - maim_clone(mob_occupant) //Remove every bit that's grown back so far to drop later, also destroys bits that haven't grown yet - update_icon() - if(mob_occupant.mind != clonemind) - clonemind.transfer_to(mob_occupant) - mob_occupant.grab_ghost() // We really just want to make you suffer. - flash_color(mob_occupant, flash_color="#960000", flash_time=100) - to_chat(mob_occupant, "Agony blazes across your consciousness as your body is torn apart.
    Is this what dying is like? Yes it is.
    ") - playsound(src.loc, 'sound/machines/warning-buzzer.ogg', 50, 0) - SEND_SOUND(mob_occupant, sound('sound/hallucinations/veryfar_noise.ogg',0,1,50)) - QDEL_IN(mob_occupant, 40) - -/obj/machinery/clonepod/relaymove(mob/user) - container_resist(user) - -/obj/machinery/clonepod/container_resist(mob/living/user) - if(user.stat == CONSCIOUS) - go_out() - -/obj/machinery/clonepod/emp_act(severity) - . = ..() - if (!(. & EMP_PROTECT_SELF)) - var/mob/living/mob_occupant = occupant - if(mob_occupant && prob(100/(severity*efficiency))) - connected_message(Gibberish("EMP-caused Accidental Ejection", 0)) - SPEAK(Gibberish("Exposure to electromagnetic fields has caused the ejection of, ERROR: John Doe, prematurely." ,0)) - mob_occupant.apply_vore_prefs() - go_out() - -/obj/machinery/clonepod/ex_act(severity, target) - ..() - if(!QDELETED(src)) - go_out() - -/obj/machinery/clonepod/handle_atom_del(atom/A) - if(A == occupant) - occupant = null - countdown.stop() - -/obj/machinery/clonepod/proc/horrifyingsound() - for(var/i in 1 to 5) - playsound(loc,pick('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg'), 100, rand(0.95,1.05)) - sleep(1) - sleep(10) - playsound(loc,'sound/hallucinations/wail.ogg',100,1) - -/obj/machinery/clonepod/deconstruct(disassembled = TRUE) - if(occupant) - go_out() - ..() - -/obj/machinery/clonepod/proc/maim_clone(mob/living/carbon/human/H) - if(!unattached_flesh) - unattached_flesh = list() - else - for(var/fl in unattached_flesh) - qdel(fl) - unattached_flesh.Cut() - - H.setCloneLoss(CLONE_INITIAL_DAMAGE) //Yeah, clones start with very low health, not with random, because why would they start with random health - //H.setOrganLoss(ORGAN_SLOT_BRAIN, CLONE_INITIAL_DAMAGE) - // In addition to being cellularly damaged and having barely any - - // brain function, they also have no limbs or internal organs. - - if(!HAS_TRAIT(H, TRAIT_NODISMEMBER)) - var/static/list/zones = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - for(var/zone in zones) - var/obj/item/bodypart/BP = H.get_bodypart(zone) - if(BP) - BP.drop_limb() - BP.forceMove(src) - unattached_flesh += BP - - for(var/o in H.internal_organs) - var/obj/item/organ/organ = o - if(!istype(organ) || (organ.organ_flags & ORGAN_VITAL)) - continue - organ.organ_flags |= ORGAN_FROZEN - organ.Remove(H, special=TRUE) - organ.forceMove(src) - unattached_flesh += organ - - flesh_number = unattached_flesh.len - -/obj/machinery/clonepod/update_icon() - cut_overlays() - - if(mess) - icon_state = "pod_g" - var/image/gib1 = image(CRYOMOBS, "gibup") - var/image/gib2 = image(CRYOMOBS, "gibdown") - gib1.pixel_y = 27 + round(sin(world.time) * 3) - gib1.pixel_x = round(sin(world.time * 3)) - gib2.pixel_y = 27 + round(cos(world.time) * 3) - gib2.pixel_x = round(cos(world.time * 3)) - add_overlay(gib2) - add_overlay(gib1) - add_overlay("cover-on") - - else if(occupant) - icon_state = "pod_1" - - var/image/occupant_overlay - var/completion = (flesh_number - unattached_flesh.len) / flesh_number - - if(unattached_flesh.len <= 0) - occupant_overlay = image(occupant.icon, occupant.icon_state) - occupant_overlay.copy_overlays(occupant) - else - occupant_overlay = image(CRYOMOBS, "clone_meat") - var/matrix/tform = matrix() - tform.Scale(completion) - tform.Turn(cos(world.time * 2) * 3) - occupant_overlay.transform = tform - occupant_overlay.appearance_flags = 0 - - occupant_overlay.dir = SOUTH - occupant_overlay.pixel_y = 27 + round(sin(world.time) * 3) - occupant_overlay.pixel_x = round(sin(world.time * 3)) - - add_overlay(occupant_overlay) - add_overlay("cover-on") - else - icon_state = "pod_0" - - if(panel_open) - icon_state = "pod_0_maintenance" - - add_overlay("panel") - -/* - * Manual -- A big ol' manual. - */ - -/obj/item/paper/guides/jobs/medical/cloning - name = "paper - 'H-87 Cloning Apparatus Manual" - info = {"

    Getting Started

    - Congratulations, your station has purchased the H-87 industrial cloning device!
    - Using the H-87 is almost as simple as brain surgery! Simply insert the target humanoid into the scanning chamber and select the scan option to create a new profile!
    - That's all there is to it!
    - Notice, cloning system cannot scan inorganic life or small primates. Scan may fail if subject has suffered extreme brain damage.
    -

    Clone profiles may be viewed through the profiles menu. Scanning implants a complementary HEALTH MONITOR IMPLANT into the subject, which may be viewed from each profile. - Profile Deletion has been restricted to \[Station Head\] level access.

    -

    Cloning from a profile

    - Cloning is as simple as pressing the CLONE option at the bottom of the desired profile.
    - Per your company's EMPLOYEE PRIVACY RIGHTS agreement, the H-87 has been blocked from cloning crewmembers while they are still alive.
    -
    -

    The provided CLONEPOD SYSTEM will produce the desired clone. Standard clone maturation times (With SPEEDCLONE technology) are roughly 90 seconds. - The cloning pod may be unlocked early with any \[Medical Researcher\] ID after initial maturation is complete.


    - Please note that resulting clones may have a small DEVELOPMENTAL DEFECT as a result of genetic drift.
    -

    Profile Management

    -

    The H-87 (as well as your station's standard genetics machine) can accept STANDARD DATA DISKETTES. - These diskettes are used to transfer genetic information between machines and profiles. - A load/save dialog will become available in each profile if a disk is inserted.


    - A good diskette is a great way to counter aforementioned genetic drift!
    -
    - This technology produced under license from Thinktronic Systems, LTD."} - -#undef CLONE_INITIAL_DAMAGE -#undef SPEAK -#undef MINIMUM_HEAL_LEVEL +//Cloning revival method. +//The pod handles the actual cloning while the computer manages the clone profiles + +//Potential replacement for genetics revives or something I dunno (?) + +#define CLONE_INITIAL_DAMAGE 150 //Clones in clonepods start with 150 cloneloss damage and 150 brainloss damage, thats just logical +#define MINIMUM_HEAL_LEVEL 40 + +#define SPEAK(message) radio.talk_into(src, message, radio_channel) + +/obj/machinery/clonepod + name = "cloning pod" + desc = "An electronically-lockable pod for growing organic tissue." + density = TRUE + icon = 'icons/obj/machines/cloning.dmi' + icon_state = "pod_0" + req_access = list(ACCESS_CLONING) //FOR PREMATURE UNLOCKING. + verb_say = "states" + circuit = /obj/item/circuitboard/machine/clonepod + + var/heal_level //The clone is released once its health reaches this level. + var/obj/machinery/computer/cloning/connected = null //So we remember the connected clone machine. + var/mess = FALSE //Need to clean out it if it's full of exploded clone. + var/attempting = FALSE //One clone attempt at a time thanks + var/speed_coeff + var/efficiency + + var/datum/mind/clonemind + var/grab_ghost_when = CLONER_MATURE_CLONE + + var/internal_radio = TRUE + var/obj/item/radio/radio + var/radio_key = /obj/item/encryptionkey/headset_med + var/radio_channel = RADIO_CHANNEL_MEDICAL + + var/obj/effect/countdown/clonepod/countdown + + var/list/unattached_flesh + var/flesh_number = 0 + +/obj/machinery/clonepod/Initialize() + . = ..() + + countdown = new(src) + + if(internal_radio) + radio = new(src) + radio.keyslot = new radio_key + radio.subspace_transmission = TRUE + radio.canhear_range = 0 + radio.recalculateChannels() + + update_icon() + +/obj/machinery/clonepod/Destroy() + go_out() + QDEL_NULL(radio) + QDEL_NULL(countdown) + if(connected) + connected.DetachCloner(src) + QDEL_LIST(unattached_flesh) + . = ..() + +/obj/machinery/clonepod/RefreshParts() + speed_coeff = 0 + efficiency = 0 + for(var/obj/item/stock_parts/scanning_module/S in component_parts) + efficiency += S.rating + for(var/obj/item/stock_parts/manipulator/P in component_parts) + speed_coeff += P.rating + heal_level = (efficiency * 15) + 10 + if(heal_level < MINIMUM_HEAL_LEVEL) + heal_level = MINIMUM_HEAL_LEVEL + if(heal_level > 100) + heal_level = 100 + +//The return of data disks?? Just for transferring between genetics machine/cloning machine. +//TO-DO: Make the genetics machine accept them. +/obj/item/disk/data + name = "cloning data disk" + icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk. + var/list/fields = list() + var/read_only = 0 //Well,it's still a floppy disk + +//Disk stuff. +/obj/item/disk/data/Initialize() + . = ..() + icon_state = "datadisk[rand(0,6)]" + add_overlay("datadisk_gene") + +/obj/item/disk/data/attack_self(mob/user) + read_only = !read_only + to_chat(user, "You flip the write-protect tab to [read_only ? "protected" : "unprotected"].") + +/obj/item/disk/data/examine(mob/user) + . = ..() + . += "The write-protect tab is set to [read_only ? "protected" : "unprotected"]." + + +//Clonepod + +/obj/machinery/clonepod/examine(mob/user) + . = ..() + var/mob/living/mob_occupant = occupant + if(mess) + . += "It's filled with blood and viscera. You swear you can see it moving..." + if(is_operational() && mob_occupant) + if(mob_occupant.stat != DEAD) + . += "Current clone cycle is [round(get_completion())]% complete." + +/obj/machinery/clonepod/return_air() + // We want to simulate the clone not being in contact with + // the atmosphere, so we'll put them in a constant pressure + // nitrogen. They don't need to breathe while cloning anyway. + var/static/datum/gas_mixture/immutable/cloner/GM //global so that there's only one instance made for all cloning pods + if(!GM) + GM = new + return GM + +/obj/machinery/clonepod/proc/get_completion() + . = FALSE + var/mob/living/mob_occupant = occupant + if(mob_occupant) + . = (100 * ((mob_occupant.health + 100) / (heal_level + 100))) + +/obj/machinery/clonepod/attack_ai(mob/user) + return examine(user) + +//Start growing a human clone in the pod! +/obj/machinery/clonepod/proc/growclone(ckey, clonename, ui, se, mindref, datum/species/mrace, list/features, factions, list/quirks) + if(panel_open) + return FALSE + if(mess || attempting) + return FALSE + clonemind = locate(mindref) in SSticker.minds + if(!istype(clonemind)) //not a mind + return FALSE + if(!QDELETED(clonemind.current)) + if(clonemind.current.stat != DEAD) //mind is associated with a non-dead body + return FALSE + if(clonemind.current.suiciding) // Mind is associated with a body that is suiciding. + return FALSE + if(clonemind.active) //somebody is using that mind + if( ckey(clonemind.key)!=ckey ) + return FALSE + else + // get_ghost() will fail if they're unable to reenter their body + var/mob/dead/observer/G = clonemind.get_ghost() + if(!G) + return FALSE + if(G.suiciding) // The ghost came from a body that is suiciding. + return FALSE + if(clonemind.damnation_type) //Can't clone the damned. + INVOKE_ASYNC(src, .proc/horrifyingsound) + mess = TRUE + update_icon() + return FALSE + if(isvamp(clonemind)) //If the mind is a bloodsucker + return FALSE + + attempting = TRUE //One at a time!! + countdown.start() + + var/mob/living/carbon/human/H = new /mob/living/carbon/human(src) + + H.hardset_dna(ui, se, H.real_name, null, mrace, features) + + if(prob(50 - efficiency*10)) //Chance to give a bad mutation. + H.randmutb() //100% bad mutation. Can be cured with mutadone. + + H.silent = 20 //Prevents an extreme edge case where clones could speak if they said something at exactly the right moment. + occupant = H + + if(!clonename) //to prevent null names + clonename = "clone ([rand(1,999)])" + H.real_name = clonename + + //Get the clone body ready + maim_clone(H) + ADD_TRAIT(H, TRAIT_STABLEHEART, CLONING_POD_TRAIT) + ADD_TRAIT(H, TRAIT_STABLELIVER, CLONING_POD_TRAIT) + ADD_TRAIT(H, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT) + ADD_TRAIT(H, TRAIT_MUTE, CLONING_POD_TRAIT) + ADD_TRAIT(H, TRAIT_NOBREATH, CLONING_POD_TRAIT) + ADD_TRAIT(H, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT) + H.Unconscious(80) + + clonemind.transfer_to(H) + + if(grab_ghost_when == CLONER_FRESH_CLONE) + H.grab_ghost() + to_chat(H, "Consciousness slowly creeps over you as your body regenerates.
    So this is what cloning feels like?
    ") + + if(grab_ghost_when == CLONER_MATURE_CLONE) + H.ghostize(TRUE) //Only does anything if they were still in their old body and not already a ghost + to_chat(H.get_ghost(TRUE), "Your body is beginning to regenerate in a cloning pod. You will become conscious when it is complete.") + + if(H) + H.faction |= factions + + for(var/V in quirks) + var/datum/quirk/Q = new V(H) + Q.on_clone(quirks[V]) + + H.set_cloned_appearance() + H.give_genitals(TRUE) + + H.suiciding = FALSE + attempting = FALSE + return TRUE + +//Grow clones to maturity then kick them out. FREELOADERS +/obj/machinery/clonepod/process() + var/mob/living/mob_occupant = occupant + + if(!is_operational()) //Autoeject if power is lost + if(mob_occupant) + go_out() + mob_occupant.apply_vore_prefs() + connected_message("Clone Ejected: Loss of power.") + + else if(mob_occupant && (mob_occupant.loc == src)) + if((mob_occupant.stat == DEAD) || (mob_occupant.suiciding) || mob_occupant.hellbound) //Autoeject corpses and suiciding dudes. + connected_message("Clone Rejected: Deceased.") + if(internal_radio) + SPEAK("The cloning has been \ + aborted due to unrecoverable tissue failure.") + go_out() + mob_occupant.apply_vore_prefs() + + else if(mob_occupant.cloneloss > (100 - heal_level)) + mob_occupant.Unconscious(80) + var/dmg_mult = CONFIG_GET(number/damage_multiplier) + //Slowly get that clone healed and finished. + mob_occupant.adjustCloneLoss(-((speed_coeff / 2) * dmg_mult)) + var/progress = CLONE_INITIAL_DAMAGE - mob_occupant.getCloneLoss() + // To avoid the default cloner making incomplete clones + progress += (100 - MINIMUM_HEAL_LEVEL) + var/milestone = CLONE_INITIAL_DAMAGE / flesh_number + var/installed = flesh_number - unattached_flesh.len + + if((progress / milestone) >= installed) + // attach some flesh + var/obj/item/I = pick_n_take(unattached_flesh) + if(isorgan(I)) + var/obj/item/organ/O = I + O.organ_flags &= ~ORGAN_FROZEN + O.Insert(mob_occupant) + else if(isbodypart(I)) + var/obj/item/bodypart/BP = I + BP.attach_limb(mob_occupant) + + //Premature clones may have brain damage. + mob_occupant.adjustOrganLoss(ORGAN_SLOT_BRAIN, -((speed_coeff / 2) * dmg_mult)) + + use_power(7500) //This might need tweaking. + + else if((mob_occupant.cloneloss <= (100 - heal_level))) + connected_message("Cloning Process Complete.") + if(internal_radio) + SPEAK("The cloning cycle is complete.") + + // If the cloner is upgraded to debugging high levels, sometimes + // organs and limbs can be missing. + for(var/i in unattached_flesh) + if(isorgan(i)) + var/obj/item/organ/O = i + O.organ_flags &= ~ORGAN_FROZEN + O.Insert(mob_occupant) + else if(isbodypart(i)) + var/obj/item/bodypart/BP = i + BP.attach_limb(mob_occupant) + + go_out() + mob_occupant.apply_vore_prefs() + + else if (!mob_occupant || mob_occupant.loc != src) + occupant = null + use_power(200) + + update_icon() + +//Let's unlock this early I guess. Might be too early, needs tweaking. +/obj/machinery/clonepod/attackby(obj/item/W, mob/user, params) + if(!(occupant || mess)) + if(default_deconstruction_screwdriver(user, "[icon_state]_maintenance", "[initial(icon_state)]",W)) + return + + if(default_deconstruction_crowbar(W)) + return + + if(istype(W, /obj/item/multitool)) + var/obj/item/multitool/P = W + + if(istype(P.buffer, /obj/machinery/computer/cloning)) + if(get_area(P.buffer) != get_area(src)) + to_chat(user, "-% Cannot link machines across power zones. Buffer cleared %-") + P.buffer = null + return + to_chat(user, "-% Successfully linked [P.buffer] with [src] %-") + var/obj/machinery/computer/cloning/comp = P.buffer + if(connected) + connected.DetachCloner(src) + comp.AttachCloner(src) + else + P.buffer = src + to_chat(user, "-% Successfully stored [REF(P.buffer)] [P.buffer.name] in buffer %-") + return + + var/mob/living/mob_occupant = occupant + if(W.GetID()) + if(!check_access(W)) + to_chat(user, "Access Denied.") + return + if(!(mob_occupant || mess)) + to_chat(user, "Error: Pod has no occupant.") + return + else + connected_message("Emergency Ejection") + SPEAK("An emergency ejection of the current clone has occurred. Survival not guaranteed.") + to_chat(user, "You force an emergency ejection. ") + go_out() + mob_occupant.apply_vore_prefs() + else + return ..() + +/obj/machinery/clonepod/emag_act(mob/user) + . = ..() + if(!occupant) + return + to_chat(user, "You corrupt the genetic compiler.") + malfunction() + return TRUE + +//Put messages in the connected computer's temp var for display. +/obj/machinery/clonepod/proc/connected_message(message) + if ((isnull(connected)) || (!istype(connected, /obj/machinery/computer/cloning))) + return FALSE + if (!message) + return FALSE + + connected.temp = message + connected.updateUsrDialog() + return TRUE + +/obj/machinery/clonepod/proc/go_out() + countdown.stop() + var/mob/living/mob_occupant = occupant + var/turf/T = get_turf(src) + + if(mess) //Clean that mess and dump those gibs! + for(var/obj/fl in unattached_flesh) + fl.forceMove(T) + if(istype(fl, /obj/item/organ)) + var/obj/item/organ/O = fl + O.organ_flags &= ~ORGAN_FROZEN + unattached_flesh.Cut() + mess = FALSE + if(mob_occupant) + mob_occupant.spawn_gibs() + audible_message("You hear a splat.") + update_icon() + return + + if(!mob_occupant) + return + + REMOVE_TRAIT(mob_occupant, TRAIT_STABLEHEART, CLONING_POD_TRAIT) + REMOVE_TRAIT(mob_occupant, TRAIT_STABLELIVER, CLONING_POD_TRAIT) + REMOVE_TRAIT(mob_occupant, TRAIT_EMOTEMUTE, CLONING_POD_TRAIT) + REMOVE_TRAIT(mob_occupant, TRAIT_MUTE, CLONING_POD_TRAIT) + REMOVE_TRAIT(mob_occupant, TRAIT_NOCRITDAMAGE, CLONING_POD_TRAIT) + REMOVE_TRAIT(mob_occupant, TRAIT_NOBREATH, CLONING_POD_TRAIT) + + if(grab_ghost_when == CLONER_MATURE_CLONE) + mob_occupant.grab_ghost() + to_chat(occupant, "There is a bright flash!
    You feel like a new being.
    ") + mob_occupant.flash_act() + + occupant.forceMove(T) + update_icon() + mob_occupant.domutcheck(1) //Waiting until they're out before possible monkeyizing. The 1 argument forces powers to manifest. + for(var/fl in unattached_flesh) + qdel(fl) + unattached_flesh.Cut() + + occupant = null + +/obj/machinery/clonepod/proc/malfunction() + var/mob/living/mob_occupant = occupant + if(mob_occupant) + connected_message("Critical Error!") + SPEAK("Critical error! Please contact a Thinktronic Systems \ + technician, as your warranty may be affected.") + mess = TRUE + maim_clone(mob_occupant) //Remove every bit that's grown back so far to drop later, also destroys bits that haven't grown yet + update_icon() + if(mob_occupant.mind != clonemind) + clonemind.transfer_to(mob_occupant) + mob_occupant.grab_ghost() // We really just want to make you suffer. + flash_color(mob_occupant, flash_color="#960000", flash_time=100) + to_chat(mob_occupant, "Agony blazes across your consciousness as your body is torn apart.
    Is this what dying is like? Yes it is.
    ") + playsound(src.loc, 'sound/machines/warning-buzzer.ogg', 50, 0) + SEND_SOUND(mob_occupant, sound('sound/hallucinations/veryfar_noise.ogg',0,1,50)) + QDEL_IN(mob_occupant, 40) + +/obj/machinery/clonepod/relaymove(mob/user) + container_resist(user) + +/obj/machinery/clonepod/container_resist(mob/living/user) + if(user.stat == CONSCIOUS) + go_out() + +/obj/machinery/clonepod/emp_act(severity) + . = ..() + if (!(. & EMP_PROTECT_SELF)) + var/mob/living/mob_occupant = occupant + if(mob_occupant && prob(100/(severity*efficiency))) + connected_message(Gibberish("EMP-caused Accidental Ejection", 0)) + SPEAK(Gibberish("Exposure to electromagnetic fields has caused the ejection of, ERROR: John Doe, prematurely." ,0)) + mob_occupant.apply_vore_prefs() + go_out() + +/obj/machinery/clonepod/ex_act(severity, target) + ..() + if(!QDELETED(src)) + go_out() + +/obj/machinery/clonepod/handle_atom_del(atom/A) + if(A == occupant) + occupant = null + countdown.stop() + +/obj/machinery/clonepod/proc/horrifyingsound() + for(var/i in 1 to 5) + playsound(loc,pick('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg'), 100, rand(0.95,1.05)) + sleep(1) + sleep(10) + playsound(loc,'sound/hallucinations/wail.ogg',100,1) + +/obj/machinery/clonepod/deconstruct(disassembled = TRUE) + if(occupant) + go_out() + ..() + +/obj/machinery/clonepod/proc/maim_clone(mob/living/carbon/human/H) + if(!unattached_flesh) + unattached_flesh = list() + else + for(var/fl in unattached_flesh) + qdel(fl) + unattached_flesh.Cut() + + H.setCloneLoss(CLONE_INITIAL_DAMAGE) //Yeah, clones start with very low health, not with random, because why would they start with random health + //H.setOrganLoss(ORGAN_SLOT_BRAIN, CLONE_INITIAL_DAMAGE) + // In addition to being cellularly damaged and having barely any + + // brain function, they also have no limbs or internal organs. + + if(!HAS_TRAIT(H, TRAIT_NODISMEMBER)) + var/static/list/zones = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) + for(var/zone in zones) + var/obj/item/bodypart/BP = H.get_bodypart(zone) + if(BP) + BP.drop_limb() + BP.forceMove(src) + unattached_flesh += BP + + for(var/o in H.internal_organs) + var/obj/item/organ/organ = o + if(!istype(organ) || (organ.organ_flags & ORGAN_VITAL)) + continue + organ.organ_flags |= ORGAN_FROZEN + organ.Remove(H, special=TRUE) + organ.forceMove(src) + unattached_flesh += organ + + flesh_number = unattached_flesh.len + +/obj/machinery/clonepod/update_icon() + cut_overlays() + + if(mess) + icon_state = "pod_g" + var/image/gib1 = image(CRYOMOBS, "gibup") + var/image/gib2 = image(CRYOMOBS, "gibdown") + gib1.pixel_y = 27 + round(sin(world.time) * 3) + gib1.pixel_x = round(sin(world.time * 3)) + gib2.pixel_y = 27 + round(cos(world.time) * 3) + gib2.pixel_x = round(cos(world.time * 3)) + add_overlay(gib2) + add_overlay(gib1) + add_overlay("cover-on") + + else if(occupant) + icon_state = "pod_1" + + var/image/occupant_overlay + var/completion = (flesh_number - unattached_flesh.len) / flesh_number + + if(unattached_flesh.len <= 0) + occupant_overlay = image(occupant.icon, occupant.icon_state) + occupant_overlay.copy_overlays(occupant) + else + occupant_overlay = image(CRYOMOBS, "clone_meat") + var/matrix/tform = matrix() + tform.Scale(completion) + tform.Turn(cos(world.time * 2) * 3) + occupant_overlay.transform = tform + occupant_overlay.appearance_flags = 0 + + occupant_overlay.dir = SOUTH + occupant_overlay.pixel_y = 27 + round(sin(world.time) * 3) + occupant_overlay.pixel_x = round(sin(world.time * 3)) + + add_overlay(occupant_overlay) + add_overlay("cover-on") + else + icon_state = "pod_0" + + if(panel_open) + icon_state = "pod_0_maintenance" + + add_overlay("panel") + +/* + * Manual -- A big ol' manual. + */ + +/obj/item/paper/guides/jobs/medical/cloning + name = "paper - 'H-87 Cloning Apparatus Manual" + info = {"

    Getting Started

    + Congratulations, your station has purchased the H-87 industrial cloning device!
    + Using the H-87 is almost as simple as brain surgery! Simply insert the target humanoid into the scanning chamber and select the scan option to create a new profile!
    + That's all there is to it!
    + Notice, cloning system cannot scan inorganic life or small primates. Scan may fail if subject has suffered extreme brain damage.
    +

    Clone profiles may be viewed through the profiles menu. Scanning implants a complementary HEALTH MONITOR IMPLANT into the subject, which may be viewed from each profile. + Profile Deletion has been restricted to \[Station Head\] level access.

    +

    Cloning from a profile

    + Cloning is as simple as pressing the CLONE option at the bottom of the desired profile.
    + Per your company's EMPLOYEE PRIVACY RIGHTS agreement, the H-87 has been blocked from cloning crewmembers while they are still alive.
    +
    +

    The provided CLONEPOD SYSTEM will produce the desired clone. Standard clone maturation times (With SPEEDCLONE technology) are roughly 90 seconds. + The cloning pod may be unlocked early with any \[Medical Researcher\] ID after initial maturation is complete.


    + Please note that resulting clones may have a small DEVELOPMENTAL DEFECT as a result of genetic drift.
    +

    Profile Management

    +

    The H-87 (as well as your station's standard genetics machine) can accept STANDARD DATA DISKETTES. + These diskettes are used to transfer genetic information between machines and profiles. + A load/save dialog will become available in each profile if a disk is inserted.


    + A good diskette is a great way to counter aforementioned genetic drift!
    +
    + This technology produced under license from Thinktronic Systems, LTD."} + +#undef CLONE_INITIAL_DAMAGE +#undef SPEAK +#undef MINIMUM_HEAL_LEVEL diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm index 858a0a995b..3c021d0bd1 100644 --- a/code/game/machinery/computer/Operating.dm +++ b/code/game/machinery/computer/Operating.dm @@ -1,128 +1,128 @@ -#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." - icon_screen = "crew" - icon_keyboard = "med_key" - circuit = /obj/item/circuitboard/computer/operating - var/mob/living/carbon/human/patient - var/obj/structure/table/optable/table - var/list/advanced_surgeries = list() - var/datum/techweb/linked_techweb - var/menu = MENU_OPERATION - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/operating/Initialize() - . = ..() - linked_techweb = SSresearch.science_tech - find_table() - -/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.cardinals) - table = locate(/obj/structure/table/optable, get_step(src, direction)) - if(table) - table.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, "operating_computer", name, 350, 470, master_ui, state) - ui.open() - -/obj/machinery/computer/operating/ui_data(mob/user) - var/list/data = list() - data["table"] = table - if(table) - data["menu"] = menu - - 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"] = list() - if(table.check_patient()) - patient = table.patient - 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() - if(patient.surgeries.len) - data["procedures"] = list() - 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(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("change_menu") - menu = text2num(params["menu"]) - . = TRUE - 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." + icon_screen = "crew" + icon_keyboard = "med_key" + circuit = /obj/item/circuitboard/computer/operating + var/mob/living/carbon/human/patient + var/obj/structure/table/optable/table + var/list/advanced_surgeries = list() + var/datum/techweb/linked_techweb + var/menu = MENU_OPERATION + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/operating/Initialize() + . = ..() + linked_techweb = SSresearch.science_tech + find_table() + +/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.cardinals) + table = locate(/obj/structure/table/optable, get_step(src, direction)) + if(table) + table.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, "operating_computer", name, 350, 470, master_ui, state) + ui.open() + +/obj/machinery/computer/operating/ui_data(mob/user) + var/list/data = list() + data["table"] = table + if(table) + data["menu"] = menu + + 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"] = list() + if(table.check_patient()) + patient = table.patient + 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() + if(patient.surgeries.len) + data["procedures"] = list() + 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(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("change_menu") + menu = text2num(params["menu"]) + . = TRUE + if("sync") + sync_surgeries() + . = TRUE + . = TRUE + +#undef MENU_OPERATION +#undef MENU_SURGERIES diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm index 5d6c92a97d..8bb0894cdb 100644 --- a/code/game/machinery/computer/aifixer.dm +++ b/code/game/machinery/computer/aifixer.dm @@ -1,150 +1,150 @@ -/obj/machinery/computer/aifixer - name = "\improper AI system integrity restorer" - desc = "Used with intelliCards containing nonfunctional AIs to restore them to working order." - req_access = list(ACCESS_CAPTAIN, ACCESS_ROBOTICS, ACCESS_HEADS) - var/mob/living/silicon/ai/occupier = null - var/active = 0 - circuit = /obj/item/circuitboard/computer/aifixer - icon_keyboard = "tech_key" - icon_screen = "ai-fixer" - light_color = LIGHT_COLOR_PINK - -/obj/machinery/computer/aifixer/attackby(obj/I, mob/user, params) - if(occupier && istype(I, /obj/item/screwdriver)) - if(stat & (NOPOWER|BROKEN)) - to_chat(user, "The screws on [name]'s screen won't budge.") - else - to_chat(user, "The screws on [name]'s screen won't budge and it emits a warning beep.") - else - return ..() - -/obj/machinery/computer/aifixer/ui_interact(mob/user) - . = ..() - - var/dat = "" - - if (src.occupier) - var/laws - dat += "

    Stored AI: [src.occupier.name]

    " - dat += "System integrity: [(src.occupier.health+100)/2]%
    " - - if (src.occupier.laws.zeroth) - laws += "0: [src.occupier.laws.zeroth]
    " - - for (var/index = 1, index <= src.occupier.laws.hacked.len, index++) - var/law = src.occupier.laws.hacked[index] - if (length(law) > 0) - var/num = ionnum() - laws += "[num]: [law]
    " - - for (var/index = 1, index <= src.occupier.laws.ion.len, index++) - var/law = src.occupier.laws.ion[index] - if (length(law) > 0) - var/num = ionnum() - laws += "[num]: [law]
    " - - var/number = 1 - for (var/index = 1, index <= src.occupier.laws.inherent.len, index++) - var/law = src.occupier.laws.inherent[index] - if (length(law) > 0) - laws += "[number]: [law]
    " - number++ - - for (var/index = 1, index <= src.occupier.laws.supplied.len, index++) - var/law = src.occupier.laws.supplied[index] - if (length(law) > 0) - laws += "[number]: [law]
    " - number++ - - dat += "Laws:
    [laws]
    " - - if (src.occupier.stat == DEAD) - dat += "AI non-functional" - else - dat += "AI functional" - if (!src.active) - dat += {"

    Begin Reconstruction"} - else - dat += "

    Reconstruction in process, please wait.
    " - dat += {"
    Close"} - var/datum/browser/popup = new(user, "computer", "AI System Integrity Restorer", 400, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - return - -/obj/machinery/computer/aifixer/proc/Fix() - use_power(1000) - occupier.adjustOxyLoss(-1, 0) - occupier.adjustFireLoss(-1, 0) - occupier.adjustToxLoss(-1, 0) - occupier.adjustBruteLoss(-1, 0) - occupier.updatehealth() - if(occupier.health >= 0 && occupier.stat == DEAD) - occupier.revive() - return occupier.health < 100 - -/obj/machinery/computer/aifixer/process() - if(..()) - if(active) - var/oldstat = occupier.stat - active = Fix() - if(oldstat != occupier.stat) - update_icon() - updateDialog() - -/obj/machinery/computer/aifixer/Topic(href, href_list) - if(..()) - return - if(href_list["fix"]) - to_chat(usr, "Reconstruction in progress. This will take several minutes.") - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0) - active = TRUE - add_fingerprint(usr) - updateUsrDialog() - -/obj/machinery/computer/aifixer/update_icon() - ..() - if(stat & (NOPOWER|BROKEN)) - return - else - if(active) - add_overlay("ai-fixer-on") - if (occupier) - switch (occupier.stat) - if (0) - add_overlay("ai-fixer-full") - if (2) - add_overlay("ai-fixer-404") - else - add_overlay("ai-fixer-empty") - -/obj/machinery/computer/aifixer/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(!..()) - return - //Downloading AI from card to terminal. - if(interaction == AI_TRANS_FROM_CARD) - if(stat & (NOPOWER|BROKEN)) - to_chat(user, "[src] is offline and cannot take an AI at this time!") - return - AI.forceMove(src) - occupier = AI - AI.control_disabled = 1 - AI.radio_enabled = 0 - to_chat(AI, "You have been uploaded to a stationary terminal. Sadly, there is no remote access from here.") - to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") - card.AI = null - update_icon() - - else //Uploading AI from terminal to card - if(occupier && !active) - to_chat(occupier, "You have been downloaded to a mobile storage device. Still no remote access.") - to_chat(user, "Transfer successful: [occupier.name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.") - occupier.forceMove(card) - card.AI = occupier - occupier = null - update_icon() - else if (active) - to_chat(user, "ERROR: Reconstruction in progress.") - else if (!occupier) - to_chat(user, "ERROR: Unable to locate artificial intelligence.") +/obj/machinery/computer/aifixer + name = "\improper AI system integrity restorer" + desc = "Used with intelliCards containing nonfunctional AIs to restore them to working order." + req_access = list(ACCESS_CAPTAIN, ACCESS_ROBOTICS, ACCESS_HEADS) + var/mob/living/silicon/ai/occupier = null + var/active = 0 + circuit = /obj/item/circuitboard/computer/aifixer + icon_keyboard = "tech_key" + icon_screen = "ai-fixer" + light_color = LIGHT_COLOR_PINK + +/obj/machinery/computer/aifixer/attackby(obj/I, mob/user, params) + if(occupier && istype(I, /obj/item/screwdriver)) + if(stat & (NOPOWER|BROKEN)) + to_chat(user, "The screws on [name]'s screen won't budge.") + else + to_chat(user, "The screws on [name]'s screen won't budge and it emits a warning beep.") + else + return ..() + +/obj/machinery/computer/aifixer/ui_interact(mob/user) + . = ..() + + var/dat = "" + + if (src.occupier) + var/laws + dat += "

    Stored AI: [src.occupier.name]

    " + dat += "System integrity: [(src.occupier.health+100)/2]%
    " + + if (src.occupier.laws.zeroth) + laws += "0: [src.occupier.laws.zeroth]
    " + + for (var/index = 1, index <= src.occupier.laws.hacked.len, index++) + var/law = src.occupier.laws.hacked[index] + if (length(law) > 0) + var/num = ionnum() + laws += "[num]: [law]
    " + + for (var/index = 1, index <= src.occupier.laws.ion.len, index++) + var/law = src.occupier.laws.ion[index] + if (length(law) > 0) + var/num = ionnum() + laws += "[num]: [law]
    " + + var/number = 1 + for (var/index = 1, index <= src.occupier.laws.inherent.len, index++) + var/law = src.occupier.laws.inherent[index] + if (length(law) > 0) + laws += "[number]: [law]
    " + number++ + + for (var/index = 1, index <= src.occupier.laws.supplied.len, index++) + var/law = src.occupier.laws.supplied[index] + if (length(law) > 0) + laws += "[number]: [law]
    " + number++ + + dat += "Laws:
    [laws]
    " + + if (src.occupier.stat == DEAD) + dat += "AI non-functional" + else + dat += "AI functional" + if (!src.active) + dat += {"

    Begin Reconstruction"} + else + dat += "

    Reconstruction in process, please wait.
    " + dat += {"
    Close"} + var/datum/browser/popup = new(user, "computer", "AI System Integrity Restorer", 400, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + return + +/obj/machinery/computer/aifixer/proc/Fix() + use_power(1000) + occupier.adjustOxyLoss(-1, 0) + occupier.adjustFireLoss(-1, 0) + occupier.adjustToxLoss(-1, 0) + occupier.adjustBruteLoss(-1, 0) + occupier.updatehealth() + if(occupier.health >= 0 && occupier.stat == DEAD) + occupier.revive() + return occupier.health < 100 + +/obj/machinery/computer/aifixer/process() + if(..()) + if(active) + var/oldstat = occupier.stat + active = Fix() + if(oldstat != occupier.stat) + update_icon() + updateDialog() + +/obj/machinery/computer/aifixer/Topic(href, href_list) + if(..()) + return + if(href_list["fix"]) + to_chat(usr, "Reconstruction in progress. This will take several minutes.") + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0) + active = TRUE + add_fingerprint(usr) + updateUsrDialog() + +/obj/machinery/computer/aifixer/update_icon() + ..() + if(stat & (NOPOWER|BROKEN)) + return + else + if(active) + add_overlay("ai-fixer-on") + if (occupier) + switch (occupier.stat) + if (0) + add_overlay("ai-fixer-full") + if (2) + add_overlay("ai-fixer-404") + else + add_overlay("ai-fixer-empty") + +/obj/machinery/computer/aifixer/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(!..()) + return + //Downloading AI from card to terminal. + if(interaction == AI_TRANS_FROM_CARD) + if(stat & (NOPOWER|BROKEN)) + to_chat(user, "[src] is offline and cannot take an AI at this time!") + return + AI.forceMove(src) + occupier = AI + AI.control_disabled = 1 + AI.radio_enabled = 0 + to_chat(AI, "You have been uploaded to a stationary terminal. Sadly, there is no remote access from here.") + to_chat(user, "Transfer successful: [AI.name] ([rand(1000,9999)].exe) installed and executed successfully. Local copy has been removed.") + card.AI = null + update_icon() + + else //Uploading AI from terminal to card + if(occupier && !active) + to_chat(occupier, "You have been downloaded to a mobile storage device. Still no remote access.") + to_chat(user, "Transfer successful: [occupier.name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.") + occupier.forceMove(card) + card.AI = occupier + occupier = null + update_icon() + else if (active) + to_chat(user, "ERROR: Reconstruction in progress.") + else if (!occupier) + to_chat(user, "ERROR: Unable to locate artificial intelligence.") diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm index cb3cbf2aa6..4c9e9aed19 100644 --- a/code/game/machinery/computer/atmos_alert.dm +++ b/code/game/machinery/computer/atmos_alert.dm @@ -1,100 +1,100 @@ -/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 - 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, "atmos_alert", name, 350, 300, 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_icon() - ..() - cut_overlays() - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - var/overlay_state = icon_screen - if(stat & (NOPOWER|BROKEN)) - add_overlay("[icon_keyboard]_off") - return - add_overlay(icon_keyboard) - if(priority_alarms.len) - overlay_state = "alert:2" - add_overlay("alert:2") - else if(minor_alarms.len) - overlay_state = "alert:1" - add_overlay("alert:1") - else - overlay_state = "alert:0" - add_overlay("alert:0") - SSvis_overlays.add_vis_overlay(src, icon, overlay_state, layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir, alpha=128) +/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 + 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, "atmos_alert", name, 350, 300, 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_icon() + ..() + cut_overlays() + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + var/overlay_state = icon_screen + if(stat & (NOPOWER|BROKEN)) + add_overlay("[icon_keyboard]_off") + return + add_overlay(icon_keyboard) + if(priority_alarms.len) + overlay_state = "alert:2" + add_overlay("alert:2") + else if(minor_alarms.len) + overlay_state = "alert:1" + add_overlay("alert:1") + else + overlay_state = "alert:0" + add_overlay("alert:0") + SSvis_overlays.add_vis_overlay(src, icon, overlay_state, layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir, alpha=128) diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm index 6576f51e46..f9d2a3c103 100644 --- a/code/game/machinery/computer/atmos_control.dm +++ b/code/game/machinery/computer/atmos_control.dm @@ -1,305 +1,305 @@ -///////////////////////////////////////////////////////////// -// AIR SENSOR (found in gas tanks) -///////////////////////////////////////////////////////////// - -/obj/machinery/air_sensor - name = "gas sensor" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "gsensor1" - - 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() - 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 = GLOB.meta_gas_names[gas_id] - signal.data["gases"][gas_name] = air_sample.gases[gas_id] / 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 - - 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, "atmos_control", name, 400, 925, 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) - -///////////////////////////////////////////////////////////// -// 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 - -/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") - -/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") - -/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") - -/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") - -/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") - -/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") - -/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") - -/obj/machinery/computer/atmos_control/tank/incinerator - name = "Incinerator Air Control" - input_tag = ATMOS_GAS_MONITOR_INPUT_INCINERATOR - output_tag = ATMOS_GAS_MONITOR_OUTPUT_INCINERATOR - sensors = list(ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber") - -// 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(FREQ_ATMOS_STORAGE) - 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 IO - if(src) - src.input_tag = "[S]_in" - src.output_tag = "[S]_out" - name = "[uppertext(S)] Supply Control" - var/list/new_devices = freq.devices["4"] - 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" = "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, "atmos_control", name, 500, 305, 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("output") - signal.data += list("tag" = output_tag, "power_toggle" = TRUE) - . = TRUE - if("pressure") - var/target = input("New target pressure:", name, output_info ? output_info["internal"] : 0) as num|null - if(!isnull(target) && !..()) - target = CLAMP(target, 0, 50 * ONE_ATMOSPHERE) - 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" + + 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() + 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 = GLOB.meta_gas_names[gas_id] + signal.data["gases"][gas_name] = air_sample.gases[gas_id] / 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 + + 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, "atmos_control", name, 400, 925, 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) + +///////////////////////////////////////////////////////////// +// 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 + +/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") + +/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") + +/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") + +/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") + +/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") + +/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") + +/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") + +/obj/machinery/computer/atmos_control/tank/incinerator + name = "Incinerator Air Control" + input_tag = ATMOS_GAS_MONITOR_INPUT_INCINERATOR + output_tag = ATMOS_GAS_MONITOR_OUTPUT_INCINERATOR + sensors = list(ATMOS_GAS_MONITOR_SENSOR_INCINERATOR = "Incinerator Chamber") + +// 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(FREQ_ATMOS_STORAGE) + 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 IO + if(src) + src.input_tag = "[S]_in" + src.output_tag = "[S]_out" + name = "[uppertext(S)] Supply Control" + var/list/new_devices = freq.devices["4"] + 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" = "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, "atmos_control", name, 500, 305, 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("output") + signal.data += list("tag" = output_tag, "power_toggle" = TRUE) + . = TRUE + if("pressure") + var/target = input("New target pressure:", name, output_info ? output_info["internal"] : 0) as num|null + if(!isnull(target) && !..()) + target = CLAMP(target, 0, 50 * ONE_ATMOSPHERE) + 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 e8e15ff586..a21ebbeccf 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -1,145 +1,145 @@ -/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(istype(P, /obj/item/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(istype(P, /obj/item/weldingtool)) - 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) && state == 0) - 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(istype(P, /obj/item/wrench)) - to_chat(user, "You start to unfasten the frame...") - if(P.use_tool(src, user, 20, volume=50) && state == 1) - 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, 1) - 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(istype(P, /obj/item/screwdriver) && circuit) - P.play_tool_sound(src) - to_chat(user, "You screw [circuit] into place.") - state = 2 - icon_state = "2" - return - if(istype(P, /obj/item/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(istype(P, /obj/item/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, 5, 50, CALLBACK(src, .proc/check_state, 2))) - to_chat(user, "You add cables to the frame.") - state = 3 - icon_state = "3" - return - if(3) - if(istype(P, /obj/item/wirecutters)) - 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, 1) - to_chat(user, "You start to put in the glass panel...") - if(P.use_tool(src, user, 20, 2, 0, CALLBACK(src, .proc/check_state, 3))) - to_chat(user, "You put in the glass panel.") - state = 4 - src.icon_state = "4" - return - if(4) - if(istype(P, /obj/item/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(istype(P, /obj/item/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 ..() - -//callback proc used on stacks use_tool to stop unnecessary amounts being wasted from spam clicking. -/obj/structure/frame/computer/proc/check_state(target_state) - if(state == target_state) - return TRUE - return FALSE - -/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 TRUE - - setDir(turn(dir, -90)) - return TRUE +/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(istype(P, /obj/item/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(istype(P, /obj/item/weldingtool)) + 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) && state == 0) + 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(istype(P, /obj/item/wrench)) + to_chat(user, "You start to unfasten the frame...") + if(P.use_tool(src, user, 20, volume=50) && state == 1) + 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, 1) + 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(istype(P, /obj/item/screwdriver) && circuit) + P.play_tool_sound(src) + to_chat(user, "You screw [circuit] into place.") + state = 2 + icon_state = "2" + return + if(istype(P, /obj/item/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(istype(P, /obj/item/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, 5, 50, CALLBACK(src, .proc/check_state, 2))) + to_chat(user, "You add cables to the frame.") + state = 3 + icon_state = "3" + return + if(3) + if(istype(P, /obj/item/wirecutters)) + 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, 1) + to_chat(user, "You start to put in the glass panel...") + if(P.use_tool(src, user, 20, 2, 0, CALLBACK(src, .proc/check_state, 3))) + to_chat(user, "You put in the glass panel.") + state = 4 + src.icon_state = "4" + return + if(4) + if(istype(P, /obj/item/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(istype(P, /obj/item/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 ..() + +//callback proc used on stacks use_tool to stop unnecessary amounts being wasted from spam clicking. +/obj/structure/frame/computer/proc/check_state(target_state) + if(state == target_state) + return TRUE + return FALSE + +/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 TRUE + + setDir(turn(dir, -90)) + return TRUE diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 1bafa73aaa..1d54e8f772 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -1,254 +1,254 @@ -/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 - var/last_pic = 1 - var/list/network = list("ss13") - var/list/watchers = list() //who's using the console, associated with the camera they're on. - - light_color = LIGHT_COLOR_RED - -/obj/machinery/computer/security/Initialize() - . = ..() - for(var/i in network) - network -= i - network += lowertext(i) - -/obj/machinery/computer/security/check_eye(mob/user) - if(!can_interact(user) || !(user in watchers) || !watchers[user]) - user.unset_machine() - return - var/obj/machinery/camera/C = watchers[user] - if(!C.can_use()) - user.unset_machine() - return - -/obj/machinery/computer/security/on_unset_machine(mob/user) - watchers.Remove(user) - user.reset_perspective(null) - -/obj/machinery/computer/security/Destroy() - if(watchers.len) - for(var/mob/M in watchers) - M.unset_machine() //to properly reset the view of the users if the console is deleted. - return ..() - -/obj/machinery/computer/security/can_interact(mob/user) - if((!issilicon(user) && !Adjacent(user)) || is_blind(user) || !in_view_range(user, src)) - return FALSE - return ..() - -/obj/machinery/computer/security/interact(mob/user, special_state) - . = ..() - if (ismob(user) && !isliving(user)) // ghosts don't need cameras - return - if (!network) - CRASH("No camera network") - user.unset_machine() - return FALSE - if (!(islist(network))) - CRASH("Camera network is not a list") - user.unset_machine() - return FALSE - - var/list/camera_list = get_available_cameras() - if(!(user in watchers)) - for(var/Num in camera_list) - var/obj/machinery/camera/CAM = camera_list[Num] - if(istype(CAM) && CAM.can_use()) - watchers[user] = CAM //let's give the user the first usable camera, and then let him change to the camera he wants. - break - if(!(user in watchers)) - user.unset_machine() // no usable camera on the network, we disconnect the user from the computer. - return FALSE - playsound(src, 'sound/machines/terminal_prompt.ogg', 25, 0) - use_camera_console(user) - -/obj/machinery/computer/security/proc/use_camera_console(mob/user) - var/list/camera_list = get_available_cameras() - var/t = input(user, "Which camera should you change to?") as null|anything in camera_list - if(!src || user.machine != src) //while we were choosing we got disconnected from our computer or are using another machine. - return - if(!t || t == "Cancel") - user.unset_machine() - playsound(src, 'sound/machines/terminal_off.ogg', 25, 0) - return - - var/obj/machinery/camera/C = camera_list[t] - - if(!C || !C.can_use() || !can_interact(user)) - user.unset_machine() - return FALSE - - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0) - if(isAI(user)) - var/mob/living/silicon/ai/A = user - A.eyeobj.setLoc(get_turf(C)) - A.client.eye = A.eyeobj - else - user.reset_perspective(C) - user.overlay_fullscreen("flash", /obj/screen/fullscreen/flash/static) - user.clear_fullscreen("flash", 5) - watchers[user] = C - use_power(50) - addtimer(CALLBACK(src, .proc/use_camera_console, user), 5) - -//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) - - camera_sort(L) - - var/list/D = list() - D["Cancel"] = "Cancel" - 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.status ? null : " (Deactivated)")]"] = 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 = null - icon_screen = "detective_tv" - clockwork = TRUE //it'd look weird - 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 = "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 = "Quartermaster's camera console" - desc = "A console with access to the mining, auxillary 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" - network = list("thunder") - density = FALSE - circuit = null - clockwork = TRUE //it'd look very weird - light_power = 0 - -/obj/machinery/computer/security/telescreen/update_icon() - icon_state = initial(icon_state) - if(stat & BROKEN) - icon_state += "b" - return - -/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" - network = list("thunder") - -/obj/machinery/computer/security/telescreen/rd - name = "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/circuitry - name = "circuitry telescreen" - desc = "Used for watching the other eggheads from the safety of the circuitry lab." - network = list("rd") - -/obj/machinery/computer/security/telescreen/ce - name = "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 = "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("toxin") - -/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 = "auxillary base monitor" - desc = "A telescreen that connects to the auxillary 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 = "AI upload monitor" - desc = "A telescreen that connects to the AI upload's camera network." - network = list("aiupload") +/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 + var/last_pic = 1 + var/list/network = list("ss13") + var/list/watchers = list() //who's using the console, associated with the camera they're on. + + light_color = LIGHT_COLOR_RED + +/obj/machinery/computer/security/Initialize() + . = ..() + for(var/i in network) + network -= i + network += lowertext(i) + +/obj/machinery/computer/security/check_eye(mob/user) + if(!can_interact(user) || !(user in watchers) || !watchers[user]) + user.unset_machine() + return + var/obj/machinery/camera/C = watchers[user] + if(!C.can_use()) + user.unset_machine() + return + +/obj/machinery/computer/security/on_unset_machine(mob/user) + watchers.Remove(user) + user.reset_perspective(null) + +/obj/machinery/computer/security/Destroy() + if(watchers.len) + for(var/mob/M in watchers) + M.unset_machine() //to properly reset the view of the users if the console is deleted. + return ..() + +/obj/machinery/computer/security/can_interact(mob/user) + if((!issilicon(user) && !Adjacent(user)) || is_blind(user) || !in_view_range(user, src)) + return FALSE + return ..() + +/obj/machinery/computer/security/interact(mob/user, special_state) + . = ..() + if (ismob(user) && !isliving(user)) // ghosts don't need cameras + return + if (!network) + CRASH("No camera network") + user.unset_machine() + return FALSE + if (!(islist(network))) + CRASH("Camera network is not a list") + user.unset_machine() + return FALSE + + var/list/camera_list = get_available_cameras() + if(!(user in watchers)) + for(var/Num in camera_list) + var/obj/machinery/camera/CAM = camera_list[Num] + if(istype(CAM) && CAM.can_use()) + watchers[user] = CAM //let's give the user the first usable camera, and then let him change to the camera he wants. + break + if(!(user in watchers)) + user.unset_machine() // no usable camera on the network, we disconnect the user from the computer. + return FALSE + playsound(src, 'sound/machines/terminal_prompt.ogg', 25, 0) + use_camera_console(user) + +/obj/machinery/computer/security/proc/use_camera_console(mob/user) + var/list/camera_list = get_available_cameras() + var/t = input(user, "Which camera should you change to?") as null|anything in camera_list + if(!src || user.machine != src) //while we were choosing we got disconnected from our computer or are using another machine. + return + if(!t || t == "Cancel") + user.unset_machine() + playsound(src, 'sound/machines/terminal_off.ogg', 25, 0) + return + + var/obj/machinery/camera/C = camera_list[t] + + if(!C || !C.can_use() || !can_interact(user)) + user.unset_machine() + return FALSE + + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0) + if(isAI(user)) + var/mob/living/silicon/ai/A = user + A.eyeobj.setLoc(get_turf(C)) + A.client.eye = A.eyeobj + else + user.reset_perspective(C) + user.overlay_fullscreen("flash", /obj/screen/fullscreen/flash/static) + user.clear_fullscreen("flash", 5) + watchers[user] = C + use_power(50) + addtimer(CALLBACK(src, .proc/use_camera_console, user), 5) + +//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) + + camera_sort(L) + + var/list/D = list() + D["Cancel"] = "Cancel" + 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.status ? null : " (Deactivated)")]"] = 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 = null + icon_screen = "detective_tv" + clockwork = TRUE //it'd look weird + 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 = "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 = "Quartermaster's camera console" + desc = "A console with access to the mining, auxillary 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" + network = list("thunder") + density = FALSE + circuit = null + clockwork = TRUE //it'd look very weird + light_power = 0 + +/obj/machinery/computer/security/telescreen/update_icon() + icon_state = initial(icon_state) + if(stat & BROKEN) + icon_state += "b" + return + +/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" + network = list("thunder") + +/obj/machinery/computer/security/telescreen/rd + name = "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/circuitry + name = "circuitry telescreen" + desc = "Used for watching the other eggheads from the safety of the circuitry lab." + network = list("rd") + +/obj/machinery/computer/security/telescreen/ce + name = "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 = "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("toxin") + +/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 = "auxillary base monitor" + desc = "A telescreen that connects to the auxillary 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 = "AI upload monitor" + desc = "A telescreen that connects to the AI upload's camera network." + network = list("aiupload") diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm index 3bdb635d39..6703bae525 100644 --- a/code/game/machinery/computer/card.dm +++ b/code/game/machinery/computer/card.dm @@ -1,630 +1,630 @@ - - -//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", - "Quartermaster") - - //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 TRUE - if(inserted_scan_id) - if(id_eject(user, inserted_scan_id)) - inserted_scan_id = null - updateUsrDialog() - return TRUE - -/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" - - 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: - -
    - 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 - 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((ACCESS_QM in inserted_scan_id.access) && ((target_dept==6) || !target_dept)) - region_access |= 6 - get_subordinates("Quartermaster") - 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 - - 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/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("civilian","security","medical","science","engineering","cargo") - 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 - -/obj/machinery/computer/card/minor/qm - target_dept = 6 - icon_screen = "idqm" - - light_color = LIGHT_COLOR_ORANGE - -#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", + "Quartermaster") + + //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 TRUE + if(inserted_scan_id) + if(id_eject(user, inserted_scan_id)) + inserted_scan_id = null + updateUsrDialog() + return TRUE + +/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" + + 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: + +
    + 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 + 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((ACCESS_QM in inserted_scan_id.access) && ((target_dept==6) || !target_dept)) + region_access |= 6 + get_subordinates("Quartermaster") + 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 + + 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/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("civilian","security","medical","science","engineering","cargo") + 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 + +/obj/machinery/computer/card/minor/qm + target_dept = 6 + icon_screen = "idqm" + + light_color = LIGHT_COLOR_ORANGE + +#undef JOB_ALLOWED +#undef JOB_COOLDOWN +#undef JOB_MAX_POSITIONS +#undef JOB_DENIED diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm index 43cfbdb33b..90969bf4a1 100644 --- a/code/game/machinery/computer/cloning.dm +++ b/code/game/machinery/computer/cloning.dm @@ -1,514 +1,514 @@ -#define AUTOCLONING_MINIMAL_LEVEL 4 - -/obj/machinery/computer/cloning - name = "cloning console" - desc = "Used to clone people and manage DNA." - icon_screen = "dna" - icon_keyboard = "med_key" - circuit = /obj/item/circuitboard/computer/cloning - req_access = list(ACCESS_HEADS) //ONLY USED FOR RECORD DELETION RIGHT NOW. - var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning. - var/list/pods //Linked cloning pods - var/temp = "Inactive" - var/scantemp_ckey - var/scantemp = "Ready to Scan" - var/menu = 1 //Which menu screen to display - var/datum/data/record/active_record = null - var/obj/item/disk/data/diskette = null //Mostly so the geneticist can steal everything. - var/loading = 0 // Nice loading text - var/autoprocess = 0 - var/list/records = list() - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/cloning/Initialize() - . = ..() - updatemodules(TRUE) - var/obj/item/circuitboard/computer/cloning/board = circuit - records = board.records - - -/obj/machinery/computer/cloning/Destroy() - if(pods) - for(var/P in pods) - DetachCloner(P) - pods = null - return ..() - -/obj/machinery/computer/cloning/proc/GetAvailablePod(mind = null) - if(pods) - for(var/P in pods) - var/obj/machinery/clonepod/pod = P - if(pod.occupant && pod.clonemind == mind) - return null - if(pod.is_operational() && !(pod.occupant || pod.mess)) - return pod - -/obj/machinery/computer/cloning/proc/HasEfficientPod() - if(pods) - for(var/P in pods) - var/obj/machinery/clonepod/pod = P - if(pod.is_operational() && pod.efficiency > 5) - return TRUE - -/obj/machinery/computer/cloning/proc/GetAvailableEfficientPod(mind = null) - if(pods) - for(var/P in pods) - var/obj/machinery/clonepod/pod = P - if(pod.occupant && pod.clonemind == mind) - return pod - else if(!. && pod.is_operational() && !(pod.occupant || pod.mess) && pod.efficiency > 5) - . = pod - -/obj/machinery/computer/cloning/process() - if(!(scanner && LAZYLEN(pods) && autoprocess)) - return - - for(var/datum/data/record/R in records) - var/obj/machinery/clonepod/pod = GetAvailableEfficientPod(R.fields["mind"]) - - if(!pod) - return - - if(pod.occupant) - continue //how though? - - if(pod.growclone(R.fields["ckey"], R.fields["name"], R.fields["UI"], R.fields["SE"], R.fields["mind"], R.fields["mrace"], R.fields["features"], R.fields["factions"], R.fields["quirks"])) - temp = "[R.fields["name"]] => Cloning cycle in progress..." - records -= R - -/obj/machinery/computer/cloning/proc/updatemodules(findfirstcloner) - src.scanner = findscanner() - if(findfirstcloner && !LAZYLEN(pods)) - findcloner() - if(!autoprocess) - STOP_PROCESSING(SSmachines, src) - else - START_PROCESSING(SSmachines, src) - -/obj/machinery/computer/cloning/proc/findscanner() - var/obj/machinery/dna_scannernew/scannerf = null - - // Loop through every direction - for(var/direction in GLOB.cardinals) - - // Try to find a scanner in that direction - scannerf = locate(/obj/machinery/dna_scannernew, get_step(src, direction)) - - // If found and operational, return the scanner - if (!isnull(scannerf) && scannerf.is_operational()) - return scannerf - - // If no scanner was found, it will return null - return null - -/obj/machinery/computer/cloning/proc/findcloner() - var/obj/machinery/clonepod/podf = null - - for(var/direction in GLOB.cardinals) - - podf = locate(/obj/machinery/clonepod, get_step(src, direction)) - if (!isnull(podf) && podf.is_operational()) - AttachCloner(podf) - -/obj/machinery/computer/cloning/proc/AttachCloner(obj/machinery/clonepod/pod) - if(!pod.connected) - pod.connected = src - LAZYADD(pods, pod) - -/obj/machinery/computer/cloning/proc/DetachCloner(obj/machinery/clonepod/pod) - pod.connected = null - LAZYREMOVE(pods, pod) - -/obj/machinery/computer/cloning/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/disk/data)) //INSERT SOME DISKETTES - if (!src.diskette) - if (!user.transferItemToLoc(W,src)) - return - src.diskette = W - to_chat(user, "You insert [W].") - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - src.updateUsrDialog() - else if(istype(W, /obj/item/multitool)) - var/obj/item/multitool/P = W - - if(istype(P.buffer, /obj/machinery/clonepod)) - if(get_area(P.buffer) != get_area(src)) - to_chat(user, "-% Cannot link machines across power zones. Buffer cleared %-") - P.buffer = null - return - to_chat(user, "-% Successfully linked [P.buffer] with [src] %-") - var/obj/machinery/clonepod/pod = P.buffer - if(pod.connected) - pod.connected.DetachCloner(pod) - AttachCloner(pod) - else - P.buffer = src - to_chat(user, "-% Successfully stored [REF(P.buffer)] [P.buffer.name] in buffer %-") - return - else - return ..() - -/obj/machinery/computer/cloning/ui_interact(mob/user) - . = ..() - - updatemodules(TRUE) - - var/dat = "" - dat += "Refresh" - - if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL) - if(!autoprocess) - dat += "Autoclone" - else - dat += "Stop autoclone" - else - dat += "Autoclone" - dat += "

    Cloning Pod Status

    " - dat += "
    [temp] 
    " - - switch(src.menu) - if(1) - // Modules - if (isnull(src.scanner) || !LAZYLEN(pods)) - dat += "

    Modules

    " - //dat += "Reload Modules" - if (isnull(src.scanner)) - dat += "ERROR: No Scanner detected!
    " - if (!LAZYLEN(pods)) - dat += "ERROR: No Pod detected
    " - - // Scanner - if (!isnull(src.scanner)) - var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner.occupant) - - dat += "

    Scanner Functions

    " - - dat += "
    " - if(!scanner_occupant) - dat += "Scanner Unoccupied" - else if(loading) - dat += "[scanner_occupant] => Scanning..." - else - if(scanner_occupant.ckey != scantemp_ckey) - scantemp = "Ready to Scan" - scantemp_ckey = scanner_occupant.ckey - dat += "[scanner_occupant] => [scantemp]" - dat += "
    " - - if(scanner_occupant) - dat += "Start Scan" - dat += "
    [src.scanner.locked ? "Unlock Scanner" : "Lock Scanner"]" - else - dat += "Start Scan" - - // Database - dat += "

    Database Functions

    " - if (src.records.len && src.records.len > 0) - dat += "View Records ([src.records.len])
    " - else - dat += "View Records (0)
    " - if (src.diskette) - dat += "Eject Disk
    " - - - - if(2) - dat += "

    Current records

    " - dat += "<< Back

    " - for(var/datum/data/record/R in records) - dat += "

    [R.fields["name"]]

    Scan ID [R.fields["id"]] View Record" - if(3) - dat += "

    Selected Record

    " - dat += "<< Back
    " - - if (!src.active_record) - dat += "Record not found." - else - dat += "

    [src.active_record.fields["name"]]

    " - dat += "Scan ID [src.active_record.fields["id"]] Clone
    " - - var/obj/item/implant/health/H = locate(active_record.fields["imp"]) - - if ((H) && (istype(H))) - dat += "Health Implant Data:
    [H.sensehealth()]

    " - else - dat += "Unable to locate Health Implant.

    " - - dat += "Unique Identifier:
    [src.active_record.fields["UI"]]
    " - dat += "Structural Enzymes:
    [src.active_record.fields["SE"]]
    " - - if(diskette && diskette.fields) - dat += "
    " - dat += "

    Inserted Disk

    " - dat += "Contents: " - var/list/L = list() - if(diskette.fields["UI"]) - L += "Unique Identifier" - if(diskette.fields["UE"] && diskette.fields["name"] && diskette.fields["blood_type"]) - L += "Unique Enzymes" - if(diskette.fields["SE"]) - L += "Structural Enzymes" - dat += english_list(L, "Empty", " + ", " + ") - dat += "
    Load from Disk" - - dat += "
    Save to Disk" - dat += "
    " - - dat += "Delete Record" - - if(4) - if (!src.active_record) - src.menu = 2 - dat = "[src.temp]
    " - dat += "

    Confirm Record Deletion

    " - - dat += "Scan card to confirm.
    " - dat += "Cancel" - - - var/datum/browser/popup = new(user, "cloning", "Cloning System Control") - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/cloning/Topic(href, href_list) - if(..()) - return - - if(loading) - return - - if(href_list["task"]) - switch(href_list["task"]) - if("autoprocess") - if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL) - autoprocess = TRUE - START_PROCESSING(SSmachines, src) - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - if("stopautoprocess") - autoprocess = FALSE - STOP_PROCESSING(SSmachines, src) - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - - else if ((href_list["scan"]) && !isnull(scanner) && scanner.is_operational()) - scantemp = "" - - loading = 1 - src.updateUsrDialog() - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) - say("Initiating scan...") - var/prev_locked = scanner.locked - scanner.locked = TRUE - spawn(20) - src.scan_occupant(scanner.occupant) - - loading = 0 - src.updateUsrDialog() - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - scanner.locked = prev_locked - - - //No locking an open scanner. - else if ((href_list["lock"]) && !isnull(scanner) && scanner.is_operational()) - if ((!scanner.locked) && (scanner.occupant)) - scanner.locked = TRUE - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - else - scanner.locked = FALSE - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - - else if(href_list["view_rec"]) - playsound(src, "terminal_type", 25, 0) - src.active_record = find_record("id", href_list["view_rec"], records) - if(active_record) - if(!active_record.fields["ckey"]) - records -= active_record - active_record = null - src.temp = "Record Corrupt" - else - src.menu = 3 - else - src.temp = "Record missing." - - else if (href_list["del_rec"]) - if ((!src.active_record) || (src.menu < 3)) - return - if (src.menu == 3) //If we are viewing a record, confirm deletion - src.temp = "Delete record?" - src.menu = 4 - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) - - else if (src.menu == 4) - var/obj/item/card/id/C = usr.get_active_held_item() - if (istype(C)||istype(C, /obj/item/pda)) - if(src.check_access(C)) - src.temp = "[src.active_record.fields["name"]] => Record deleted." - src.records.Remove(active_record) - active_record = null - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - src.menu = 2 - var/obj/item/circuitboard/computer/cloning/board = circuit - board.records = records - else - src.temp = "Access Denied." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - - else if (href_list["disk"]) //Load or eject. - switch(href_list["disk"]) - if("load") - if (!diskette || !istype(diskette.fields) || !diskette.fields["name"] || !diskette.fields) - src.temp = "Load error." - src.updateUsrDialog() - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - return - if (!src.active_record) - src.temp = "Record error." - src.menu = 1 - src.updateUsrDialog() - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - return - - for(var/key in diskette.fields) - src.active_record.fields[key] = diskette.fields[key] - src.temp = "Load successful." - var/obj/item/circuitboard/computer/cloning/board = circuit - board.records = records - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - - if("eject") - if(src.diskette) - src.diskette.forceMove(drop_location()) - src.diskette = null - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - if("save") - if(!diskette || diskette.read_only || !active_record || !active_record.fields) - src.temp = "Save error." - src.updateUsrDialog() - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - return - - diskette.fields = active_record.fields.Copy() - diskette.name = "data disk - '[src.diskette.fields["name"]]'" - src.temp = "Save successful." - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - - else if (href_list["refresh"]) - src.updateUsrDialog() - playsound(src, "terminal_type", 25, 0) - - else if (href_list["clone"]) - var/datum/data/record/C = find_record("id", href_list["clone"], records) - //Look for that player! They better be dead! - if(C) - var/obj/machinery/clonepod/pod = GetAvailablePod() - //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs. - if(!LAZYLEN(pods)) - temp = "No Clonepods detected." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - else if(!pod) - temp = "No Clonepods available." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - else if(!CONFIG_GET(flag/revival_cloning)) - temp = "Unable to initiate cloning cycle." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - else if(pod.occupant) - temp = "Cloning cycle already in progress." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - else if(pod.growclone(C.fields["ckey"], C.fields["name"], C.fields["UI"], C.fields["SE"], C.fields["mind"], C.fields["mrace"], C.fields["features"], C.fields["factions"], C.fields["quirks"])) - temp = "[C.fields["name"]] => Cloning cycle in progress..." - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - records.Remove(C) - if(active_record == C) - active_record = null - menu = 1 - else - temp = "[C.fields["name"]] => Initialisation failure." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - - else - temp = "Data corruption." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - - else if (href_list["menu"]) - src.menu = text2num(href_list["menu"]) - playsound(src, "terminal_type", 25, 0) - - src.add_fingerprint(usr) - src.updateUsrDialog() - return - -/obj/machinery/computer/cloning/proc/scan_occupant(occupant) - var/mob/living/mob_occupant = get_mob_or_brainmob(occupant) - var/datum/dna/dna - if(ishuman(mob_occupant)) - var/mob/living/carbon/C = mob_occupant - dna = C.has_dna() - if(isbrain(mob_occupant)) - var/mob/living/brain/B = mob_occupant - dna = B.stored_dna - - if(!istype(dna)) - scantemp = "Unable to locate valid genetic data." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - return - if(mob_occupant.suiciding || mob_occupant.hellbound) - scantemp = "Subject's brain is not responding to scanning stimuli." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - return - if((HAS_TRAIT(mob_occupant, TRAIT_NOCLONE)) && (src.scanner.scan_level < 2)) - scantemp = "Subject no longer contains the fundamental materials required to create a living clone." - playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) - return - if ((!mob_occupant.ckey) || (!mob_occupant.client)) - scantemp = "Mental interface failure." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - return - if (find_record("ckey", mob_occupant.ckey, records)) - scantemp = "Subject already in database." - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - return - - var/datum/data/record/R = new() - if(dna.species) - // We store the instance rather than the path, because some - // species (abductors, slimepeople) store state in their - // species datums - dna.delete_species = FALSE - R.fields["mrace"] = dna.species - else - var/datum/species/rando_race = pick(GLOB.roundstart_races) - R.fields["mrace"] = rando_race.type - - R.fields["ckey"] = mob_occupant.ckey - R.fields["name"] = mob_occupant.real_name - R.fields["id"] = copytext(md5(mob_occupant.real_name), 2, 6) - R.fields["UE"] = dna.unique_enzymes - R.fields["UI"] = dna.uni_identity - R.fields["SE"] = dna.struc_enzymes - R.fields["blood_type"] = dna.blood_type - R.fields["features"] = dna.features - R.fields["factions"] = mob_occupant.faction - R.fields["quirks"] = list() - for(var/V in mob_occupant.roundstart_quirks) - var/datum/quirk/T = V - R.fields["quirks"][T.type] = T.clone_data() - - if (!isnull(mob_occupant.mind)) //Save that mind so traitors can continue traitoring after cloning. - R.fields["mind"] = "[REF(mob_occupant.mind)]" - - //Add an implant if needed - var/obj/item/implant/health/imp - for(var/obj/item/implant/health/HI in mob_occupant.implants) - imp = HI - break - if(!imp) - imp = new /obj/item/implant/health(mob_occupant) - imp.implant(mob_occupant) - R.fields["imp"] = "[REF(imp)]" - - src.records += R - var/obj/item/circuitboard/computer/cloning/board = circuit - board.records = records - scantemp = "Subject successfully scanned." - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) +#define AUTOCLONING_MINIMAL_LEVEL 4 + +/obj/machinery/computer/cloning + name = "cloning console" + desc = "Used to clone people and manage DNA." + icon_screen = "dna" + icon_keyboard = "med_key" + circuit = /obj/item/circuitboard/computer/cloning + req_access = list(ACCESS_HEADS) //ONLY USED FOR RECORD DELETION RIGHT NOW. + var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning. + var/list/pods //Linked cloning pods + var/temp = "Inactive" + var/scantemp_ckey + var/scantemp = "Ready to Scan" + var/menu = 1 //Which menu screen to display + var/datum/data/record/active_record = null + var/obj/item/disk/data/diskette = null //Mostly so the geneticist can steal everything. + var/loading = 0 // Nice loading text + var/autoprocess = 0 + var/list/records = list() + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/cloning/Initialize() + . = ..() + updatemodules(TRUE) + var/obj/item/circuitboard/computer/cloning/board = circuit + records = board.records + + +/obj/machinery/computer/cloning/Destroy() + if(pods) + for(var/P in pods) + DetachCloner(P) + pods = null + return ..() + +/obj/machinery/computer/cloning/proc/GetAvailablePod(mind = null) + if(pods) + for(var/P in pods) + var/obj/machinery/clonepod/pod = P + if(pod.occupant && pod.clonemind == mind) + return null + if(pod.is_operational() && !(pod.occupant || pod.mess)) + return pod + +/obj/machinery/computer/cloning/proc/HasEfficientPod() + if(pods) + for(var/P in pods) + var/obj/machinery/clonepod/pod = P + if(pod.is_operational() && pod.efficiency > 5) + return TRUE + +/obj/machinery/computer/cloning/proc/GetAvailableEfficientPod(mind = null) + if(pods) + for(var/P in pods) + var/obj/machinery/clonepod/pod = P + if(pod.occupant && pod.clonemind == mind) + return pod + else if(!. && pod.is_operational() && !(pod.occupant || pod.mess) && pod.efficiency > 5) + . = pod + +/obj/machinery/computer/cloning/process() + if(!(scanner && LAZYLEN(pods) && autoprocess)) + return + + for(var/datum/data/record/R in records) + var/obj/machinery/clonepod/pod = GetAvailableEfficientPod(R.fields["mind"]) + + if(!pod) + return + + if(pod.occupant) + continue //how though? + + if(pod.growclone(R.fields["ckey"], R.fields["name"], R.fields["UI"], R.fields["SE"], R.fields["mind"], R.fields["mrace"], R.fields["features"], R.fields["factions"], R.fields["quirks"])) + temp = "[R.fields["name"]] => Cloning cycle in progress..." + records -= R + +/obj/machinery/computer/cloning/proc/updatemodules(findfirstcloner) + src.scanner = findscanner() + if(findfirstcloner && !LAZYLEN(pods)) + findcloner() + if(!autoprocess) + STOP_PROCESSING(SSmachines, src) + else + START_PROCESSING(SSmachines, src) + +/obj/machinery/computer/cloning/proc/findscanner() + var/obj/machinery/dna_scannernew/scannerf = null + + // Loop through every direction + for(var/direction in GLOB.cardinals) + + // Try to find a scanner in that direction + scannerf = locate(/obj/machinery/dna_scannernew, get_step(src, direction)) + + // If found and operational, return the scanner + if (!isnull(scannerf) && scannerf.is_operational()) + return scannerf + + // If no scanner was found, it will return null + return null + +/obj/machinery/computer/cloning/proc/findcloner() + var/obj/machinery/clonepod/podf = null + + for(var/direction in GLOB.cardinals) + + podf = locate(/obj/machinery/clonepod, get_step(src, direction)) + if (!isnull(podf) && podf.is_operational()) + AttachCloner(podf) + +/obj/machinery/computer/cloning/proc/AttachCloner(obj/machinery/clonepod/pod) + if(!pod.connected) + pod.connected = src + LAZYADD(pods, pod) + +/obj/machinery/computer/cloning/proc/DetachCloner(obj/machinery/clonepod/pod) + pod.connected = null + LAZYREMOVE(pods, pod) + +/obj/machinery/computer/cloning/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/disk/data)) //INSERT SOME DISKETTES + if (!src.diskette) + if (!user.transferItemToLoc(W,src)) + return + src.diskette = W + to_chat(user, "You insert [W].") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + src.updateUsrDialog() + else if(istype(W, /obj/item/multitool)) + var/obj/item/multitool/P = W + + if(istype(P.buffer, /obj/machinery/clonepod)) + if(get_area(P.buffer) != get_area(src)) + to_chat(user, "-% Cannot link machines across power zones. Buffer cleared %-") + P.buffer = null + return + to_chat(user, "-% Successfully linked [P.buffer] with [src] %-") + var/obj/machinery/clonepod/pod = P.buffer + if(pod.connected) + pod.connected.DetachCloner(pod) + AttachCloner(pod) + else + P.buffer = src + to_chat(user, "-% Successfully stored [REF(P.buffer)] [P.buffer.name] in buffer %-") + return + else + return ..() + +/obj/machinery/computer/cloning/ui_interact(mob/user) + . = ..() + + updatemodules(TRUE) + + var/dat = "" + dat += "Refresh" + + if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL) + if(!autoprocess) + dat += "Autoclone" + else + dat += "Stop autoclone" + else + dat += "Autoclone" + dat += "

    Cloning Pod Status

    " + dat += "
    [temp] 
    " + + switch(src.menu) + if(1) + // Modules + if (isnull(src.scanner) || !LAZYLEN(pods)) + dat += "

    Modules

    " + //dat += "Reload Modules" + if (isnull(src.scanner)) + dat += "ERROR: No Scanner detected!
    " + if (!LAZYLEN(pods)) + dat += "ERROR: No Pod detected
    " + + // Scanner + if (!isnull(src.scanner)) + var/mob/living/scanner_occupant = get_mob_or_brainmob(scanner.occupant) + + dat += "

    Scanner Functions

    " + + dat += "
    " + if(!scanner_occupant) + dat += "Scanner Unoccupied" + else if(loading) + dat += "[scanner_occupant] => Scanning..." + else + if(scanner_occupant.ckey != scantemp_ckey) + scantemp = "Ready to Scan" + scantemp_ckey = scanner_occupant.ckey + dat += "[scanner_occupant] => [scantemp]" + dat += "
    " + + if(scanner_occupant) + dat += "Start Scan" + dat += "
    [src.scanner.locked ? "Unlock Scanner" : "Lock Scanner"]" + else + dat += "Start Scan" + + // Database + dat += "

    Database Functions

    " + if (src.records.len && src.records.len > 0) + dat += "View Records ([src.records.len])
    " + else + dat += "View Records (0)
    " + if (src.diskette) + dat += "Eject Disk
    " + + + + if(2) + dat += "

    Current records

    " + dat += "<< Back

    " + for(var/datum/data/record/R in records) + dat += "

    [R.fields["name"]]

    Scan ID [R.fields["id"]] View Record" + if(3) + dat += "

    Selected Record

    " + dat += "<< Back
    " + + if (!src.active_record) + dat += "Record not found." + else + dat += "

    [src.active_record.fields["name"]]

    " + dat += "Scan ID [src.active_record.fields["id"]] Clone
    " + + var/obj/item/implant/health/H = locate(active_record.fields["imp"]) + + if ((H) && (istype(H))) + dat += "Health Implant Data:
    [H.sensehealth()]

    " + else + dat += "Unable to locate Health Implant.

    " + + dat += "Unique Identifier:
    [src.active_record.fields["UI"]]
    " + dat += "Structural Enzymes:
    [src.active_record.fields["SE"]]
    " + + if(diskette && diskette.fields) + dat += "
    " + dat += "

    Inserted Disk

    " + dat += "Contents: " + var/list/L = list() + if(diskette.fields["UI"]) + L += "Unique Identifier" + if(diskette.fields["UE"] && diskette.fields["name"] && diskette.fields["blood_type"]) + L += "Unique Enzymes" + if(diskette.fields["SE"]) + L += "Structural Enzymes" + dat += english_list(L, "Empty", " + ", " + ") + dat += "
    Load from Disk" + + dat += "
    Save to Disk" + dat += "
    " + + dat += "Delete Record" + + if(4) + if (!src.active_record) + src.menu = 2 + dat = "[src.temp]
    " + dat += "

    Confirm Record Deletion

    " + + dat += "Scan card to confirm.
    " + dat += "Cancel" + + + var/datum/browser/popup = new(user, "cloning", "Cloning System Control") + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/cloning/Topic(href, href_list) + if(..()) + return + + if(loading) + return + + if(href_list["task"]) + switch(href_list["task"]) + if("autoprocess") + if(scanner && HasEfficientPod() && scanner.scan_level >= AUTOCLONING_MINIMAL_LEVEL) + autoprocess = TRUE + START_PROCESSING(SSmachines, src) + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + if("stopautoprocess") + autoprocess = FALSE + STOP_PROCESSING(SSmachines, src) + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + + else if ((href_list["scan"]) && !isnull(scanner) && scanner.is_operational()) + scantemp = "" + + loading = 1 + src.updateUsrDialog() + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) + say("Initiating scan...") + var/prev_locked = scanner.locked + scanner.locked = TRUE + spawn(20) + src.scan_occupant(scanner.occupant) + + loading = 0 + src.updateUsrDialog() + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + scanner.locked = prev_locked + + + //No locking an open scanner. + else if ((href_list["lock"]) && !isnull(scanner) && scanner.is_operational()) + if ((!scanner.locked) && (scanner.occupant)) + scanner.locked = TRUE + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + else + scanner.locked = FALSE + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + + else if(href_list["view_rec"]) + playsound(src, "terminal_type", 25, 0) + src.active_record = find_record("id", href_list["view_rec"], records) + if(active_record) + if(!active_record.fields["ckey"]) + records -= active_record + active_record = null + src.temp = "Record Corrupt" + else + src.menu = 3 + else + src.temp = "Record missing." + + else if (href_list["del_rec"]) + if ((!src.active_record) || (src.menu < 3)) + return + if (src.menu == 3) //If we are viewing a record, confirm deletion + src.temp = "Delete record?" + src.menu = 4 + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) + + else if (src.menu == 4) + var/obj/item/card/id/C = usr.get_active_held_item() + if (istype(C)||istype(C, /obj/item/pda)) + if(src.check_access(C)) + src.temp = "[src.active_record.fields["name"]] => Record deleted." + src.records.Remove(active_record) + active_record = null + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + src.menu = 2 + var/obj/item/circuitboard/computer/cloning/board = circuit + board.records = records + else + src.temp = "Access Denied." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + + else if (href_list["disk"]) //Load or eject. + switch(href_list["disk"]) + if("load") + if (!diskette || !istype(diskette.fields) || !diskette.fields["name"] || !diskette.fields) + src.temp = "Load error." + src.updateUsrDialog() + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + return + if (!src.active_record) + src.temp = "Record error." + src.menu = 1 + src.updateUsrDialog() + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + return + + for(var/key in diskette.fields) + src.active_record.fields[key] = diskette.fields[key] + src.temp = "Load successful." + var/obj/item/circuitboard/computer/cloning/board = circuit + board.records = records + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + + if("eject") + if(src.diskette) + src.diskette.forceMove(drop_location()) + src.diskette = null + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + if("save") + if(!diskette || diskette.read_only || !active_record || !active_record.fields) + src.temp = "Save error." + src.updateUsrDialog() + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + return + + diskette.fields = active_record.fields.Copy() + diskette.name = "data disk - '[src.diskette.fields["name"]]'" + src.temp = "Save successful." + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + + else if (href_list["refresh"]) + src.updateUsrDialog() + playsound(src, "terminal_type", 25, 0) + + else if (href_list["clone"]) + var/datum/data/record/C = find_record("id", href_list["clone"], records) + //Look for that player! They better be dead! + if(C) + var/obj/machinery/clonepod/pod = GetAvailablePod() + //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs. + if(!LAZYLEN(pods)) + temp = "No Clonepods detected." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + else if(!pod) + temp = "No Clonepods available." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + else if(!CONFIG_GET(flag/revival_cloning)) + temp = "Unable to initiate cloning cycle." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + else if(pod.occupant) + temp = "Cloning cycle already in progress." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + else if(pod.growclone(C.fields["ckey"], C.fields["name"], C.fields["UI"], C.fields["SE"], C.fields["mind"], C.fields["mrace"], C.fields["features"], C.fields["factions"], C.fields["quirks"])) + temp = "[C.fields["name"]] => Cloning cycle in progress..." + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + records.Remove(C) + if(active_record == C) + active_record = null + menu = 1 + else + temp = "[C.fields["name"]] => Initialisation failure." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + + else + temp = "Data corruption." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + + else if (href_list["menu"]) + src.menu = text2num(href_list["menu"]) + playsound(src, "terminal_type", 25, 0) + + src.add_fingerprint(usr) + src.updateUsrDialog() + return + +/obj/machinery/computer/cloning/proc/scan_occupant(occupant) + var/mob/living/mob_occupant = get_mob_or_brainmob(occupant) + var/datum/dna/dna + if(ishuman(mob_occupant)) + var/mob/living/carbon/C = mob_occupant + dna = C.has_dna() + if(isbrain(mob_occupant)) + var/mob/living/brain/B = mob_occupant + dna = B.stored_dna + + if(!istype(dna)) + scantemp = "Unable to locate valid genetic data." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + return + if(mob_occupant.suiciding || mob_occupant.hellbound) + scantemp = "Subject's brain is not responding to scanning stimuli." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + return + if((HAS_TRAIT(mob_occupant, TRAIT_NOCLONE)) && (src.scanner.scan_level < 2)) + scantemp = "Subject no longer contains the fundamental materials required to create a living clone." + playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) + return + if ((!mob_occupant.ckey) || (!mob_occupant.client)) + scantemp = "Mental interface failure." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + return + if (find_record("ckey", mob_occupant.ckey, records)) + scantemp = "Subject already in database." + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + return + + var/datum/data/record/R = new() + if(dna.species) + // We store the instance rather than the path, because some + // species (abductors, slimepeople) store state in their + // species datums + dna.delete_species = FALSE + R.fields["mrace"] = dna.species + else + var/datum/species/rando_race = pick(GLOB.roundstart_races) + R.fields["mrace"] = rando_race.type + + R.fields["ckey"] = mob_occupant.ckey + R.fields["name"] = mob_occupant.real_name + R.fields["id"] = copytext(md5(mob_occupant.real_name), 2, 6) + R.fields["UE"] = dna.unique_enzymes + R.fields["UI"] = dna.uni_identity + R.fields["SE"] = dna.struc_enzymes + R.fields["blood_type"] = dna.blood_type + R.fields["features"] = dna.features + R.fields["factions"] = mob_occupant.faction + R.fields["quirks"] = list() + for(var/V in mob_occupant.roundstart_quirks) + var/datum/quirk/T = V + R.fields["quirks"][T.type] = T.clone_data() + + if (!isnull(mob_occupant.mind)) //Save that mind so traitors can continue traitoring after cloning. + R.fields["mind"] = "[REF(mob_occupant.mind)]" + + //Add an implant if needed + var/obj/item/implant/health/imp + for(var/obj/item/implant/health/HI in mob_occupant.implants) + imp = HI + break + if(!imp) + imp = new /obj/item/implant/health(mob_occupant) + imp.implant(mob_occupant) + R.fields["imp"] = "[REF(imp)]" + + src.records += R + var/obj/item/circuitboard/computer/cloning/board = circuit + board.records = records + scantemp = "Subject successfully scanned." + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index 22a523c58b..1bc8a82147 100755 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -1,759 +1,759 @@ -// 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/const/STATE_DEFAULT = 1 - var/const/STATE_CALLSHUTTLE = 2 - var/const/STATE_CANCELSHUTTLE = 3 - var/const/STATE_MESSAGELIST = 4 - var/const/STATE_VIEWMESSAGE = 5 - var/const/STATE_DELMESSAGE = 6 - var/const/STATE_STATUSDISPLAY = 7 - var/const/STATE_ALERT_LEVEL = 8 - var/const/STATE_CONFIRM_LEVEL = 9 - var/const/STATE_TOGGLE_EMERGENCY = 10 - var/const/STATE_PURCHASE = 11 - - 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)) - 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, 0) - 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((ACCESS_CAPTAIN in I.access)) - authenticated = 2 - playsound(src, 'sound/machines/terminal_on.ogg', 50, 0) - 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, 0) - playsound(src, 'sound/machines/terminal_alert.ogg', 25, 0) - 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, 0) - - 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 - if(tmp_alertlevel > SEC_LEVEL_AMBER) - tmp_alertlevel = SEC_LEVEL_AMBER //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, 0) - //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("[usr.real_name] has changed the security level to [security_level] with [src] at [get_area_name(usr, TRUE)].", usr) - tmp_alertlevel = 0 - else - to_chat(usr, "You are not authorized to do this!") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - 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, 0) - - if("announce") - if(authenticated==2) - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) - make_announcement(usr) - - if("crossserver") - if(authenticated==2) - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - return - var/input = stripped_multiline_input(usr, "Please choose a message to transmit to allied stations. 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, 0) - send2otherserver("[station_name()]", input,"Comms_Console") - 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("[usr.real_name] has sent an outgoing message to the other station(s).", usr) - 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 - if(SSshuttle.points >= S.credit_cost) - var/obj/machinery/shuttle_manipulator/M = locate() in GLOB.machines - if(M) - SSshuttle.shuttle_purchased = TRUE - SSshuttle.points -= S.credit_cost - minor_announce("[usr.real_name] has purchased [S.name] for [S.credit_cost] credits." , "Shuttle Purchase") - message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [S.name].") - SSblackbox.record_feedback("text", "shuttle_purchase", 1, "[S.name]") - M.unload_preview() - M.load_template(S) - M.existing_shuttle = SSshuttle.emergency - M.action_load(S) - message_admins("[S.name] loaded, purchased by [usr]") - else - to_chat(usr, "Something went wrong! The shuttle exchange system seems to be down.") - else - to_chat(usr, "Not enough credits.") - - if("callshuttle") - state = STATE_DEFAULT - if(authenticated) - 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) - if((world.realtime - SSshuttle.realtimeofstart) > SSshuttle.auto_call) //Citadel Edit Removing auto_call caused recall. - say("Warning: Emergency shuttle recalls have been blocked by Central Command due to ongoing crew transfer procedures.") - else - 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("[usr.real_name] enabled emergency maintenance access at [get_area_name(usr, TRUE)].", usr) - 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("[usr.real_name] disabled emergency maintenance access at [get_area_name(usr, TRUE)].", usr) - state = STATE_DEFAULT - - // Status display stuff - if("setstat") - playsound(src, "terminal_type", 50, 0) - 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, 0) - CentCom_announce(input, usr) - to_chat(usr, "Message transmitted to Central Command.") - usr.log_talk(input, LOG_SAY, tag="CentCom announcement") - deadchat_broadcast("[usr.real_name] has messaged CentCom, \"[input]\" at [get_area_name(usr, TRUE)].", usr) - CM.lastTimeUsed = world.time - - // OMG SYNDICATE ...LETTERHEAD - if("MessageSyndicate") - if((authenticated==2) && (obj_flags & EMAGGED)) - if(!checkCCcooldown()) - to_chat(usr, "Arrays recycling. Please stand by.") - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) - 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, 0) - 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("[usr.real_name] has messaged the Syndicate, \"[input]\" at [get_area_name(usr, TRUE)].", usr) - CM.lastTimeUsed = world.time - - if("RestoreBackup") - to_chat(usr, "Backup routing data restored!") - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) - 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", 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","commandreport") - CM.lastTimeUsed = world.time - - - // AI interface - if("ai-main") - aicurrmsg = null - aistate = STATE_DEFAULT - if("ai-callshuttle") - 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 - if(tmp_alertlevel > SEC_LEVEL_AMBER) - tmp_alertlevel = SEC_LEVEL_AMBER //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("[usr.real_name] has changed the security level to [security_level] from [src] at [get_area_name(usr, TRUE)].", usr) - 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("[usr.real_name] enabled emergency maintenance access.", usr) - 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("[usr.real_name] disabled emergency maintenance access.", usr) - 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 - SSshuttle.shuttle_purchase_requirements_met |= "emagged" - if(authenticated == 1) - authenticated = 2 - to_chat(user, "You scramble the communication routing circuits!") - playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) - return TRUE - -/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_zero(num2text(timeleft % 60), 2)]" - - - 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/cross_servers_count = length(CONFIG_GET(keyed_list/cross_server)) - if(cross_servers_count) - dat += "
    \[ Send a message to [cross_servers_count == 1 ? "an " : ""]allied station[cross_servers_count > 1 ? "s" : ""] \]" - 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, 0) - if(STATE_CANCELSHUTTLE) - dat += get_cancel_shuttle_form() - playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) - 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, 0) - 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, 0) - 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 += "Amber
    " - 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, 0) - 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) - dat += "Budget: [SSshuttle.points] 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 += "Amber
    " - 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)) - return - SScommunications.make_announcement(user, is_silicon, input) - deadchat_broadcast("[user.real_name] made an priority announcement from [get_area_name(usr, TRUE)].", user) - -/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 +// 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/const/STATE_DEFAULT = 1 + var/const/STATE_CALLSHUTTLE = 2 + var/const/STATE_CANCELSHUTTLE = 3 + var/const/STATE_MESSAGELIST = 4 + var/const/STATE_VIEWMESSAGE = 5 + var/const/STATE_DELMESSAGE = 6 + var/const/STATE_STATUSDISPLAY = 7 + var/const/STATE_ALERT_LEVEL = 8 + var/const/STATE_CONFIRM_LEVEL = 9 + var/const/STATE_TOGGLE_EMERGENCY = 10 + var/const/STATE_PURCHASE = 11 + + 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)) + 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, 0) + 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((ACCESS_CAPTAIN in I.access)) + authenticated = 2 + playsound(src, 'sound/machines/terminal_on.ogg', 50, 0) + 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, 0) + playsound(src, 'sound/machines/terminal_alert.ogg', 25, 0) + 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, 0) + + 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 + if(tmp_alertlevel > SEC_LEVEL_AMBER) + tmp_alertlevel = SEC_LEVEL_AMBER //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, 0) + //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("[usr.real_name] has changed the security level to [security_level] with [src] at [get_area_name(usr, TRUE)].", usr) + tmp_alertlevel = 0 + else + to_chat(usr, "You are not authorized to do this!") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + 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, 0) + + if("announce") + if(authenticated==2) + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) + make_announcement(usr) + + if("crossserver") + if(authenticated==2) + if(!checkCCcooldown()) + to_chat(usr, "Arrays recycling. Please stand by.") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + return + var/input = stripped_multiline_input(usr, "Please choose a message to transmit to allied stations. 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, 0) + send2otherserver("[station_name()]", input,"Comms_Console") + 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("[usr.real_name] has sent an outgoing message to the other station(s).", usr) + 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 + if(SSshuttle.points >= S.credit_cost) + var/obj/machinery/shuttle_manipulator/M = locate() in GLOB.machines + if(M) + SSshuttle.shuttle_purchased = TRUE + SSshuttle.points -= S.credit_cost + minor_announce("[usr.real_name] has purchased [S.name] for [S.credit_cost] credits." , "Shuttle Purchase") + message_admins("[ADMIN_LOOKUPFLW(usr)] purchased [S.name].") + SSblackbox.record_feedback("text", "shuttle_purchase", 1, "[S.name]") + M.unload_preview() + M.load_template(S) + M.existing_shuttle = SSshuttle.emergency + M.action_load(S) + message_admins("[S.name] loaded, purchased by [usr]") + else + to_chat(usr, "Something went wrong! The shuttle exchange system seems to be down.") + else + to_chat(usr, "Not enough credits.") + + if("callshuttle") + state = STATE_DEFAULT + if(authenticated) + 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) + if((world.realtime - SSshuttle.realtimeofstart) > SSshuttle.auto_call) //Citadel Edit Removing auto_call caused recall. + say("Warning: Emergency shuttle recalls have been blocked by Central Command due to ongoing crew transfer procedures.") + else + 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("[usr.real_name] enabled emergency maintenance access at [get_area_name(usr, TRUE)].", usr) + 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("[usr.real_name] disabled emergency maintenance access at [get_area_name(usr, TRUE)].", usr) + state = STATE_DEFAULT + + // Status display stuff + if("setstat") + playsound(src, "terminal_type", 50, 0) + 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, 0) + CentCom_announce(input, usr) + to_chat(usr, "Message transmitted to Central Command.") + usr.log_talk(input, LOG_SAY, tag="CentCom announcement") + deadchat_broadcast("[usr.real_name] has messaged CentCom, \"[input]\" at [get_area_name(usr, TRUE)].", usr) + CM.lastTimeUsed = world.time + + // OMG SYNDICATE ...LETTERHEAD + if("MessageSyndicate") + if((authenticated==2) && (obj_flags & EMAGGED)) + if(!checkCCcooldown()) + to_chat(usr, "Arrays recycling. Please stand by.") + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0) + 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, 0) + 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("[usr.real_name] has messaged the Syndicate, \"[input]\" at [get_area_name(usr, TRUE)].", usr) + CM.lastTimeUsed = world.time + + if("RestoreBackup") + to_chat(usr, "Backup routing data restored!") + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0) + 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", 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","commandreport") + CM.lastTimeUsed = world.time + + + // AI interface + if("ai-main") + aicurrmsg = null + aistate = STATE_DEFAULT + if("ai-callshuttle") + 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 + if(tmp_alertlevel > SEC_LEVEL_AMBER) + tmp_alertlevel = SEC_LEVEL_AMBER //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("[usr.real_name] has changed the security level to [security_level] from [src] at [get_area_name(usr, TRUE)].", usr) + 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("[usr.real_name] enabled emergency maintenance access.", usr) + 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("[usr.real_name] disabled emergency maintenance access.", usr) + 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 + SSshuttle.shuttle_purchase_requirements_met |= "emagged" + if(authenticated == 1) + authenticated = 2 + to_chat(user, "You scramble the communication routing circuits!") + playsound(src, 'sound/machines/terminal_alert.ogg', 50, 0) + return TRUE + +/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_zero(num2text(timeleft % 60), 2)]" + + + 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/cross_servers_count = length(CONFIG_GET(keyed_list/cross_server)) + if(cross_servers_count) + dat += "
    \[ Send a message to [cross_servers_count == 1 ? "an " : ""]allied station[cross_servers_count > 1 ? "s" : ""] \]" + 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, 0) + if(STATE_CANCELSHUTTLE) + dat += get_cancel_shuttle_form() + playsound(src, 'sound/machines/terminal_prompt.ogg', 50, 0) + 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, 0) + 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, 0) + 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 += "Amber
    " + 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, 0) + 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) + dat += "Budget: [SSshuttle.points] 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 += "Amber
    " + 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)) + return + SScommunications.make_announcement(user, is_silicon, input) + deadchat_broadcast("[user.real_name] made an priority announcement from [get_area_name(usr, TRUE)].", user) + +/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 diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index c11f7d9edc..f6d0fcfc0d 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -1,210 +1,210 @@ -#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 Security"] = 10 - jobs["Warden"] = 11 - jobs["Security Officer"] = 12 - jobs["Detective"] = 13 - jobs["Chief Medical Officer"] = 20 - jobs["Chemist"] = 21 - jobs["Geneticist"] = 22 - jobs["Virologist"] = 23 - jobs["Medical Doctor"] = 24 - jobs["Research Director"] = 30 - jobs["Scientist"] = 31 - jobs["Roboticist"] = 32 - jobs["Chief Engineer"] = 40 - jobs["Station Engineer"] = 41 - jobs["Atmospheric Technician"] = 42 - jobs["Quartermaster"] = 50 - jobs["Shaft Miner"] = 51 - jobs["Cargo Technician"] = 52 - jobs["Head of Personnel"] = 60 - 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["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, "crew", "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_damaged = list() - var/list/results_undamaged = 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/totaldam - var/area - var/pos_x - var/pos_y - var/life_status - - for(var/mob/living/carbon/human/H in GLOB.carbon_list) - 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) && istype(H.w_uniform, /obj/item/clothing/under) || nanite_sensors) - U = H.w_uniform - - // Are the suit sensors on? - if (nanite_sensors || ((U.has_sensor > 0) && U.sensor_mode)) - pos = H.z == 0 || (nanite_sensors || 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 (nanite_sensors || U.sensor_mode >= SENSOR_LIVING) - life_status = (!H.stat ? TRUE : FALSE) - else - life_status = null - - if (nanite_sensors || 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) - totaldam = oxydam + toxdam + burndam + brutedam - else - oxydam = null - toxdam = null - burndam = null - brutedam = null - totaldam = 0 - - if (nanite_sensors || 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 - - var/total_list = list("name" = name, "assignment" = assignment, "ijob" = ijob, "life_status" = life_status, "oxydam" = oxydam, "toxdam" = toxdam, "burndam" = burndam, "brutedam" = brutedam, "totaldam" = totaldam, "area" = area, "pos_x" = pos_x, "pos_y" = pos_y, "can_track" = H.can_track(null)) - - if(totaldam) - results_damaged[++results_damaged.len] = total_list - else - results_undamaged[++results_undamaged.len] = total_list - - var/list/returning = sortTim(results_damaged,/proc/damage_compare) + sortTim(results_undamaged,/proc/ijob_compare) - - data_by_z["[z]"] = returning - last_update["[z]"] = world.time - - return returning - -/proc/ijob_compare(list/a,list/b) - return a["ijob"] - b["ijob"] - -/proc/damage_compare(list/a,list/b) - return b["totaldam"] - a["totaldam"] - -/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"]) - +#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 Security"] = 10 + jobs["Warden"] = 11 + jobs["Security Officer"] = 12 + jobs["Detective"] = 13 + jobs["Chief Medical Officer"] = 20 + jobs["Chemist"] = 21 + jobs["Geneticist"] = 22 + jobs["Virologist"] = 23 + jobs["Medical Doctor"] = 24 + jobs["Research Director"] = 30 + jobs["Scientist"] = 31 + jobs["Roboticist"] = 32 + jobs["Chief Engineer"] = 40 + jobs["Station Engineer"] = 41 + jobs["Atmospheric Technician"] = 42 + jobs["Quartermaster"] = 50 + jobs["Shaft Miner"] = 51 + jobs["Cargo Technician"] = 52 + jobs["Head of Personnel"] = 60 + 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["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, "crew", "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_damaged = list() + var/list/results_undamaged = 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/totaldam + var/area + var/pos_x + var/pos_y + var/life_status + + for(var/mob/living/carbon/human/H in GLOB.carbon_list) + 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) && istype(H.w_uniform, /obj/item/clothing/under) || nanite_sensors) + U = H.w_uniform + + // Are the suit sensors on? + if (nanite_sensors || ((U.has_sensor > 0) && U.sensor_mode)) + pos = H.z == 0 || (nanite_sensors || 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 (nanite_sensors || U.sensor_mode >= SENSOR_LIVING) + life_status = (!H.stat ? TRUE : FALSE) + else + life_status = null + + if (nanite_sensors || 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) + totaldam = oxydam + toxdam + burndam + brutedam + else + oxydam = null + toxdam = null + burndam = null + brutedam = null + totaldam = 0 + + if (nanite_sensors || 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 + + var/total_list = list("name" = name, "assignment" = assignment, "ijob" = ijob, "life_status" = life_status, "oxydam" = oxydam, "toxdam" = toxdam, "burndam" = burndam, "brutedam" = brutedam, "totaldam" = totaldam, "area" = area, "pos_x" = pos_x, "pos_y" = pos_y, "can_track" = H.can_track(null)) + + if(totaldam) + results_damaged[++results_damaged.len] = total_list + else + results_undamaged[++results_undamaged.len] = total_list + + var/list/returning = sortTim(results_damaged,/proc/damage_compare) + sortTim(results_undamaged,/proc/ijob_compare) + + data_by_z["[z]"] = returning + last_update["[z]"] = world.time + + return returning + +/proc/ijob_compare(list/a,list/b) + return a["ijob"] - b["ijob"] + +/proc/damage_compare(list/a,list/b) + return b["totaldam"] - a["totaldam"] + +/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 \ No newline at end of file diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm index 25b82ac143..fa6d4327a4 100644 --- a/code/game/machinery/computer/law.dm +++ b/code/game/machinery/computer/law.dm @@ -1,72 +1,72 @@ - - -/obj/machinery/computer/upload - var/mob/living/silicon/current = null //The target of future law uploads - icon_screen = "command" - -/obj/machinery/computer/upload/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/aiModule)) - var/obj/item/aiModule/M = O - if(src.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 0 - return 1 - -/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) - src.current = select_active_ai(user) - - if (!src.current) - to_chat(user, "No active AIs detected!") - else - to_chat(user, "[src.current.name] selected for law changes.") - -/obj/machinery/computer/upload/ai/can_upload_to(mob/living/silicon/ai/A) - if(!A || !isAI(A)) - return 0 - if(A.control_disabled) - return 0 - 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) - src.current = select_active_free_borg(user) - - if(!src.current) - to_chat(user, "No active unslaved cyborgs detected!") - else - to_chat(user, "[src.current.name] selected for law changes.") - -/obj/machinery/computer/upload/borg/can_upload_to(mob/living/silicon/robot/B) - if(!B || !iscyborg(B)) - return 0 - if(B.scrambledcodes || B.emagged) - return 0 + + +/obj/machinery/computer/upload + var/mob/living/silicon/current = null //The target of future law uploads + icon_screen = "command" + +/obj/machinery/computer/upload/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/aiModule)) + var/obj/item/aiModule/M = O + if(src.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 0 + return 1 + +/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) + src.current = select_active_ai(user) + + if (!src.current) + to_chat(user, "No active AIs detected!") + else + to_chat(user, "[src.current.name] selected for law changes.") + +/obj/machinery/computer/upload/ai/can_upload_to(mob/living/silicon/ai/A) + if(!A || !isAI(A)) + return 0 + if(A.control_disabled) + return 0 + 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) + src.current = select_active_free_borg(user) + + if(!src.current) + to_chat(user, "No active unslaved cyborgs detected!") + else + to_chat(user, "[src.current.name] selected for law changes.") + +/obj/machinery/computer/upload/borg/can_upload_to(mob/living/silicon/robot/B) + if(!B || !iscyborg(B)) + return 0 + if(B.scrambledcodes || B.emagged) + return 0 return ..() \ No newline at end of file diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index 33de8bfce2..29055e87c3 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -1,585 +1,585 @@ - - -/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 (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 += "" - dat += "" - dat += "" //(per disease info placed in log/comment section) - 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"]] 

    Allergies:

     [active2.fields["alg"]] 
    Details: [active2.fields["alg_d"]] 

    Current Diseases:

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

    Important Notes:

     [active2.fields["notes"]] 

    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((!isnull(M.reagent_glass)) && M.use_beaker) - bdat += "Reservoir: \[[M.reagent_glass.reagents.total_volume]/[M.reagent_glass.reagents.maximum_volume]\]
    " - else - bdat += "Using Internal Synthesizer.
    " - - 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("hh:mm:ss"), time2text(world.realtime, "MMM DD"), GLOB.year_integer, 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, 1) - 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(!(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" - clockwork = TRUE //it'd look weird - 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 (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 += "" + dat += "" + dat += "" //(per disease info placed in log/comment section) + 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"]] 

    Allergies:

     [active2.fields["alg"]] 
    Details: [active2.fields["alg_d"]] 

    Current Diseases:

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

    Important Notes:

     [active2.fields["notes"]] 

    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((!isnull(M.reagent_glass)) && M.use_beaker) + bdat += "Reservoir: \[[M.reagent_glass.reagents.total_volume]/[M.reagent_glass.reagents.maximum_volume]\]
    " + else + bdat += "Using Internal Synthesizer.
    " + + 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("hh:mm:ss"), time2text(world.realtime, "MMM DD"), GLOB.year_integer, 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, 1) + 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(!(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" + clockwork = TRUE //it'd look weird + pass_flags = PASSTABLE diff --git a/code/game/machinery/computer/pod.dm b/code/game/machinery/computer/pod.dm index b71f572e68..1388e3c8de 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(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)] - - + +" - 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 = null - -/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(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)] - - + +" + 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 = null + +/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 5c8d189a7f..5573c56386 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -1,165 +1,165 @@ -/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 - var/temp = null - - light_color = LIGHT_COLOR_PINK - -/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R) - if(!istype(R)) - return 0 - if(isAI(user)) - if (R.connected_ai != user) - return 0 - if(iscyborg(user)) - if (R != user) - return 0 - if(R.scrambledcodes) - return 0 - return 1 - -/obj/machinery/computer/robotics/ui_interact(mob/user) - . = ..() - if (src.z > 6) - to_chat(user, "Unable to establish a connection: \black You're too far away from the station!") - return - user.set_machine(src) - var/dat - var/robots = 0 - for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs) - if(!can_control(user, R)) - continue - robots++ - dat += "[R.name] |" - if(R.stat) - dat += " Not Responding |" - else if (!R.canmove) - dat += " Locked Down |" - else - dat += " Operating Normally |" - if (!R.canmove) - else if(R.cell) - dat += " Battery Installed ([R.cell.charge]/[R.cell.maxcharge]) |" - else - dat += " No Cell Installed |" - if(R.module) - dat += " Module Installed ([R.module.name]) |" - else - dat += " No Module Installed |" - if(R.connected_ai) - dat += " Slaved to [R.connected_ai.name] |" - else - dat += " Independent from AI |" - if(issilicon(user) && user != R) - var/mob/living/silicon/S = user - if(is_servant_of_ratvar(S)) - dat += "(Convert) " - else if(S.hack_software && !R.emagged) - dat += "(Hack) " - else if(IsAdminGhost(user) && !R.emagged) - dat += "(Hack) " - dat += "([R.canmove ? "Lockdown" : "Release"]) " - dat += "(Destroy)" - dat += "
    " - - if(!robots) - dat += "No Cyborg Units detected within access parameters." - dat += "
    " - - var/drones = 0 - for(var/mob/living/simple_animal/drone/D in GLOB.drones_list) - if(D.hacked) - continue - drones++ - dat += "[D.name] |" - if(D.stat) - dat += " Not Responding |" - dat += "(Destroy)" - dat += "
    " - - if(!drones) - dat += "No Drone Units detected within access parameters." - - var/datum/browser/popup = new(user, "computer", "Cyborg Control Console", 400, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - return - -/obj/machinery/computer/robotics/Topic(href, href_list) - if(..()) - return - - if (href_list["temp"]) - src.temp = null - - else if (href_list["killbot"]) - if(src.allowed(usr)) - var/mob/living/silicon/robot/R = locate(href_list["killbot"]) in GLOB.silicon_mobs - if(can_control(usr, R)) - var/choice = input("Are you certain you wish to detonate [R.name]?") in list("Confirm", "Abort") - if(choice == "Confirm" && 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.") - - else if (href_list["stopbot"]) - if(src.allowed(usr)) - var/mob/living/silicon/robot/R = locate(href_list["stopbot"]) in GLOB.silicon_mobs - if(can_control(usr, R)) - var/choice = input("Are you certain you wish to [R.canmove ? "lock down" : "release"] [R.name]?") in list("Confirm", "Abort") - if(choice == "Confirm" && can_control(usr, R) && !..()) - message_admins("[ADMIN_LOOKUPFLW(usr)] [R.canmove ? "locked down" : "released"] [key_name(R, R.client)][ADMIN_LOOKUPFLW(R)]!") - log_game("[key_name(usr)] [R.canmove ? "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.") - - else if (href_list["magbot"]) - var/mob/living/silicon/S = usr - if((istype(S) && S.hack_software) || IsAdminGhost(usr)) - var/mob/living/silicon/robot/R = locate(href_list["magbot"]) 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(1) - - else if(href_list["convert"]) - if(isAI(usr) && is_servant_of_ratvar(usr)) - var/mob/living/silicon/robot/R = locate(href_list["convert"]) in GLOB.silicon_mobs - if(istype(R) && !is_servant_of_ratvar(R) && R.connected_ai == usr) - log_game("[key_name(usr)] converted [key_name(R)] using robotic console!") - message_admins("[ADMIN_LOOKUPFLW(usr)] converted cyborg [key_name_admin(R)] using robotic console!") - add_servant_of_ratvar(R) - - else if (href_list["killdrone"]) - if(src.allowed(usr)) - var/mob/living/simple_animal/drone/D = locate(href_list["killdrone"]) 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, 1, D) - s.start() - D.visible_message("\the [D] self destructs!") - D.gib() - - - src.updateUsrDialog() - return +/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 + var/temp = null + + light_color = LIGHT_COLOR_PINK + +/obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R) + if(!istype(R)) + return 0 + if(isAI(user)) + if (R.connected_ai != user) + return 0 + if(iscyborg(user)) + if (R != user) + return 0 + if(R.scrambledcodes) + return 0 + return 1 + +/obj/machinery/computer/robotics/ui_interact(mob/user) + . = ..() + if (src.z > 6) + to_chat(user, "Unable to establish a connection: \black You're too far away from the station!") + return + user.set_machine(src) + var/dat + var/robots = 0 + for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs) + if(!can_control(user, R)) + continue + robots++ + dat += "[R.name] |" + if(R.stat) + dat += " Not Responding |" + else if (!R.canmove) + dat += " Locked Down |" + else + dat += " Operating Normally |" + if (!R.canmove) + else if(R.cell) + dat += " Battery Installed ([R.cell.charge]/[R.cell.maxcharge]) |" + else + dat += " No Cell Installed |" + if(R.module) + dat += " Module Installed ([R.module.name]) |" + else + dat += " No Module Installed |" + if(R.connected_ai) + dat += " Slaved to [R.connected_ai.name] |" + else + dat += " Independent from AI |" + if(issilicon(user) && user != R) + var/mob/living/silicon/S = user + if(is_servant_of_ratvar(S)) + dat += "(Convert) " + else if(S.hack_software && !R.emagged) + dat += "(Hack) " + else if(IsAdminGhost(user) && !R.emagged) + dat += "(Hack) " + dat += "([R.canmove ? "Lockdown" : "Release"]) " + dat += "(Destroy)" + dat += "
    " + + if(!robots) + dat += "No Cyborg Units detected within access parameters." + dat += "
    " + + var/drones = 0 + for(var/mob/living/simple_animal/drone/D in GLOB.drones_list) + if(D.hacked) + continue + drones++ + dat += "[D.name] |" + if(D.stat) + dat += " Not Responding |" + dat += "(Destroy)" + dat += "
    " + + if(!drones) + dat += "No Drone Units detected within access parameters." + + var/datum/browser/popup = new(user, "computer", "Cyborg Control Console", 400, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + return + +/obj/machinery/computer/robotics/Topic(href, href_list) + if(..()) + return + + if (href_list["temp"]) + src.temp = null + + else if (href_list["killbot"]) + if(src.allowed(usr)) + var/mob/living/silicon/robot/R = locate(href_list["killbot"]) in GLOB.silicon_mobs + if(can_control(usr, R)) + var/choice = input("Are you certain you wish to detonate [R.name]?") in list("Confirm", "Abort") + if(choice == "Confirm" && 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.") + + else if (href_list["stopbot"]) + if(src.allowed(usr)) + var/mob/living/silicon/robot/R = locate(href_list["stopbot"]) in GLOB.silicon_mobs + if(can_control(usr, R)) + var/choice = input("Are you certain you wish to [R.canmove ? "lock down" : "release"] [R.name]?") in list("Confirm", "Abort") + if(choice == "Confirm" && can_control(usr, R) && !..()) + message_admins("[ADMIN_LOOKUPFLW(usr)] [R.canmove ? "locked down" : "released"] [key_name(R, R.client)][ADMIN_LOOKUPFLW(R)]!") + log_game("[key_name(usr)] [R.canmove ? "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.") + + else if (href_list["magbot"]) + var/mob/living/silicon/S = usr + if((istype(S) && S.hack_software) || IsAdminGhost(usr)) + var/mob/living/silicon/robot/R = locate(href_list["magbot"]) 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(1) + + else if(href_list["convert"]) + if(isAI(usr) && is_servant_of_ratvar(usr)) + var/mob/living/silicon/robot/R = locate(href_list["convert"]) in GLOB.silicon_mobs + if(istype(R) && !is_servant_of_ratvar(R) && R.connected_ai == usr) + log_game("[key_name(usr)] converted [key_name(R)] using robotic console!") + message_admins("[ADMIN_LOOKUPFLW(usr)] converted cyborg [key_name_admin(R)] using robotic console!") + add_servant_of_ratvar(R) + + else if (href_list["killdrone"]) + if(src.allowed(usr)) + var/mob/living/simple_animal/drone/D = locate(href_list["killdrone"]) 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, 1, D) + s.start() + D.visible_message("\the [D] self destructs!") + D.gib() + + + src.updateUsrDialog() + return diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm index c9f2f28ab1..aabb1fe408 100644 --- a/code/game/machinery/computer/security.dm +++ b/code/game/machinery/computer/security.dm @@ -1,812 +1,812 @@ -/obj/machinery/computer/secure_data//TODO:SANITY - name = "security records console" - desc = "Used to view and edit personnel's security records." - icon_screen = "security" - icon_keyboard = "security_key" - req_one_access = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS) - circuit = /obj/item/circuitboard/computer/secure_data - var/rank = null - var/screen = null - var/datum/data/record/active1 = null - var/datum/data/record/active2 = null - var/temp = null - var/printing = null - var/can_change_id = 0 - var/list/Perp - var/tempname = null - //Sorting Variables - var/sortBy = "name" - var/order = 1 // -1 = Descending - 1 = Ascending - - light_color = LIGHT_COLOR_RED - -/obj/machinery/computer/secure_data/syndie - icon_keyboard = "syndie_key" - -/obj/machinery/computer/secure_data/laptop - name = "security laptop" - desc = "A cheap Nanotrasen security laptop, it functions as a security records console. It's bolted to the table." - icon_state = "laptop" - icon_screen = "seclaptop" - icon_keyboard = "laptop_key" - clockwork = TRUE //it'd look weird - pass_flags = PASSTABLE - -//Someone needs to break down the dat += into chunks instead of long ass lines. -/obj/machinery/computer/secure_data/ui_interact(mob/user) - . = ..() - if(isliving(user)) - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - if(src.z > 6) - to_chat(user, "Unable to establish a connection: \black You're too far away from the station!") - return - var/dat - - if(temp) - dat = "[temp]

    Clear Screen" - else - dat = "" - if(authenticated) - switch(screen) - if(1) - - //body tag start + onload and onkeypress (onkeyup) javascript event calls - dat += "" - //search bar javascript - dat += {" - - - - - - - - "} - dat += {" -

    "} - dat += "New Record
    " - //search bar - dat += {" - - - - -
    - Search: -
    - "} - dat += {" -

    - - - - -
    Records:
    - - - - - - - - - -"} - if(!isnull(GLOB.data_core.general)) - for(var/datum/data/record/R in sortRecord(GLOB.data_core.general, sortBy, order)) - var/crimstat = "" - for(var/datum/data/record/E in GLOB.data_core.security) - if((E.fields["name"] == R.fields["name"]) && (E.fields["id"] == R.fields["id"])) - crimstat = E.fields["criminal"] - var/background - switch(crimstat) - if("*Arrest*") - background = "'background-color:#990000;'" - if("Incarcerated") - background = "'background-color:#CD6500;'" - if("Paroled") - background = "'background-color:#CD6500;'" - if("Discharged") - background = "'background-color:#006699;'" - if("None") - background = "'background-color:#4F7529;'" - if("") - background = "''" //"'background-color:#FFFFFF;'" - crimstat = "No Record." - dat += "" - dat += text("", R.fields["name"], R.fields["id"], R.fields["rank"], R.fields["fingerprint"], R.fields["name"]) - dat += text("", R.fields["id"]) - dat += text("", R.fields["rank"]) - dat += text("", R.fields["fingerprint"]) - dat += text("", crimstat) - dat += {" -
    NameIDRankFingerprintsCriminal Status
    [][][][][]
    - -
    "} - dat += "Record Maintenance

    " - dat += "{Log Out}" - if(2) - dat += "Records Maintenance
    " - dat += "
    Delete All Records

    Back" - if(3) - dat += "Security Record
    " - if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1)) - 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 += {" - - - -
    Name: [active1.fields["name"]] 
    ID: [active1.fields["id"]] 
    Gender: [active1.fields["gender"]] 
    Age: [active1.fields["age"]] 
    Species: [active1.fields["species"]] 
    Rank: [active1.fields["rank"]] 
    Fingerprint: [active1.fields["fingerprint"]] 
    Physical Status: [active1.fields["p_stat"]] 
    Mental Status: [active1.fields["m_stat"]] 
    -

    - Print photo
    - Update front photo

    - Print photo
    - Update side photo
    -
    "} - else - dat += "
    General Record Lost!
    " - if((istype(active2, /datum/data/record) && GLOB.data_core.security.Find(active2))) - dat += "Security Data" - dat += "
    Criminal Status: [active2.fields["criminal"]]" - dat += "

    Minor Crimes: Add New" - - - dat +={" - - - - - - - "} - for(var/datum/data/crime/c in active2.fields["mi_crim"]) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
    CrimeDetailsAuthorTime AddedDel
    [c.crimeName][c.crimeDetails][c.author][c.time]\[X\]
    " - - - dat += "
    Major Crimes: Add New" - - dat +={" - - - - - - - "} - for(var/datum/data/crime/c in active2.fields["ma_crim"]) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
    CrimeDetailsAuthorTime AddedDel
    [c.crimeName][c.crimeDetails][c.author][c.time]\[X\]
    " - - dat += "
    \nImportant Notes:
    \n\t [active2.fields["notes"]] " - dat += "

    Comments/Log
    " - var/counter = 1 - while(active2.fields[text("com_[]", counter)]) - dat += (active2.fields[text("com_[]", counter)] + "
    ") - if(active2.fields[text("com_[]", counter)] != "Deleted") - dat += text("Delete Entry

    ", counter) - counter++ - dat += "Add Entry

    " - dat += "Delete Record (Security Only)
    " - else - dat += "Security Record Lost!
    " - dat += "New Security Record

    " - dat += "Delete Record (ALL)
    Print Record
    Print Wanted Poster
    Back

    " - dat += "{Log Out}" - else - else - dat += "{Log In}" - var/datum/browser/popup = new(user, "secure_rec", "Security Records Console", 600, 400) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - return - -/*Revised /N -I can't be bothered to look more of the actual code outside of switch but that probably needs revising too. -What a mess.*/ -/obj/machinery/computer/secure_data/Topic(href, href_list) - . = ..() - if(.) - return . - if(!( GLOB.data_core.general.Find(active1) )) - active1 = null - if(!( GLOB.data_core.security.Find(active2) )) - active2 = null - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr) || IsAdminGhost(usr)) - usr.set_machine(src) - switch(href_list["choice"]) -// SORTING! - if("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) -//BASIC FUNCTIONS - if("Clear Screen") - temp = null - - if("Return") - screen = 1 - active1 = null - active2 = null - - if("Log Out") - authenticated = null - screen = null - active1 = null - active2 = null - playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) - - if("Log In") - var/mob/M = usr - var/obj/item/card/id/I = M.get_idcard(TRUE) - if(issilicon(M)) - var/mob/living/silicon/borg = M - active1 = null - active2 = null - authenticated = borg.name - rank = "AI" - screen = 1 - else if(IsAdminGhost(M)) - active1 = null - active2 = null - authenticated = M.client.holder.admin_signature - rank = "Central Command" - screen = 1 - else if(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) - -//RECORD FUNCTIONS - if("Record Maintenance") - screen = 2 - active1 = null - active2 = null - - if("Browse Record") - var/datum/data/record/R = locate(href_list["d_rec"]) in GLOB.data_core.general - if(!R) - temp = "Record Not Found!" - else - active1 = active2 = R - for(var/datum/data/record/E in GLOB.data_core.security) - if((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"])) - active2 = E - screen = 3 - - - if("Print Record") - if(!( printing )) - printing = 1 - GLOB.data_core.securityPrintCount++ - playsound(loc, 'sound/items/poster_being_created.ogg', 100, 1) - sleep(30) - var/obj/item/paper/P = new /obj/item/paper( loc ) - P.info = "
    Security Record - (SR-[GLOB.data_core.securityPrintCount])

    " - if((istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1))) - 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((istype(active2, /datum/data/record) && GLOB.data_core.security.Find(active2))) - P.info += text("
    \n
    Security Data

    \nCriminal Status: []", active2.fields["criminal"]) - - P.info += "
    \n
    \nMinor Crimes:
    \n" - P.info +={" - - - - - -"} - for(var/datum/data/crime/c in active2.fields["mi_crim"]) - P.info += "" - P.info += "" - P.info += "" - P.info += "" - P.info += "" - P.info += "
    CrimeDetailsAuthorTime Added
    [c.crimeName][c.crimeDetails][c.author][c.time]
    " - - P.info += "
    \nMajor Crimes:
    \n" - P.info +={" - - - - - -"} - for(var/datum/data/crime/c in active2.fields["ma_crim"]) - P.info += "" - P.info += "" - P.info += "" - P.info += "" - P.info += "" - P.info += "
    CrimeDetailsAuthorTime Added
    [c.crimeName][c.crimeDetails][c.author][c.time]
    " - - - P.info += text("
    \nImportant Notes:
    \n\t[]
    \n
    \n
    Comments/Log

    ", active2.fields["notes"]) - var/counter = 1 - while(active2.fields[text("com_[]", counter)]) - P.info += text("[]
    ", active2.fields[text("com_[]", counter)]) - counter++ - P.name = text("SR-[] '[]'", GLOB.data_core.securityPrintCount, active1.fields["name"]) - else - P.info += "Security Record Lost!
    " - P.name = text("SR-[] '[]'", GLOB.data_core.securityPrintCount, "Record Lost") - P.info += "" - P.update_icon() - printing = null - if("Print Poster") - if(!( printing )) - var/wanted_name = stripped_input(usr, "Please enter an alias for the criminal:", "Print Wanted Poster", active1.fields["name"]) - if(wanted_name) - var/default_description = "A poster declaring [wanted_name] to be a dangerous individual, wanted by Nanotrasen. Report any sightings to security immediately." - var/list/major_crimes = active2.fields["ma_crim"] - var/list/minor_crimes = active2.fields["mi_crim"] - if(major_crimes.len + minor_crimes.len) - default_description += "\n[wanted_name] is wanted for the following crimes:\n" - if(minor_crimes.len) - default_description += "\nMinor Crimes:" - for(var/datum/data/crime/c in active2.fields["mi_crim"]) - default_description += "\n[c.crimeName]\n" - default_description += "[c.crimeDetails]\n" - if(major_crimes.len) - default_description += "\nMajor Crimes:" - for(var/datum/data/crime/c in active2.fields["ma_crim"]) - default_description += "\n[c.crimeName]\n" - default_description += "[c.crimeDetails]\n" - - var/info = stripped_multiline_input(usr, "Please input a description for the poster:", "Print Wanted Poster", default_description, null) - if(info) - playsound(loc, 'sound/items/poster_being_created.ogg', 100, 1) - printing = 1 - sleep(30) - if((istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1)))//make sure the record still exists. - var/obj/item/photo/photo = active1.fields["photo_front"] - new /obj/item/poster/wanted(loc, photo.picture.picture_image, wanted_name, info) - printing = 0 - -//RECORD DELETE - if("Delete All Records") - temp = "" - temp += "Are you sure you wish to delete all Security records?
    " - temp += "Yes
    " - temp += "No" - - if("Purge All Records") - investigate_log("[key_name(usr)] has purged all the security records.", INVESTIGATE_RECORDS) - for(var/datum/data/record/R in GLOB.data_core.security) - qdel(R) - GLOB.data_core.security.Cut() - temp = "All Security records deleted." - - if("Add Entry") - if(!( istype(active2, /datum/data/record) )) - return - var/a2 = active2 - var/t1 = stripped_multiline_input("Add Comment:", "Secure. records", null, null) - if(!canUseSecurityRecordsConsole(usr, t1, null, a2)) - return - var/counter = 1 - while(active2.fields[text("com_[]", counter)]) - counter++ - active2.fields[text("com_[]", counter)] = text("Made by [] ([]) on [] [], []
    []", src.authenticated, src.rank, STATION_TIME_TIMESTAMP("hh:mm:ss"), time2text(world.realtime, "MMM DD"), GLOB.year_integer, t1) - - if("Delete Record (ALL)") - if(active1) - temp = "
    Are you sure you wish to delete the record (ALL)?
    " - temp += "Yes
    " - temp += "No" - - if("Delete Record (Security)") - if(active2) - temp = "
    Are you sure you wish to delete the record (Security Portion Only)?
    " - temp += "Yes
    " - temp += "No" - - if("Delete Entry") - if((istype(active2, /datum/data/record) && active2.fields[text("com_[]", href_list["del_c"])])) - active2.fields[text("com_[]", href_list["del_c"])] = "Deleted" -//RECORD CREATE - if("New Record (Security)") - 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("Security Record #[]", R.fields["id"]) - R.fields["criminal"] = "None" - R.fields["mi_crim"] = list() - R.fields["ma_crim"] = list() - R.fields["notes"] = "No notes." - GLOB.data_core.security += R - active2 = R - screen = 3 - - if("New Record (General)") - //General Record - var/datum/data/record/G = new /datum/data/record() - G.fields["name"] = "New Record" - G.fields["id"] = "[num2hex(rand(1, 1.6777215E7), 6)]" - G.fields["rank"] = "Unassigned" - G.fields["gender"] = "Male" - G.fields["age"] = "Unknown" - G.fields["species"] = "Human" - G.fields["photo_front"] = new /icon() - G.fields["photo_side"] = new /icon() - G.fields["fingerprint"] = "?????" - G.fields["p_stat"] = "Active" - G.fields["m_stat"] = "Stable" - GLOB.data_core.general += G - active1 = G - - //Security 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("Security Record #[]", R.fields["id"]) - R.fields["criminal"] = "None" - R.fields["mi_crim"] = list() - R.fields["ma_crim"] = list() - R.fields["notes"] = "No notes." - GLOB.data_core.security += R - active2 = R - - //Medical Record - var/datum/data/record/M = new /datum/data/record() - M.fields["id"] = active1.fields["id"] - M.fields["name"] = active1.fields["name"] - M.fields["blood_type"] = "?" - M.fields["b_dna"] = "?????" - M.fields["mi_dis"] = "None" - M.fields["mi_dis_d"] = "No minor disabilities have been declared." - M.fields["ma_dis"] = "None" - M.fields["ma_dis_d"] = "No major disabilities have been diagnosed." - M.fields["alg"] = "None" - M.fields["alg_d"] = "No allergies have been detected in this patient." - M.fields["cdi"] = "None" - M.fields["cdi_d"] = "No diseases have been diagnosed at the moment." - M.fields["notes"] = "No notes." - GLOB.data_core.medical += M - - - -//FIELD FUNCTIONS - if("Edit Field") - var/a1 = active1 - var/a2 = active2 - - switch(href_list["field"]) - if("name") - if(istype(active1, /datum/data/record) || istype(active2, /datum/data/record)) - var/t1 = copytext(sanitize(input("Please input name:", "Secure. records", active1.fields["name"], null) as text),1,MAX_MESSAGE_LEN) - if(!canUseSecurityRecordsConsole(usr, t1, a1)) - return - if(istype(active1, /datum/data/record)) - active1.fields["name"] = t1 - if(istype(active2, /datum/data/record)) - active2.fields["name"] = t1 - if("id") - if(istype(active2, /datum/data/record) || istype(active1, /datum/data/record)) - var/t1 = stripped_input(usr, "Please input id:", "Secure. records", active1.fields["id"], null) - if(!canUseSecurityRecordsConsole(usr, t1, a1)) - return - if(istype(active1, /datum/data/record)) - active1.fields["id"] = t1 - if(istype(active2, /datum/data/record)) - active2.fields["id"] = t1 - if("fingerprint") - if(istype(active1, /datum/data/record)) - var/t1 = stripped_input(usr, "Please input fingerprint hash:", "Secure. records", active1.fields["fingerprint"], null) - if(!canUseSecurityRecordsConsole(usr, t1, a1)) - return - active1.fields["fingerprint"] = t1 - if("gender") - if(istype(active1, /datum/data/record)) - 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(istype(active1, /datum/data/record)) - var/t1 = input("Please input age:", "Secure. records", active1.fields["age"], null) as num - if(!canUseSecurityRecordsConsole(usr, "age", a1)) - return - active1.fields["age"] = t1 - if("species") - if(istype(active1, /datum/data/record)) - var/t1 = input("Select a species", "Species Selection") as null|anything in GLOB.roundstart_races - if(!canUseSecurityRecordsConsole(usr, t1, a1)) - return - active1.fields["species"] = t1 - if("show_photo_front") - 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("upd_photo_front") - var/obj/item/photo/photo = get_photo(usr) - if(photo) - qdel(active1.fields["photo_front"]) - //Lets center it to a 32x32. - var/icon/I = photo.picture.picture_image - var/w = I.Width() - var/h = I.Height() - var/dw = w - 32 - var/dh = w - 32 - I.Crop(dw/2, dh/2, w - dw/2, h - dh/2) - active1.fields["photo_front"] = photo - if("print_photo_front") - if(active1.fields["photo_front"]) - if(istype(active1.fields["photo_front"], /obj/item/photo)) - var/obj/item/photo/P = active1.fields["photo_front"] - print_photo(P.picture.picture_image, active1.fields["name"]) - if("show_photo_side") - 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) - if("upd_photo_side") - var/obj/item/photo/photo = get_photo(usr) - if(photo) - qdel(active1.fields["photo_side"]) - //Lets center it to a 32x32. - var/icon/I = photo.picture.picture_image - var/w = I.Width() - var/h = I.Height() - var/dw = w - 32 - var/dh = w - 32 - I.Crop(dw/2, dh/2, w - dw/2, h - dh/2) - active1.fields["photo_side"] = photo - if("print_photo_side") - if(active1.fields["photo_side"]) - if(istype(active1.fields["photo_side"], /obj/item/photo)) - var/obj/item/photo/P = active1.fields["photo_side"] - print_photo(P.picture.picture_image, active1.fields["name"]) - if("mi_crim_add") - if(istype(active1, /datum/data/record)) - var/t1 = stripped_input(usr, "Please input minor crime names:", "Secure. records", "", null) - var/t2 = stripped_input(usr, "Please input minor crime details:", "Secure. records", "", null) - if(!canUseSecurityRecordsConsole(usr, t1, null, a2)) - return - var/crime = GLOB.data_core.createCrimeEntry(t1, t2, authenticated, STATION_TIME_TIMESTAMP("hh:mm:ss")) - GLOB.data_core.addMinorCrime(active1.fields["id"], crime) - investigate_log("New Minor Crime: [t1]: [t2] | Added to [active1.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) - if("mi_crim_delete") - if(istype(active1, /datum/data/record)) - if(href_list["cdataid"]) - if(!canUseSecurityRecordsConsole(usr, "delete", null, a2)) - return - GLOB.data_core.removeMinorCrime(active1.fields["id"], href_list["cdataid"]) - if("ma_crim_add") - if(istype(active1, /datum/data/record)) - var/t1 = stripped_input(usr, "Please input major crime names:", "Secure. records", "", null) - var/t2 = stripped_input(usr, "Please input major crime details:", "Secure. records", "", null) - if(!canUseSecurityRecordsConsole(usr, t1, null, a2)) - return - var/crime = GLOB.data_core.createCrimeEntry(t1, t2, authenticated, STATION_TIME_TIMESTAMP("hh:mm:ss")) - GLOB.data_core.addMajorCrime(active1.fields["id"], crime) - investigate_log("New Major Crime: [t1]: [t2] | Added to [active1.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) - if("ma_crim_delete") - if(istype(active1, /datum/data/record)) - if(href_list["cdataid"]) - if(!canUseSecurityRecordsConsole(usr, "delete", null, a2)) - return - GLOB.data_core.removeMajorCrime(active1.fields["id"], href_list["cdataid"]) - if("notes") - if(istype(active2, /datum/data/record)) - var/t1 = stripped_input(usr, "Please summarize notes:", "Secure. records", active2.fields["notes"], null) - if(!canUseSecurityRecordsConsole(usr, t1, null, a2)) - return - active2.fields["notes"] = t1 - if("criminal") - if(istype(active2, /datum/data/record)) - temp = "
    Criminal Status:
    " - temp += "" - if("rank") - var/list/L = list( "Head of Personnel", "Captain", "AI", "Central Command" ) - //This was so silly before the change. Now it actually works without beating your head against the keyboard. /N - if((istype(active1, /datum/data/record) && L.Find(rank))) - temp = "
    Rank:
    " - temp += "
      " - for(var/rank in get_all_jobs()) - temp += "
    • [rank]
    • " - temp += "
    " - else - alert(usr, "You do not have the required rank to do this!") -//TEMPORARY MENU FUNCTIONS - else//To properly clear as per clear screen. - temp=null - switch(href_list["choice"]) - if("Change Rank") - if(active1) - active1.fields["rank"] = href_list["rank"] - if(href_list["rank"] in get_all_jobs()) - active1.fields["real_rank"] = href_list["real_rank"] - - if("Change Criminal Status") - if(active2) - var/old_field = active2.fields["criminal"] - switch(href_list["criminal2"]) - if("none") - active2.fields["criminal"] = "None" - if("arrest") - active2.fields["criminal"] = "*Arrest*" - if("incarcerated") - active2.fields["criminal"] = "Incarcerated" - if("paroled") - active2.fields["criminal"] = "Paroled" - if("released") - active2.fields["criminal"] = "Discharged" - investigate_log("[active1.fields["name"]] has been set from [old_field] to [active2.fields["criminal"]] by [key_name(usr)].", INVESTIGATE_RECORDS) - for(var/mob/living/carbon/human/H in GLOB.carbon_list) - H.sec_hud_set_security_status() - if("Delete Record (Security) Execute") - investigate_log("[key_name(usr)] has deleted the security records for [active1.fields["name"]].", INVESTIGATE_RECORDS) - if(active2) - qdel(active2) - active2 = null - - if("Delete Record (ALL) Execute") - if(active1) - investigate_log("[key_name(usr)] has deleted all records for [active1.fields["name"]].", INVESTIGATE_RECORDS) - for(var/datum/data/record/R in GLOB.data_core.medical) - if((R.fields["name"] == active1.fields["name"] || R.fields["id"] == active1.fields["id"])) - qdel(R) - break - qdel(active1) - active1 = null - - if(active2) - qdel(active2) - active2 = null - else - temp = "This function does not appear to be working at the moment. Our apologies." - - add_fingerprint(usr) - updateUsrDialog() - return - -/obj/machinery/computer/secure_data/proc/get_photo(mob/user) - var/obj/item/photo/P = null - if(issilicon(user)) - var/mob/living/silicon/tempAI = user - var/datum/picture/selection = tempAI.GetPhoto(user) - if(selection) - P = new(null, selection) - else if(istype(user.get_active_held_item(), /obj/item/photo)) - P = user.get_active_held_item() - return P - -/obj/machinery/computer/secure_data/proc/print_photo(icon/temp, person_name) - if (printing) - return - printing = TRUE - sleep(20) - var/obj/item/photo/P = new/obj/item/photo(drop_location()) - var/datum/picture/toEmbed = new(name = person_name, desc = "The photo on file for [person_name].", image = temp) - P.set_picture(toEmbed, TRUE, TRUE) - P.pixel_x = rand(-10, 10) - P.pixel_y = rand(-10, 10) - printing = FALSE - -/obj/machinery/computer/secure_data/emp_act(severity) - . = ..() - - if(stat & (BROKEN|NOPOWER) || . & EMP_PROTECT_SELF) - return - - for(var/datum/data/record/R in GLOB.data_core.security) - if(prob(10/severity)) - switch(rand(1,8)) - if(1) - if(prob(10)) - R.fields["name"] = "[pick(lizard_name(MALE),lizard_name(FEMALE))]" - else - R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]" - if(2) - R.fields["gender"] = pick("Male", "Female", "Other") - if(3) - R.fields["age"] = rand(5, 85) - if(4) - R.fields["criminal"] = pick("None", "*Arrest*", "Incarcerated", "Paroled", "Discharged") - if(5) - R.fields["p_stat"] = pick("*Unconscious*", "Active", "Physically Unfit") - if(6) - R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable") - if(7) - R.fields["species"] = pick(GLOB.roundstart_races) - if(8) - var/datum/data/record/G = pick(GLOB.data_core.general) - R.fields["photo_front"] = G.fields["photo_front"] - R.fields["photo_side"] = G.fields["photo_side"] - continue - - else if(prob(1)) - qdel(R) - continue - -/obj/machinery/computer/secure_data/proc/canUseSecurityRecordsConsole(mob/user, message1 = 0, record1, record2) - if(user) - if(authenticated) - if(user.canUseTopic(src, !issilicon(user))) - if(!trim(message1)) - return 0 - if(!record1 || record1 == active1) - if(!record2 || record2 == active2) - return 1 - return 0 - +/obj/machinery/computer/secure_data//TODO:SANITY + name = "security records console" + desc = "Used to view and edit personnel's security records." + icon_screen = "security" + icon_keyboard = "security_key" + req_one_access = list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS) + circuit = /obj/item/circuitboard/computer/secure_data + var/rank = null + var/screen = null + var/datum/data/record/active1 = null + var/datum/data/record/active2 = null + var/temp = null + var/printing = null + var/can_change_id = 0 + var/list/Perp + var/tempname = null + //Sorting Variables + var/sortBy = "name" + var/order = 1 // -1 = Descending - 1 = Ascending + + light_color = LIGHT_COLOR_RED + +/obj/machinery/computer/secure_data/syndie + icon_keyboard = "syndie_key" + +/obj/machinery/computer/secure_data/laptop + name = "security laptop" + desc = "A cheap Nanotrasen security laptop, it functions as a security records console. It's bolted to the table." + icon_state = "laptop" + icon_screen = "seclaptop" + icon_keyboard = "laptop_key" + clockwork = TRUE //it'd look weird + pass_flags = PASSTABLE + +//Someone needs to break down the dat += into chunks instead of long ass lines. +/obj/machinery/computer/secure_data/ui_interact(mob/user) + . = ..() + if(isliving(user)) + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + if(src.z > 6) + to_chat(user, "Unable to establish a connection: \black You're too far away from the station!") + return + var/dat + + if(temp) + dat = "[temp]

    Clear Screen" + else + dat = "" + if(authenticated) + switch(screen) + if(1) + + //body tag start + onload and onkeypress (onkeyup) javascript event calls + dat += "" + //search bar javascript + dat += {" + + + + + + + + "} + dat += {" +

    "} + dat += "New Record
    " + //search bar + dat += {" + + + + +
    + Search: +
    + "} + dat += {" +

    + + + + +
    Records:
    + + + + + + + + + +"} + if(!isnull(GLOB.data_core.general)) + for(var/datum/data/record/R in sortRecord(GLOB.data_core.general, sortBy, order)) + var/crimstat = "" + for(var/datum/data/record/E in GLOB.data_core.security) + if((E.fields["name"] == R.fields["name"]) && (E.fields["id"] == R.fields["id"])) + crimstat = E.fields["criminal"] + var/background + switch(crimstat) + if("*Arrest*") + background = "'background-color:#990000;'" + if("Incarcerated") + background = "'background-color:#CD6500;'" + if("Paroled") + background = "'background-color:#CD6500;'" + if("Discharged") + background = "'background-color:#006699;'" + if("None") + background = "'background-color:#4F7529;'" + if("") + background = "''" //"'background-color:#FFFFFF;'" + crimstat = "No Record." + dat += "" + dat += text("", R.fields["name"], R.fields["id"], R.fields["rank"], R.fields["fingerprint"], R.fields["name"]) + dat += text("", R.fields["id"]) + dat += text("", R.fields["rank"]) + dat += text("", R.fields["fingerprint"]) + dat += text("", crimstat) + dat += {" +
    NameIDRankFingerprintsCriminal Status
    [][][][][]
    + +
    "} + dat += "Record Maintenance

    " + dat += "{Log Out}" + if(2) + dat += "Records Maintenance
    " + dat += "
    Delete All Records

    Back" + if(3) + dat += "Security Record
    " + if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1)) + 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 += {" + + + +
    Name: [active1.fields["name"]] 
    ID: [active1.fields["id"]] 
    Gender: [active1.fields["gender"]] 
    Age: [active1.fields["age"]] 
    Species: [active1.fields["species"]] 
    Rank: [active1.fields["rank"]] 
    Fingerprint: [active1.fields["fingerprint"]] 
    Physical Status: [active1.fields["p_stat"]] 
    Mental Status: [active1.fields["m_stat"]] 
    +

    + Print photo
    + Update front photo

    + Print photo
    + Update side photo
    +
    "} + else + dat += "
    General Record Lost!
    " + if((istype(active2, /datum/data/record) && GLOB.data_core.security.Find(active2))) + dat += "Security Data" + dat += "
    Criminal Status: [active2.fields["criminal"]]" + dat += "

    Minor Crimes: Add New" + + + dat +={" + + + + + + + "} + for(var/datum/data/crime/c in active2.fields["mi_crim"]) + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
    CrimeDetailsAuthorTime AddedDel
    [c.crimeName][c.crimeDetails][c.author][c.time]\[X\]
    " + + + dat += "
    Major Crimes: Add New" + + dat +={" + + + + + + + "} + for(var/datum/data/crime/c in active2.fields["ma_crim"]) + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
    CrimeDetailsAuthorTime AddedDel
    [c.crimeName][c.crimeDetails][c.author][c.time]\[X\]
    " + + dat += "
    \nImportant Notes:
    \n\t [active2.fields["notes"]] " + dat += "

    Comments/Log
    " + var/counter = 1 + while(active2.fields[text("com_[]", counter)]) + dat += (active2.fields[text("com_[]", counter)] + "
    ") + if(active2.fields[text("com_[]", counter)] != "Deleted") + dat += text("Delete Entry

    ", counter) + counter++ + dat += "Add Entry

    " + dat += "Delete Record (Security Only)
    " + else + dat += "Security Record Lost!
    " + dat += "New Security Record

    " + dat += "Delete Record (ALL)
    Print Record
    Print Wanted Poster
    Back

    " + dat += "{Log Out}" + else + else + dat += "{Log In}" + var/datum/browser/popup = new(user, "secure_rec", "Security Records Console", 600, 400) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + return + +/*Revised /N +I can't be bothered to look more of the actual code outside of switch but that probably needs revising too. +What a mess.*/ +/obj/machinery/computer/secure_data/Topic(href, href_list) + . = ..() + if(.) + return . + if(!( GLOB.data_core.general.Find(active1) )) + active1 = null + if(!( GLOB.data_core.security.Find(active2) )) + active2 = null + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr) || IsAdminGhost(usr)) + usr.set_machine(src) + switch(href_list["choice"]) +// SORTING! + if("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) +//BASIC FUNCTIONS + if("Clear Screen") + temp = null + + if("Return") + screen = 1 + active1 = null + active2 = null + + if("Log Out") + authenticated = null + screen = null + active1 = null + active2 = null + playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) + + if("Log In") + var/mob/M = usr + var/obj/item/card/id/I = M.get_idcard(TRUE) + if(issilicon(M)) + var/mob/living/silicon/borg = M + active1 = null + active2 = null + authenticated = borg.name + rank = "AI" + screen = 1 + else if(IsAdminGhost(M)) + active1 = null + active2 = null + authenticated = M.client.holder.admin_signature + rank = "Central Command" + screen = 1 + else if(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) + +//RECORD FUNCTIONS + if("Record Maintenance") + screen = 2 + active1 = null + active2 = null + + if("Browse Record") + var/datum/data/record/R = locate(href_list["d_rec"]) in GLOB.data_core.general + if(!R) + temp = "Record Not Found!" + else + active1 = active2 = R + for(var/datum/data/record/E in GLOB.data_core.security) + if((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"])) + active2 = E + screen = 3 + + + if("Print Record") + if(!( printing )) + printing = 1 + GLOB.data_core.securityPrintCount++ + playsound(loc, 'sound/items/poster_being_created.ogg', 100, 1) + sleep(30) + var/obj/item/paper/P = new /obj/item/paper( loc ) + P.info = "
    Security Record - (SR-[GLOB.data_core.securityPrintCount])

    " + if((istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1))) + 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((istype(active2, /datum/data/record) && GLOB.data_core.security.Find(active2))) + P.info += text("
    \n
    Security Data

    \nCriminal Status: []", active2.fields["criminal"]) + + P.info += "
    \n
    \nMinor Crimes:
    \n" + P.info +={" + + + + + +"} + for(var/datum/data/crime/c in active2.fields["mi_crim"]) + P.info += "" + P.info += "" + P.info += "" + P.info += "" + P.info += "" + P.info += "
    CrimeDetailsAuthorTime Added
    [c.crimeName][c.crimeDetails][c.author][c.time]
    " + + P.info += "
    \nMajor Crimes:
    \n" + P.info +={" + + + + + +"} + for(var/datum/data/crime/c in active2.fields["ma_crim"]) + P.info += "" + P.info += "" + P.info += "" + P.info += "" + P.info += "" + P.info += "
    CrimeDetailsAuthorTime Added
    [c.crimeName][c.crimeDetails][c.author][c.time]
    " + + + P.info += text("
    \nImportant Notes:
    \n\t[]
    \n
    \n
    Comments/Log

    ", active2.fields["notes"]) + var/counter = 1 + while(active2.fields[text("com_[]", counter)]) + P.info += text("[]
    ", active2.fields[text("com_[]", counter)]) + counter++ + P.name = text("SR-[] '[]'", GLOB.data_core.securityPrintCount, active1.fields["name"]) + else + P.info += "Security Record Lost!
    " + P.name = text("SR-[] '[]'", GLOB.data_core.securityPrintCount, "Record Lost") + P.info += "" + P.update_icon() + printing = null + if("Print Poster") + if(!( printing )) + var/wanted_name = stripped_input(usr, "Please enter an alias for the criminal:", "Print Wanted Poster", active1.fields["name"]) + if(wanted_name) + var/default_description = "A poster declaring [wanted_name] to be a dangerous individual, wanted by Nanotrasen. Report any sightings to security immediately." + var/list/major_crimes = active2.fields["ma_crim"] + var/list/minor_crimes = active2.fields["mi_crim"] + if(major_crimes.len + minor_crimes.len) + default_description += "\n[wanted_name] is wanted for the following crimes:\n" + if(minor_crimes.len) + default_description += "\nMinor Crimes:" + for(var/datum/data/crime/c in active2.fields["mi_crim"]) + default_description += "\n[c.crimeName]\n" + default_description += "[c.crimeDetails]\n" + if(major_crimes.len) + default_description += "\nMajor Crimes:" + for(var/datum/data/crime/c in active2.fields["ma_crim"]) + default_description += "\n[c.crimeName]\n" + default_description += "[c.crimeDetails]\n" + + var/info = stripped_multiline_input(usr, "Please input a description for the poster:", "Print Wanted Poster", default_description, null) + if(info) + playsound(loc, 'sound/items/poster_being_created.ogg', 100, 1) + printing = 1 + sleep(30) + if((istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1)))//make sure the record still exists. + var/obj/item/photo/photo = active1.fields["photo_front"] + new /obj/item/poster/wanted(loc, photo.picture.picture_image, wanted_name, info) + printing = 0 + +//RECORD DELETE + if("Delete All Records") + temp = "" + temp += "Are you sure you wish to delete all Security records?
    " + temp += "Yes
    " + temp += "No" + + if("Purge All Records") + investigate_log("[key_name(usr)] has purged all the security records.", INVESTIGATE_RECORDS) + for(var/datum/data/record/R in GLOB.data_core.security) + qdel(R) + GLOB.data_core.security.Cut() + temp = "All Security records deleted." + + if("Add Entry") + if(!( istype(active2, /datum/data/record) )) + return + var/a2 = active2 + var/t1 = stripped_multiline_input("Add Comment:", "Secure. records", null, null) + if(!canUseSecurityRecordsConsole(usr, t1, null, a2)) + return + var/counter = 1 + while(active2.fields[text("com_[]", counter)]) + counter++ + active2.fields[text("com_[]", counter)] = text("Made by [] ([]) on [] [], []
    []", src.authenticated, src.rank, STATION_TIME_TIMESTAMP("hh:mm:ss"), time2text(world.realtime, "MMM DD"), GLOB.year_integer, t1) + + if("Delete Record (ALL)") + if(active1) + temp = "
    Are you sure you wish to delete the record (ALL)?
    " + temp += "Yes
    " + temp += "No" + + if("Delete Record (Security)") + if(active2) + temp = "
    Are you sure you wish to delete the record (Security Portion Only)?
    " + temp += "Yes
    " + temp += "No" + + if("Delete Entry") + if((istype(active2, /datum/data/record) && active2.fields[text("com_[]", href_list["del_c"])])) + active2.fields[text("com_[]", href_list["del_c"])] = "Deleted" +//RECORD CREATE + if("New Record (Security)") + 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("Security Record #[]", R.fields["id"]) + R.fields["criminal"] = "None" + R.fields["mi_crim"] = list() + R.fields["ma_crim"] = list() + R.fields["notes"] = "No notes." + GLOB.data_core.security += R + active2 = R + screen = 3 + + if("New Record (General)") + //General Record + var/datum/data/record/G = new /datum/data/record() + G.fields["name"] = "New Record" + G.fields["id"] = "[num2hex(rand(1, 1.6777215E7), 6)]" + G.fields["rank"] = "Unassigned" + G.fields["gender"] = "Male" + G.fields["age"] = "Unknown" + G.fields["species"] = "Human" + G.fields["photo_front"] = new /icon() + G.fields["photo_side"] = new /icon() + G.fields["fingerprint"] = "?????" + G.fields["p_stat"] = "Active" + G.fields["m_stat"] = "Stable" + GLOB.data_core.general += G + active1 = G + + //Security 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("Security Record #[]", R.fields["id"]) + R.fields["criminal"] = "None" + R.fields["mi_crim"] = list() + R.fields["ma_crim"] = list() + R.fields["notes"] = "No notes." + GLOB.data_core.security += R + active2 = R + + //Medical Record + var/datum/data/record/M = new /datum/data/record() + M.fields["id"] = active1.fields["id"] + M.fields["name"] = active1.fields["name"] + M.fields["blood_type"] = "?" + M.fields["b_dna"] = "?????" + M.fields["mi_dis"] = "None" + M.fields["mi_dis_d"] = "No minor disabilities have been declared." + M.fields["ma_dis"] = "None" + M.fields["ma_dis_d"] = "No major disabilities have been diagnosed." + M.fields["alg"] = "None" + M.fields["alg_d"] = "No allergies have been detected in this patient." + M.fields["cdi"] = "None" + M.fields["cdi_d"] = "No diseases have been diagnosed at the moment." + M.fields["notes"] = "No notes." + GLOB.data_core.medical += M + + + +//FIELD FUNCTIONS + if("Edit Field") + var/a1 = active1 + var/a2 = active2 + + switch(href_list["field"]) + if("name") + if(istype(active1, /datum/data/record) || istype(active2, /datum/data/record)) + var/t1 = copytext(sanitize(input("Please input name:", "Secure. records", active1.fields["name"], null) as text),1,MAX_MESSAGE_LEN) + if(!canUseSecurityRecordsConsole(usr, t1, a1)) + return + if(istype(active1, /datum/data/record)) + active1.fields["name"] = t1 + if(istype(active2, /datum/data/record)) + active2.fields["name"] = t1 + if("id") + if(istype(active2, /datum/data/record) || istype(active1, /datum/data/record)) + var/t1 = stripped_input(usr, "Please input id:", "Secure. records", active1.fields["id"], null) + if(!canUseSecurityRecordsConsole(usr, t1, a1)) + return + if(istype(active1, /datum/data/record)) + active1.fields["id"] = t1 + if(istype(active2, /datum/data/record)) + active2.fields["id"] = t1 + if("fingerprint") + if(istype(active1, /datum/data/record)) + var/t1 = stripped_input(usr, "Please input fingerprint hash:", "Secure. records", active1.fields["fingerprint"], null) + if(!canUseSecurityRecordsConsole(usr, t1, a1)) + return + active1.fields["fingerprint"] = t1 + if("gender") + if(istype(active1, /datum/data/record)) + 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(istype(active1, /datum/data/record)) + var/t1 = input("Please input age:", "Secure. records", active1.fields["age"], null) as num + if(!canUseSecurityRecordsConsole(usr, "age", a1)) + return + active1.fields["age"] = t1 + if("species") + if(istype(active1, /datum/data/record)) + var/t1 = input("Select a species", "Species Selection") as null|anything in GLOB.roundstart_races + if(!canUseSecurityRecordsConsole(usr, t1, a1)) + return + active1.fields["species"] = t1 + if("show_photo_front") + 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("upd_photo_front") + var/obj/item/photo/photo = get_photo(usr) + if(photo) + qdel(active1.fields["photo_front"]) + //Lets center it to a 32x32. + var/icon/I = photo.picture.picture_image + var/w = I.Width() + var/h = I.Height() + var/dw = w - 32 + var/dh = w - 32 + I.Crop(dw/2, dh/2, w - dw/2, h - dh/2) + active1.fields["photo_front"] = photo + if("print_photo_front") + if(active1.fields["photo_front"]) + if(istype(active1.fields["photo_front"], /obj/item/photo)) + var/obj/item/photo/P = active1.fields["photo_front"] + print_photo(P.picture.picture_image, active1.fields["name"]) + if("show_photo_side") + 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) + if("upd_photo_side") + var/obj/item/photo/photo = get_photo(usr) + if(photo) + qdel(active1.fields["photo_side"]) + //Lets center it to a 32x32. + var/icon/I = photo.picture.picture_image + var/w = I.Width() + var/h = I.Height() + var/dw = w - 32 + var/dh = w - 32 + I.Crop(dw/2, dh/2, w - dw/2, h - dh/2) + active1.fields["photo_side"] = photo + if("print_photo_side") + if(active1.fields["photo_side"]) + if(istype(active1.fields["photo_side"], /obj/item/photo)) + var/obj/item/photo/P = active1.fields["photo_side"] + print_photo(P.picture.picture_image, active1.fields["name"]) + if("mi_crim_add") + if(istype(active1, /datum/data/record)) + var/t1 = stripped_input(usr, "Please input minor crime names:", "Secure. records", "", null) + var/t2 = stripped_input(usr, "Please input minor crime details:", "Secure. records", "", null) + if(!canUseSecurityRecordsConsole(usr, t1, null, a2)) + return + var/crime = GLOB.data_core.createCrimeEntry(t1, t2, authenticated, STATION_TIME_TIMESTAMP("hh:mm:ss")) + GLOB.data_core.addMinorCrime(active1.fields["id"], crime) + investigate_log("New Minor Crime: [t1]: [t2] | Added to [active1.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) + if("mi_crim_delete") + if(istype(active1, /datum/data/record)) + if(href_list["cdataid"]) + if(!canUseSecurityRecordsConsole(usr, "delete", null, a2)) + return + GLOB.data_core.removeMinorCrime(active1.fields["id"], href_list["cdataid"]) + if("ma_crim_add") + if(istype(active1, /datum/data/record)) + var/t1 = stripped_input(usr, "Please input major crime names:", "Secure. records", "", null) + var/t2 = stripped_input(usr, "Please input major crime details:", "Secure. records", "", null) + if(!canUseSecurityRecordsConsole(usr, t1, null, a2)) + return + var/crime = GLOB.data_core.createCrimeEntry(t1, t2, authenticated, STATION_TIME_TIMESTAMP("hh:mm:ss")) + GLOB.data_core.addMajorCrime(active1.fields["id"], crime) + investigate_log("New Major Crime: [t1]: [t2] | Added to [active1.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) + if("ma_crim_delete") + if(istype(active1, /datum/data/record)) + if(href_list["cdataid"]) + if(!canUseSecurityRecordsConsole(usr, "delete", null, a2)) + return + GLOB.data_core.removeMajorCrime(active1.fields["id"], href_list["cdataid"]) + if("notes") + if(istype(active2, /datum/data/record)) + var/t1 = stripped_input(usr, "Please summarize notes:", "Secure. records", active2.fields["notes"], null) + if(!canUseSecurityRecordsConsole(usr, t1, null, a2)) + return + active2.fields["notes"] = t1 + if("criminal") + if(istype(active2, /datum/data/record)) + temp = "
    Criminal Status:
    " + temp += "" + if("rank") + var/list/L = list( "Head of Personnel", "Captain", "AI", "Central Command" ) + //This was so silly before the change. Now it actually works without beating your head against the keyboard. /N + if((istype(active1, /datum/data/record) && L.Find(rank))) + temp = "
    Rank:
    " + temp += "
      " + for(var/rank in get_all_jobs()) + temp += "
    • [rank]
    • " + temp += "
    " + else + alert(usr, "You do not have the required rank to do this!") +//TEMPORARY MENU FUNCTIONS + else//To properly clear as per clear screen. + temp=null + switch(href_list["choice"]) + if("Change Rank") + if(active1) + active1.fields["rank"] = href_list["rank"] + if(href_list["rank"] in get_all_jobs()) + active1.fields["real_rank"] = href_list["real_rank"] + + if("Change Criminal Status") + if(active2) + var/old_field = active2.fields["criminal"] + switch(href_list["criminal2"]) + if("none") + active2.fields["criminal"] = "None" + if("arrest") + active2.fields["criminal"] = "*Arrest*" + if("incarcerated") + active2.fields["criminal"] = "Incarcerated" + if("paroled") + active2.fields["criminal"] = "Paroled" + if("released") + active2.fields["criminal"] = "Discharged" + investigate_log("[active1.fields["name"]] has been set from [old_field] to [active2.fields["criminal"]] by [key_name(usr)].", INVESTIGATE_RECORDS) + for(var/mob/living/carbon/human/H in GLOB.carbon_list) + H.sec_hud_set_security_status() + if("Delete Record (Security) Execute") + investigate_log("[key_name(usr)] has deleted the security records for [active1.fields["name"]].", INVESTIGATE_RECORDS) + if(active2) + qdel(active2) + active2 = null + + if("Delete Record (ALL) Execute") + if(active1) + investigate_log("[key_name(usr)] has deleted all records for [active1.fields["name"]].", INVESTIGATE_RECORDS) + for(var/datum/data/record/R in GLOB.data_core.medical) + if((R.fields["name"] == active1.fields["name"] || R.fields["id"] == active1.fields["id"])) + qdel(R) + break + qdel(active1) + active1 = null + + if(active2) + qdel(active2) + active2 = null + else + temp = "This function does not appear to be working at the moment. Our apologies." + + add_fingerprint(usr) + updateUsrDialog() + return + +/obj/machinery/computer/secure_data/proc/get_photo(mob/user) + var/obj/item/photo/P = null + if(issilicon(user)) + var/mob/living/silicon/tempAI = user + var/datum/picture/selection = tempAI.GetPhoto(user) + if(selection) + P = new(null, selection) + else if(istype(user.get_active_held_item(), /obj/item/photo)) + P = user.get_active_held_item() + return P + +/obj/machinery/computer/secure_data/proc/print_photo(icon/temp, person_name) + if (printing) + return + printing = TRUE + sleep(20) + var/obj/item/photo/P = new/obj/item/photo(drop_location()) + var/datum/picture/toEmbed = new(name = person_name, desc = "The photo on file for [person_name].", image = temp) + P.set_picture(toEmbed, TRUE, TRUE) + P.pixel_x = rand(-10, 10) + P.pixel_y = rand(-10, 10) + printing = FALSE + +/obj/machinery/computer/secure_data/emp_act(severity) + . = ..() + + if(stat & (BROKEN|NOPOWER) || . & EMP_PROTECT_SELF) + return + + for(var/datum/data/record/R in GLOB.data_core.security) + if(prob(10/severity)) + switch(rand(1,8)) + if(1) + if(prob(10)) + R.fields["name"] = "[pick(lizard_name(MALE),lizard_name(FEMALE))]" + else + R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]" + if(2) + R.fields["gender"] = pick("Male", "Female", "Other") + if(3) + R.fields["age"] = rand(5, 85) + if(4) + R.fields["criminal"] = pick("None", "*Arrest*", "Incarcerated", "Paroled", "Discharged") + if(5) + R.fields["p_stat"] = pick("*Unconscious*", "Active", "Physically Unfit") + if(6) + R.fields["m_stat"] = pick("*Insane*", "*Unstable*", "*Watch*", "Stable") + if(7) + R.fields["species"] = pick(GLOB.roundstart_races) + if(8) + var/datum/data/record/G = pick(GLOB.data_core.general) + R.fields["photo_front"] = G.fields["photo_front"] + R.fields["photo_side"] = G.fields["photo_side"] + continue + + else if(prob(1)) + qdel(R) + continue + +/obj/machinery/computer/secure_data/proc/canUseSecurityRecordsConsole(mob/user, message1 = 0, record1, record2) + if(user) + if(authenticated) + if(user.canUseTopic(src, !issilicon(user))) + if(!trim(message1)) + return 0 + if(!record1 || record1 == active1) + if(!record2 || record2 == active2) + return 1 + return 0 + diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index 36ba4b0c94..1b700fdf36 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -1,98 +1,98 @@ -/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 - 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, "station_alert", name, 300, 500, master_ui, state) - ui.open() - -/obj/machinery/computer/station_alert/ui_data(mob/user) - . = list() - - .["alarms"] = list() - for(var/class in alarms) - .["alarms"][class] = list() - for(var/area in alarms[class]) - .["alarms"][class] += area - -/obj/machinery/computer/station_alert/proc/triggerAlarm(class, area/A, O, obj/source) - if(source.z != z) - return - if(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(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_icon() - ..() - cut_overlays() - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - var/overlay_state = icon_screen - if(stat & (NOPOWER|BROKEN)) - add_overlay("[icon_keyboard]_off") - return - add_overlay(icon_keyboard) - var/active_alarms = FALSE - for(var/cat in alarms) - var/list/L = alarms[cat] - if(L.len) - active_alarms = TRUE - if(active_alarms) - overlay_state = "alert:2" - add_overlay("alert:2") - else - overlay_state = "alert:0" - add_overlay("alert:0") - SSvis_overlays.add_vis_overlay(src, icon, overlay_state, layer, plane, dir) - SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir, alpha=128) +/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 + 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, "station_alert", name, 300, 500, master_ui, state) + ui.open() + +/obj/machinery/computer/station_alert/ui_data(mob/user) + . = list() + + .["alarms"] = list() + for(var/class in alarms) + .["alarms"][class] = list() + for(var/area in alarms[class]) + .["alarms"][class] += area + +/obj/machinery/computer/station_alert/proc/triggerAlarm(class, area/A, O, obj/source) + if(source.z != z) + return + if(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(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_icon() + ..() + cut_overlays() + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + var/overlay_state = icon_screen + if(stat & (NOPOWER|BROKEN)) + add_overlay("[icon_keyboard]_off") + return + add_overlay(icon_keyboard) + var/active_alarms = FALSE + for(var/cat in alarms) + var/list/L = alarms[cat] + if(L.len) + active_alarms = TRUE + if(active_alarms) + overlay_state = "alert:2" + add_overlay("alert:2") + else + overlay_state = "alert:0" + add_overlay("alert:0") + SSvis_overlays.add_vis_overlay(src, icon, overlay_state, layer, plane, dir) + SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir, alpha=128) diff --git a/code/game/machinery/computer/telecrystalconsoles.dm b/code/game/machinery/computer/telecrystalconsoles.dm index 75e4a866f0..768faa8301 100644 --- a/code/game/machinery/computer/telecrystalconsoles.dm +++ b/code/game/machinery/computer/telecrystalconsoles.dm @@ -1,215 +1,215 @@ -#define NUKESCALINGMODIFIER 1 - -GLOBAL_LIST_INIT(possible_uplinker_IDs, list("Alfa","Bravo","Charlie","Delta","Echo","Foxtrot","Zero", "Niner")) - -/obj/machinery/computer/telecrystals - name = "\improper telecrystal assignment station" - desc = "A device used to manage telecrystals during group operations. You shouldn't be looking at this particular one..." - icon_state = "tcstation" - icon_keyboard = "tcstation_key" - icon_screen = "syndie" - clockwork = TRUE //it'd look weird, at least if ratvar ever got there - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - - light_color = LIGHT_COLOR_RED - -///////////////////////////////////////////// -/obj/machinery/computer/telecrystals/uplinker - name = "\improper telecrystal upload/receive station" - desc = "A device used to manage telecrystals during group operations. To use, simply insert your uplink. With your uplink installed \ - you can upload your telecrystals to the group's pool using the console, or be assigned additional telecrystals by your lieutenant." - var/obj/item/uplinkholder = null - var/obj/machinery/computer/telecrystals/boss/linkedboss = null - -/obj/machinery/computer/telecrystals/uplinker/Initialize() - . = ..() - - var/ID = pick_n_take(GLOB.possible_uplinker_IDs) - if(!ID) - ID = rand(1,999) - name = "[name] [ID]" - -/obj/machinery/computer/telecrystals/uplinker/attackby(obj/item/I, mob/user, params) - if(uplinkholder) - to_chat(user, "[src] already has an uplink in it.") - return - var/datum/component/uplink/hidden_uplink = I.GetComponent(/datum/component/uplink) - if(hidden_uplink) - if(!user.transferItemToLoc(I, src)) - return - uplinkholder = I - I.add_fingerprint(user) - update_icon() - updateUsrDialog() - else - to_chat(user, "[I] doesn't appear to be an uplink...") - -/obj/machinery/computer/telecrystals/uplinker/update_icon() - ..() - if(uplinkholder) - add_overlay("[initial(icon_state)]-closed") - -/obj/machinery/computer/telecrystals/uplinker/proc/ejectuplink() - if(uplinkholder) - uplinkholder.forceMove(drop_location()) - uplinkholder = null - update_icon() - -/obj/machinery/computer/telecrystals/uplinker/proc/donateTC(amt, addLog = 1) - if(uplinkholder && linkedboss) - var/datum/component/uplink/hidden_uplink = uplinkholder.GetComponent(/datum/component/uplink) - if(amt < 0) - linkedboss.storedcrystals += hidden_uplink.telecrystals - if(addLog) - linkedboss.logTransfer("[src] donated [hidden_uplink.telecrystals] telecrystals to [linkedboss].") - hidden_uplink.telecrystals = 0 - else if(amt <= hidden_uplink.telecrystals) - hidden_uplink.telecrystals -= amt - linkedboss.storedcrystals += amt - if(addLog) - linkedboss.logTransfer("[src] donated [amt] telecrystals to [linkedboss].") - -/obj/machinery/computer/telecrystals/uplinker/proc/giveTC(amt, addLog = 1) - if(uplinkholder && linkedboss) - var/datum/component/uplink/hidden_uplink = uplinkholder.GetComponent(/datum/component/uplink) - if(amt < 0) - hidden_uplink.telecrystals += linkedboss.storedcrystals - if(addLog) - linkedboss.logTransfer("[src] received [linkedboss.storedcrystals] telecrystals from [linkedboss].") - linkedboss.storedcrystals = 0 - else if(amt <= linkedboss.storedcrystals) - hidden_uplink.telecrystals += amt - linkedboss.storedcrystals -= amt - if(addLog) - linkedboss.logTransfer("[src] received [amt] telecrystals from [linkedboss].") - -/////// - -/obj/machinery/computer/telecrystals/uplinker/ui_interact(mob/user) - . = ..() - var/dat = "" - if(linkedboss) - dat += "[linkedboss] has [linkedboss.storedcrystals] telecrystals available for distribution.

    " - else - dat += "No linked management consoles detected. Scan for uplink stations using the management console.

    " - - if(uplinkholder) - var/datum/component/uplink/hidden_uplink = uplinkholder.GetComponent(/datum/component/uplink) - dat += "[hidden_uplink.telecrystals] telecrystals remain in this uplink.
    " - if(linkedboss) - dat += "Donate TC: 1 | 5 | All" - dat += "
    Eject Uplink" - - - var/datum/browser/popup = new(user, "computer", "Telecrystal Upload/Receive Station", 700, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/telecrystals/uplinker/Topic(href, href_list) - if(..()) - return - - if(href_list["donate"]) - var/tcamt = text2num(href_list["donate"]) - donateTC(tcamt) - - if(href_list["eject"]) - ejectuplink() - - src.updateUsrDialog() - - -///////////////////////////////////////// -/obj/machinery/computer/telecrystals/boss - name = "team telecrystal management console" - desc = "A device used to manage telecrystals during group operations. To use, simply initialize the machine by scanning for nearby uplink stations. \ - Once the consoles are linked up, you can assign any telecrystals amongst your operatives; be they donated by your agents or rationed to the squad \ - based on the danger rating of the mission." - icon_state = "computer" - icon_screen = "tcboss" - icon_keyboard = "syndie_key" - var/virgin = 1 - var/scanrange = 10 - var/storedcrystals = 0 - var/list/TCstations = list() - var/list/transferlog = list() - -/obj/machinery/computer/telecrystals/boss/proc/logTransfer(logmessage) - transferlog += ("[STATION_TIME_TIMESTAMP("hh:mm:ss")] [logmessage]") - -/obj/machinery/computer/telecrystals/boss/proc/scanUplinkers() - for(var/obj/machinery/computer/telecrystals/uplinker/A in urange(scanrange, src.loc)) - if(!A.linkedboss) - TCstations += A - A.linkedboss = src - if(virgin) - getDangerous() - virgin = 0 - -/obj/machinery/computer/telecrystals/boss/proc/getDangerous()//This scales the TC assigned with the round population. - var/list/nukeops = get_antag_minds(/datum/antagonist/nukeop) - var/danger = GLOB.joined_player_list.len - nukeops.len - danger = CEILING(danger, 10) - scaleTC(danger) - -/obj/machinery/computer/telecrystals/boss/proc/scaleTC(amt)//Its own proc, since it'll probably need a lot of tweaks for balance, use a fancier algorhithm, etc. - storedcrystals += amt * NUKESCALINGMODIFIER - -///////// - -/obj/machinery/computer/telecrystals/boss/ui_interact(mob/user) - . = ..() - var/dat = "" - dat += "Scan for TC stations.
    " - dat += "[storedcrystals] telecrystals are available for distribution.
    " - dat += "

    " - - - for(var/obj/machinery/computer/telecrystals/uplinker/A in TCstations) - dat += "[A.name] | " - if(A.uplinkholder) - var/datum/component/uplink/hidden_uplink = A.uplinkholder.GetComponent(/datum/component/uplink) - dat += "[hidden_uplink.telecrystals] telecrystals." - if(storedcrystals) - dat+= "
    Add TC: 1 | 5 | 10 | All" - dat += "
    " - - if(TCstations.len && storedcrystals) - dat += "

    Evenly distribute remaining TC.

    " - - - for(var/entry in transferlog) - dat += "[entry]
    " - - - var/datum/browser/popup = new(user, "computer", "Team Telecrystal Management Console", 700, 500) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/telecrystals/boss/Topic(href, href_list) - if(..()) - return - - if(href_list["scan"]) - scanUplinkers() - - if(href_list["give"]) - var/tcamt = text2num(href_list["give"]) - if(TCstations.len) // sanity - var/obj/machinery/computer/telecrystals/uplinker/A = locate(href_list["target"]) in TCstations - A.giveTC(tcamt) - - if(href_list["distrib"]) - var/sanity = 0 - while(storedcrystals && sanity < 100) - for(var/obj/machinery/computer/telecrystals/uplinker/A in TCstations) - A.giveTC(1,0) - sanity++ - logTransfer("[src] evenly distributed telecrystals.") - - src.updateUsrDialog() - return - -#undef NUKESCALINGMODIFIER +#define NUKESCALINGMODIFIER 1 + +GLOBAL_LIST_INIT(possible_uplinker_IDs, list("Alfa","Bravo","Charlie","Delta","Echo","Foxtrot","Zero", "Niner")) + +/obj/machinery/computer/telecrystals + name = "\improper telecrystal assignment station" + desc = "A device used to manage telecrystals during group operations. You shouldn't be looking at this particular one..." + icon_state = "tcstation" + icon_keyboard = "tcstation_key" + icon_screen = "syndie" + clockwork = TRUE //it'd look weird, at least if ratvar ever got there + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + + light_color = LIGHT_COLOR_RED + +///////////////////////////////////////////// +/obj/machinery/computer/telecrystals/uplinker + name = "\improper telecrystal upload/receive station" + desc = "A device used to manage telecrystals during group operations. To use, simply insert your uplink. With your uplink installed \ + you can upload your telecrystals to the group's pool using the console, or be assigned additional telecrystals by your lieutenant." + var/obj/item/uplinkholder = null + var/obj/machinery/computer/telecrystals/boss/linkedboss = null + +/obj/machinery/computer/telecrystals/uplinker/Initialize() + . = ..() + + var/ID = pick_n_take(GLOB.possible_uplinker_IDs) + if(!ID) + ID = rand(1,999) + name = "[name] [ID]" + +/obj/machinery/computer/telecrystals/uplinker/attackby(obj/item/I, mob/user, params) + if(uplinkholder) + to_chat(user, "[src] already has an uplink in it.") + return + var/datum/component/uplink/hidden_uplink = I.GetComponent(/datum/component/uplink) + if(hidden_uplink) + if(!user.transferItemToLoc(I, src)) + return + uplinkholder = I + I.add_fingerprint(user) + update_icon() + updateUsrDialog() + else + to_chat(user, "[I] doesn't appear to be an uplink...") + +/obj/machinery/computer/telecrystals/uplinker/update_icon() + ..() + if(uplinkholder) + add_overlay("[initial(icon_state)]-closed") + +/obj/machinery/computer/telecrystals/uplinker/proc/ejectuplink() + if(uplinkholder) + uplinkholder.forceMove(drop_location()) + uplinkholder = null + update_icon() + +/obj/machinery/computer/telecrystals/uplinker/proc/donateTC(amt, addLog = 1) + if(uplinkholder && linkedboss) + var/datum/component/uplink/hidden_uplink = uplinkholder.GetComponent(/datum/component/uplink) + if(amt < 0) + linkedboss.storedcrystals += hidden_uplink.telecrystals + if(addLog) + linkedboss.logTransfer("[src] donated [hidden_uplink.telecrystals] telecrystals to [linkedboss].") + hidden_uplink.telecrystals = 0 + else if(amt <= hidden_uplink.telecrystals) + hidden_uplink.telecrystals -= amt + linkedboss.storedcrystals += amt + if(addLog) + linkedboss.logTransfer("[src] donated [amt] telecrystals to [linkedboss].") + +/obj/machinery/computer/telecrystals/uplinker/proc/giveTC(amt, addLog = 1) + if(uplinkholder && linkedboss) + var/datum/component/uplink/hidden_uplink = uplinkholder.GetComponent(/datum/component/uplink) + if(amt < 0) + hidden_uplink.telecrystals += linkedboss.storedcrystals + if(addLog) + linkedboss.logTransfer("[src] received [linkedboss.storedcrystals] telecrystals from [linkedboss].") + linkedboss.storedcrystals = 0 + else if(amt <= linkedboss.storedcrystals) + hidden_uplink.telecrystals += amt + linkedboss.storedcrystals -= amt + if(addLog) + linkedboss.logTransfer("[src] received [amt] telecrystals from [linkedboss].") + +/////// + +/obj/machinery/computer/telecrystals/uplinker/ui_interact(mob/user) + . = ..() + var/dat = "" + if(linkedboss) + dat += "[linkedboss] has [linkedboss.storedcrystals] telecrystals available for distribution.

    " + else + dat += "No linked management consoles detected. Scan for uplink stations using the management console.

    " + + if(uplinkholder) + var/datum/component/uplink/hidden_uplink = uplinkholder.GetComponent(/datum/component/uplink) + dat += "[hidden_uplink.telecrystals] telecrystals remain in this uplink.
    " + if(linkedboss) + dat += "Donate TC: 1 | 5 | All" + dat += "
    Eject Uplink" + + + var/datum/browser/popup = new(user, "computer", "Telecrystal Upload/Receive Station", 700, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/telecrystals/uplinker/Topic(href, href_list) + if(..()) + return + + if(href_list["donate"]) + var/tcamt = text2num(href_list["donate"]) + donateTC(tcamt) + + if(href_list["eject"]) + ejectuplink() + + src.updateUsrDialog() + + +///////////////////////////////////////// +/obj/machinery/computer/telecrystals/boss + name = "team telecrystal management console" + desc = "A device used to manage telecrystals during group operations. To use, simply initialize the machine by scanning for nearby uplink stations. \ + Once the consoles are linked up, you can assign any telecrystals amongst your operatives; be they donated by your agents or rationed to the squad \ + based on the danger rating of the mission." + icon_state = "computer" + icon_screen = "tcboss" + icon_keyboard = "syndie_key" + var/virgin = 1 + var/scanrange = 10 + var/storedcrystals = 0 + var/list/TCstations = list() + var/list/transferlog = list() + +/obj/machinery/computer/telecrystals/boss/proc/logTransfer(logmessage) + transferlog += ("[STATION_TIME_TIMESTAMP("hh:mm:ss")] [logmessage]") + +/obj/machinery/computer/telecrystals/boss/proc/scanUplinkers() + for(var/obj/machinery/computer/telecrystals/uplinker/A in urange(scanrange, src.loc)) + if(!A.linkedboss) + TCstations += A + A.linkedboss = src + if(virgin) + getDangerous() + virgin = 0 + +/obj/machinery/computer/telecrystals/boss/proc/getDangerous()//This scales the TC assigned with the round population. + var/list/nukeops = get_antag_minds(/datum/antagonist/nukeop) + var/danger = GLOB.joined_player_list.len - nukeops.len + danger = CEILING(danger, 10) + scaleTC(danger) + +/obj/machinery/computer/telecrystals/boss/proc/scaleTC(amt)//Its own proc, since it'll probably need a lot of tweaks for balance, use a fancier algorhithm, etc. + storedcrystals += amt * NUKESCALINGMODIFIER + +///////// + +/obj/machinery/computer/telecrystals/boss/ui_interact(mob/user) + . = ..() + var/dat = "" + dat += "Scan for TC stations.
    " + dat += "[storedcrystals] telecrystals are available for distribution.
    " + dat += "

    " + + + for(var/obj/machinery/computer/telecrystals/uplinker/A in TCstations) + dat += "[A.name] | " + if(A.uplinkholder) + var/datum/component/uplink/hidden_uplink = A.uplinkholder.GetComponent(/datum/component/uplink) + dat += "[hidden_uplink.telecrystals] telecrystals." + if(storedcrystals) + dat+= "
    Add TC: 1 | 5 | 10 | All" + dat += "
    " + + if(TCstations.len && storedcrystals) + dat += "

    Evenly distribute remaining TC.

    " + + + for(var/entry in transferlog) + dat += "[entry]
    " + + + var/datum/browser/popup = new(user, "computer", "Team Telecrystal Management Console", 700, 500) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/telecrystals/boss/Topic(href, href_list) + if(..()) + return + + if(href_list["scan"]) + scanUplinkers() + + if(href_list["give"]) + var/tcamt = text2num(href_list["give"]) + if(TCstations.len) // sanity + var/obj/machinery/computer/telecrystals/uplinker/A = locate(href_list["target"]) in TCstations + A.giveTC(tcamt) + + if(href_list["distrib"]) + var/sanity = 0 + while(storedcrystals && sanity < 100) + for(var/obj/machinery/computer/telecrystals/uplinker/A in TCstations) + A.giveTC(1,0) + sanity++ + logTransfer("[src] evenly distributed telecrystals.") + + src.updateUsrDialog() + return + +#undef NUKESCALINGMODIFIER diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index 7a39e73ad3..96487915dd 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -1,279 +1,279 @@ -/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(istype(P, /obj/item/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(istype(P, /obj/item/wrench)) - to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") - if(P.use_tool(src, user, 40, volume=75)) - if(state == 1) - to_chat(user, "You [anchored ? "un" : ""]secure [name].") - setAnchored(!anchored) - return - - if(2) - if(istype(P, /obj/item/wrench)) - to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") - if(P.use_tool(src, user, 40, volume=75)) - to_chat(user, "You [anchored ? "un" : ""]secure [name].") - setAnchored(!anchored) - return - - if(istype(P, /obj/item/circuitboard/machine)) - var/obj/item/circuitboard/machine/B = P - 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, 1) - 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(istype(P, /obj/item/wirecutters)) - 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(istype(P, /obj/item/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(istype(P, /obj/item/wrench) && !circuit.needs_anchored) - to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") - if(P.use_tool(src, user, 40, volume=75)) - to_chat(user, "You [anchored ? "un" : ""]secure [name].") - setAnchored(!anchored) - return - - if(istype(P, /obj/item/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) - 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 - if(new_machine.circuit) - QDEL_NULL(new_machine.circuit) - new_machine.circuit = circuit - 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(istype(P, /obj/item/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(istype(P, /obj/item/wrench)) + to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") + if(P.use_tool(src, user, 40, volume=75)) + if(state == 1) + to_chat(user, "You [anchored ? "un" : ""]secure [name].") + setAnchored(!anchored) + return + + if(2) + if(istype(P, /obj/item/wrench)) + to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") + if(P.use_tool(src, user, 40, volume=75)) + to_chat(user, "You [anchored ? "un" : ""]secure [name].") + setAnchored(!anchored) + return + + if(istype(P, /obj/item/circuitboard/machine)) + var/obj/item/circuitboard/machine/B = P + 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, 1) + 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(istype(P, /obj/item/wirecutters)) + 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(istype(P, /obj/item/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(istype(P, /obj/item/wrench) && !circuit.needs_anchored) + to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") + if(P.use_tool(src, user, 40, volume=75)) + to_chat(user, "You [anchored ? "un" : ""]secure [name].") + setAnchored(!anchored) + return + + if(istype(P, /obj/item/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) + 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 + if(new_machine.circuit) + QDEL_NULL(new_machine.circuit) + new_machine.circuit = circuit + 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 cf46e6b0fd..42649a9ff2 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(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 +/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(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() \ No newline at end of file diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index 5c8cd4ffb8..beaf47d0a3 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -1,256 +1,256 @@ -#define CHARS_PER_LINE 5 -#define FONT_SIZE "5pt" -#define FONT_COLOR "#09f" -#define FONT_STYLE "Arial Black" -#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 - -/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) - stat |= BROKEN - 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(stat & (NOPOWER|BROKEN)) - return - - if(timing) - if(world.realtime - activation_time >= timer_duration) - timer_end() // open doors, reset timer, clear status screen - update_icon() - -// has the door power sitatuation changed, if so update icon. -/obj/machinery/door_timer/power_change() - ..() - 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(stat & (NOPOWER|BROKEN)) - return 0 - - activation_time = world.realtime - 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(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.realtime - 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, "brig_timer", name, 300, 200, 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(stat & (NOPOWER)) - icon_state = "frame" - return - - if(stat & (BROKEN)) - set_picture("ai_bsod") - return - - if(timing) - var/disp1 = id - var/time_left = time_left(seconds = TRUE) - var/disp2 = "[add_zero(num2text((time_left / 60) % 60),2)]~[add_zero(num2text(time_left % 60), 2)]" - 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) - 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.realtime - 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 "Arial Black" +#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 + +/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) + stat |= BROKEN + 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(stat & (NOPOWER|BROKEN)) + return + + if(timing) + if(world.realtime - activation_time >= timer_duration) + timer_end() // open doors, reset timer, clear status screen + update_icon() + +// has the door power sitatuation changed, if so update icon. +/obj/machinery/door_timer/power_change() + ..() + 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(stat & (NOPOWER|BROKEN)) + return 0 + + activation_time = world.realtime + 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(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.realtime - 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, "brig_timer", name, 300, 200, 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(stat & (NOPOWER)) + icon_state = "frame" + return + + if(stat & (BROKEN)) + set_picture("ai_bsod") + return + + if(timing) + var/disp1 = id + var/time_left = time_left(seconds = TRUE) + var/disp2 = "[add_zero(num2text((time_left / 60) % 60),2)]~[add_zero(num2text(time_left % 60), 2)]" + 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) + 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.realtime + 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 ee8742e84f..2e43670624 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -1,385 +1,385 @@ -/obj/machinery/door - name = "door" - desc = "It opens and closes." - icon = 'icons/obj/doors/Doorint.dmi' - icon_state = "door1" - opacity = 1 - density = TRUE - layer = OPEN_DOOR_LAYER - power_channel = 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 - - interaction_flags_atom = INTERACT_ATOM_UI_INTERACT - - var/secondsElectrified = 0 - 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/damage_deflection = 10 - 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 - -/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." - -/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) - - //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/power_change() - ..() - update_icon() - -/obj/machinery/door/Destroy() - update_freelook_sight() - GLOB.airlocks -= src - if(spark_system) - qdel(spark_system) - spark_system = null - return ..() - -/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 - 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 - return - -/obj/machinery/door/Move() - var/turf/T = loc - . = ..() - move_update_air(T) - -/obj/machinery/door/CanPass(atom/movable/mover, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return !opacity - return !density - -/obj/machinery/door/proc/bumpopen(mob/user) - if(operating) - return - src.add_fingerprint(user) - if(!src.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 - 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 - 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 && (istype(I, /obj/item/crowbar) || istype(I, /obj/item/twohanded/fireaxe))) - try_to_crowbar(I, user) - return 1 - else if(istype(I, /obj/item/weldingtool)) - try_to_weld(I, user) - return 1 - else if(!(I.item_flags & NOBLUDGEON) && user.a_intent != INTENT_HARM) - try_to_activate_door(user) - return 1 - return ..() - -/obj/machinery/door/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(damage_flag == "melee" && damage_amount < damage_deflection) - return 0 - . = ..() - -/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, 1) - else if(damage_amount) - playsound(loc, 'sound/weapons/smash.ogg', 50, 1) - else - playsound(src, 'sound/weapons/tap.ogg', 50, 1) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, 1) - -/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 == 0) - secondsElectrified = -1 - LAZYADD(shockedby, "\[[TIME_STAMP("hh:mm:ss", FALSE)]\]EM Pulse") - addtimer(CALLBACK(src, .proc/unelectrify), 300) - -/obj/machinery/door/proc/unelectrify() - secondsElectrified = 0 - -/obj/machinery/door/update_icon() - 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(!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 - sleep(5) - layer = initial(layer) - update_icon() - set_opacity(0) - operating = FALSE - air_update_turf(1) - update_freelook_sight() - if(autoclose) - spawn(autoclose) - close() - 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(60) - return - - operating = TRUE - - do_animate("closing") - layer = closingLayer - sleep(5) - density = TRUE - sleep(5) - update_icon() - if(visible && !glass) - set_opacity(1) - operating = FALSE - air_update_turf(1) - update_freelook_sight() - if(safe) - CheckForMobs() - else - crush() - return 1 - -/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(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.Knockdown(100) - else if(ismonkey(L)) //For monkeys - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - L.Knockdown(100) - else //for simple_animals & borgs - L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) - var/turf/location = get_turf(src) - //add_blood_DNA doesn't work for borgs/xenos, but add_blood_floor does. - if(iscarbon(L)) - var/mob/living/carbon/C = L - C.bleed(DOOR_CRUSH_DAMAGE) - else - L.add_splatter_floor(location) - for(var/obj/mecha/M in get_turf(src)) - M.take_damage(DOOR_CRUSH_DAMAGE) - -/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 !(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(!stat) //So that only powered doors are closed. - close() //Close ALL the doors! - -/obj/machinery/door/proc/disable_lockdown() - if(!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 + name = "door" + desc = "It opens and closes." + icon = 'icons/obj/doors/Doorint.dmi' + icon_state = "door1" + opacity = 1 + density = TRUE + layer = OPEN_DOOR_LAYER + power_channel = 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 + + interaction_flags_atom = INTERACT_ATOM_UI_INTERACT + + var/secondsElectrified = 0 + 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/damage_deflection = 10 + 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 + +/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." + +/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) + + //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/power_change() + ..() + update_icon() + +/obj/machinery/door/Destroy() + update_freelook_sight() + GLOB.airlocks -= src + if(spark_system) + qdel(spark_system) + spark_system = null + return ..() + +/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 + 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 + return + +/obj/machinery/door/Move() + var/turf/T = loc + . = ..() + move_update_air(T) + +/obj/machinery/door/CanPass(atom/movable/mover, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return !opacity + return !density + +/obj/machinery/door/proc/bumpopen(mob/user) + if(operating) + return + src.add_fingerprint(user) + if(!src.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 + 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 + 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 && (istype(I, /obj/item/crowbar) || istype(I, /obj/item/twohanded/fireaxe))) + try_to_crowbar(I, user) + return 1 + else if(istype(I, /obj/item/weldingtool)) + try_to_weld(I, user) + return 1 + else if(!(I.item_flags & NOBLUDGEON) && user.a_intent != INTENT_HARM) + try_to_activate_door(user) + return 1 + return ..() + +/obj/machinery/door/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(damage_flag == "melee" && damage_amount < damage_deflection) + return 0 + . = ..() + +/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, 1) + else if(damage_amount) + playsound(loc, 'sound/weapons/smash.ogg', 50, 1) + else + playsound(src, 'sound/weapons/tap.ogg', 50, 1) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, 1) + +/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 == 0) + secondsElectrified = -1 + LAZYADD(shockedby, "\[[TIME_STAMP("hh:mm:ss", FALSE)]\]EM Pulse") + addtimer(CALLBACK(src, .proc/unelectrify), 300) + +/obj/machinery/door/proc/unelectrify() + secondsElectrified = 0 + +/obj/machinery/door/update_icon() + 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(!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 + sleep(5) + layer = initial(layer) + update_icon() + set_opacity(0) + operating = FALSE + air_update_turf(1) + update_freelook_sight() + if(autoclose) + spawn(autoclose) + close() + 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(60) + return + + operating = TRUE + + do_animate("closing") + layer = closingLayer + sleep(5) + density = TRUE + sleep(5) + update_icon() + if(visible && !glass) + set_opacity(1) + operating = FALSE + air_update_turf(1) + update_freelook_sight() + if(safe) + CheckForMobs() + else + crush() + return 1 + +/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(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.Knockdown(100) + else if(ismonkey(L)) //For monkeys + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + L.Knockdown(100) + else //for simple_animals & borgs + L.adjustBruteLoss(DOOR_CRUSH_DAMAGE) + var/turf/location = get_turf(src) + //add_blood_DNA doesn't work for borgs/xenos, but add_blood_floor does. + if(iscarbon(L)) + var/mob/living/carbon/C = L + C.bleed(DOOR_CRUSH_DAMAGE) + else + L.add_splatter_floor(location) + for(var/obj/mecha/M in get_turf(src)) + M.take_damage(DOOR_CRUSH_DAMAGE) + +/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 !(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(!stat) //So that only powered doors are closed. + close() //Close ALL the doors! + +/obj/machinery/door/proc/disable_lockdown() + if(!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 diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 39b6c8da1d..0429db5792 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -1,476 +1,476 @@ -#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() - if(powered(power_channel)) - stat &= ~NOPOWER - latetoggle() - else - stat |= NOPOWER - -/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(istype(C, /obj/item/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, 1) - user.visible_message("[user] unfastens [src]'s bolts.", \ - "You undo [src]'s floor bolts.") - deconstruct(TRUE) - return - if(istype(C, /obj/item/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 || 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() - cut_overlays() - if(density) - icon_state = "door_closed" - if(welded) - add_overlay("welded") - else - icon_state = "door_open" - if(welded) - add_overlay("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 || 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' - 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/CanPass(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 !density - else - 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" - 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() - ..() - icon_state = "frame[constructionStep]" - -/obj/structure/firelock_frame/attackby(obj/item/C, mob/user) - switch(constructionStep) - if(CONSTRUCTION_PANEL_OPEN) - if(istype(C, /obj/item/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, 1) - 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(istype(C, /obj/item/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, 1) - 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, 1) - 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, 1) - P.use(2) - reinforced = 1 - return - - if(CONSTRUCTION_WIRES_EXPOSED) - if(istype(C, /obj/item/wirecutters)) - 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(istype(C, /obj/item/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, 1) - 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(istype(C, /obj/item/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, 1) - 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, 1) - B.use(5) - constructionStep = CONSTRUCTION_WIRES_EXPOSED - update_icon() - return - if(CONSTRUCTION_NOCIRCUIT) - if(istype(C, /obj/item/weldingtool)) - 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, 1) - 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, 1) - 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((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 - 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() + if(powered(power_channel)) + stat &= ~NOPOWER + latetoggle() + else + stat |= NOPOWER + +/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(istype(C, /obj/item/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, 1) + user.visible_message("[user] unfastens [src]'s bolts.", \ + "You undo [src]'s floor bolts.") + deconstruct(TRUE) + return + if(istype(C, /obj/item/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 || 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() + cut_overlays() + if(density) + icon_state = "door_closed" + if(welded) + add_overlay("welded") + else + icon_state = "door_open" + if(welded) + add_overlay("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 || 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' + 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/CanPass(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 !density + else + 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" + 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() + ..() + icon_state = "frame[constructionStep]" + +/obj/structure/firelock_frame/attackby(obj/item/C, mob/user) + switch(constructionStep) + if(CONSTRUCTION_PANEL_OPEN) + if(istype(C, /obj/item/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, 1) + 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(istype(C, /obj/item/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, 1) + 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, 1) + 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, 1) + P.use(2) + reinforced = 1 + return + + if(CONSTRUCTION_WIRES_EXPOSED) + if(istype(C, /obj/item/wirecutters)) + 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(istype(C, /obj/item/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, 1) + 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(istype(C, /obj/item/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, 1) + 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, 1) + B.use(5) + constructionStep = CONSTRUCTION_WIRES_EXPOSED + update_icon() + return + if(CONSTRUCTION_NOCIRCUIT) + if(istype(C, /obj/item/weldingtool)) + 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, 1) + 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, 1) + 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((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 + 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 ebc1e94e97..af95ee230c 100644 --- a/code/game/machinery/doors/poddoor.dm +++ b/code/game/machinery/doors/poddoor.dm @@ -1,91 +1,91 @@ -/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/preopen - icon_state = "open" - density = FALSE - opacity = 0 - -/obj/machinery/door/poddoor/ert - desc = "A heavy duty blast door that only opens for dire emergencies." - -//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, 1) - if("closing") - flick("closing", src) - playsound(src, 'sound/machines/blastdoor.ogg', 30, 1) - -/obj/machinery/door/poddoor/update_icon() - 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(stat & NOPOWER) - open(1) +/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/preopen + icon_state = "open" + density = FALSE + opacity = 0 + +/obj/machinery/door/poddoor/ert + desc = "A heavy duty blast door that only opens for dire emergencies." + +//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, 1) + if("closing") + flick("closing", src) + playsound(src, 'sound/machines/blastdoor.ogg', 30, 1) + +/obj/machinery/door/poddoor/update_icon() + 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(stat & NOPOWER) + open(1) diff --git a/code/game/machinery/doors/shutters.dm b/code/game/machinery/doors/shutters.dm index 7ee9891c4c..bd58649b97 100644 --- a/code/game/machinery/doors/shutters.dm +++ b/code/game/machinery/doors/shutters.dm @@ -1,13 +1,13 @@ -/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 + 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 diff --git a/code/game/machinery/doors/unpowered.dm b/code/game/machinery/doors/unpowered.dm index 702f700617..828624adb4 100644 --- a/code/game/machinery/doors/unpowered.dm +++ b/code/game/machinery/doors/unpowered.dm @@ -1,22 +1,22 @@ -/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/shuttle - icon = 'icons/turf/shuttle.dmi' - name = "door" - icon_state = "door1" - opacity = 1 - density = TRUE +/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/shuttle + icon = 'icons/turf/shuttle.dmi' + name = "door" + icon_state = "door1" + opacity = 1 + density = TRUE explosion_block = 1 \ No newline at end of file diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 933bb4f42a..bd9b7325fb 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -1,535 +1,535 @@ -/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) - . = ..() - if(set_dir) - setDir(set_dir) - if(src.req_access && src.req_access.len) - src.icon_state = "[src.icon_state]" - src.base_state = src.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, 1) - electronics = null - return ..() - -/obj/machinery/door/window/update_icon() - if(density) - icon_state = base_state - else - icon_state = "[src.base_state]open" - -/obj/machinery/door/window/proc/open_and_close() - open() - if(src.check_access(null)) - sleep(50) - else //secure doors close faster - sleep(20) - close() - -/obj/machinery/door/window/Bumped(atom/movable/AM) - if( operating || !src.density ) - return - if (!( ismob(AM) )) - if(ismecha(AM)) - var/obj/mecha/mecha = AM - if(mecha.occupant && src.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 || !src.density ) - return - src.add_fingerprint(user) - if(!src.requiresID()) - user = null - - if(allowed(user)) - open_and_close() - else - do_animate("deny") - return - -/obj/machinery/door/window/CanPass(atom/movable/mover, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return 1 - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border - return !density - 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 1 - -/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=0) - if (src.operating == 1) //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(!src.operating) //in case of emag - operating = TRUE - do_animate("opening") - playsound(src.loc, 'sound/machines/windowdoor.ogg', 100, 1) - src.icon_state ="[src.base_state]open" - sleep(10) - - density = FALSE -// src.sd_set_opacity(0) //TODO: why is this here? Opaque windoors? ~Carn - air_update_turf(1) - update_freelook_sight() - - if(operating == 1) //emag again - operating = FALSE - return 1 - -/obj/machinery/door/window/close(forced=0) - if (src.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.loc, 'sound/machines/windowdoor.ogg', 100, 1) - src.icon_state = src.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(loc, 'sound/effects/glasshit.ogg', 90, 1) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, 1) - - -/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/ratvar_act() - var/obj/machinery/door/window/clockwork/C = new(loc, dir) - C.name = name - qdel(src) - -/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) - return - obj_flags |= EMAGGED - operating = TRUE - flick("[src.base_state]spark", src) - playsound(src, "sparks", 75, 1) - addtimer(CALLBACK(src, .proc/open_windows_me), 6) - return TRUE - -/obj/machinery/door/window/proc/open_windows_me() - 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(istype(I, /obj/item/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 [src.name].") - return - - if(istype(I, /obj/item/crowbar)) - if(panel_open && !density && !operating) - user.visible_message("[user] removes the electronics from the [src.name].", \ - "You start to remove electronics from the [src.name]...") - if(I.use_tool(src, user, 40, volume=50)) - if(panel_open && !density && !operating && src.loc) - var/obj/structure/windoor_assembly/WA = new /obj/structure/windoor_assembly(src.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(src.dir) - WA.ini_dir = src.dir - WA.update_icon() - WA.created_name = src.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( src.loc ) - if(req_one_access) - ae.one_access = 1 - ae.accesses = src.req_one_access - else - ae.accesses = src.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_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("[src.base_state]opening", src) - if("closing") - flick("[src.base_state]closing", src) - if("deny") - flick("[src.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/clockwork - name = "brass windoor" - desc = "A thin door with translucent brass paneling." - icon_state = "clockwork" - base_state = "clockwork" - shards = 0 - rods = 0 - resistance_flags = FIRE_PROOF | ACID_PROOF - var/made_glow = FALSE - -/obj/machinery/door/window/clockwork/Initialize(mapload, set_dir) - . = ..() - for(var/i in 1 to 2) - debris += new/obj/item/clockwork/alloy_shards/medium/gear_bit/large(src) - change_construction_value(2) - -/obj/machinery/door/window/clockwork/setDir(direct) - if(!made_glow) - var/obj/effect/E = new /obj/effect/temp_visual/ratvar/door/window(get_turf(src)) - E.setDir(direct) - made_glow = TRUE - ..() - -/obj/machinery/door/window/clockwork/Destroy() - change_construction_value(-2) - return ..() - -/obj/machinery/door/window/clockwork/emp_act(severity) - if(prob(80/severity)) - open() - -/obj/machinery/door/window/clockwork/ratvar_act() - if(GLOB.ratvar_awakens) - obj_integrity = max_integrity - -/obj/machinery/door/window/clockwork/hasPower() - return TRUE //yup that's power all right - -/obj/machinery/door/window/clockwork/narsie_act() - take_damage(rand(30, 60), BRUTE) - if(src) - var/previouscolor = color - color = "#960000" - animate(src, color = previouscolor, time = 8) - addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) - -/obj/machinery/door/window/clockwork/allowed(mob/M) - if(is_servant_of_ratvar(M)) - return 1 - return 0 - -/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) + . = ..() + if(set_dir) + setDir(set_dir) + if(src.req_access && src.req_access.len) + src.icon_state = "[src.icon_state]" + src.base_state = src.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, 1) + electronics = null + return ..() + +/obj/machinery/door/window/update_icon() + if(density) + icon_state = base_state + else + icon_state = "[src.base_state]open" + +/obj/machinery/door/window/proc/open_and_close() + open() + if(src.check_access(null)) + sleep(50) + else //secure doors close faster + sleep(20) + close() + +/obj/machinery/door/window/Bumped(atom/movable/AM) + if( operating || !src.density ) + return + if (!( ismob(AM) )) + if(ismecha(AM)) + var/obj/mecha/mecha = AM + if(mecha.occupant && src.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 || !src.density ) + return + src.add_fingerprint(user) + if(!src.requiresID()) + user = null + + if(allowed(user)) + open_and_close() + else + do_animate("deny") + return + +/obj/machinery/door/window/CanPass(atom/movable/mover, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return 1 + if(get_dir(loc, target) == dir) //Make sure looking at appropriate border + return !density + 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 1 + +/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=0) + if (src.operating == 1) //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(!src.operating) //in case of emag + operating = TRUE + do_animate("opening") + playsound(src.loc, 'sound/machines/windowdoor.ogg', 100, 1) + src.icon_state ="[src.base_state]open" + sleep(10) + + density = FALSE +// src.sd_set_opacity(0) //TODO: why is this here? Opaque windoors? ~Carn + air_update_turf(1) + update_freelook_sight() + + if(operating == 1) //emag again + operating = FALSE + return 1 + +/obj/machinery/door/window/close(forced=0) + if (src.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.loc, 'sound/machines/windowdoor.ogg', 100, 1) + src.icon_state = src.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(loc, 'sound/effects/glasshit.ogg', 90, 1) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, 1) + + +/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/ratvar_act() + var/obj/machinery/door/window/clockwork/C = new(loc, dir) + C.name = name + qdel(src) + +/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) + return + obj_flags |= EMAGGED + operating = TRUE + flick("[src.base_state]spark", src) + playsound(src, "sparks", 75, 1) + addtimer(CALLBACK(src, .proc/open_windows_me), 6) + return TRUE + +/obj/machinery/door/window/proc/open_windows_me() + 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(istype(I, /obj/item/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 [src.name].") + return + + if(istype(I, /obj/item/crowbar)) + if(panel_open && !density && !operating) + user.visible_message("[user] removes the electronics from the [src.name].", \ + "You start to remove electronics from the [src.name]...") + if(I.use_tool(src, user, 40, volume=50)) + if(panel_open && !density && !operating && src.loc) + var/obj/structure/windoor_assembly/WA = new /obj/structure/windoor_assembly(src.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(src.dir) + WA.ini_dir = src.dir + WA.update_icon() + WA.created_name = src.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( src.loc ) + if(req_one_access) + ae.one_access = 1 + ae.accesses = src.req_one_access + else + ae.accesses = src.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_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("[src.base_state]opening", src) + if("closing") + flick("[src.base_state]closing", src) + if("deny") + flick("[src.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/clockwork + name = "brass windoor" + desc = "A thin door with translucent brass paneling." + icon_state = "clockwork" + base_state = "clockwork" + shards = 0 + rods = 0 + resistance_flags = FIRE_PROOF | ACID_PROOF + var/made_glow = FALSE + +/obj/machinery/door/window/clockwork/Initialize(mapload, set_dir) + . = ..() + for(var/i in 1 to 2) + debris += new/obj/item/clockwork/alloy_shards/medium/gear_bit/large(src) + change_construction_value(2) + +/obj/machinery/door/window/clockwork/setDir(direct) + if(!made_glow) + var/obj/effect/E = new /obj/effect/temp_visual/ratvar/door/window(get_turf(src)) + E.setDir(direct) + made_glow = TRUE + ..() + +/obj/machinery/door/window/clockwork/Destroy() + change_construction_value(-2) + return ..() + +/obj/machinery/door/window/clockwork/emp_act(severity) + if(prob(80/severity)) + open() + +/obj/machinery/door/window/clockwork/ratvar_act() + if(GLOB.ratvar_awakens) + obj_integrity = max_integrity + +/obj/machinery/door/window/clockwork/hasPower() + return TRUE //yup that's power all right + +/obj/machinery/door/window/clockwork/narsie_act() + take_damage(rand(30, 60), BRUTE) + if(src) + var/previouscolor = color + color = "#960000" + animate(src, color = previouscolor, time = 8) + addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) + +/obj/machinery/door/window/clockwork/allowed(mob/M) + if(is_servant_of_ratvar(M)) + return 1 + return 0 + +/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 65f7602215..db806ef3ac 100644 --- a/code/game/machinery/doppler_array.dm +++ b/code/game/machinery/doppler_array.dm @@ -1,188 +1,188 @@ -GLOBAL_LIST_EMPTY(doppler_arrays) - -/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 - var/integrated = FALSE - var/list_limit = 100 - var/cooldown = 10 - var/next_announce = 0 - var/max_dist = 150 - verb_say = "states coldly" - var/list/message_log = list() - -/obj/machinery/doppler_array/Initialize() - . = ..() - GLOB.doppler_arrays += src - -/obj/machinery/doppler_array/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE,null,null,CALLBACK(src,.proc/rot_message)) - -/obj/machinery/doppler_array/Destroy() - GLOB.doppler_arrays -= src - return ..() - -/obj/machinery/doppler_array/ui_interact(mob/user) - . = ..() - if(stat) - return FALSE - - var/list/dat = list() - for(var/i in 1 to LAZYLEN(message_log)) - dat += "Log recording #[i]: [message_log[i]]

    " - dat += "Delete logs
    " - dat += "
    " - dat += "(Refresh)
    " - dat += "" - var/datum/browser/popup = new(user, "computer", name, 400, 500) - popup.set_content(dat.Join(" ")) - popup.open() - -/obj/machinery/doppler_array/Topic(href, href_list) - if(..()) - return - if(href_list["delete_log"]) - LAZYCLEARLIST(message_log) - if(href_list["refresh"]) - updateUsrDialog() - - updateUsrDialog() - return - -/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/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) - else - 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, 1) - -/obj/machinery/doppler_array/proc/sense_explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, - took, orig_dev_range, orig_heavy_range, orig_light_range) - if(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) && !integrated) - return FALSE - - - 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]." - - if(integrated) - var/obj/item/clothing/head/helmet/space/hardsuit/helm = loc - if(!helm || !istype(helm, /obj/item/clothing/head/helmet/space/hardsuit)) - return FALSE - helm.display_visor_message("Explosion detected! Epicenter: [devastation_range], Outer: [heavy_impact_range], Shock: [light_impact_range]") - else - for(var/message in messages) - say(message) - if(LAZYLEN(message_log) > list_limit) - say("Storage buffer is full! Clearing buffers...") - LAZYCLEARLIST(message_log) - LAZYADD(message_log, messages.Join(" ")) - return TRUE - -/obj/machinery/doppler_array/examine(mob/user) - . = ..() - . += "Its dish is facing to the [dir2text(dir)]." - -/obj/machinery/doppler_array/process() - return PROCESS_KILL - -/obj/machinery/doppler_array/power_change() - if(stat & BROKEN) - icon_state = "[initial(icon_state)]-broken" - else - if(powered() && anchored) - icon_state = initial(icon_state) - stat &= ~NOPOWER - else - icon_state = "[initial(icon_state)]-off" - stat |= NOPOWER - -//Portable version, built into EOD equipment. It simply provides an explosion's three damage levels. -/obj/machinery/doppler_array/integrated - name = "integrated tachyon-doppler module" - integrated = TRUE - max_dist = 21 //Should detect most explosions in hearing range. - use_power = NO_POWER_USE - -/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(turf/epicenter, dev, heavy, light, time, orig_dev, orig_heavy, orig_light) //probably needs a way to ignore admin explosives later on - . = ..() - if(!.) - return FALSE - if(!istype(linked_techweb)) - say("Warning: No linked research system!") - return - - var/point_gain = 0 - - /*****The Point Calculator*****/ - - if(orig_light < 10) - say("Explosion not large enough for research calculations.") - return - else if(orig_light < 4500) - point_gain = (83300 * orig_light) / (orig_light + 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 - - linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain) - say("Gained [point_gain] points from explosion dataset.") - - else //you've made smaller bombs - say("Data already captured. Aborting.") - return - - -/obj/machinery/doppler_array/research/science/Initialize() - . = ..() +GLOBAL_LIST_EMPTY(doppler_arrays) + +/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 + var/integrated = FALSE + var/list_limit = 100 + var/cooldown = 10 + var/next_announce = 0 + var/max_dist = 150 + verb_say = "states coldly" + var/list/message_log = list() + +/obj/machinery/doppler_array/Initialize() + . = ..() + GLOB.doppler_arrays += src + +/obj/machinery/doppler_array/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE,null,null,CALLBACK(src,.proc/rot_message)) + +/obj/machinery/doppler_array/Destroy() + GLOB.doppler_arrays -= src + return ..() + +/obj/machinery/doppler_array/ui_interact(mob/user) + . = ..() + if(stat) + return FALSE + + var/list/dat = list() + for(var/i in 1 to LAZYLEN(message_log)) + dat += "Log recording #[i]: [message_log[i]]

    " + dat += "Delete logs
    " + dat += "
    " + dat += "(Refresh)
    " + dat += "" + var/datum/browser/popup = new(user, "computer", name, 400, 500) + popup.set_content(dat.Join(" ")) + popup.open() + +/obj/machinery/doppler_array/Topic(href, href_list) + if(..()) + return + if(href_list["delete_log"]) + LAZYCLEARLIST(message_log) + if(href_list["refresh"]) + updateUsrDialog() + + updateUsrDialog() + return + +/obj/machinery/doppler_array/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/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) + else + 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, 1) + +/obj/machinery/doppler_array/proc/sense_explosion(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, + took, orig_dev_range, orig_heavy_range, orig_light_range) + if(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) && !integrated) + return FALSE + + + 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]." + + if(integrated) + var/obj/item/clothing/head/helmet/space/hardsuit/helm = loc + if(!helm || !istype(helm, /obj/item/clothing/head/helmet/space/hardsuit)) + return FALSE + helm.display_visor_message("Explosion detected! Epicenter: [devastation_range], Outer: [heavy_impact_range], Shock: [light_impact_range]") + else + for(var/message in messages) + say(message) + if(LAZYLEN(message_log) > list_limit) + say("Storage buffer is full! Clearing buffers...") + LAZYCLEARLIST(message_log) + LAZYADD(message_log, messages.Join(" ")) + return TRUE + +/obj/machinery/doppler_array/examine(mob/user) + . = ..() + . += "Its dish is facing to the [dir2text(dir)]." + +/obj/machinery/doppler_array/process() + return PROCESS_KILL + +/obj/machinery/doppler_array/power_change() + if(stat & BROKEN) + icon_state = "[initial(icon_state)]-broken" + else + if(powered() && anchored) + icon_state = initial(icon_state) + stat &= ~NOPOWER + else + icon_state = "[initial(icon_state)]-off" + stat |= NOPOWER + +//Portable version, built into EOD equipment. It simply provides an explosion's three damage levels. +/obj/machinery/doppler_array/integrated + name = "integrated tachyon-doppler module" + integrated = TRUE + max_dist = 21 //Should detect most explosions in hearing range. + use_power = NO_POWER_USE + +/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(turf/epicenter, dev, heavy, light, time, orig_dev, orig_heavy, orig_light) //probably needs a way to ignore admin explosives later on + . = ..() + if(!.) + return FALSE + if(!istype(linked_techweb)) + say("Warning: No linked research system!") + return + + var/point_gain = 0 + + /*****The Point Calculator*****/ + + if(orig_light < 10) + say("Explosion not large enough for research calculations.") + return + else if(orig_light < 4500) + point_gain = (83300 * orig_light) / (orig_light + 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 + + linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain) + say("Gained [point_gain] points from explosion dataset.") + + else //you've made smaller bombs + say("Data already captured. Aborting.") + return + + +/obj/machinery/doppler_array/research/science/Initialize() + . = ..() linked_techweb = SSresearch.science_tech \ No newline at end of file diff --git a/code/game/machinery/embedded_controller/access_controller.dm b/code/game/machinery/embedded_controller/access_controller.dm index 3b9322d207..ad49abb398 100644 --- a/code/game/machinery/embedded_controller/access_controller.dm +++ b/code/game/machinery/embedded_controller/access_controller.dm @@ -1,315 +1,315 @@ -#define CLOSING 1 -#define OPENING 2 -#define CYCLE 3 -#define CYCLE_EXTERIOR 4 -#define CYCLE_INTERIOR 5 - -/obj/machinery/doorButtons - power_channel = 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/doorButtons/attackby(obj/O, mob/user) - return attack_hand(user) - -/obj/machinery/doorButtons/proc/findObjsByTag() - return - -/obj/machinery/doorButtons/Initialize() - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/machinery/doorButtons/LateInitialize() - findObjsByTag() - -/obj/machinery/doorButtons/emag_act(mob/user) - . = ..() - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - req_access = list() - req_one_access = list() - playsound(src, "sparks", 100, 1) - to_chat(user, "You short out the access controller.") - return TRUE - -/obj/machinery/doorButtons/proc/removeMe() - - -/obj/machinery/doorButtons/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/doorButtons/airlock_controller/controller - var/busy - -/obj/machinery/doorButtons/access_button/findObjsByTag() - for(var/obj/machinery/doorButtons/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/doorButtons/access_button/interact(mob/user) - if(busy) - return - if(!allowed(user)) - to_chat(user, "Access denied.") - return - if(controller && !controller.busy && door) - if(controller.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/doorButtons/access_button/update_icon() - if(stat & NOPOWER) - icon_state = "access_button_off" - else - if(busy) - icon_state = "access_button_cycle" - else - icon_state = "access_button_standby" - -/obj/machinery/doorButtons/access_button/power_change() - ..() - update_icon() - -/obj/machinery/doorButtons/access_button/removeMe(obj/O) - if(O == door) - door = null - - - -/obj/machinery/doorButtons/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/doorButtons/airlock_controller/removeMe(obj/O) - if(O == interiorAirlock) - interiorAirlock = null - else if(O == exteriorAirlock) - exteriorAirlock = null - -/obj/machinery/doorButtons/airlock_controller/Destroy() - for(var/obj/machinery/doorButtons/access_button/A in GLOB.machines) - if(A.controller == src) - A.controller = null - return ..() - -/obj/machinery/doorButtons/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/doorButtons/airlock_controller/proc/onlyOpen(obj/machinery/door/airlock/A) - if(A) - busy = CLOSING - update_icon() - openDoor(A) - -/obj/machinery/doorButtons/airlock_controller/proc/onlyClose(obj/machinery/door/airlock/A) - if(A) - busy = CLOSING - closeDoor(A) - -/obj/machinery/doorButtons/airlock_controller/proc/closeDoor(obj/machinery/door/airlock/A) - set waitfor = FALSE - if(A.density) - goIdle() - return 0 - update_icon() - A.unbolt() - . = 1 - if(A && A.close()) - if(stat & NOPOWER || lostPower || !A || QDELETED(A)) - goIdle(1) - return - A.bolt() - if(busy == CLOSING) - goIdle(1) - else - goIdle(1) - -/obj/machinery/doorButtons/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/doorButtons/airlock_controller/proc/cycleOpen(obj/machinery/door/airlock/A) - if(!A) - goIdle(1) - 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/doorButtons/airlock_controller/proc/openDoor(obj/machinery/door/airlock/A) - if(exteriorAirlock && interiorAirlock && (!exteriorAirlock.density || !interiorAirlock.density)) - goIdle(1) - return - A.unbolt() - spawn() - if(A && A.open()) - if(stat | (NOPOWER) && !lostPower && A && !QDELETED(A)) - A.bolt() - goIdle(1) - -/obj/machinery/doorButtons/airlock_controller/proc/goIdle(update) - lostPower = 0 - busy = FALSE - if(update) - update_icon() - updateUsrDialog() - -/obj/machinery/doorButtons/airlock_controller/process() - if(stat & NOPOWER) - return - if(busy == CYCLE_EXTERIOR) - cycleOpen(exteriorAirlock) - else if(busy == CYCLE_INTERIOR) - cycleOpen(interiorAirlock) - -/obj/machinery/doorButtons/airlock_controller/power_change() - ..() - if(stat & NOPOWER) - lostPower = 1 - else - if(!busy) - lostPower = 0 - update_icon() - -/obj/machinery/doorButtons/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/doorButtons/airlock_controller/update_icon() - if(stat & NOPOWER) - icon_state = "access_control_off" - return - if(busy || lostPower) - icon_state = "access_control_process" - else - icon_state = "access_control_standby" - -/obj/machinery/doorButtons/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/doorButtons/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/doorButtons + power_channel = 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/doorButtons/attackby(obj/O, mob/user) + return attack_hand(user) + +/obj/machinery/doorButtons/proc/findObjsByTag() + return + +/obj/machinery/doorButtons/Initialize() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/doorButtons/LateInitialize() + findObjsByTag() + +/obj/machinery/doorButtons/emag_act(mob/user) + . = ..() + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + req_access = list() + req_one_access = list() + playsound(src, "sparks", 100, 1) + to_chat(user, "You short out the access controller.") + return TRUE + +/obj/machinery/doorButtons/proc/removeMe() + + +/obj/machinery/doorButtons/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/doorButtons/airlock_controller/controller + var/busy + +/obj/machinery/doorButtons/access_button/findObjsByTag() + for(var/obj/machinery/doorButtons/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/doorButtons/access_button/interact(mob/user) + if(busy) + return + if(!allowed(user)) + to_chat(user, "Access denied.") + return + if(controller && !controller.busy && door) + if(controller.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/doorButtons/access_button/update_icon() + if(stat & NOPOWER) + icon_state = "access_button_off" + else + if(busy) + icon_state = "access_button_cycle" + else + icon_state = "access_button_standby" + +/obj/machinery/doorButtons/access_button/power_change() + ..() + update_icon() + +/obj/machinery/doorButtons/access_button/removeMe(obj/O) + if(O == door) + door = null + + + +/obj/machinery/doorButtons/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/doorButtons/airlock_controller/removeMe(obj/O) + if(O == interiorAirlock) + interiorAirlock = null + else if(O == exteriorAirlock) + exteriorAirlock = null + +/obj/machinery/doorButtons/airlock_controller/Destroy() + for(var/obj/machinery/doorButtons/access_button/A in GLOB.machines) + if(A.controller == src) + A.controller = null + return ..() + +/obj/machinery/doorButtons/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/doorButtons/airlock_controller/proc/onlyOpen(obj/machinery/door/airlock/A) + if(A) + busy = CLOSING + update_icon() + openDoor(A) + +/obj/machinery/doorButtons/airlock_controller/proc/onlyClose(obj/machinery/door/airlock/A) + if(A) + busy = CLOSING + closeDoor(A) + +/obj/machinery/doorButtons/airlock_controller/proc/closeDoor(obj/machinery/door/airlock/A) + set waitfor = FALSE + if(A.density) + goIdle() + return 0 + update_icon() + A.unbolt() + . = 1 + if(A && A.close()) + if(stat & NOPOWER || lostPower || !A || QDELETED(A)) + goIdle(1) + return + A.bolt() + if(busy == CLOSING) + goIdle(1) + else + goIdle(1) + +/obj/machinery/doorButtons/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/doorButtons/airlock_controller/proc/cycleOpen(obj/machinery/door/airlock/A) + if(!A) + goIdle(1) + 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/doorButtons/airlock_controller/proc/openDoor(obj/machinery/door/airlock/A) + if(exteriorAirlock && interiorAirlock && (!exteriorAirlock.density || !interiorAirlock.density)) + goIdle(1) + return + A.unbolt() + spawn() + if(A && A.open()) + if(stat | (NOPOWER) && !lostPower && A && !QDELETED(A)) + A.bolt() + goIdle(1) + +/obj/machinery/doorButtons/airlock_controller/proc/goIdle(update) + lostPower = 0 + busy = FALSE + if(update) + update_icon() + updateUsrDialog() + +/obj/machinery/doorButtons/airlock_controller/process() + if(stat & NOPOWER) + return + if(busy == CYCLE_EXTERIOR) + cycleOpen(exteriorAirlock) + else if(busy == CYCLE_INTERIOR) + cycleOpen(interiorAirlock) + +/obj/machinery/doorButtons/airlock_controller/power_change() + ..() + if(stat & NOPOWER) + lostPower = 1 + else + if(!busy) + lostPower = 0 + update_icon() + +/obj/machinery/doorButtons/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/doorButtons/airlock_controller/update_icon() + if(stat & NOPOWER) + icon_state = "access_control_off" + return + if(busy || lostPower) + icon_state = "access_control_process" + else + icon_state = "access_control_standby" + +/obj/machinery/doorButtons/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/doorButtons/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 0036b41804..9f1ebe67a9 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 = 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() - 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]"} - +//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 = 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() + 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 \ No newline at end of file diff --git a/code/game/machinery/embedded_controller/embedded_controller_base.dm b/code/game/machinery/embedded_controller/embedded_controller_base.dm index 063cfa6805..f1a2ef9acc 100644 --- a/code/game/machinery/embedded_controller/embedded_controller_base.dm +++ b/code/game/machinery/embedded_controller/embedded_controller_base.dm @@ -1,87 +1,87 @@ -/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/update_icon() - -/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/update_icon() + +/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 9901d51074..33be1e3f79 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 = 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() - 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 = 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() + 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/flasher.dm b/code/game/machinery/flasher.dm index def530a14d..6f89ce0d45 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 = 100 - light_color = LIGHT_COLOR_WHITE - light_power = FLASH_LIGHT_POWER - 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/Destroy() - QDEL_NULL(bulb) - return ..() - -/obj/machinery/flasher/power_change() - if (powered() && anchored && bulb) - stat &= ~NOPOWER - if(bulb.crit_fail) - icon_state = "[base_state]1-p" - else - icon_state = "[base_state]1" - else - stat |= NOPOWER - 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 (istype(W, /obj/item/wirecutters)) - 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] has disconnected [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 (istype(W, /obj/item/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/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(damage_flag == "melee" && damage_amount < 10) //any melee attack below 10 dmg does nothing - return 0 - . = ..() - -/obj/machinery/flasher/proc/flash() - if (!powered() || !bulb) - return - - if (bulb.crit_fail || (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, 1) - 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.Knockdown(strength) - flashed = TRUE - - if(flashed) - bulb.times_used++ - - return 1 - - -/obj/machinery/flasher/emp_act(severity) - . = ..() - if(!(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(!(flags_1 & NODECONSTRUCT_1)) - if(!(stat & BROKEN)) - stat |= BROKEN - 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, 1) - 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 (istype(W, /obj/item/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 = 100 + light_color = LIGHT_COLOR_WHITE + light_power = FLASH_LIGHT_POWER + 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/Destroy() + QDEL_NULL(bulb) + return ..() + +/obj/machinery/flasher/power_change() + if (powered() && anchored && bulb) + stat &= ~NOPOWER + if(bulb.crit_fail) + icon_state = "[base_state]1-p" + else + icon_state = "[base_state]1" + else + stat |= NOPOWER + 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 (istype(W, /obj/item/wirecutters)) + 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] has disconnected [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 (istype(W, /obj/item/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/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(damage_flag == "melee" && damage_amount < 10) //any melee attack below 10 dmg does nothing + return 0 + . = ..() + +/obj/machinery/flasher/proc/flash() + if (!powered() || !bulb) + return + + if (bulb.crit_fail || (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, 1) + 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.Knockdown(strength) + flashed = TRUE + + if(flashed) + bulb.times_used++ + + return 1 + + +/obj/machinery/flasher/emp_act(severity) + . = ..() + if(!(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(!(flags_1 & NODECONSTRUCT_1)) + if(!(stat & BROKEN)) + stat |= BROKEN + 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, 1) + 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 (istype(W, /obj/item/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 b1783ba940..40fc52d6a1 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -1,700 +1,700 @@ -/* 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 - */ - -GLOBAL_LIST_EMPTY(network_holopads) - -#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 - 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 - var/list/masters //List of living mobs that use the holopad - var/list/holorays //Holoray-mob link. - var/last_request = 0 //to prevent request spam. ~Carn - var/holo_range = 5 // Change to change how far the AI can move away from the holopad before deactivating. - var/temp = "" - var/list/holo_calls //array of /datum/holocalls - var/datum/holocall/outgoing_call //do not modify the datums only check and call the public procs - var/obj/item/disk/holodisk/disk //Record disk - var/replay_mode = FALSE //currently replaying a recording - var/loop_mode = FALSE //currently looping a recording - var/record_mode = FALSE //currently recording - var/record_start = 0 //recording start time - var/record_user //user that inititiated the recording - var/obj/effect/overlay/holo_pad_hologram/replay_holo //replay hologram - var/static/force_answer_call = FALSE //Calls will be automatically answered after a couple rings, here for debugging - var/obj/effect/overlay/holoray/ray - var/ringing = FALSE - var/offset = FALSE - var/on_network = TRUE - -/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) - GLOB.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) - - GLOB.network_holopads -= src - return ..() - -/obj/machinery/holopad/power_change() - if (powered()) - stat &= ~NOPOWER - else - stat |= NOPOWER - 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/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 - updateDialog() - return - - return ..() - - -/obj/machinery/holopad/ui_interact(mob/living/carbon/human/user) //Carn: Hologram requests. - . = ..() - if(!istype(user)) - return - - if(outgoing_call || user.incapacitated() || !is_operational()) - return - - user.set_machine(src) - var/dat - if(temp) - dat = temp - else - if(on_network) - dat += "Request an AI's presence
    " - dat += "Call another holopad
    " - if(disk) - if(disk.record) - //Replay - dat += "Replay disk recording
    " - dat += "Loop disk recording
    " - //Clear - dat += "Clear disk recording
    " - else - //Record - dat += "Start new recording
    " - //Eject - dat += "Eject disk
    " - - if(LAZYLEN(holo_calls)) - dat += "=====================================================
    " - - if(on_network) - var/one_answered_call = FALSE - var/one_unanswered_call = FALSE - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad != src) - dat += "Answer call from [get_area(HC.calling_holopad)]
    " - one_unanswered_call = TRUE - else - one_answered_call = TRUE - - if(one_answered_call && one_unanswered_call) - dat += "=====================================================
    " - //we loop twice for formatting - for(var/I in holo_calls) - var/datum/holocall/HC = I - if(HC.connected_holopad == src) - dat += "Disconnect call from [HC.user]
    " - - - var/datum/browser/popup = new(user, "holopad", name, 300, 175) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -//Stop ringing the AI!! -/obj/machinery/holopad/proc/hangup_all_calls() - for(var/I in holo_calls) - var/datum/holocall/HC = I - HC.Disconnect(src) - -/obj/machinery/holopad/Topic(href, href_list) - if(..() || isAI(usr)) - return - add_fingerprint(usr) - if(!is_operational()) - return - if (href_list["AIrequest"]) - if(last_request + 200 < world.time) - last_request = world.time - temp = "You requested an AI's presence.
    " - temp += "Main Menu" - 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].") - else - temp = "A request for AI presence was already sent recently.
    " - temp += "Main Menu" - - else if(href_list["Holocall"]) - if(outgoing_call) - return - - temp = "You must stand on the holopad to make a call!
    " - temp += "Main Menu" - if(usr.loc == loc) - var/list/callnames = list() - for(var/I in GLOB.network_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 callnames - if(QDELETED(usr) || !result || outgoing_call) - return - - if(usr.loc == loc) - temp = "Dialing...
    " - temp += "Main Menu" - new /datum/holocall(usr, src, callnames[result]) - - else if(href_list["connectcall"]) - var/datum/holocall/call_to_connect = locate(href_list["connectcall"]) - if(!QDELETED(call_to_connect)) - call_to_connect.Answer(src) - temp = "" - - else if(href_list["disconnectcall"]) - var/datum/holocall/call_to_disconnect = locate(href_list["disconnectcall"]) - if(!QDELETED(call_to_disconnect)) - call_to_disconnect.Disconnect(src) - temp = "" - - else if(href_list["mainmenu"]) - temp = "" - if(outgoing_call) - outgoing_call.Disconnect() - - else if(href_list["disk_eject"]) - if(disk && !replay_mode) - disk.forceMove(drop_location()) - disk = null - - else if(href_list["replay_stop"]) - replay_stop() - else if(href_list["replay_start"]) - replay_start() - else if(href_list["loop_start"]) - loop_mode = TRUE - replay_start() - else if(href_list["record_start"]) - record_start(usr) - else if(href_list["record_stop"]) - record_stop() - else if(href_list["record_clear"]) - record_clear() - else if(href_list["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) - updateDialog() - -//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(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.copy_known_languages_from(user,replace = TRUE) - 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, atom/movable/source) - . = ..() - 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, source) - - 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() - 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, var/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 GLOB.network_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, direction) - 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(direction) - holo.setDir(direction) - 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 - Hologram.grant_all_languages(omnitongue=TRUE) - var/datum/language_holder/holder = Hologram.get_language_holder() - holder.selected_default_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) - temp = "Replaying...
    " - temp += "Change offset
    " - temp += "End replay" - SetLightsAndPower() - replay_entry(1) - return - -/obj/machinery/holopad/proc/replay_stop() - if(replay_mode) - replay_mode = FALSE - loop_mode = FALSE - offset = FALSE - temp = null - QDEL_NULL(replay_holo) - SetLightsAndPower() - updateDialog() - -/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) - temp = "Recording...
    " - temp += "End recording." - -/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,1) - 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_default_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 - temp = null - record_user = null - updateDialog() - -/obj/machinery/holopad/proc/record_clear() - if(disk && disk.record) - QDEL_NULL(disk.record) - updateDialog() - -/obj/effect/overlay/holo_pad_hologram - 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 + */ + +GLOBAL_LIST_EMPTY(network_holopads) + +#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 + 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 + var/list/masters //List of living mobs that use the holopad + var/list/holorays //Holoray-mob link. + var/last_request = 0 //to prevent request spam. ~Carn + var/holo_range = 5 // Change to change how far the AI can move away from the holopad before deactivating. + var/temp = "" + var/list/holo_calls //array of /datum/holocalls + var/datum/holocall/outgoing_call //do not modify the datums only check and call the public procs + var/obj/item/disk/holodisk/disk //Record disk + var/replay_mode = FALSE //currently replaying a recording + var/loop_mode = FALSE //currently looping a recording + var/record_mode = FALSE //currently recording + var/record_start = 0 //recording start time + var/record_user //user that inititiated the recording + var/obj/effect/overlay/holo_pad_hologram/replay_holo //replay hologram + var/static/force_answer_call = FALSE //Calls will be automatically answered after a couple rings, here for debugging + var/obj/effect/overlay/holoray/ray + var/ringing = FALSE + var/offset = FALSE + var/on_network = TRUE + +/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) + GLOB.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) + + GLOB.network_holopads -= src + return ..() + +/obj/machinery/holopad/power_change() + if (powered()) + stat &= ~NOPOWER + else + stat |= NOPOWER + 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/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 + updateDialog() + return + + return ..() + + +/obj/machinery/holopad/ui_interact(mob/living/carbon/human/user) //Carn: Hologram requests. + . = ..() + if(!istype(user)) + return + + if(outgoing_call || user.incapacitated() || !is_operational()) + return + + user.set_machine(src) + var/dat + if(temp) + dat = temp + else + if(on_network) + dat += "Request an AI's presence
    " + dat += "Call another holopad
    " + if(disk) + if(disk.record) + //Replay + dat += "Replay disk recording
    " + dat += "Loop disk recording
    " + //Clear + dat += "Clear disk recording
    " + else + //Record + dat += "Start new recording
    " + //Eject + dat += "Eject disk
    " + + if(LAZYLEN(holo_calls)) + dat += "=====================================================
    " + + if(on_network) + var/one_answered_call = FALSE + var/one_unanswered_call = FALSE + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad != src) + dat += "Answer call from [get_area(HC.calling_holopad)]
    " + one_unanswered_call = TRUE + else + one_answered_call = TRUE + + if(one_answered_call && one_unanswered_call) + dat += "=====================================================
    " + //we loop twice for formatting + for(var/I in holo_calls) + var/datum/holocall/HC = I + if(HC.connected_holopad == src) + dat += "Disconnect call from [HC.user]
    " + + + var/datum/browser/popup = new(user, "holopad", name, 300, 175) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +//Stop ringing the AI!! +/obj/machinery/holopad/proc/hangup_all_calls() + for(var/I in holo_calls) + var/datum/holocall/HC = I + HC.Disconnect(src) + +/obj/machinery/holopad/Topic(href, href_list) + if(..() || isAI(usr)) + return + add_fingerprint(usr) + if(!is_operational()) + return + if (href_list["AIrequest"]) + if(last_request + 200 < world.time) + last_request = world.time + temp = "You requested an AI's presence.
    " + temp += "Main Menu" + 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].") + else + temp = "A request for AI presence was already sent recently.
    " + temp += "Main Menu" + + else if(href_list["Holocall"]) + if(outgoing_call) + return + + temp = "You must stand on the holopad to make a call!
    " + temp += "Main Menu" + if(usr.loc == loc) + var/list/callnames = list() + for(var/I in GLOB.network_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 callnames + if(QDELETED(usr) || !result || outgoing_call) + return + + if(usr.loc == loc) + temp = "Dialing...
    " + temp += "Main Menu" + new /datum/holocall(usr, src, callnames[result]) + + else if(href_list["connectcall"]) + var/datum/holocall/call_to_connect = locate(href_list["connectcall"]) + if(!QDELETED(call_to_connect)) + call_to_connect.Answer(src) + temp = "" + + else if(href_list["disconnectcall"]) + var/datum/holocall/call_to_disconnect = locate(href_list["disconnectcall"]) + if(!QDELETED(call_to_disconnect)) + call_to_disconnect.Disconnect(src) + temp = "" + + else if(href_list["mainmenu"]) + temp = "" + if(outgoing_call) + outgoing_call.Disconnect() + + else if(href_list["disk_eject"]) + if(disk && !replay_mode) + disk.forceMove(drop_location()) + disk = null + + else if(href_list["replay_stop"]) + replay_stop() + else if(href_list["replay_start"]) + replay_start() + else if(href_list["loop_start"]) + loop_mode = TRUE + replay_start() + else if(href_list["record_start"]) + record_start(usr) + else if(href_list["record_stop"]) + record_stop() + else if(href_list["record_clear"]) + record_clear() + else if(href_list["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) + updateDialog() + +//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(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.copy_known_languages_from(user,replace = TRUE) + 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, atom/movable/source) + . = ..() + 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, source) + + 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() + 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, var/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 GLOB.network_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, direction) + 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(direction) + holo.setDir(direction) + 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 + Hologram.grant_all_languages(omnitongue=TRUE) + var/datum/language_holder/holder = Hologram.get_language_holder() + holder.selected_default_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) + temp = "Replaying...
    " + temp += "Change offset
    " + temp += "End replay" + SetLightsAndPower() + replay_entry(1) + return + +/obj/machinery/holopad/proc/replay_stop() + if(replay_mode) + replay_mode = FALSE + loop_mode = FALSE + offset = FALSE + temp = null + QDEL_NULL(replay_holo) + SetLightsAndPower() + updateDialog() + +/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) + temp = "Recording...
    " + temp += "End recording." + +/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,1) + 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_default_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 + temp = null + record_user = null + updateDialog() + +/obj/machinery/holopad/proc/record_clear() + if(disk && disk.record) + QDEL_NULL(disk.record) + updateDialog() + +/obj/effect/overlay/holo_pad_hologram + 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 acc0505256..099b51db82 100644 --- a/code/game/machinery/igniter.dm +++ b/code/game/machinery/igniter.dm @@ -1,137 +1,137 @@ -/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 ) - icon_state = "igniter[on]" - -/obj/machinery/igniter/process() //ugh why is this even in process()? - if (src.on && !(stat & NOPOWER) ) - var/turf/location = src.loc - if (isturf(location)) - location.hotspot_expose(700,10,1) - return 1 - -/obj/machinery/igniter/Initialize() - . = ..() - icon_state = "igniter[on]" - -/obj/machinery/igniter/power_change() - if(!( stat & NOPOWER) ) - icon_state = "igniter[src.on]" - else - icon_state = "igniter0" - -// 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/base_state = "migniter" - 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/power_change() - if ( powered() && disable == 0 ) - stat &= ~NOPOWER - icon_state = "[base_state]" -// src.sd_SetLuminosity(2) - else - stat |= ~NOPOWER - icon_state = "[base_state]-p" -// src.sd_SetLuminosity(0) - -/obj/machinery/sparker/attackby(obj/item/W, mob/user, params) - if (istype(W, /obj/item/screwdriver)) - add_fingerprint(user) - src.disable = !src.disable - if (src.disable) - user.visible_message("[user] has disabled \the [src]!", "You disable the connection to \the [src].") - icon_state = "[base_state]-d" - if (!src.disable) - user.visible_message("[user] has reconnected \the [src]!", "You fix the connection to \the [src].") - if(src.powered()) - icon_state = "[base_state]" - else - icon_state = "[base_state]-p" - 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("[base_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,100,1) - return 1 - -/obj/machinery/sparker/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(!(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 ) + icon_state = "igniter[on]" + +/obj/machinery/igniter/process() //ugh why is this even in process()? + if (src.on && !(stat & NOPOWER) ) + var/turf/location = src.loc + if (isturf(location)) + location.hotspot_expose(700,10,1) + return 1 + +/obj/machinery/igniter/Initialize() + . = ..() + icon_state = "igniter[on]" + +/obj/machinery/igniter/power_change() + if(!( stat & NOPOWER) ) + icon_state = "igniter[src.on]" + else + icon_state = "igniter0" + +// 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/base_state = "migniter" + 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/power_change() + if ( powered() && disable == 0 ) + stat &= ~NOPOWER + icon_state = "[base_state]" +// src.sd_SetLuminosity(2) + else + stat |= ~NOPOWER + icon_state = "[base_state]-p" +// src.sd_SetLuminosity(0) + +/obj/machinery/sparker/attackby(obj/item/W, mob/user, params) + if (istype(W, /obj/item/screwdriver)) + add_fingerprint(user) + src.disable = !src.disable + if (src.disable) + user.visible_message("[user] has disabled \the [src]!", "You disable the connection to \the [src].") + icon_state = "[base_state]-d" + if (!src.disable) + user.visible_message("[user] has reconnected \the [src]!", "You fix the connection to \the [src].") + if(src.powered()) + icon_state = "[base_state]" + else + icon_state = "[base_state]-p" + 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("[base_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,100,1) + return 1 + +/obj/machinery/sparker/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(!(stat & (BROKEN|NOPOWER))) + ignite() diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm index 2a4b903906..e14033a959 100644 --- a/code/game/machinery/lightswitch.dm +++ b/code/game/machinery/lightswitch.dm @@ -1,67 +1,67 @@ -// the light switch -// can have multiple per area -// can also operate on non-loc area through "otherarea" var -/obj/machinery/light_switch - name = "light switch" - icon = 'icons/obj/power.dmi' - icon_state = "light1" - desc = "Make dark." - var/on = TRUE - var/area/area = null - var/otherarea = null - -/obj/machinery/light_switch/Initialize() - . = ..() - area = get_area(src) - - if(otherarea) - area = locate(text2path("/area/[otherarea]")) - - if(!name) - name = "light switch ([area.name])" - - on = area.lightswitch - update_icon() - -/obj/machinery/light_switch/update_icon() - if(stat & NOPOWER) - icon_state = "light-p" - else - if(on) - icon_state = "light1" - else - icon_state = "light0" - -/obj/machinery/light_switch/examine(mob/user) - . = ..() - . += "It is [on? "on" : "off"]." - -/obj/machinery/light_switch/interact(mob/user) - . = ..() - on = !on - - area.lightswitch = on - area.update_icon() - - for(var/obj/machinery/light_switch/L in area) - L.on = on - L.update_icon() - - area.power_change() - -/obj/machinery/light_switch/power_change() - - if(!otherarea) - if(powered(LIGHT)) - stat &= ~NOPOWER - else - stat |= NOPOWER - - update_icon() - -/obj/machinery/light_switch/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - if(!(stat & (BROKEN|NOPOWER))) - power_change() +// the light switch +// can have multiple per area +// can also operate on non-loc area through "otherarea" var +/obj/machinery/light_switch + name = "light switch" + icon = 'icons/obj/power.dmi' + icon_state = "light1" + desc = "Make dark." + var/on = TRUE + var/area/area = null + var/otherarea = null + +/obj/machinery/light_switch/Initialize() + . = ..() + area = get_area(src) + + if(otherarea) + area = locate(text2path("/area/[otherarea]")) + + if(!name) + name = "light switch ([area.name])" + + on = area.lightswitch + update_icon() + +/obj/machinery/light_switch/update_icon() + if(stat & NOPOWER) + icon_state = "light-p" + else + if(on) + icon_state = "light1" + else + icon_state = "light0" + +/obj/machinery/light_switch/examine(mob/user) + . = ..() + . += "It is [on? "on" : "off"]." + +/obj/machinery/light_switch/interact(mob/user) + . = ..() + on = !on + + area.lightswitch = on + area.update_icon() + + for(var/obj/machinery/light_switch/L in area) + L.on = on + L.update_icon() + + area.power_change() + +/obj/machinery/light_switch/power_change() + + if(!otherarea) + if(powered(LIGHT)) + stat &= ~NOPOWER + else + stat |= NOPOWER + + update_icon() + +/obj/machinery/light_switch/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(!(stat & (BROKEN|NOPOWER))) + power_change() diff --git a/code/game/machinery/magnet.dm b/code/game/machinery/magnet.dm index 370621e0c8..9a10ede1ac 100644 --- a/code/game/machinery/magnet.dm +++ b/code/game/machinery/magnet.dm @@ -1,380 +1,380 @@ -// 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." - level = 1 // underfloor - 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 - hide(T.intact) - center = T - SSradio.add_object(src, freq, RADIO_MAGNETS) - 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 invisibility and icon -/obj/machinery/magnetic_module/hide(intact) - invisibility = intact ? INVISIBILITY_MAXIMUM : 0 - update_icon() - -// update the icon_state -/obj/machinery/magnetic_module/update_icon() - var/state="floor_magnet" - var/onstate="" - if(!on) - onstate="0" - - if(invisibility) - icon_state = "[state][onstate]-f" // if invisible, set icon to faded version - // in case of being revealed by T-scanner - else - 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(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) - - spawn(1) - updateUsrDialog() // pretty sure this increases responsiveness - - 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 = copytext(sanitize(input(usr, "Please define a new path!",,path) as text|null),1,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) - spawn() MagnetMove() - - - updateUsrDialog() - -/obj/machinery/magnetic_controller/proc/MagnetMove() - if(looping) - return - - while(moving && rpath.len >= 1) - - if(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 - spawn() - radio_connection.post_signal(src, signal, filter = 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_character = min( 50, length(path) ) // chooses the maximum length of the iterator. 50 max length - - for(var/i=1, i<=maximum_character, i++) // iterates through all characters in path - - var/nextchar = copytext(path, i, i+1) // find next character - - if(!(nextchar in list(";", "&", "*", " "))) // if char is a separator, ignore - rpath += copytext(path, i, i+1) // else, add to list - - // there doesn't HAVE to be separators but it makes paths syntatically visible +// 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." + level = 1 // underfloor + 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 + hide(T.intact) + center = T + SSradio.add_object(src, freq, RADIO_MAGNETS) + 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 invisibility and icon +/obj/machinery/magnetic_module/hide(intact) + invisibility = intact ? INVISIBILITY_MAXIMUM : 0 + update_icon() + +// update the icon_state +/obj/machinery/magnetic_module/update_icon() + var/state="floor_magnet" + var/onstate="" + if(!on) + onstate="0" + + if(invisibility) + icon_state = "[state][onstate]-f" // if invisible, set icon to faded version + // in case of being revealed by T-scanner + else + 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(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) + + spawn(1) + updateUsrDialog() // pretty sure this increases responsiveness + + 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 = copytext(sanitize(input(usr, "Please define a new path!",,path) as text|null),1,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) + spawn() MagnetMove() + + + updateUsrDialog() + +/obj/machinery/magnetic_controller/proc/MagnetMove() + if(looping) + return + + while(moving && rpath.len >= 1) + + if(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 + spawn() + radio_connection.post_signal(src, signal, filter = 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_character = min( 50, length(path) ) // chooses the maximum length of the iterator. 50 max length + + for(var/i=1, i<=maximum_character, i++) // iterates through all characters in path + + var/nextchar = copytext(path, i, i+1) // find next character + + if(!(nextchar in list(";", "&", "*", " "))) // if char is a separator, ignore + rpath += copytext(path, i, i+1) // else, add to list + + // there doesn't HAVE to be separators but it makes paths syntatically visible diff --git a/code/game/machinery/mass_driver.dm b/code/game/machinery/mass_driver.dm index 7ebed0ab0c..ab0a0534ab 100644 --- a/code/game/machinery/mass_driver.dm +++ b/code/game/machinery/mass_driver.dm @@ -1,38 +1,38 @@ -/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/proc/drive(amount) - if(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. - 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(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/proc/drive(amount) + if(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. + 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(stat & (BROKEN|NOPOWER)) + return + drive() diff --git a/code/game/machinery/navbeacon.dm b/code/game/machinery/navbeacon.dm index 0f57bea656..5a567886b8 100644 --- a/code/game/machinery/navbeacon.dm +++ b/code/game/machinery/navbeacon.dm @@ -1,214 +1,214 @@ -// 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." - level = 1 // underfloor - 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" - - req_one_access = list(ACCESS_ENGINE, ACCESS_ROBOTICS) - -/obj/machinery/navbeacon/Initialize() - . = ..() - - set_codes() - - var/turf/T = loc - hide(T.intact) - 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 - -/obj/machinery/navbeacon/Destroy() - if (GLOB.navbeacons["[z]"]) - GLOB.navbeacons["[z]"] -= src //Remove from beacon list, if in one. - GLOB.deliverybeacons -= src - 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+1) - codes[key] = val - else - codes[e] = "1" - - -// called when turf state changes -// hide the object if turf is intact -/obj/machinery/navbeacon/hide(intact) - invisibility = intact ? INVISIBILITY_MAXIMUM : 0 - update_icon() - -// update the icon_state -/obj/machinery/navbeacon/update_icon() - var/state="navbeacon[open]" - - if(invisibility) - icon_state = "[state]-f" // if invisible, set icon to faded version - // in case revealed by T-scanner - else - icon_state = "[state]" - -/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(istype(I, /obj/item/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 = copytext(sanitize(input("Enter New Location", "Navigation Beacon", location) as text|null),1,MAX_MESSAGE_LEN) - if(newloc) - location = newloc - 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 - - updateDialog() - - else if(href_list["delete"]) - var/codekey = href_list["code"] - codes.Remove(codekey) - 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 - - 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." + level = 1 // underfloor + 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" + + req_one_access = list(ACCESS_ENGINE, ACCESS_ROBOTICS) + +/obj/machinery/navbeacon/Initialize() + . = ..() + + set_codes() + + var/turf/T = loc + hide(T.intact) + 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 + +/obj/machinery/navbeacon/Destroy() + if (GLOB.navbeacons["[z]"]) + GLOB.navbeacons["[z]"] -= src //Remove from beacon list, if in one. + GLOB.deliverybeacons -= src + 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+1) + codes[key] = val + else + codes[e] = "1" + + +// called when turf state changes +// hide the object if turf is intact +/obj/machinery/navbeacon/hide(intact) + invisibility = intact ? INVISIBILITY_MAXIMUM : 0 + update_icon() + +// update the icon_state +/obj/machinery/navbeacon/update_icon() + var/state="navbeacon[open]" + + if(invisibility) + icon_state = "[state]-f" // if invisible, set icon to faded version + // in case revealed by T-scanner + else + icon_state = "[state]" + +/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(istype(I, /obj/item/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 = copytext(sanitize(input("Enter New Location", "Navigation Beacon", location) as text|null),1,MAX_MESSAGE_LEN) + if(newloc) + location = newloc + 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 + + updateDialog() + + else if(href_list["delete"]) + var/codekey = href_list["code"] + codes.Remove(codekey) + 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 + + updateDialog() diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm index 6b8cac544e..b8c537a4fc 100644 --- a/code/game/machinery/pipe/construction.dm +++ b/code/game/machinery/pipe/construction.dm @@ -1,236 +1,236 @@ -/*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" - item_state = "buildpipe" - w_class = WEIGHT_CLASS_NORMAL - level = 2 - 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 - - pixel_x += (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X - pixel_y += (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y - 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.stat || usr.restrained() || !usr.canmove ) - 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" - item_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 - pixel_x = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X - pixel_y = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y +/*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" + item_state = "buildpipe" + w_class = WEIGHT_CLASS_NORMAL + level = 2 + 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 + + pixel_x += (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X + pixel_y += (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y + 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.stat || usr.restrained() || !usr.canmove ) + 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" + item_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 + pixel_x = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X + pixel_y = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y diff --git a/code/game/machinery/pipe/pipe_dispenser.dm b/code/game/machinery/pipe/pipe_dispenser.dm index effdc174b6..4cba24bac7 100644 --- a/code/game/machinery/pipe/pipe_dispenser.dm +++ b/code/game/machinery/pipe/pipe_dispenser.dm @@ -1,212 +1,212 @@ -/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 - if(!anchored|| !usr.canmove || 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.canmove || usr.stat || usr.restrained()) - 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 + if(!anchored|| !usr.canmove || 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.canmove || usr.stat || usr.restrained()) + 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 8c2a04bfbc..a27cb9b78c 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -1,1093 +1,1093 @@ -#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 - -/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_SEC_DOORS) - power_channel = EQUIP //drains power from the EQUIPMENT channel - - var/base_icon_state = "standard" - var/scan_range = 7 - var/atom/base = null //for turrets inside other objects - - var/raised = 0 //if the turret cover is "open" and the turret is raised - var/raising= 0 //if the turret is currently opening or closing its cover - - max_integrity = 160 //the turret's health - integrity_failure = 80 - armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - - var/locked = TRUE //if the turret's behaviour control access is locked - var/controllock = FALSE //if the turret responds to control panels - - var/installation = /obj/item/gun/energy/e_gun/turret //the type of weapon installed by default - var/obj/item/gun/stored_gun = null - var/gun_charge = 0 //the charge of the gun when retrieved from wreckage - - var/mode = TURRET_STUN - - var/stun_projectile = null //stun mode projectile type - var/stun_projectile_sound - var/nonlethal_projectile //projectile to use in stun mode when the target is resting, if any - var/nonlethal_projectile_sound - var/lethal_projectile = null //lethal mode projectile type - var/lethal_projectile_sound - - var/reqpower = 500 //power needed per shot - var/always_up = 0 //Will stay active - var/has_cover = 1 //Hides the cover - - var/obj/machinery/porta_turret_cover/cover = null //the cover that is covering this turret - - var/last_fired = 0 //world.time the turret last fired - var/shot_delay = 15 //ticks until next shot (1.5 ?) - - - var/check_records = 1 //checks if it can use the security records - var/criminals = 1 //checks if it can shoot people on arrest - var/auth_weapons = 0 //checks if it can shoot people that have a weapon they aren't authorized to have - var/stun_all = 0 //if this is active, the turret shoots everything that isn't security or head of staff - var/check_anomalies = 1 //checks if it can shoot at unidentified lifeforms (ie xenos) - var/shoot_unloyal = 0 //checks if it can shoot people that aren't loyalty implantd - - var/attacked = 0 //if set to 1, the turret gets pissed off and shoots at people nearby (unless they have sec access!) - - var/on = TRUE //determines if the turret is on - - var/list/faction = list("turret") // Same faction mobs will never be shot at, no matter the other settings - - var/datum/effect_system/spark_spread/spark_system //the spark system, used for generating... sparks? - - var/obj/machinery/turretid/cp = null - - var/wall_turret_direction //The turret will try to shoot from a turf in that direction when in a wall - - var/manual_control = FALSE // - var/datum/action/turret_quit/quit_action - var/datum/action/turret_toggle/toggle_action - 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/update_icon() - cut_overlays() - if(!anchored) - icon_state = "turretCover" - return - if(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) - . = ..() - var/dat - dat += "Status: [on ? "On" : "Off"]
                    " - dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
                    " - - if(!locked) - dat += "Check for Weapon Authorization: [auth_weapons ? "Yes" : "No"]
                    " - dat += "Check Security Records: [check_records ? "Yes" : "No"]
                    " - dat += "Neutralize Identified Criminals: [criminals ? "Yes" : "No"]
                    " - dat += "Neutralize All Non-Security and Non-Command Personnel: [stun_all ? "Yes" : "No"]
                    " - dat += "Neutralize All Unidentified Life Signs: [check_anomalies ? "Yes" : "No"]
                    " - dat += "Neutralize All Non-Loyalty Implanted Personnel: [shoot_unloyal ? "Yes" : "No"]
                    " - if(issilicon(user)) - if(!manual_control) - var/mob/living/silicon/S = user - if(S.hack_software) - dat += "Assume direct control : Manual Control
                    " - else - dat += "Warning! Remote control protocol enabled.
                    " - - - var/datum/browser/popup = new(user, "autosec", "Automatic Portable Turret Installation", 300, 300) - popup.set_content(dat) - popup.open() - -/obj/machinery/porta_turret/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - add_fingerprint(usr) - - if(href_list["power"] && !locked) - if(anchored) //you can't turn a turret on/off if it's not anchored/secured - on = !on //toggle on/off - else - to_chat(usr, "It has to be secured first!") - interact(usr) - return - - if(href_list["operation"]) - switch(href_list["operation"]) //toggles customizable behavioural protocols - if("authweapon") - auth_weapons = !auth_weapons - if("checkrecords") - check_records = !check_records - if("shootcrooks") - criminals = !criminals - if("shootall") - stun_all = !stun_all - if("checkxenos") - check_anomalies = !check_anomalies - if("checkloyal") - shoot_unloyal = !shoot_unloyal - if("manual") - if(issilicon(usr) && !manual_control) - give_control(usr) - interact(usr) - -/obj/machinery/porta_turret/power_change() - if(!anchored) - update_icon() - remove_control() - return - if(stat & BROKEN) - update_icon() - remove_control() - else - if( powered() ) - stat &= ~NOPOWER - update_icon() - else - spawn(rand(0, 15)) - stat |= NOPOWER - remove_control() - update_icon() - - -/obj/machinery/porta_turret/attackby(obj/item/I, mob/user, params) - if(stat & BROKEN) - if(istype(I, /obj/item/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) - 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((istype(I, /obj/item/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(istype(I, /obj/item/multitool) && !locked) - 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.") - visible_message("[src] hums oddly...") - obj_flags |= EMAGGED - controllock = TRUE - on = FALSE //turns off the turret temporarily - update_icon() - sleep(60) //6 seconds for the traitor to gtfo of the area before the turret decides to ruin his shit - on = TRUE //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here - return TRUE - - -/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 - check_records = pick(0, 1) - criminals = pick(0, 1) - auth_weapons = pick(0, 1) - stun_all = pick(0, 0, 0, 0, 1) //stun_all is a pretty big deal, so it's least likely to get turned on - - on = FALSE - remove_control() - - spawn(rand(60,600)) - if(!on) - on = TRUE - -/obj/machinery/porta_turret/take_damage(damage, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) - . = ..() - if(.) //damage received - if(prob(30)) - spark_system.start() - if(on && !attacked && !(obj_flags & EMAGGED)) - attacked = TRUE - addtimer(CALLBACK(src, .proc/reset_attacked), 60) - -/obj/machinery/porta_turret/proc/reset_attacked() - attacked = FALSE - -/obj/machinery/porta_turret/deconstruct(disassembled = TRUE) - qdel(src) - -/obj/machinery/porta_turret/obj_break(damage_flag) - if(!(flags_1 & NODECONSTRUCT_1) && !(stat & BROKEN)) - stat |= BROKEN //enables the BROKEN bit - 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(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 || (stat & (NOPOWER|BROKEN)) || manual_control) - return - - var/list/targets = list() - for(var/mob/A in view(scan_range, base)) - if(A.invisibility > SEE_INVISIBLE_LIVING) - continue - - if(check_anomalies)//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 - if(issilicon(A)) - var/mob/living/silicon/sillycone = A - 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 - - targets += sillycone - - if(iscarbon(A)) - var/mob/living/carbon/C = A - //If not emagged, only target non downed carbons - if(mode != TURRET_LETHAL && (C.stat || C.handcuffed || C.recoveringstam))//CIT CHANGE - replaces check for lying with check for recoveringstam - 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(check_anomalies) //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(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(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(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((stun_all || attacked) && !allowed(perp)) - //if the turret has been attacked or is angry, target all non-sec people - if(!allowed(perp)) - return 10 - - if(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(check_records) //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(shoot_unloyal) - if (!HAS_TRAIT(perp, TRAIT_MINDSHIELD)) - threatcount += 4 - - 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 - - var/mob/living/carbon/C - if(iscarbon(target)) - C = target - - update_icon() - var/obj/item/projectile/A - //any emagged turrets drains 2x power and uses a different projectile? - if(mode == TURRET_STUN) - if(nonlethal_projectile && C && C.resting) - use_power(reqpower*0.5) - A = new nonlethal_projectile(T) - playsound(loc, nonlethal_projectile_sound, 75, 1) - else - use_power(reqpower) - A = new stun_projectile(T) - playsound(loc, stun_projectile_sound, 75, 1) - else - use_power(reqpower * 2) - A = new lethal_projectile(T) - playsound(loc, lethal_projectile_sound, 75, 1) - - - //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) - if(controllock) - return - src.on = on - if(!on) - popDown() - 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/item/projectile/bullet - lethal_projectile = /obj/item/projectile/bullet - lethal_projectile_sound = 'sound/weapons/gunshot.ogg' - stun_projectile_sound = 'sound/weapons/gunshot.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/energy - icon_state = "standard_stun" - base_icon_state = "standard" - stun_projectile = /obj/item/projectile/energy/electrode - stun_projectile_sound = 'sound/weapons/taser.ogg' - nonlethal_projectile = /obj/item/projectile/beam/disabler - nonlethal_projectile_sound = 'sound/weapons/taser2.ogg' - lethal_projectile = /obj/item/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_stun" - base_icon_state = "standard" - stun_projectile = /obj/item/projectile/energy/electrode - stun_projectile_sound = 'sound/weapons/taser.ogg' - nonlethal_projectile = /obj/item/projectile/beam/disabler - nonlethal_projectile_sound = 'sound/weapons/taser2.ogg' - lethal_projectile = /obj/item/projectile/beam/laser/heavylaser - lethal_projectile_sound = 'sound/weapons/lasercannonfire.ogg' - desc = "An energy blaster auto-turret." - - -/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/pod - integrity_failure = 20 - max_integrity = 40 - stun_projectile = /obj/item/projectile/bullet/syndicate_turret - lethal_projectile = /obj/item/projectile/bullet/syndicate_turret - -/obj/machinery/porta_turret/syndicate/shuttle - scan_range = 9 - shot_delay = 3 - stun_projectile = /obj/item/projectile/bullet/p50/penetrator/shuttle - lethal_projectile = /obj/item/projectile/bullet/p50/penetrator/shuttle - lethal_projectile_sound = 'sound/weapons/gunshot_smg.ogg' - stun_projectile_sound = 'sound/weapons/gunshot_smg.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") - nonlethal_projectile = /obj/item/projectile/beam/disabler - nonlethal_projectile_sound = 'sound/weapons/taser2.ogg' - -/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/item/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/item/projectile/beam/laser - lethal_projectile = /obj/item/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 = 60 - 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/item/projectile/beam/weak/penetrator - lethal_projectile = /obj/item/projectile/beam/weak/penetrator - faction = list("neutral","silicon","turret") - -/obj/machinery/porta_turret/centcom_shuttle/ballistic - stun_projectile = /obj/item/projectile/bullet - lethal_projectile = /obj/item/projectile/bullet - lethal_projectile_sound = 'sound/weapons/gunshot.ogg' - stun_projectile_sound = 'sound/weapons/gunshot.ogg' - desc = "A ballistic machine gun auto-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 - var/enabled = 1 - var/lethal = 0 - var/locked = TRUE - var/control_area = null //can be area name, path or nothing. - var/ailock = 0 // AI cannot use this - req_access = list(ACCESS_AI_UPLOAD) - var/list/obj/machinery/porta_turret/turrets = list() - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/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) && (!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(stat & BROKEN) - return - - if (istype(I, /obj/item/multitool)) - 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.") - if (locked) - if (user.machine==src) - user.unset_machine() - user << browse(null, "window=turretid") - else - if (user.machine==src) - attack_hand(user) - 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 - if(user && user.machine == src) - attack_hand(user) - return TRUE - -/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) - . = ..() - if ( get_dist(src, user) > 0 ) - if ( !(issilicon(user) || IsAdminGhost(user)) ) - to_chat(user, "You are too far away.") - user.unset_machine() - user << browse(null, "window=turretid") - return - - var/t = "" - - if(locked && !(issilicon(user) || IsAdminGhost(user))) - t += "
                    Swipe ID card to unlock interface
                    " - else - if(!issilicon(user) && !IsAdminGhost(user)) - t += "
                    Swipe ID card to lock interface
                    " - t += "Turrets [enabled?"activated":"deactivated"] - [enabled?"Disable":"Enable"]?
                    " - t += "Currently set for [lethal?"lethal":"stun repeatedly"] - Change to [lethal?"Stun repeatedly":"Lethal"]?
                    " - - var/datum/browser/popup = new(user, "turretid", "Turret Control Panel ([get_area_name(src, TRUE)])") - popup.set_content(t) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/turretid/Topic(href, href_list) - if(..()) - return - if (locked) - if(!(issilicon(usr) || IsAdminGhost(usr))) - to_chat(usr, "Control panel is locked!") - return - if (href_list["toggleOn"]) - toggle_on() - else if (href_list["toggleLethal"]) - toggle_lethal() - attack_hand(usr) - -/obj/machinery/turretid/proc/toggle_lethal() - lethal = !lethal - updateTurrets() - -/obj/machinery/turretid/proc/toggle_on() - enabled = !enabled - updateTurrets() - -/obj/machinery/turretid/proc/updateTurrets() - for (var/obj/machinery/porta_turret/aTurret in turrets) - aTurret.setState(enabled, lethal) - update_icon() - -/obj/machinery/turretid/power_change() - ..() - update_icon() - -/obj/machinery/turretid/update_icon() - ..() - if(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 - materials = list(MAT_METAL=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/item/projectile/beam/lasertag/bluetag - .["lethal_projectile"] = /obj/item/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/item/projectile/beam/lasertag/redtag - .["lethal_projectile"] = /obj/item/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) - check_records = 0 - criminals = 0 - auth_weapons = 1 - stun_all = 0 - check_anomalies = 0 - 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_interact(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 - if(team_color == "red" && istype(H.wear_suit, /obj/item/clothing/suit/bluetag)) - return - - var/dat = "Status: [on ? "On" : "Off"]" - - var/datum/browser/popup = new(user, "autosec", "Automatic Portable Turret Installation", 300, 300) - popup.set_content(dat) - popup.open() - -//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/item/projectile/P) - . = ..() - if(on) - if(team_color == "blue") - if(istype(P, /obj/item/projectile/beam/lasertag/redtag)) - on = FALSE - spawn(100) - on = TRUE - else if(team_color == "red") - if(istype(P, /obj/item/projectile/beam/lasertag/bluetag)) - on = FALSE - spawn(100) - on = TRUE +#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 + +/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_SEC_DOORS) + power_channel = EQUIP //drains power from the EQUIPMENT channel + + var/base_icon_state = "standard" + var/scan_range = 7 + var/atom/base = null //for turrets inside other objects + + var/raised = 0 //if the turret cover is "open" and the turret is raised + var/raising= 0 //if the turret is currently opening or closing its cover + + max_integrity = 160 //the turret's health + integrity_failure = 80 + armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + + var/locked = TRUE //if the turret's behaviour control access is locked + var/controllock = FALSE //if the turret responds to control panels + + var/installation = /obj/item/gun/energy/e_gun/turret //the type of weapon installed by default + var/obj/item/gun/stored_gun = null + var/gun_charge = 0 //the charge of the gun when retrieved from wreckage + + var/mode = TURRET_STUN + + var/stun_projectile = null //stun mode projectile type + var/stun_projectile_sound + var/nonlethal_projectile //projectile to use in stun mode when the target is resting, if any + var/nonlethal_projectile_sound + var/lethal_projectile = null //lethal mode projectile type + var/lethal_projectile_sound + + var/reqpower = 500 //power needed per shot + var/always_up = 0 //Will stay active + var/has_cover = 1 //Hides the cover + + var/obj/machinery/porta_turret_cover/cover = null //the cover that is covering this turret + + var/last_fired = 0 //world.time the turret last fired + var/shot_delay = 15 //ticks until next shot (1.5 ?) + + + var/check_records = 1 //checks if it can use the security records + var/criminals = 1 //checks if it can shoot people on arrest + var/auth_weapons = 0 //checks if it can shoot people that have a weapon they aren't authorized to have + var/stun_all = 0 //if this is active, the turret shoots everything that isn't security or head of staff + var/check_anomalies = 1 //checks if it can shoot at unidentified lifeforms (ie xenos) + var/shoot_unloyal = 0 //checks if it can shoot people that aren't loyalty implantd + + var/attacked = 0 //if set to 1, the turret gets pissed off and shoots at people nearby (unless they have sec access!) + + var/on = TRUE //determines if the turret is on + + var/list/faction = list("turret") // Same faction mobs will never be shot at, no matter the other settings + + var/datum/effect_system/spark_spread/spark_system //the spark system, used for generating... sparks? + + var/obj/machinery/turretid/cp = null + + var/wall_turret_direction //The turret will try to shoot from a turf in that direction when in a wall + + var/manual_control = FALSE // + var/datum/action/turret_quit/quit_action + var/datum/action/turret_toggle/toggle_action + 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/update_icon() + cut_overlays() + if(!anchored) + icon_state = "turretCover" + return + if(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) + . = ..() + var/dat + dat += "Status: [on ? "On" : "Off"]
                    " + dat += "Behaviour controls are [locked ? "locked" : "unlocked"]
                    " + + if(!locked) + dat += "Check for Weapon Authorization: [auth_weapons ? "Yes" : "No"]
                    " + dat += "Check Security Records: [check_records ? "Yes" : "No"]
                    " + dat += "Neutralize Identified Criminals: [criminals ? "Yes" : "No"]
                    " + dat += "Neutralize All Non-Security and Non-Command Personnel: [stun_all ? "Yes" : "No"]
                    " + dat += "Neutralize All Unidentified Life Signs: [check_anomalies ? "Yes" : "No"]
                    " + dat += "Neutralize All Non-Loyalty Implanted Personnel: [shoot_unloyal ? "Yes" : "No"]
                    " + if(issilicon(user)) + if(!manual_control) + var/mob/living/silicon/S = user + if(S.hack_software) + dat += "Assume direct control : Manual Control
                    " + else + dat += "Warning! Remote control protocol enabled.
                    " + + + var/datum/browser/popup = new(user, "autosec", "Automatic Portable Turret Installation", 300, 300) + popup.set_content(dat) + popup.open() + +/obj/machinery/porta_turret/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + add_fingerprint(usr) + + if(href_list["power"] && !locked) + if(anchored) //you can't turn a turret on/off if it's not anchored/secured + on = !on //toggle on/off + else + to_chat(usr, "It has to be secured first!") + interact(usr) + return + + if(href_list["operation"]) + switch(href_list["operation"]) //toggles customizable behavioural protocols + if("authweapon") + auth_weapons = !auth_weapons + if("checkrecords") + check_records = !check_records + if("shootcrooks") + criminals = !criminals + if("shootall") + stun_all = !stun_all + if("checkxenos") + check_anomalies = !check_anomalies + if("checkloyal") + shoot_unloyal = !shoot_unloyal + if("manual") + if(issilicon(usr) && !manual_control) + give_control(usr) + interact(usr) + +/obj/machinery/porta_turret/power_change() + if(!anchored) + update_icon() + remove_control() + return + if(stat & BROKEN) + update_icon() + remove_control() + else + if( powered() ) + stat &= ~NOPOWER + update_icon() + else + spawn(rand(0, 15)) + stat |= NOPOWER + remove_control() + update_icon() + + +/obj/machinery/porta_turret/attackby(obj/item/I, mob/user, params) + if(stat & BROKEN) + if(istype(I, /obj/item/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) + 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((istype(I, /obj/item/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(istype(I, /obj/item/multitool) && !locked) + 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.") + visible_message("[src] hums oddly...") + obj_flags |= EMAGGED + controllock = TRUE + on = FALSE //turns off the turret temporarily + update_icon() + sleep(60) //6 seconds for the traitor to gtfo of the area before the turret decides to ruin his shit + on = TRUE //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here + return TRUE + + +/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 + check_records = pick(0, 1) + criminals = pick(0, 1) + auth_weapons = pick(0, 1) + stun_all = pick(0, 0, 0, 0, 1) //stun_all is a pretty big deal, so it's least likely to get turned on + + on = FALSE + remove_control() + + spawn(rand(60,600)) + if(!on) + on = TRUE + +/obj/machinery/porta_turret/take_damage(damage, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) + . = ..() + if(.) //damage received + if(prob(30)) + spark_system.start() + if(on && !attacked && !(obj_flags & EMAGGED)) + attacked = TRUE + addtimer(CALLBACK(src, .proc/reset_attacked), 60) + +/obj/machinery/porta_turret/proc/reset_attacked() + attacked = FALSE + +/obj/machinery/porta_turret/deconstruct(disassembled = TRUE) + qdel(src) + +/obj/machinery/porta_turret/obj_break(damage_flag) + if(!(flags_1 & NODECONSTRUCT_1) && !(stat & BROKEN)) + stat |= BROKEN //enables the BROKEN bit + 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(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 || (stat & (NOPOWER|BROKEN)) || manual_control) + return + + var/list/targets = list() + for(var/mob/A in view(scan_range, base)) + if(A.invisibility > SEE_INVISIBLE_LIVING) + continue + + if(check_anomalies)//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 + if(issilicon(A)) + var/mob/living/silicon/sillycone = A + 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 + + targets += sillycone + + if(iscarbon(A)) + var/mob/living/carbon/C = A + //If not emagged, only target non downed carbons + if(mode != TURRET_LETHAL && (C.stat || C.handcuffed || C.recoveringstam))//CIT CHANGE - replaces check for lying with check for recoveringstam + 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(check_anomalies) //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(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(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(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((stun_all || attacked) && !allowed(perp)) + //if the turret has been attacked or is angry, target all non-sec people + if(!allowed(perp)) + return 10 + + if(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(check_records) //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(shoot_unloyal) + if (!HAS_TRAIT(perp, TRAIT_MINDSHIELD)) + threatcount += 4 + + 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 + + var/mob/living/carbon/C + if(iscarbon(target)) + C = target + + update_icon() + var/obj/item/projectile/A + //any emagged turrets drains 2x power and uses a different projectile? + if(mode == TURRET_STUN) + if(nonlethal_projectile && C && C.resting) + use_power(reqpower*0.5) + A = new nonlethal_projectile(T) + playsound(loc, nonlethal_projectile_sound, 75, 1) + else + use_power(reqpower) + A = new stun_projectile(T) + playsound(loc, stun_projectile_sound, 75, 1) + else + use_power(reqpower * 2) + A = new lethal_projectile(T) + playsound(loc, lethal_projectile_sound, 75, 1) + + + //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) + if(controllock) + return + src.on = on + if(!on) + popDown() + 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/item/projectile/bullet + lethal_projectile = /obj/item/projectile/bullet + lethal_projectile_sound = 'sound/weapons/gunshot.ogg' + stun_projectile_sound = 'sound/weapons/gunshot.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/energy + icon_state = "standard_stun" + base_icon_state = "standard" + stun_projectile = /obj/item/projectile/energy/electrode + stun_projectile_sound = 'sound/weapons/taser.ogg' + nonlethal_projectile = /obj/item/projectile/beam/disabler + nonlethal_projectile_sound = 'sound/weapons/taser2.ogg' + lethal_projectile = /obj/item/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_stun" + base_icon_state = "standard" + stun_projectile = /obj/item/projectile/energy/electrode + stun_projectile_sound = 'sound/weapons/taser.ogg' + nonlethal_projectile = /obj/item/projectile/beam/disabler + nonlethal_projectile_sound = 'sound/weapons/taser2.ogg' + lethal_projectile = /obj/item/projectile/beam/laser/heavylaser + lethal_projectile_sound = 'sound/weapons/lasercannonfire.ogg' + desc = "An energy blaster auto-turret." + + +/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/pod + integrity_failure = 20 + max_integrity = 40 + stun_projectile = /obj/item/projectile/bullet/syndicate_turret + lethal_projectile = /obj/item/projectile/bullet/syndicate_turret + +/obj/machinery/porta_turret/syndicate/shuttle + scan_range = 9 + shot_delay = 3 + stun_projectile = /obj/item/projectile/bullet/p50/penetrator/shuttle + lethal_projectile = /obj/item/projectile/bullet/p50/penetrator/shuttle + lethal_projectile_sound = 'sound/weapons/gunshot_smg.ogg' + stun_projectile_sound = 'sound/weapons/gunshot_smg.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") + nonlethal_projectile = /obj/item/projectile/beam/disabler + nonlethal_projectile_sound = 'sound/weapons/taser2.ogg' + +/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/item/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/item/projectile/beam/laser + lethal_projectile = /obj/item/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 = 60 + 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/item/projectile/beam/weak/penetrator + lethal_projectile = /obj/item/projectile/beam/weak/penetrator + faction = list("neutral","silicon","turret") + +/obj/machinery/porta_turret/centcom_shuttle/ballistic + stun_projectile = /obj/item/projectile/bullet + lethal_projectile = /obj/item/projectile/bullet + lethal_projectile_sound = 'sound/weapons/gunshot.ogg' + stun_projectile_sound = 'sound/weapons/gunshot.ogg' + desc = "A ballistic machine gun auto-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 + var/enabled = 1 + var/lethal = 0 + var/locked = TRUE + var/control_area = null //can be area name, path or nothing. + var/ailock = 0 // AI cannot use this + req_access = list(ACCESS_AI_UPLOAD) + var/list/obj/machinery/porta_turret/turrets = list() + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/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) && (!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(stat & BROKEN) + return + + if (istype(I, /obj/item/multitool)) + 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.") + if (locked) + if (user.machine==src) + user.unset_machine() + user << browse(null, "window=turretid") + else + if (user.machine==src) + attack_hand(user) + 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 + if(user && user.machine == src) + attack_hand(user) + return TRUE + +/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) + . = ..() + if ( get_dist(src, user) > 0 ) + if ( !(issilicon(user) || IsAdminGhost(user)) ) + to_chat(user, "You are too far away.") + user.unset_machine() + user << browse(null, "window=turretid") + return + + var/t = "" + + if(locked && !(issilicon(user) || IsAdminGhost(user))) + t += "
                    Swipe ID card to unlock interface
                    " + else + if(!issilicon(user) && !IsAdminGhost(user)) + t += "
                    Swipe ID card to lock interface
                    " + t += "Turrets [enabled?"activated":"deactivated"] - [enabled?"Disable":"Enable"]?
                    " + t += "Currently set for [lethal?"lethal":"stun repeatedly"] - Change to [lethal?"Stun repeatedly":"Lethal"]?
                    " + + var/datum/browser/popup = new(user, "turretid", "Turret Control Panel ([get_area_name(src, TRUE)])") + popup.set_content(t) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/turretid/Topic(href, href_list) + if(..()) + return + if (locked) + if(!(issilicon(usr) || IsAdminGhost(usr))) + to_chat(usr, "Control panel is locked!") + return + if (href_list["toggleOn"]) + toggle_on() + else if (href_list["toggleLethal"]) + toggle_lethal() + attack_hand(usr) + +/obj/machinery/turretid/proc/toggle_lethal() + lethal = !lethal + updateTurrets() + +/obj/machinery/turretid/proc/toggle_on() + enabled = !enabled + updateTurrets() + +/obj/machinery/turretid/proc/updateTurrets() + for (var/obj/machinery/porta_turret/aTurret in turrets) + aTurret.setState(enabled, lethal) + update_icon() + +/obj/machinery/turretid/power_change() + ..() + update_icon() + +/obj/machinery/turretid/update_icon() + ..() + if(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 + materials = list(MAT_METAL=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/item/projectile/beam/lasertag/bluetag + .["lethal_projectile"] = /obj/item/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/item/projectile/beam/lasertag/redtag + .["lethal_projectile"] = /obj/item/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) + check_records = 0 + criminals = 0 + auth_weapons = 1 + stun_all = 0 + check_anomalies = 0 + 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_interact(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 + if(team_color == "red" && istype(H.wear_suit, /obj/item/clothing/suit/bluetag)) + return + + var/dat = "Status: [on ? "On" : "Off"]" + + var/datum/browser/popup = new(user, "autosec", "Automatic Portable Turret Installation", 300, 300) + popup.set_content(dat) + popup.open() + +//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/item/projectile/P) + . = ..() + if(on) + if(team_color == "blue") + if(istype(P, /obj/item/projectile/beam/lasertag/redtag)) + on = FALSE + spawn(100) + on = TRUE + else if(team_color == "red") + if(istype(P, /obj/item/projectile/beam/lasertag/bluetag)) + on = FALSE + spawn(100) + on = TRUE diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index 927608d3d5..a581981746 100755 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -1,163 +1,163 @@ -/obj/machinery/recharger - name = "recharger" - icon = 'icons/obj/stationobjs.dmi' - icon_state = "recharger0" - 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/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/item/gun/ballistic/automatic/magrifle_e, - /obj/item/gun/ballistic/automatic/pistol/mag_e)) - -/obj/machinery/recharger/RefreshParts() - for(var/obj/item/stock_parts/capacitor/C in component_parts) - recharge_coeff = C.rating - -/obj/machinery/recharger/proc/setCharging(new_charging) - charging = new_charging - if (new_charging) - START_PROCESSING(SSmachines, src) - use_power = ACTIVE_POWER_USE - update_icon(scan = TRUE) - else - use_power = IDLE_POWER_USE - update_icon() - -/obj/machinery/recharger/attackby(obj/item/G, mob/user, params) - if(istype(G, /obj/item/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, "rechargeropen", "recharger0", G)) - return - - if(panel_open && istype(G, /obj/item/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(stat & (NOPOWER|BROKEN) || !anchored) - return PROCESS_KILL - - var/using_power = 0 - 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 = 1 - update_icon(using_power) - - 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 = 1 - update_icon(using_power) - return - else - return PROCESS_KILL - -/obj/machinery/recharger/power_change() - ..() - update_icon() - -/obj/machinery/recharger/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_CONTENTS) - return - if(!(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_icon(using_power = 0, scan) //we have an update_icon() in addition to the stuff in process to make it feel a tiny bit snappier. - if(stat & (NOPOWER|BROKEN) || !anchored) - icon_state = "rechargeroff" - return - if(scan) - icon_state = "rechargeroff" - return - if(panel_open) - icon_state = "rechargeropen" - return - if(charging) - if(using_power) - icon_state = "recharger1" - else - icon_state = "recharger2" - return - icon_state = "recharger0" +/obj/machinery/recharger + name = "recharger" + icon = 'icons/obj/stationobjs.dmi' + icon_state = "recharger0" + 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/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/item/gun/ballistic/automatic/magrifle_e, + /obj/item/gun/ballistic/automatic/pistol/mag_e)) + +/obj/machinery/recharger/RefreshParts() + for(var/obj/item/stock_parts/capacitor/C in component_parts) + recharge_coeff = C.rating + +/obj/machinery/recharger/proc/setCharging(new_charging) + charging = new_charging + if (new_charging) + START_PROCESSING(SSmachines, src) + use_power = ACTIVE_POWER_USE + update_icon(scan = TRUE) + else + use_power = IDLE_POWER_USE + update_icon() + +/obj/machinery/recharger/attackby(obj/item/G, mob/user, params) + if(istype(G, /obj/item/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, "rechargeropen", "recharger0", G)) + return + + if(panel_open && istype(G, /obj/item/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(stat & (NOPOWER|BROKEN) || !anchored) + return PROCESS_KILL + + var/using_power = 0 + 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 = 1 + update_icon(using_power) + + 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 = 1 + update_icon(using_power) + return + else + return PROCESS_KILL + +/obj/machinery/recharger/power_change() + ..() + update_icon() + +/obj/machinery/recharger/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_CONTENTS) + return + if(!(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_icon(using_power = 0, scan) //we have an update_icon() in addition to the stuff in process to make it feel a tiny bit snappier. + if(stat & (NOPOWER|BROKEN) || !anchored) + icon_state = "rechargeroff" + return + if(scan) + icon_state = "rechargeroff" + return + if(panel_open) + icon_state = "rechargeropen" + return + if(charging) + if(using_power) + icon_state = "recharger1" + else + icon_state = "recharger2" + return + icon_state = "recharger0" diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm index c0f563d44d..cd5f7ad6bf 100644 --- a/code/game/machinery/rechargestation.dm +++ b/code/game/machinery/rechargestation.dm @@ -1,135 +1,135 @@ -/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) - var/recharge_speed - var/repairs - -/obj/machinery/recharge_station/Initialize() - . = ..() - update_icon() - -/obj/machinery/recharge_station/upgraded - -/obj/machinery/recharge_station/upgraded/Initialize() - . = ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/machine/cyborgrecharger(null) - component_parts += new /obj/item/stock_parts/capacitor/super(null) - component_parts += new /obj/item/stock_parts/manipulator/pico(null) - component_parts += new /obj/item/stock_parts/cell/hyper(null) - RefreshParts() - -/obj/machinery/recharge_station/fullupgrade - -/obj/machinery/recharge_station/fullupgrade/Initialize() - . = ..() - component_parts = list() - component_parts += new /obj/item/circuitboard/machine/cyborgrecharger(null) - component_parts += new /obj/item/stock_parts/capacitor/quadratic(null) - component_parts += new /obj/item/stock_parts/manipulator/femto(null) - component_parts += new /obj/item/stock_parts/cell/bluespace(null) - RefreshParts() - -/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/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(!(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() - . = ..() - if(iscyborg(occupant)) - use_power = IDLE_POWER_USE - -/obj/machinery/recharge_station/close_machine() - . = ..() - if(occupant) - if(iscyborg(occupant)) - use_power = ACTIVE_POWER_USE - add_fingerprint(occupant) - -/obj/machinery/recharge_station/update_icon() - 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/power_change() - ..() - update_icon() - -/obj/machinery/recharge_station/proc/process_occupant() - if(occupant && iscyborg(occupant)) - var/mob/living/silicon/robot/R = occupant - restock_modules() - if(repairs) - R.heal_bodypart_damage(repairs, repairs - 1) - if(R.cell) - R.cell.charge = min(R.cell.charge + recharge_speed, R.cell.maxcharge) - -/obj/machinery/recharge_station/proc/restock_modules() - if(occupant) - var/mob/living/silicon/robot/R = occupant - if(R && R.module) - var/coeff = recharge_speed * 0.005 - R.module.respawn_consumable(R, coeff) +/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) + var/recharge_speed + var/repairs + +/obj/machinery/recharge_station/Initialize() + . = ..() + update_icon() + +/obj/machinery/recharge_station/upgraded + +/obj/machinery/recharge_station/upgraded/Initialize() + . = ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/machine/cyborgrecharger(null) + component_parts += new /obj/item/stock_parts/capacitor/super(null) + component_parts += new /obj/item/stock_parts/manipulator/pico(null) + component_parts += new /obj/item/stock_parts/cell/hyper(null) + RefreshParts() + +/obj/machinery/recharge_station/fullupgrade + +/obj/machinery/recharge_station/fullupgrade/Initialize() + . = ..() + component_parts = list() + component_parts += new /obj/item/circuitboard/machine/cyborgrecharger(null) + component_parts += new /obj/item/stock_parts/capacitor/quadratic(null) + component_parts += new /obj/item/stock_parts/manipulator/femto(null) + component_parts += new /obj/item/stock_parts/cell/bluespace(null) + RefreshParts() + +/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/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(!(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() + . = ..() + if(iscyborg(occupant)) + use_power = IDLE_POWER_USE + +/obj/machinery/recharge_station/close_machine() + . = ..() + if(occupant) + if(iscyborg(occupant)) + use_power = ACTIVE_POWER_USE + add_fingerprint(occupant) + +/obj/machinery/recharge_station/update_icon() + 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/power_change() + ..() + update_icon() + +/obj/machinery/recharge_station/proc/process_occupant() + if(occupant && iscyborg(occupant)) + var/mob/living/silicon/robot/R = occupant + restock_modules() + if(repairs) + R.heal_bodypart_damage(repairs, repairs - 1) + if(R.cell) + R.cell.charge = min(R.cell.charge + recharge_speed, R.cell.maxcharge) + +/obj/machinery/recharge_station/proc/restock_modules() + if(occupant) + var/mob/living/silicon/robot/R = occupant + if(R && R.module) + var/coeff = recharge_speed * 0.005 + R.module.respawn_consumable(R, coeff) diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm index c8beeb8c83..c879ca73e8 100644 --- a/code/game/machinery/recycler.dm +++ b/code/game/machinery/recycler.dm @@ -1,212 +1,212 @@ -#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(MAT_METAL, MAT_GLASS, MAT_PLASMA, MAT_SILVER, MAT_GOLD, MAT_DIAMOND, MAT_URANIUM, MAT_BANANIUM, MAT_TITANIUM, MAT_BLUESPACE, MAT_PLASTIC), 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) - . = ..() - . += "The power light is [(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/power_change() - ..() - update_icon() - - -/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, 1, -1) - to_chat(user, "You use the cryptographic sequencer on [src].") - return TRUE - -/obj/machinery/recycler/update_icon() - ..() - var/is_powered = !(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/CanPass(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/AM0, sound=TRUE) - if(stat & (BROKEN|NOPOWER)) - return - if(safety_mode) - return - var/list/to_eat - if(isitem(AM0)) - to_eat = AM0.GetAllContentsIgnoring(GLOB.typecache_mob) - else - to_eat = list(AM0) - - var/items_recycled = 0 - - for(var/i in to_eat) - var/atom/movable/AM = i - var/obj/item/bodypart/head/as_head = AM - var/obj/item/mmi/as_mmi = AM - var/brain_holder = istype(AM, /obj/item/organ/brain) || (istype(as_head) && as_head.brain) || (istype(as_mmi) && as_mmi.brain) || isbrain(AM) || istype(AM, /obj/item/dullahan_relay) - if(brain_holder) - emergency_stop(AM) - else if(isliving(AM)) - if((obj_flags & EMAGGED)||((!allowed(AM))&&(!ishuman(AM)))) - crush_living(AM) - else - emergency_stop(AM) - else if(isitem(AM)) - var/obj/O = AM - if(O.resistance_flags & INDESTRUCTIBLE) - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 0) - O.forceMove(loc) - else - recycle_item(AM) - items_recycled++ - else - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 0) - AM.forceMove(loc) - - if(items_recycled && sound) - playsound(src, item_recycle_sound, 50, 1) - -/obj/machinery/recycler/proc/recycle_item(obj/item/I) - - I.forceMove(loc) - 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(src.loc, 1 + seed_modifier) - qdel(L) - return - else - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/material_amount = materials.get_item_material_amount(I) - if(!material_amount) - qdel(I) - return - materials.insert_item(I, multiplier = (amount_produced / 100)) - qdel(I) - materials.retrieve_all() - - -/obj/machinery/recycler/proc/emergency_stop(mob/living/L) - playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 0) - safety_mode = TRUE - update_icon() - L.forceMove(loc) - addtimer(CALLBACK(src, .proc/reboot), SAFETY_COOLDOWN) - -/obj/machinery/recycler/proc/reboot() - playsound(src, 'sound/machines/ping.ogg', 50, 0) - 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, 1) - else - playsound(src, 'sound/effects/splat.ogg', 50, 1) - - // By default, the emagged recycler will gib all non-carbons. (human simple animal mobs don't count) - 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() - - // Remove and recycle the equipped items - if(eat_victim_items) - for(var/obj/item/I in L.get_equipped_items(TRUE)) - if(L.dropItemToGround(I)) - eat(I, sound=FALSE) - - // 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(MAT_METAL, MAT_GLASS, MAT_PLASMA, MAT_SILVER, MAT_GOLD, MAT_DIAMOND, MAT_URANIUM, MAT_BANANIUM, MAT_TITANIUM, MAT_BLUESPACE, MAT_PLASTIC), 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) + . = ..() + . += "The power light is [(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/power_change() + ..() + update_icon() + + +/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, 1, -1) + to_chat(user, "You use the cryptographic sequencer on [src].") + return TRUE + +/obj/machinery/recycler/update_icon() + ..() + var/is_powered = !(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/CanPass(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/AM0, sound=TRUE) + if(stat & (BROKEN|NOPOWER)) + return + if(safety_mode) + return + var/list/to_eat + if(isitem(AM0)) + to_eat = AM0.GetAllContentsIgnoring(GLOB.typecache_mob) + else + to_eat = list(AM0) + + var/items_recycled = 0 + + for(var/i in to_eat) + var/atom/movable/AM = i + var/obj/item/bodypart/head/as_head = AM + var/obj/item/mmi/as_mmi = AM + var/brain_holder = istype(AM, /obj/item/organ/brain) || (istype(as_head) && as_head.brain) || (istype(as_mmi) && as_mmi.brain) || isbrain(AM) || istype(AM, /obj/item/dullahan_relay) + if(brain_holder) + emergency_stop(AM) + else if(isliving(AM)) + if((obj_flags & EMAGGED)||((!allowed(AM))&&(!ishuman(AM)))) + crush_living(AM) + else + emergency_stop(AM) + else if(isitem(AM)) + var/obj/O = AM + if(O.resistance_flags & INDESTRUCTIBLE) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 0) + O.forceMove(loc) + else + recycle_item(AM) + items_recycled++ + else + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 0) + AM.forceMove(loc) + + if(items_recycled && sound) + playsound(src, item_recycle_sound, 50, 1) + +/obj/machinery/recycler/proc/recycle_item(obj/item/I) + + I.forceMove(loc) + 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(src.loc, 1 + seed_modifier) + qdel(L) + return + else + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/material_amount = materials.get_item_material_amount(I) + if(!material_amount) + qdel(I) + return + materials.insert_item(I, multiplier = (amount_produced / 100)) + qdel(I) + materials.retrieve_all() + + +/obj/machinery/recycler/proc/emergency_stop(mob/living/L) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 0) + safety_mode = TRUE + update_icon() + L.forceMove(loc) + addtimer(CALLBACK(src, .proc/reboot), SAFETY_COOLDOWN) + +/obj/machinery/recycler/proc/reboot() + playsound(src, 'sound/machines/ping.ogg', 50, 0) + 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, 1) + else + playsound(src, 'sound/effects/splat.ogg', 50, 1) + + // By default, the emagged recycler will gib all non-carbons. (human simple animal mobs don't count) + 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() + + // Remove and recycle the equipped items + if(eat_victim_items) + for(var/obj/item/I in L.get_equipped_items(TRUE)) + if(L.dropItemToGround(I)) + eat(I, sound=FALSE) + + // 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 ad9a846bdc..ec5d0343aa 100644 --- a/code/game/machinery/requests_console.dm +++ b/code/game/machinery/requests_console.dm @@ -1,534 +1,534 @@ -/******************** 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) - -#define NO_NEW_MESSAGE 0 -#define NORMAL_MESSAGE_PRIORITY 1 -#define HIGH_MESSAGE_PRIORITY 2 -#define EXTREME_MESSAGE_PRIORITY 3 // not implemented, will probably require some hacking... everything needs to have a hidden feature in this game. - -/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 - // 0 = none (not listed, can only replied to) - // 1 = assistance - // 2 = supplies - // 3 = info - // 4 = ass + sup //Erro goddamn you just HAD to shorten "assistance" down to "ass" - // 5 = ass + info - // 6 = sup + info - // 7 = ass + sup + info - var/newmessagepriority = NO_NEW_MESSAGE - var/screen = 0 - // 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/dpt = ""; //the department which will be receiving the message - var/priority = -1 ; //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/power_change() - ..() - update_icon() - -/obj/machinery/requests_console/update_icon() - if(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(stat & NOPOWER) - if(icon_state != "req_comp_off") - icon_state = "req_comp_off" - else - if(emergency || (newmessagepriority == EXTREME_MESSAGE_PRIORITY)) - icon_state = "req_comp3" - else if(newmessagepriority == HIGH_MESSAGE_PRIORITY) - icon_state = "req_comp2" - else if(newmessagepriority == 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 - switch(departmentType) - if(1) - if(!("[department]" in GLOB.req_console_assistance)) - GLOB.req_console_assistance += department - if(2) - if(!("[department]" in GLOB.req_console_supplies)) - GLOB.req_console_supplies += department - if(3) - if(!("[department]" in GLOB.req_console_information)) - GLOB.req_console_information += department - if(4) - if(!("[department]" in GLOB.req_console_assistance)) - GLOB.req_console_assistance += department - if(!("[department]" in GLOB.req_console_supplies)) - GLOB.req_console_supplies += department - if(5) - if(!("[department]" in GLOB.req_console_assistance)) - GLOB.req_console_assistance += department - if(!("[department]" in GLOB.req_console_information)) - GLOB.req_console_information += department - if(6) - if(!("[department]" in GLOB.req_console_supplies)) - GLOB.req_console_supplies += department - if(!("[department]" in GLOB.req_console_information)) - GLOB.req_console_information += department - if(7) - if(!("[department]" in GLOB.req_console_assistance)) - GLOB.req_console_assistance += department - if(!("[department]" in GLOB.req_console_supplies)) - GLOB.req_console_supplies += department - if(!("[department]" in GLOB.req_console_information)) - GLOB.req_console_information += 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(1) //req. assistance - dat += "Which department do you need assistance from?

                    " - dat += "" - for(var/dpt in GLOB.req_console_assistance) - if (dpt != department) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
                    [dpt]Normal High" - if(hackState) - dat += "EXTREME" - dat += "
                    " - dat += "
                    << Back
                    " - - if(2) //req. supplies - dat += "Which department do you need supplies from?

                    " - dat += "" - for(var/dpt in GLOB.req_console_supplies) - if (dpt != department) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
                    [dpt]Normal High" - if(hackState) - dat += "EXTREME" - dat += "
                    " - dat += "
                    << Back
                    " - - if(3) //relay information - dat += "Which department would you like to send information to?

                    " - dat += "" - for(var/dpt in GLOB.req_console_information) - if (dpt != department) - dat += "" - dat += "" - dat += "" - dat += "" - dat += "
                    [dpt]Normal High" - if(hackState) - dat += "EXTREME" - dat += "
                    " - dat += "
                    << Back
                    " - - if(6) //sent successfully - dat += "Message sent.

                    " - dat += "Continue
                    " - - if(7) //unsuccessful; not sent - dat += "An error occurred.

                    " - dat += "Continue
                    " - - if(8) //view messages - for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) - if (Console.department == department) - Console.newmessagepriority = NO_NEW_MESSAGE - Console.update_icon() - - newmessagepriority = 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(9) //authentication before sending - dat += "Message Authentication

                    " - dat += "Message for [dpt]: [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(10) //send announcement - 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
                    " - - else //main menu - screen = 0 - announceAuth = FALSE - if (newmessagepriority == NORMAL_MESSAGE_PRIORITY) - dat += "
                    There are new messages

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

                    " - if (newmessagepriority == 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" - 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/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - add_fingerprint(usr) - - if(reject_bad_text(href_list["write"])) - dpt = ckey(href_list["write"]) //write contains the string of the receiving department's name - - var/new_message = copytext(reject_bad_text(input(usr, "Write your message:", "Awaiting Input", "")),1,MAX_MESSAGE_LEN) - if(new_message) - message = new_message - screen = 9 - if (text2num(href_list["priority"]) < 2) - priority = -1 - else - priority = text2num(href_list["priority"]) - else - dpt = ""; - msgVerified = "" - msgStamped = "" - screen = 0 - priority = -1 - - if(href_list["writeAnnouncement"]) - var/new_message = copytext(reject_bad_text(input(usr, "Write your message:", "Awaiting Input", "")),1,MAX_MESSAGE_LEN) - if(new_message) - message = new_message - if (text2num(href_list["priority"]) < 2) - priority = -1 - else - priority = text2num(href_list["priority"]) - else - message = "" - announceAuth = FALSE - screen = 0 - - 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)].") - announceAuth = FALSE - message = "" - screen = 0 - - if(href_list["emergency"]) - if(!emergency) - var/radio_freq - switch(text2num(href_list["emergency"])) - if(1) //Security - radio_freq = FREQ_SECURITY - emergency = "Security" - if(2) //Engineering - radio_freq = FREQ_ENGINEERING - emergency = "Engineering" - if(3) //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), 3000) - - if( href_list["department"] && message ) - var/log_msg = message - var/sending = message - sending += "
                    " - if (msgVerified) - sending += msgVerified - sending += "
                    " - if (msgStamped) - sending += msgStamped - sending += "
                    " - screen = 7 //if it's successful, this will get overrwritten (7 = unsufccessfull, 6 = successfull) - if (sending) - var/pass = FALSE - var/datum/data_rc_msg/log = new(href_list["department"], department, log_msg, msgStamped, msgVerified, priority) - for (var/obj/machinery/telecomms/message_server/MS in GLOB.telecomms_list) - if (MS.toggled) - MS.rc_msgs += log - pass = TRUE - - if(pass) - var/radio_freq = 0 - switch(href_list["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 - Radio.set_frequency(radio_freq) - var/authentic - if(msgVerified || msgStamped) - authentic = " (Authenticated)" - - var/alert = "" - for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) - if (ckey(Console.department) == ckey(href_list["department"])) - switch(priority) - if(2) //High priority - alert = "PRIORITY Alert in [department][authentic]" - Console.createmessage(src, alert, sending, 2, 1) - if(3) // Extreme Priority - alert = "EXTREME PRIORITY Alert from [department][authentic]" - Console.createmessage(src, alert , sending, 3, 1) - else // Normal priority - alert = "Message from [department][authentic]" - Console.createmessage(src, alert , sending, 1, 1) - screen = 6 - - if(radio_freq) - Radio.talk_into(src, "[alert]: [message]", radio_freq) - - switch(priority) - if(2) - messages += "High Priority
                    To: [dpt]
                    [sending]" - else - messages += "To: [dpt]
                    [sending]" - else - say("NOTICE: No server detected!") - - - //Handle screen switching - switch(text2num(href_list["setScreen"])) - if(null) //skip - if(1) //req. assistance - screen = 1 - if(2) //req. supplies - screen = 2 - if(3) //relay information - screen = 3 -// if(4) //write message -// screen = 4 - if(5) //choose priority - screen = 5 - if(6) //sent successfully - screen = 6 - if(7) //unsuccessfull; not sent - screen = 7 - if(8) //view messages - screen = 8 - if(9) //authentication - screen = 9 - if(10) //send announcement - if(!announcementConsole) - return - screen = 10 - else //main menu - dpt = "" - msgVerified = "" - msgStamped = "" - message = "" - priority = -1 - screen = 0 - - //Handle silencing the console - switch( href_list["setSilent"] ) - if(null) //skip - if("1") - silent = TRUE - else - silent = FALSE - - updateUsrDialog() - return - -/obj/machinery/requests_console/say_mod(input, message_mode) - var/ending = copytext(input, length(input) - 2) - if (ending == "!!!") - . = "blares" - else - . = ..() - -/obj/machinery/requests_console/proc/clear_emergency() - emergency = null - update_icon() - -/obj/machinery/requests_console/proc/createmessage(source, title, message, priority) - var/linkedsender - if(istype(source, /obj/machinery/requests_console)) - var/obj/machinery/requests_console/sender = source - linkedsender = "[sender.department]" - else - capitalize(source) - linkedsender = source - capitalize(title) - switch(priority) - if(2) //High priority - if(newmessagepriority < HIGH_MESSAGE_PRIORITY) - newmessagepriority = HIGH_MESSAGE_PRIORITY - update_icon() - if(!silent) - playsound(src, 'sound/machines/twobeep.ogg', 50, 1) - say(title) - messages += "High Priority
                    From: [linkedsender]
                    [message]" - - if(3) // Extreme Priority - if(newmessagepriority < EXTREME_MESSAGE_PRIORITY) - newmessagepriority = EXTREME_MESSAGE_PRIORITY - update_icon() - if(1) - playsound(src, 'sound/machines/twobeep.ogg', 50, 1) - say(title) - messages += "!!!Extreme Priority!!!
                    From: [linkedsender]
                    [message]" - - else // Normal priority - if(newmessagepriority < NORMAL_MESSAGE_PRIORITY) - newmessagepriority = NORMAL_MESSAGE_PRIORITY - update_icon() - if(!src.silent) - playsound(src, 'sound/machines/twobeep.ogg', 50, 1) - say(title) - messages += "From: [linkedsender]
                    [message]" - -/obj/machinery/requests_console/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/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(istype(O, /obj/item/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 == 9) - msgVerified = "Verified by [ID.registered_name] ([ID.assignment])" - updateUsrDialog() - if(screen == 10) - 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 == 9) - var/obj/item/stamp/T = O - msgStamped = "Stamped with the [T.name]" - updateUsrDialog() - return - return ..() - -#undef NO_NEW_MESSAGE -#undef NORMAL_MESSAGE_PRIORITY -#undef HIGH_MESSAGE_PRIORITY -#undef EXTREME_MESSAGE_PRIORITY +/******************** 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) + +#define NO_NEW_MESSAGE 0 +#define NORMAL_MESSAGE_PRIORITY 1 +#define HIGH_MESSAGE_PRIORITY 2 +#define EXTREME_MESSAGE_PRIORITY 3 // not implemented, will probably require some hacking... everything needs to have a hidden feature in this game. + +/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 + // 0 = none (not listed, can only replied to) + // 1 = assistance + // 2 = supplies + // 3 = info + // 4 = ass + sup //Erro goddamn you just HAD to shorten "assistance" down to "ass" + // 5 = ass + info + // 6 = sup + info + // 7 = ass + sup + info + var/newmessagepriority = NO_NEW_MESSAGE + var/screen = 0 + // 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/dpt = ""; //the department which will be receiving the message + var/priority = -1 ; //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/power_change() + ..() + update_icon() + +/obj/machinery/requests_console/update_icon() + if(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(stat & NOPOWER) + if(icon_state != "req_comp_off") + icon_state = "req_comp_off" + else + if(emergency || (newmessagepriority == EXTREME_MESSAGE_PRIORITY)) + icon_state = "req_comp3" + else if(newmessagepriority == HIGH_MESSAGE_PRIORITY) + icon_state = "req_comp2" + else if(newmessagepriority == 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 + switch(departmentType) + if(1) + if(!("[department]" in GLOB.req_console_assistance)) + GLOB.req_console_assistance += department + if(2) + if(!("[department]" in GLOB.req_console_supplies)) + GLOB.req_console_supplies += department + if(3) + if(!("[department]" in GLOB.req_console_information)) + GLOB.req_console_information += department + if(4) + if(!("[department]" in GLOB.req_console_assistance)) + GLOB.req_console_assistance += department + if(!("[department]" in GLOB.req_console_supplies)) + GLOB.req_console_supplies += department + if(5) + if(!("[department]" in GLOB.req_console_assistance)) + GLOB.req_console_assistance += department + if(!("[department]" in GLOB.req_console_information)) + GLOB.req_console_information += department + if(6) + if(!("[department]" in GLOB.req_console_supplies)) + GLOB.req_console_supplies += department + if(!("[department]" in GLOB.req_console_information)) + GLOB.req_console_information += department + if(7) + if(!("[department]" in GLOB.req_console_assistance)) + GLOB.req_console_assistance += department + if(!("[department]" in GLOB.req_console_supplies)) + GLOB.req_console_supplies += department + if(!("[department]" in GLOB.req_console_information)) + GLOB.req_console_information += 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(1) //req. assistance + dat += "Which department do you need assistance from?

                    " + dat += "" + for(var/dpt in GLOB.req_console_assistance) + if (dpt != department) + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
                    [dpt]Normal High" + if(hackState) + dat += "EXTREME" + dat += "
                    " + dat += "
                    << Back
                    " + + if(2) //req. supplies + dat += "Which department do you need supplies from?

                    " + dat += "" + for(var/dpt in GLOB.req_console_supplies) + if (dpt != department) + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
                    [dpt]Normal High" + if(hackState) + dat += "EXTREME" + dat += "
                    " + dat += "
                    << Back
                    " + + if(3) //relay information + dat += "Which department would you like to send information to?

                    " + dat += "" + for(var/dpt in GLOB.req_console_information) + if (dpt != department) + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
                    [dpt]Normal High" + if(hackState) + dat += "EXTREME" + dat += "
                    " + dat += "
                    << Back
                    " + + if(6) //sent successfully + dat += "Message sent.

                    " + dat += "Continue
                    " + + if(7) //unsuccessful; not sent + dat += "An error occurred.

                    " + dat += "Continue
                    " + + if(8) //view messages + for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) + if (Console.department == department) + Console.newmessagepriority = NO_NEW_MESSAGE + Console.update_icon() + + newmessagepriority = 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(9) //authentication before sending + dat += "Message Authentication

                    " + dat += "Message for [dpt]: [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(10) //send announcement + 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
                    " + + else //main menu + screen = 0 + announceAuth = FALSE + if (newmessagepriority == NORMAL_MESSAGE_PRIORITY) + dat += "
                    There are new messages

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

                    " + if (newmessagepriority == 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" + 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/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + add_fingerprint(usr) + + if(reject_bad_text(href_list["write"])) + dpt = ckey(href_list["write"]) //write contains the string of the receiving department's name + + var/new_message = copytext(reject_bad_text(input(usr, "Write your message:", "Awaiting Input", "")),1,MAX_MESSAGE_LEN) + if(new_message) + message = new_message + screen = 9 + if (text2num(href_list["priority"]) < 2) + priority = -1 + else + priority = text2num(href_list["priority"]) + else + dpt = ""; + msgVerified = "" + msgStamped = "" + screen = 0 + priority = -1 + + if(href_list["writeAnnouncement"]) + var/new_message = copytext(reject_bad_text(input(usr, "Write your message:", "Awaiting Input", "")),1,MAX_MESSAGE_LEN) + if(new_message) + message = new_message + if (text2num(href_list["priority"]) < 2) + priority = -1 + else + priority = text2num(href_list["priority"]) + else + message = "" + announceAuth = FALSE + screen = 0 + + 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)].") + announceAuth = FALSE + message = "" + screen = 0 + + if(href_list["emergency"]) + if(!emergency) + var/radio_freq + switch(text2num(href_list["emergency"])) + if(1) //Security + radio_freq = FREQ_SECURITY + emergency = "Security" + if(2) //Engineering + radio_freq = FREQ_ENGINEERING + emergency = "Engineering" + if(3) //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), 3000) + + if( href_list["department"] && message ) + var/log_msg = message + var/sending = message + sending += "
                    " + if (msgVerified) + sending += msgVerified + sending += "
                    " + if (msgStamped) + sending += msgStamped + sending += "
                    " + screen = 7 //if it's successful, this will get overrwritten (7 = unsufccessfull, 6 = successfull) + if (sending) + var/pass = FALSE + var/datum/data_rc_msg/log = new(href_list["department"], department, log_msg, msgStamped, msgVerified, priority) + for (var/obj/machinery/telecomms/message_server/MS in GLOB.telecomms_list) + if (MS.toggled) + MS.rc_msgs += log + pass = TRUE + + if(pass) + var/radio_freq = 0 + switch(href_list["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 + Radio.set_frequency(radio_freq) + var/authentic + if(msgVerified || msgStamped) + authentic = " (Authenticated)" + + var/alert = "" + for (var/obj/machinery/requests_console/Console in GLOB.allConsoles) + if (ckey(Console.department) == ckey(href_list["department"])) + switch(priority) + if(2) //High priority + alert = "PRIORITY Alert in [department][authentic]" + Console.createmessage(src, alert, sending, 2, 1) + if(3) // Extreme Priority + alert = "EXTREME PRIORITY Alert from [department][authentic]" + Console.createmessage(src, alert , sending, 3, 1) + else // Normal priority + alert = "Message from [department][authentic]" + Console.createmessage(src, alert , sending, 1, 1) + screen = 6 + + if(radio_freq) + Radio.talk_into(src, "[alert]: [message]", radio_freq) + + switch(priority) + if(2) + messages += "High Priority
                    To: [dpt]
                    [sending]" + else + messages += "To: [dpt]
                    [sending]" + else + say("NOTICE: No server detected!") + + + //Handle screen switching + switch(text2num(href_list["setScreen"])) + if(null) //skip + if(1) //req. assistance + screen = 1 + if(2) //req. supplies + screen = 2 + if(3) //relay information + screen = 3 +// if(4) //write message +// screen = 4 + if(5) //choose priority + screen = 5 + if(6) //sent successfully + screen = 6 + if(7) //unsuccessfull; not sent + screen = 7 + if(8) //view messages + screen = 8 + if(9) //authentication + screen = 9 + if(10) //send announcement + if(!announcementConsole) + return + screen = 10 + else //main menu + dpt = "" + msgVerified = "" + msgStamped = "" + message = "" + priority = -1 + screen = 0 + + //Handle silencing the console + switch( href_list["setSilent"] ) + if(null) //skip + if("1") + silent = TRUE + else + silent = FALSE + + updateUsrDialog() + return + +/obj/machinery/requests_console/say_mod(input, message_mode) + var/ending = copytext(input, length(input) - 2) + if (ending == "!!!") + . = "blares" + else + . = ..() + +/obj/machinery/requests_console/proc/clear_emergency() + emergency = null + update_icon() + +/obj/machinery/requests_console/proc/createmessage(source, title, message, priority) + var/linkedsender + if(istype(source, /obj/machinery/requests_console)) + var/obj/machinery/requests_console/sender = source + linkedsender = "[sender.department]" + else + capitalize(source) + linkedsender = source + capitalize(title) + switch(priority) + if(2) //High priority + if(newmessagepriority < HIGH_MESSAGE_PRIORITY) + newmessagepriority = HIGH_MESSAGE_PRIORITY + update_icon() + if(!silent) + playsound(src, 'sound/machines/twobeep.ogg', 50, 1) + say(title) + messages += "High Priority
                    From: [linkedsender]
                    [message]" + + if(3) // Extreme Priority + if(newmessagepriority < EXTREME_MESSAGE_PRIORITY) + newmessagepriority = EXTREME_MESSAGE_PRIORITY + update_icon() + if(1) + playsound(src, 'sound/machines/twobeep.ogg', 50, 1) + say(title) + messages += "!!!Extreme Priority!!!
                    From: [linkedsender]
                    [message]" + + else // Normal priority + if(newmessagepriority < NORMAL_MESSAGE_PRIORITY) + newmessagepriority = NORMAL_MESSAGE_PRIORITY + update_icon() + if(!src.silent) + playsound(src, 'sound/machines/twobeep.ogg', 50, 1) + say(title) + messages += "From: [linkedsender]
                    [message]" + +/obj/machinery/requests_console/attackby(obj/item/O, mob/user, params) + if(istype(O, /obj/item/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(istype(O, /obj/item/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 == 9) + msgVerified = "Verified by [ID.registered_name] ([ID.assignment])" + updateUsrDialog() + if(screen == 10) + 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 == 9) + var/obj/item/stamp/T = O + msgStamped = "Stamped with the [T.name]" + updateUsrDialog() + return + return ..() + +#undef NO_NEW_MESSAGE +#undef NORMAL_MESSAGE_PRIORITY +#undef HIGH_MESSAGE_PRIORITY +#undef EXTREME_MESSAGE_PRIORITY diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm index af5f50f28d..a722fcafc3 100644 --- a/code/game/machinery/status_display.dm +++ b/code/game/machinery/status_display.dm @@ -1,368 +1,368 @@ -// Status display -// (formerly Countdown timer display) - -#define CHARS_PER_LINE 5 -#define FONT_SIZE "5pt" -#define FONT_COLOR "#09f" -#define FONT_STYLE "Arial Black" -#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 - - 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) - 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(m1) > CHARS_PER_LINE) - message1 = m1 - else - message1 = "" - index1 = 0 - - if(m2) - index2 = (length(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(stat & NOPOWER) - // No power, no processing. - remove_display() - return PROCESS_KILL - - var/line1 = message1 - if(index1) - line1 = copytext("[message1]|[message1]", index1, index1+CHARS_PER_LINE) - var/message1_len = length(message1) - index1 += SCROLL_SPEED - if(index1 > message1_len) - index1 -= message1_len - - var/line2 = message2 - if(index2) - line2 = copytext("[message2]|[message2]", index2, index2+CHARS_PER_LINE) - var/message2_len = length(message2) - index2 += SCROLL_SPEED - if(index2 > message2_len) - index2 -= message2_len - - 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(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(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(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(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(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 || (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("shuttle_id") - update() - -/obj/machinery/status_display/shuttle/proc/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 || (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 "Arial Black" +#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 + + 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) + 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(m1) > CHARS_PER_LINE) + message1 = m1 + else + message1 = "" + index1 = 0 + + if(m2) + index2 = (length(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(stat & NOPOWER) + // No power, no processing. + remove_display() + return PROCESS_KILL + + var/line1 = message1 + if(index1) + line1 = copytext("[message1]|[message1]", index1, index1+CHARS_PER_LINE) + var/message1_len = length(message1) + index1 += SCROLL_SPEED + if(index1 > message1_len) + index1 -= message1_len + + var/line2 = message2 + if(index2) + line2 = copytext("[message2]|[message2]", index2, index2+CHARS_PER_LINE) + var/message2_len = length(message2) + index2 += SCROLL_SPEED + if(index2 > message2_len) + index2 -= message2_len + + 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(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(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(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(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(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 || (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("shuttle_id") + update() + +/obj/machinery/status_display/shuttle/proc/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 || (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 adc0e5bf1e..670ce91674 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -1,443 +1,443 @@ -// 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" - density = TRUE - max_integrity = 250 - - 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 - - var/suit_type = null - var/helmet_type = null - var/mask_type = null - var/storage_type = null - - state_open = FALSE - var/locked = FALSE - panel_open = FALSE - var/safeties = TRUE - - var/uv = FALSE - var/uv_super = FALSE - var/uv_cycles = 6 - var/message_cooldown - var/breakout_time = 300 - -/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/captain - mask_type = /obj/item/clothing/mask/gas/sechailer - 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 - storage_type= /obj/item/clothing/shoes/magboots - -/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 - storage_type = /obj/item/tank/jetpack/oxygen/security - -/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/atmos - suit_type = /obj/item/clothing/suit/space/hardsuit/engine/atmos - mask_type = /obj/item/clothing/mask/gas - storage_type = /obj/item/watertank/atmos - -/obj/machinery/suit_storage_unit/mining - suit_type = /obj/item/clothing/suit/hooded/explorer/standard - 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 - -/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_icon() - cut_overlays() - - if(uv) - if(uv_super) - add_overlay("super") - else if(occupant) - add_overlay("uvhuman") - else - add_overlay("uv") - else if(state_open) - if(stat & BROKEN) - add_overlay("broken") - else - add_overlay("open") - if(suit) - add_overlay("suit") - if(helmet) - add_overlay("helm") - if(storage) - add_overlay("storage") - else if(occupant) - add_overlay("human") - -/obj/machinery/suit_storage_unit/power_change() - ..() - if(!is_operational() && state_open) - open_machine() - dump_contents() - update_icon() - -/obj/machinery/suit_storage_unit/proc/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/user) - if(user.stat || user.lying || !Adjacent(user) || !Adjacent(A) || !isliving(A)) - 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) - -/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, 1) - 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, 1) - 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) - AM.clean_blood() - AM.fingerprints = list() - 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/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/head/mob_holder)) - to_chat(user, "You can't quite fit that in while you hold it!") - return - 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 ..() - -/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, "suit_storage_unit", name, 400, 305, 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 - if(suit) - data["suit"] = suit.name - if(mask) - data["mask"] = mask.name - if(storage) - data["storage"] = storage.name - if(occupant) - data["occupied"] = 1 - 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" + density = TRUE + max_integrity = 250 + + 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 + + var/suit_type = null + var/helmet_type = null + var/mask_type = null + var/storage_type = null + + state_open = FALSE + var/locked = FALSE + panel_open = FALSE + var/safeties = TRUE + + var/uv = FALSE + var/uv_super = FALSE + var/uv_cycles = 6 + var/message_cooldown + var/breakout_time = 300 + +/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/captain + mask_type = /obj/item/clothing/mask/gas/sechailer + 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 + storage_type= /obj/item/clothing/shoes/magboots + +/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 + storage_type = /obj/item/tank/jetpack/oxygen/security + +/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/atmos + suit_type = /obj/item/clothing/suit/space/hardsuit/engine/atmos + mask_type = /obj/item/clothing/mask/gas + storage_type = /obj/item/watertank/atmos + +/obj/machinery/suit_storage_unit/mining + suit_type = /obj/item/clothing/suit/hooded/explorer/standard + 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 + +/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_icon() + cut_overlays() + + if(uv) + if(uv_super) + add_overlay("super") + else if(occupant) + add_overlay("uvhuman") + else + add_overlay("uv") + else if(state_open) + if(stat & BROKEN) + add_overlay("broken") + else + add_overlay("open") + if(suit) + add_overlay("suit") + if(helmet) + add_overlay("helm") + if(storage) + add_overlay("storage") + else if(occupant) + add_overlay("human") + +/obj/machinery/suit_storage_unit/power_change() + ..() + if(!is_operational() && state_open) + open_machine() + dump_contents() + update_icon() + +/obj/machinery/suit_storage_unit/proc/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/user) + if(user.stat || user.lying || !Adjacent(user) || !Adjacent(A) || !isliving(A)) + 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) + +/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, 1) + 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, 1) + 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) + AM.clean_blood() + AM.fingerprints = list() + 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/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/head/mob_holder)) + to_chat(user, "You can't quite fit that in while you hold it!") + return + 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 ..() + +/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, "suit_storage_unit", name, 400, 305, 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 + if(suit) + data["suit"] = suit.name + if(mask) + data["mask"] = mask.name + if(storage) + data["storage"] = storage.name + if(occupant) + data["occupied"] = 1 + 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 8a33241d46..e300afe6b9 100644 --- a/code/game/machinery/syndicatebeacon.dm +++ b/code/game/machinery/syndicatebeacon.dm @@ -1,165 +1,165 @@ -GLOBAL_VAR_INIT(singularity_counter, 0) - -#define METEOR_DISASTER_MODIFIER 0.5 - -//////////////////////////////////////// -//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 - stat = 0 - verb_say = "states" - var/cooldown = 0 - var/active = FALSE - var/meteor_buff = FALSE - var/icontype = "beacon" - -/obj/machinery/power/singularity_beacon/proc/Activate(mob/user = null) - if(active) - return FALSE - if(surplus() < 1500) - if(user) - to_chat(user, "The connected wire doesn't have enough current.") - return FALSE - if(is_station_level(z)) - increment_meteor_waves() - for(var/obj/singularity/singulo in GLOB.singularities) - if(singulo.z == z) - singulo.target = src - icon_state = "[icontype]1" - active = TRUE - if(user) - to_chat(user, "You activate the beacon.") - return TRUE - -/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user) - if(!active) - return FALSE - for(var/obj/singularity/singulo in GLOB.singularities) - if(singulo.target == src) - singulo.target = null - icon_state = "[icontype]0" - active = FALSE - if(user) - to_chat(user, "You deactivate the beacon.") - if(meteor_buff) - decrement_meteor_waves() - return TRUE - -/obj/machinery/power/singularity_beacon/proc/increment_meteor_waves() - meteor_buff = TRUE - GLOB.singularity_counter++ - for(var/datum/round_event_control/meteor_wave/W in SSevents.control) - W.weight += round(initial(W.weight) * METEOR_DISASTER_MODIFIER) - -/obj/machinery/power/singularity_beacon/proc/decrement_meteor_waves() - meteor_buff = FALSE - GLOB.singularity_counter-- - for(var/datum/round_event_control/meteor_wave/W in SSevents.control) - W.weight -= round(initial(W.weight) * METEOR_DISASTER_MODIFIER) - -/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 beacon to the floor first!") - -/obj/machinery/power/singularity_beacon/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/screwdriver)) - if(active) - to_chat(user, "You need to deactivate the beacon first!") - return - - if(anchored) - setAnchored(FALSE) - to_chat(user, "You unscrew the beacon from the floor.") - disconnect_from_network() - return - else - if(!connect_to_network()) - to_chat(user, "This device must be placed over an exposed, powered cable node!") - return - setAnchored(TRUE) - to_chat(user, "You screw the beacon to the floor and attach the cable.") - return - 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 - - var/is_on_station = is_station_level(z) - if(meteor_buff && !is_on_station) - decrement_meteor_waves() - else if(!meteor_buff && is_on_station) - increment_meteor_waves() - - 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, 1, 1) - 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 - -#undef METEOR_DISASTER_MODIFIER +GLOBAL_VAR_INIT(singularity_counter, 0) + +#define METEOR_DISASTER_MODIFIER 0.5 + +//////////////////////////////////////// +//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 + stat = 0 + verb_say = "states" + var/cooldown = 0 + var/active = FALSE + var/meteor_buff = FALSE + var/icontype = "beacon" + +/obj/machinery/power/singularity_beacon/proc/Activate(mob/user = null) + if(active) + return FALSE + if(surplus() < 1500) + if(user) + to_chat(user, "The connected wire doesn't have enough current.") + return FALSE + if(is_station_level(z)) + increment_meteor_waves() + for(var/obj/singularity/singulo in GLOB.singularities) + if(singulo.z == z) + singulo.target = src + icon_state = "[icontype]1" + active = TRUE + if(user) + to_chat(user, "You activate the beacon.") + return TRUE + +/obj/machinery/power/singularity_beacon/proc/Deactivate(mob/user) + if(!active) + return FALSE + for(var/obj/singularity/singulo in GLOB.singularities) + if(singulo.target == src) + singulo.target = null + icon_state = "[icontype]0" + active = FALSE + if(user) + to_chat(user, "You deactivate the beacon.") + if(meteor_buff) + decrement_meteor_waves() + return TRUE + +/obj/machinery/power/singularity_beacon/proc/increment_meteor_waves() + meteor_buff = TRUE + GLOB.singularity_counter++ + for(var/datum/round_event_control/meteor_wave/W in SSevents.control) + W.weight += round(initial(W.weight) * METEOR_DISASTER_MODIFIER) + +/obj/machinery/power/singularity_beacon/proc/decrement_meteor_waves() + meteor_buff = FALSE + GLOB.singularity_counter-- + for(var/datum/round_event_control/meteor_wave/W in SSevents.control) + W.weight -= round(initial(W.weight) * METEOR_DISASTER_MODIFIER) + +/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 beacon to the floor first!") + +/obj/machinery/power/singularity_beacon/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/screwdriver)) + if(active) + to_chat(user, "You need to deactivate the beacon first!") + return + + if(anchored) + setAnchored(FALSE) + to_chat(user, "You unscrew the beacon from the floor.") + disconnect_from_network() + return + else + if(!connect_to_network()) + to_chat(user, "This device must be placed over an exposed, powered cable node!") + return + setAnchored(TRUE) + to_chat(user, "You screw the beacon to the floor and attach the cable.") + return + 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 + + var/is_on_station = is_station_level(z) + if(meteor_buff && !is_on_station) + decrement_meteor_waves() + else if(!meteor_buff && is_on_station) + increment_meteor_waves() + + 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, 1, 1) + 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 + +#undef METEOR_DISASTER_MODIFIER diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm index b7737eb6d4..07028ba2d3 100644 --- a/code/game/machinery/telecomms/computers/message.dm +++ b/code/game/machinery/telecomms/computers/message.dm @@ -1,466 +1,466 @@ -/* - The monitoring computer for the messaging server. - Lets you read PDA and request console messages. -*/ - -#define LINKED_SERVER_NONRESPONSIVE (!linkedServer || (linkedServer.stat & (NOPOWER|BROKEN))) - -// 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 = 0 // 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(istype(O, /obj/item/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)) - to_chat(user, "A no server error appears on the screen.") - return - obj_flags |= EMAGGED - screen = 2 - 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 - return TRUE - -/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 - . = ..() - -/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.toggled ? "\[On\]":"\[Off\]"]

                    " - else - dat += "

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

                    " - - if(hacking || (obj_flags & EMAGGED)) - screen = 2 - else if(!auth || LINKED_SERVER_NONRESPONSIVE) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - screen = 0 - - switch(screen) - //Main menu - if(0) - // = 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(1) - 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(2) - 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(3) - 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(4) - - 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 = 0 // 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 = 0 - 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 = 1 - - //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 = trim(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 = 2 - //Time it takes to bruteforce is dependant on the password length. - spawn(100*length(linkedServer.decryptkey)) - if(src && linkedServer && usr) - BruteForce(usr) - //Delete the log. - if (href_list["delete_logs"]) - //Are they on the view logs screen? - if(screen == 1) - 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"]) - message = "NOTICE: Log Deleted!" - //Delete the request console log. - if (href_list["delete_requests"]) - //Are they on the view logs screen? - if(screen == 4) - 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"]) - message = "NOTICE: Log Deleted!" - //Create a custom message - if (href_list["msg"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - else if(auth) - screen = 3 - //Fake messaging selection - KEY REQUIRED - if (href_list["select"]) - if(LINKED_SERVER_NONRESPONSIVE) - message = noserver - screen = 0 - 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 sortNames(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/pda/signal = new(src, list( - "name" = "[customsender]", - "job" = "[customjob]", - "message" = custommessage, - "emoji_message" = emoji_parse(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 = 4 - - if (href_list["back"]) - screen = 0 - - return attack_hand(usr) - -#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." - info_links = info - add_overlay("paper_words") - -/obj/item/paper/monitorkey/LateInitialize() - for (var/obj/machinery/telecomms/message_server/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.stat & (NOPOWER|BROKEN))) + +// 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 = 0 // 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(istype(O, /obj/item/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)) + to_chat(user, "A no server error appears on the screen.") + return + obj_flags |= EMAGGED + screen = 2 + 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 + return TRUE + +/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 + . = ..() + +/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.toggled ? "\[On\]":"\[Off\]"]

                    " + else + dat += "

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

                    " + + if(hacking || (obj_flags & EMAGGED)) + screen = 2 + else if(!auth || LINKED_SERVER_NONRESPONSIVE) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + screen = 0 + + switch(screen) + //Main menu + if(0) + // = 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(1) + 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(2) + 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(3) + 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(4) + + 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 = 0 // 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 = 0 + 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 = 1 + + //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 = trim(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 = 2 + //Time it takes to bruteforce is dependant on the password length. + spawn(100*length(linkedServer.decryptkey)) + if(src && linkedServer && usr) + BruteForce(usr) + //Delete the log. + if (href_list["delete_logs"]) + //Are they on the view logs screen? + if(screen == 1) + 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"]) + message = "NOTICE: Log Deleted!" + //Delete the request console log. + if (href_list["delete_requests"]) + //Are they on the view logs screen? + if(screen == 4) + 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"]) + message = "NOTICE: Log Deleted!" + //Create a custom message + if (href_list["msg"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + else if(auth) + screen = 3 + //Fake messaging selection - KEY REQUIRED + if (href_list["select"]) + if(LINKED_SERVER_NONRESPONSIVE) + message = noserver + screen = 0 + 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 sortNames(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/pda/signal = new(src, list( + "name" = "[customsender]", + "job" = "[customjob]", + "message" = custommessage, + "emoji_message" = emoji_parse(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 = 4 + + if (href_list["back"]) + screen = 0 + + return attack_hand(usr) + +#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." + info_links = info + add_overlay("paper_words") + +/obj/item/paper/monitorkey/LateInitialize() + for (var/obj/machinery/telecomms/message_server/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 56870c5198..0cf356ad3d 100644 --- a/code/game/machinery/telecomms/machines/message_server.dm +++ b/code/game/machinery/telecomms/machines/message_server.dm @@ -1,180 +1,180 @@ -/* - 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. -/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) - - -// The message server itself. -/obj/machinery/telecomms/message_server - icon = 'icons/obj/machines/research.dmi' - icon_state = "server" - name = "Messaging Server" - desc = "A machine that attempts to gather the secret knowledge of the universe." - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 100 - - id = "Messaging Server" - network = "tcommsat" - autolinkers = list("common") - - var/list/datum/data_pda_msg/pda_msgs = list() - var/list/datum/data_rc_msg/rc_msgs = list() - var/decryptkey - -/obj/machinery/telecomms/message_server/Initialize() - . = ..() - if (!decryptkey) - decryptkey = GenerateKey() - pda_msgs += new /datum/data_pda_msg("System Administrator", "system", "This is an automated message. The messaging system is functioning correctly.") - -/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/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(toggled && (stat & (BROKEN|NOPOWER))) - toggled = FALSE - update_icon() - -/obj/machinery/telecomms/message_server/receive_information(datum/signal/subspace/pda/signal, obj/machinery/telecomms/machine_from) - // can't log non-PDA signals - if(!istype(signal) || !signal.data["message"] || !toggled) - return - - // log the signal - var/datum/data_pda_msg/M = new(signal.format_target(), "[signal.data["name"]] ([signal.data["job"]])", signal.data["message"], signal.data["photo"]) - pda_msgs += M - signal.logged = M - - // 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_icon() - if((stat & (BROKEN|NOPOWER))) - icon_state = "server-nopower" - else if (!toggled) - icon_state = "server-off" - else - icon_state = "server-on" - - -// PDA signal datum -/datum/signal/subspace/pda - frequency = FREQ_COMMON - server_type = /obj/machinery/telecomms/message_server - var/datum/data_pda_msg/logged - -/datum/signal/subspace/pda/New(source, data) - src.source = source - src.data = data - var/turf/T = get_turf(source) - levels = list(T.z) - -/datum/signal/subspace/pda/copy() - var/datum/signal/subspace/pda/copy = new(source, data.Copy()) - copy.original = src - copy.levels = levels - return copy - -/datum/signal/subspace/pda/proc/format_target() - if (length(data["targets"]) > 1) - return "Everyone" - return data["targets"][1] - -/datum/signal/subspace/pda/proc/format_message(emojify = FALSE) - var/message = emojify ? data["emoji_message"] : data["message"] - if (logged && data["photo"]) - return "\"[message]\" (Photo)" - return "\"[message]\"" - -/datum/signal/subspace/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) - - -// 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 - -/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(1) - priority = "Normal" - if(2) - priority = "High" - if(3) - priority = "Extreme" - else - priority = "Undetermined" - +/* + 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. +/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) + + +// The message server itself. +/obj/machinery/telecomms/message_server + icon = 'icons/obj/machines/research.dmi' + icon_state = "server" + name = "Messaging Server" + desc = "A machine that attempts to gather the secret knowledge of the universe." + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 100 + + id = "Messaging Server" + network = "tcommsat" + autolinkers = list("common") + + var/list/datum/data_pda_msg/pda_msgs = list() + var/list/datum/data_rc_msg/rc_msgs = list() + var/decryptkey + +/obj/machinery/telecomms/message_server/Initialize() + . = ..() + if (!decryptkey) + decryptkey = GenerateKey() + pda_msgs += new /datum/data_pda_msg("System Administrator", "system", "This is an automated message. The messaging system is functioning correctly.") + +/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/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(toggled && (stat & (BROKEN|NOPOWER))) + toggled = FALSE + update_icon() + +/obj/machinery/telecomms/message_server/receive_information(datum/signal/subspace/pda/signal, obj/machinery/telecomms/machine_from) + // can't log non-PDA signals + if(!istype(signal) || !signal.data["message"] || !toggled) + return + + // log the signal + var/datum/data_pda_msg/M = new(signal.format_target(), "[signal.data["name"]] ([signal.data["job"]])", signal.data["message"], signal.data["photo"]) + pda_msgs += M + signal.logged = M + + // 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_icon() + if((stat & (BROKEN|NOPOWER))) + icon_state = "server-nopower" + else if (!toggled) + icon_state = "server-off" + else + icon_state = "server-on" + + +// PDA signal datum +/datum/signal/subspace/pda + frequency = FREQ_COMMON + server_type = /obj/machinery/telecomms/message_server + var/datum/data_pda_msg/logged + +/datum/signal/subspace/pda/New(source, data) + src.source = source + src.data = data + var/turf/T = get_turf(source) + levels = list(T.z) + +/datum/signal/subspace/pda/copy() + var/datum/signal/subspace/pda/copy = new(source, data.Copy()) + copy.original = src + copy.levels = levels + return copy + +/datum/signal/subspace/pda/proc/format_target() + if (length(data["targets"]) > 1) + return "Everyone" + return data["targets"][1] + +/datum/signal/subspace/pda/proc/format_message(emojify = FALSE) + var/message = emojify ? data["emoji_message"] : data["message"] + if (logged && data["photo"]) + return "\"[message]\" (Photo)" + return "\"[message]\"" + +/datum/signal/subspace/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) + + +// 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 + +/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(1) + priority = "Normal" + if(2) + priority = "High" + if(3) + priority = "Extreme" + else + priority = "Undetermined" + diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm index b82b3071a3..7a9f682d34 100644 --- a/code/game/machinery/telecomms/telecomunications.dm +++ b/code/game/machinery/telecomms/telecomunications.dm @@ -1,152 +1,152 @@ - -/* - 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() - 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(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)) - if(!(stat & EMPED)) - stat |= EMPED - var/duration = (300 * 10)/severity - spawn(rand(duration - 20, duration + 20)) // Takes a long time for the machines to reboot. - 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() + 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(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)) + if(!(stat & EMPED)) + stat |= EMPED + var/duration = (300 * 10)/severity + spawn(rand(duration - 20, duration + 20)) // Takes a long time for the machines to reboot. + stat &= ~EMPED diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 6990a7a4ba..b5eed68ab0 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -1,284 +1,284 @@ -/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/has_corgi = 0 - var/obj/item/color_source - var/max_wash_capacity = 5 - -/obj/machinery/washing_machine/examine(mob/user) - . = ..() - . += "Alt-click it to start a wash cycle." - -/obj/machinery/washing_machine/AltClick(mob/user) - . = ..() - if(!user.canUseTopic(src)) - return - - if(busy) - return - - if(state_open) - to_chat(user, "Close the door first") - return TRUE - - if(bloody_mess) - to_chat(user, "[src] must be cleaned up first.") - return TRUE - - if(has_corgi) - bloody_mess = 1 - - busy = TRUE - update_icon() - addtimer(CALLBACK(src, .proc/wash_cycle), 200) - START_PROCESSING(SSfastprocess, src) - return TRUE - -/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/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_WEAK) - AM.clean_blood() - AM.machine_wash(src) - - busy = FALSE - if(color_source) - qdel(color_source) - color_source = null - update_icon() - - -//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/wetleather(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) - -/obj/item/paper/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - if(istype(WM.color_source, /obj/item/toy/crayon)) - var/obj/item/toy/crayon/CR = WM.color_source - add_atom_colour(CR.paint_color, WASHABLE_COLOUR_PRIORITY) - -/obj/item/reagents_containers/rag/towel/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - if(istype(WM.color_source, /obj/item/toy/crayon)) - var/obj/item/toy/crayon/CR = WM.color_source - add_atom_colour(CR.paint_color, WASHABLE_COLOUR_PRIORITY) - -/mob/living/simple_animal/pet/dog/corgi/machine_wash(obj/machinery/washing_machine/WM) - gib() - -/obj/item/clothing/under/color/machine_wash(obj/machinery/washing_machine/WM) - jumpsuit_wash(WM) - -/obj/item/clothing/under/rank/machine_wash(obj/machinery/washing_machine/WM) - jumpsuit_wash(WM) - -/obj/item/clothing/under/proc/jumpsuit_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - var/wash_color = WM.color_source.item_color - var/obj/item/clothing/under/U - for(var/T in typesof(/obj/item/clothing/under/color)) - var/obj/item/clothing/under/color/J = T - if(wash_color == initial(J.item_color)) - U = J - break - if(!U) - for(var/T in typesof(/obj/item/clothing/under/rank)) - var/obj/item/clothing/under/rank/R = T - if(wash_color == initial(R.item_color)) - U = R - break - if(U) - item_state = initial(U.item_state) - icon_state = initial(U.icon_state) - item_color = wash_color - name = initial(U.name) - desc = "The colors are a bit dodgy." - 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/gloves/color/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - var/wash_color = WM.color_source.item_color - for(var/T in typesof(/obj/item/clothing/gloves/color)) - var/obj/item/clothing/gloves/color/G = T - if(wash_color == initial(G.item_color)) - item_state = initial(G.item_state) - icon_state = initial(G.icon_state) - item_color = wash_color - name = initial(G.name) - desc = "The colors are a bit dodgy." - break - -/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) - if(WM.color_source) - var/wash_color = WM.color_source.item_color - for(var/T in typesof(/obj/item/clothing/shoes/sneakers)) - var/obj/item/clothing/shoes/sneakers/S = T - if(wash_color == initial(S.item_color)) - icon_state = initial(S.icon_state) - item_color = wash_color - name = initial(S.name) - desc = "The colors are a bit dodgy." - break - -/obj/item/bedsheet/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - var/wash_color = WM.color_source.item_color - for(var/T in typesof(/obj/item/bedsheet)) - var/obj/item/bedsheet/B = T - if(wash_color == initial(B.item_color)) - icon_state = initial(B.icon_state) - item_color = wash_color - name = initial(B.name) - desc = "The colors are a bit dodgy." - break - -/obj/item/clothing/head/soft/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - var/wash_color = WM.color_source.item_color - for(var/T in typesof(/obj/item/clothing/head/soft)) - var/obj/item/clothing/head/soft/H = T - if(wash_color == initial(H.item_color)) - icon_state = initial(H.icon_state) - item_color = wash_color - name = initial(H.name) - desc = "The colors are a bit dodgy." - break - - -/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() - cut_overlays() - 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]" - if(panel_open) - add_overlay("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 - - if(istype(W, /obj/item/clothing/head/mob_holder)) - to_chat(user, "It's too unwieldly to put in this way.") - return 1 - - else if(user.a_intent != INTENT_HARM) - - if (!state_open) - to_chat(user, "Open the door first!") - return 1 - - if(bloody_mess) - to_chat(user, "[src] must be cleaned up first.") - return 1 - - if(contents.len >= max_wash_capacity) - to_chat(user, "The washing machine is full!") - return 1 - - if(!user.transferItemToLoc(W, src)) - to_chat(user, "\The [W] is stuck to your hand, you cannot put it in the washing machine!") - return 1 - - if(istype(W, /obj/item/toy/crayon) || istype(W, /obj/item/stamp)) - 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(iscorgi(L)) - has_corgi = 1 - 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 (loc, 2) - qdel(src) - -/obj/machinery/washing_machine/open_machine(drop = 1) - ..() - density = TRUE //because machinery/open_machine() sets it to 0 - color_source = null - has_corgi = 0 +/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/has_corgi = 0 + var/obj/item/color_source + var/max_wash_capacity = 5 + +/obj/machinery/washing_machine/examine(mob/user) + . = ..() + . += "Alt-click it to start a wash cycle." + +/obj/machinery/washing_machine/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src)) + return + + if(busy) + return + + if(state_open) + to_chat(user, "Close the door first") + return TRUE + + if(bloody_mess) + to_chat(user, "[src] must be cleaned up first.") + return TRUE + + if(has_corgi) + bloody_mess = 1 + + busy = TRUE + update_icon() + addtimer(CALLBACK(src, .proc/wash_cycle), 200) + START_PROCESSING(SSfastprocess, src) + return TRUE + +/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/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_WEAK) + AM.clean_blood() + AM.machine_wash(src) + + busy = FALSE + if(color_source) + qdel(color_source) + color_source = null + update_icon() + + +//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/wetleather(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) + +/obj/item/paper/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + if(istype(WM.color_source, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/CR = WM.color_source + add_atom_colour(CR.paint_color, WASHABLE_COLOUR_PRIORITY) + +/obj/item/reagents_containers/rag/towel/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + if(istype(WM.color_source, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/CR = WM.color_source + add_atom_colour(CR.paint_color, WASHABLE_COLOUR_PRIORITY) + +/mob/living/simple_animal/pet/dog/corgi/machine_wash(obj/machinery/washing_machine/WM) + gib() + +/obj/item/clothing/under/color/machine_wash(obj/machinery/washing_machine/WM) + jumpsuit_wash(WM) + +/obj/item/clothing/under/rank/machine_wash(obj/machinery/washing_machine/WM) + jumpsuit_wash(WM) + +/obj/item/clothing/under/proc/jumpsuit_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + var/wash_color = WM.color_source.item_color + var/obj/item/clothing/under/U + for(var/T in typesof(/obj/item/clothing/under/color)) + var/obj/item/clothing/under/color/J = T + if(wash_color == initial(J.item_color)) + U = J + break + if(!U) + for(var/T in typesof(/obj/item/clothing/under/rank)) + var/obj/item/clothing/under/rank/R = T + if(wash_color == initial(R.item_color)) + U = R + break + if(U) + item_state = initial(U.item_state) + icon_state = initial(U.icon_state) + item_color = wash_color + name = initial(U.name) + desc = "The colors are a bit dodgy." + 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/gloves/color/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + var/wash_color = WM.color_source.item_color + for(var/T in typesof(/obj/item/clothing/gloves/color)) + var/obj/item/clothing/gloves/color/G = T + if(wash_color == initial(G.item_color)) + item_state = initial(G.item_state) + icon_state = initial(G.icon_state) + item_color = wash_color + name = initial(G.name) + desc = "The colors are a bit dodgy." + break + +/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) + if(WM.color_source) + var/wash_color = WM.color_source.item_color + for(var/T in typesof(/obj/item/clothing/shoes/sneakers)) + var/obj/item/clothing/shoes/sneakers/S = T + if(wash_color == initial(S.item_color)) + icon_state = initial(S.icon_state) + item_color = wash_color + name = initial(S.name) + desc = "The colors are a bit dodgy." + break + +/obj/item/bedsheet/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + var/wash_color = WM.color_source.item_color + for(var/T in typesof(/obj/item/bedsheet)) + var/obj/item/bedsheet/B = T + if(wash_color == initial(B.item_color)) + icon_state = initial(B.icon_state) + item_color = wash_color + name = initial(B.name) + desc = "The colors are a bit dodgy." + break + +/obj/item/clothing/head/soft/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + var/wash_color = WM.color_source.item_color + for(var/T in typesof(/obj/item/clothing/head/soft)) + var/obj/item/clothing/head/soft/H = T + if(wash_color == initial(H.item_color)) + icon_state = initial(H.icon_state) + item_color = wash_color + name = initial(H.name) + desc = "The colors are a bit dodgy." + break + + +/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() + cut_overlays() + 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]" + if(panel_open) + add_overlay("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 + + if(istype(W, /obj/item/clothing/head/mob_holder)) + to_chat(user, "It's too unwieldly to put in this way.") + return 1 + + else if(user.a_intent != INTENT_HARM) + + if (!state_open) + to_chat(user, "Open the door first!") + return 1 + + if(bloody_mess) + to_chat(user, "[src] must be cleaned up first.") + return 1 + + if(contents.len >= max_wash_capacity) + to_chat(user, "The washing machine is full!") + return 1 + + if(!user.transferItemToLoc(W, src)) + to_chat(user, "\The [W] is stuck to your hand, you cannot put it in the washing machine!") + return 1 + + if(istype(W, /obj/item/toy/crayon) || istype(W, /obj/item/stamp)) + 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(iscorgi(L)) + has_corgi = 1 + 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 (loc, 2) + qdel(src) + +/obj/machinery/washing_machine/open_machine(drop = 1) + ..() + density = TRUE //because machinery/open_machine() sets it to 0 + color_source = null + has_corgi = 0 diff --git a/code/game/machinery/wishgranter.dm b/code/game/machinery/wishgranter.dm index bec51d1d30..f618888d98 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 431d4c30b3..ae57e5c108 100644 --- a/code/game/mecha/combat/combat.dm +++ b/code/game/mecha/combat/combat.dm @@ -1,11 +1,11 @@ -/obj/mecha/combat - force = 30 - 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/mecha/mecha_mouse.dmi' - var/spawn_tracked = TRUE - -/obj/mecha/combat/Initialize() - . = ..() - if(spawn_tracked) +/obj/mecha/combat + force = 30 + 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/mecha/mecha_mouse.dmi' + var/spawn_tracked = TRUE + +/obj/mecha/combat/Initialize() + . = ..() + if(spawn_tracked) trackers += new /obj/item/mecha_parts/mecha_tracking(src) \ No newline at end of file diff --git a/code/game/mecha/combat/durand.dm b/code/game/mecha/combat/durand.dm index caaa3e3a00..498266d043 100644 --- a/code/game/mecha/combat/durand.dm +++ b/code/game/mecha/combat/durand.dm @@ -1,21 +1,21 @@ -/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" = 0, "fire" = 100, "acid" = 100) - max_temperature = 30000 - infra_luminosity = 8 - force = 40 - wreckage = /obj/structure/mecha_wreckage/durand - -/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 + 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" = 0, "fire" = 100, "acid" = 100) + max_temperature = 30000 + infra_luminosity = 8 + force = 40 + wreckage = /obj/structure/mecha_wreckage/durand + +/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) diff --git a/code/game/mecha/combat/gygax.dm b/code/game/mecha/combat/gygax.dm index 0e6980d6b7..3364bbb9a2 100644 --- a/code/game/mecha/combat/gygax.dm +++ b/code/game/mecha/combat/gygax.dm @@ -1,68 +1,68 @@ -/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 - force = 20 - armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_temperature = 25000 - infra_luminosity = 6 - wreckage = /obj/structure/mecha_wreckage/gygax - internal_damage_threshold = 35 - max_equip = 3 - step_energy_drain = 3 - leg_overload_coeff = 300 - -/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 = 15 - force = 25 - armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_temperature = 35000 - leg_overload_coeff = 100 - operation_req_access = list(ACCESS_SYNDICATE) - internals_req_access = list(ACCESS_SYNDICATE) - wreckage = /obj/structure/mecha_wreckage/gygax/dark - max_equip = 4 - spawn_tracked = FALSE - -/obj/mecha/combat/gygax/dark/loaded/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/carbine - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/teleporter - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay - ME.attach(src) - -/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/hyper(src) - -/obj/mecha/combat/gygax/GrantActions(mob/living/user, human_occupant = 0) - ..() - overload_action.Grant(user, src) - -/obj/mecha/combat/gygax/dark/GrantActions(mob/living/user, human_occupant = 0) - ..() - thrusters_action.Grant(user, src) - - -/obj/mecha/combat/gygax/RemoveActions(mob/living/user, human_occupant = 0) - ..() - overload_action.Remove(user) - -/obj/mecha/combat/gygax/dark/RemoveActions(mob/living/user, human_occupant = 0) - ..() - thrusters_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 + force = 20 + armor = list("melee" = 25, "bullet" = 20, "laser" = 30, "energy" = 15, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_temperature = 25000 + infra_luminosity = 6 + wreckage = /obj/structure/mecha_wreckage/gygax + internal_damage_threshold = 35 + max_equip = 3 + step_energy_drain = 3 + leg_overload_coeff = 300 + +/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 = 15 + force = 25 + armor = list("melee" = 40, "bullet" = 40, "laser" = 50, "energy" = 35, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_temperature = 35000 + leg_overload_coeff = 100 + operation_req_access = list(ACCESS_SYNDICATE) + internals_req_access = list(ACCESS_SYNDICATE) + wreckage = /obj/structure/mecha_wreckage/gygax/dark + max_equip = 4 + spawn_tracked = FALSE + +/obj/mecha/combat/gygax/dark/loaded/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/carbine + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/teleporter + ME.attach(src) + ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay + ME.attach(src) + +/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/hyper(src) + +/obj/mecha/combat/gygax/GrantActions(mob/living/user, human_occupant = 0) + ..() + overload_action.Grant(user, src) + +/obj/mecha/combat/gygax/dark/GrantActions(mob/living/user, human_occupant = 0) + ..() + thrusters_action.Grant(user, src) + + +/obj/mecha/combat/gygax/RemoveActions(mob/living/user, human_occupant = 0) + ..() + overload_action.Remove(user) + +/obj/mecha/combat/gygax/dark/RemoveActions(mob/living/user, human_occupant = 0) + ..() + thrusters_action.Remove(user) diff --git a/code/game/mecha/combat/honker.dm b/code/game/mecha/combat/honker.dm index ffe318def5..fb1b278919 100644 --- a/code/game/mecha/combat/honker.dm +++ b/code/game/mecha/combat/honker.dm @@ -1,156 +1,156 @@ -/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_THEATRE, ACCESS_ROBOTICS) - wreckage = /obj/structure/mecha_wreckage/honker - add_req_access = 0 - max_equip = 3 - var/squeak = 0 - -/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 powercell 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 = {"
                    -
                    Sounds of HONK:
                    - -
                    - "} - 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/mechstep(direction) - var/result = step(src,direction) - if(result) - if(!squeak) - playsound(src, "clownstep", 70, 1) - squeak = 1 - else - squeak = 0 - return result - -/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) - return - -/proc/rand_hex_color() - var/list/colors = list("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f") - var/color="" - for (var/i=0;i<6;i++) - color = color+pick(colors) +/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_THEATRE, ACCESS_ROBOTICS) + wreckage = /obj/structure/mecha_wreckage/honker + add_req_access = 0 + max_equip = 3 + var/squeak = 0 + +/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 powercell 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 = {"
                    +
                    Sounds of HONK:
                    + +
                    + "} + 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/mechstep(direction) + var/result = step(src,direction) + if(result) + if(!squeak) + playsound(src, "clownstep", 70, 1) + squeak = 1 + else + squeak = 0 + return result + +/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) + return + +/proc/rand_hex_color() + var/list/colors = list("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f") + var/color="" + for (var/i=0;i<6;i++) + color = color+pick(colors) return color \ No newline at end of file diff --git a/code/game/mecha/combat/marauder.dm b/code/game/mecha/combat/marauder.dm index fa9449937b..c796cb7c1f 100644 --- a/code/game/mecha/combat/marauder.dm +++ b/code/game/mecha/combat/marauder.dm @@ -1,94 +1,94 @@ -/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" = 0, "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, ACCESS_ROBOTICS) - wreckage = /obj/structure/mecha_wreckage/marauder - add_req_access = 0 - internal_damage_threshold = 25 - force = 45 - max_equip = 4 - bumpsmash = 1 - spawn_tracked = FALSE - -/obj/mecha/combat/marauder/GrantActions(mob/living/user, human_occupant = 0) - ..() - smoke_action.Grant(user, src) - thrusters_action.Grant(user, src) - zoom_action.Grant(user, src) - -/obj/mecha/combat/marauder/RemoveActions(mob/living/user, human_occupant = 0) - ..() - smoke_action.Remove(user) - thrusters_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/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) - -/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, ACCESS_ROBOTICS) - step_in = 3 - max_integrity = 550 - wreckage = /obj/structure/mecha_wreckage/seraph - internal_damage_threshold = 20 - force = 55 - max_equip = 5 - -/obj/mecha/combat/marauder/seraph/Initialize() - . = ..() - var/obj/item/mecha_parts/mecha_equipment/ME - 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/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) - -/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 = 5 - -/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) - - +/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" = 0, "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, ACCESS_ROBOTICS) + wreckage = /obj/structure/mecha_wreckage/marauder + add_req_access = 0 + internal_damage_threshold = 25 + force = 45 + max_equip = 4 + bumpsmash = 1 + spawn_tracked = FALSE + +/obj/mecha/combat/marauder/GrantActions(mob/living/user, human_occupant = 0) + ..() + smoke_action.Grant(user, src) + thrusters_action.Grant(user, src) + zoom_action.Grant(user, src) + +/obj/mecha/combat/marauder/RemoveActions(mob/living/user, human_occupant = 0) + ..() + smoke_action.Remove(user) + thrusters_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/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) + +/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, ACCESS_ROBOTICS) + step_in = 3 + max_integrity = 550 + wreckage = /obj/structure/mecha_wreckage/seraph + internal_damage_threshold = 20 + force = 55 + max_equip = 5 + +/obj/mecha/combat/marauder/seraph/Initialize() + . = ..() + var/obj/item/mecha_parts/mecha_equipment/ME + 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/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) + +/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 = 5 + +/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) + + diff --git a/code/game/mecha/combat/phazon.dm b/code/game/mecha/combat/phazon.dm index 15b865c1e9..11a1bc1e84 100644 --- a/code/game/mecha/combat/phazon.dm +++ b/code/game/mecha/combat/phazon.dm @@ -1,29 +1,29 @@ -/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" = 0, "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" = 0, "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 446e2e853c..62e891494d 100644 --- a/code/game/mecha/combat/reticence.dm +++ b/code/game/mecha/combat/reticence.dm @@ -1,29 +1,29 @@ -/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_THEATRE, ACCESS_ROBOTICS) - add_req_access = 0 - internal_damage_threshold = 25 - max_equip = 2 - step_energy_drain = 3 - color = "#87878715" - stepsound = null - turnsound = null - opacity = 0 - spawn_tracked = FALSE - -/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_THEATRE, ACCESS_ROBOTICS) + add_req_access = 0 + internal_damage_threshold = 25 + max_equip = 2 + step_energy_drain = 3 + color = "#87878715" + stepsound = null + turnsound = null + opacity = 0 + spawn_tracked = FALSE + +/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 23be95ff16..b5a579f38b 100644 --- a/code/game/mecha/equipment/mecha_equipment.dm +++ b/code/game/mecha/equipment/mecha_equipment.dm @@ -1,167 +1,167 @@ -//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 - var/range = MELEE //bitFflags - var/salvageable = 1 - 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. - -/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() - chassis.occupant_message("[src] is destroyed!") - chassis.log_append_to_last("[src] is destroyed.",1) - SEND_SOUND(chassis.occupant, sound(istype(src, /obj/item/mecha_parts/mecha_equipment/weapon) ? 'sound/mecha/weapdestr.ogg' : 'sound/mecha/critdestr.ogg', volume=50)) - chassis = null - return ..() - -/obj/item/mecha_parts/mecha_equipment/proc/critfail() - if(chassis) - log_message("Critical failure", color="red") - -/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&RANGED - -/obj/item/mecha_parts/mecha_equipment/proc/is_melee() - return range&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(crit_fail) - return 0 - if(energy_drain && !chassis.has_charge(energy_drain)) - 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.log_append_to_last("[src] is destroyed.",1) + SEND_SOUND(chassis.occupant, sound(istype(src, /obj/item/mecha_parts/mecha_equipment/weapon) ? 'sound/mecha/weapdestr.ogg' : 'sound/mecha/critdestr.ogg', volume=50)) + chassis = null + return ..() + +/obj/item/mecha_parts/mecha_equipment/proc/critfail() + if(chassis) + log_message("Critical failure", color="red") + +/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&RANGED + +/obj/item/mecha_parts/mecha_equipment/proc/is_melee() + return range&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(crit_fail) + return 0 + if(energy_drain && !chassis.has_charge(energy_drain)) + 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.") - -/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.") - 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]" - return - -/obj/item/mecha_parts/mecha_equipment/medical/sleeper/Topic(href,href_list) - ..() - var/datum/topic_input/afilter = new /datum/topic_input(href,href_list) - if(afilter.get("eject")) - go_out() - if(afilter.get("view_stats")) - chassis.occupant << browse(get_patient_stats(),"window=msleeper") - onclose(chassis.occupant, "msleeper") - return - if(afilter.get("inject")) - inject_reagent(afilter.getType("inject", /datum/reagent),afilter.getObj("source")) - return - -/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_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.") - 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.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 = MELEE|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/charcoal = "Charcoal") - 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/critfail() - ..() - if(reagents) - DISABLE_BITFIELD(reagents.reagents_holder_flags, NO_REACT) - -/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" - return - -/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)) - syringes -= mechsyringe - mechsyringe.icon = 'icons/obj/chemical.dmi' - mechsyringe.icon_state = "syringeproj" - playsound(chassis, 'sound/items/syringeproj.ogg', 50, 1) - log_message("Launched [mechsyringe] from [src], targeting [target].") - 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 - var/mob/living/carbon/M = safepick(mobs) - if(M) - var/R - mechsyringe.visible_message(" [M] was 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.reaction(M, INJECT) - mechsyringe.reagents.trans_to(M, mechsyringe.reagents.total_volume) - 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) - ..() - var/datum/topic_input/afilter = new (href,href_list) - if(afilter.get("toggle_mode")) - mode = !mode - update_equip_info() - return - if(afilter.get("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 = afilter.get("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.") - return - if(afilter.get("show_reagents")) - chassis.occupant << browse(get_reagents_page(),"window=msyringegun") - if(afilter.get("purge_reagent")) - var/reagent = afilter.get("purge_reagent") - if(reagent) - reagents.del_reagent(reagent) - return - if(afilter.get("purge_all")) - reagents.clear_reagents() - return - return - -/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) - 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("Analyzis 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 - return - -/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/on_reagent_change(changetype) - ..() - update_equip_info() - return - - -/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.") - 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 = MELEE|RANGED - equip_cooldown = 0 - var/obj/item/gun/medbeam/mech/medigun - materials = list(MAT_METAL = 15000, MAT_GLASS = 8000, MAT_PLASMA = 3000, MAT_GOLD = 8000, MAT_DIAMOND = 2000) - -/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 = 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.") + +/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.") + 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]" + return + +/obj/item/mecha_parts/mecha_equipment/medical/sleeper/Topic(href,href_list) + ..() + var/datum/topic_input/afilter = new /datum/topic_input(href,href_list) + if(afilter.get("eject")) + go_out() + if(afilter.get("view_stats")) + chassis.occupant << browse(get_patient_stats(),"window=msleeper") + onclose(chassis.occupant, "msleeper") + return + if(afilter.get("inject")) + inject_reagent(afilter.getType("inject", /datum/reagent),afilter.getObj("source")) + return + +/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_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.") + 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.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 = MELEE|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/charcoal = "Charcoal") + 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/critfail() + ..() + if(reagents) + DISABLE_BITFIELD(reagents.reagents_holder_flags, NO_REACT) + +/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" + return + +/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)) + syringes -= mechsyringe + mechsyringe.icon = 'icons/obj/chemical.dmi' + mechsyringe.icon_state = "syringeproj" + playsound(chassis, 'sound/items/syringeproj.ogg', 50, 1) + log_message("Launched [mechsyringe] from [src], targeting [target].") + 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 + var/mob/living/carbon/M = safepick(mobs) + if(M) + var/R + mechsyringe.visible_message(" [M] was 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.reaction(M, INJECT) + mechsyringe.reagents.trans_to(M, mechsyringe.reagents.total_volume) + 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) + ..() + var/datum/topic_input/afilter = new (href,href_list) + if(afilter.get("toggle_mode")) + mode = !mode + update_equip_info() + return + if(afilter.get("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 = afilter.get("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.") + return + if(afilter.get("show_reagents")) + chassis.occupant << browse(get_reagents_page(),"window=msyringegun") + if(afilter.get("purge_reagent")) + var/reagent = afilter.get("purge_reagent") + if(reagent) + reagents.del_reagent(reagent) + return + if(afilter.get("purge_all")) + reagents.clear_reagents() + return + return + +/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) + 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("Analyzis 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 + return + +/obj/item/mecha_parts/mecha_equipment/medical/syringe_gun/on_reagent_change(changetype) + ..() + update_equip_info() + return + + +/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.") + 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 = MELEE|RANGED + equip_cooldown = 0 + var/obj/item/gun/medbeam/mech/medigun + materials = list(MAT_METAL = 15000, MAT_GLASS = 8000, MAT_PLASMA = 3000, MAT_GOLD = 8000, MAT_DIAMOND = 2000) + +/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 9f8f3ef742..95fc96e119 100644 --- a/code/game/mecha/equipment/weapons/weapons.dm +++ b/code/game/mecha/equipment/weapons/weapons.dm @@ -1,444 +1,444 @@ -/obj/item/mecha_parts/mecha_equipment/weapon - name = "mecha weapon" - range = RANGED - 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/combat/M) - if(..()) - if(istype(M)) - return 1 - return 0 - -/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/item/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, 1) - - sleep(max(0, projectile_delay)) - - if(kickback) - chassis.newtonian_move(turn(chassis.dir,180)) - chassis.log_message("Fired from [src.name], targeting [target].") - 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/item/projectile/beam/laser - fire_sound = 'sound/weapons/laser.ogg' - harmful = TRUE - -/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/item/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/item/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/item/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/item/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" - item_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/item/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/item/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 = MELEE|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, 1) - chassis.occupant_message("HONK") - for(var/mob/living/carbon/M in ohearers(6, chassis)) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(istype(H.ears, /obj/item/clothing/ears/earmuffs)) - continue - to_chat(M, "HONK") - M.SetSleeping(0) - M.stuttering += 20 - M.adjustEarDamage(0, 30) - M.Knockdown(60) - if(prob(30)) - M.Stun(200) - M.Unconscious(80) - else - M.Jitter(500) - - log_message("Honked from [src.name]. HONK!") - 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/gunshot.ogg' - var/projectiles - var/projectile_energy_cost - -/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]\][(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 - while(chassis.get_charge() >= projectile_energy_cost && projectiles_to_add) - projectiles++ - projectiles_to_add-- - chassis.use_power(projectile_energy_cost) - send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) - log_message("Rearmed [src.name].") - return 1 - - -/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/item/projectile/bullet/incendiary/fnx99 - projectiles = 24 - projectile_energy_cost = 15 - harmful = TRUE - -/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/gunshot_silenced.ogg' - icon_state = "mecha_mime" - equip_cooldown = 30 - projectile = /obj/item/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/item/projectile/bullet/scattershot - projectiles = 40 - projectile_energy_cost = 25 - projectiles_per_shot = 4 - variance = 25 - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/seedscatter - name = "\improper Melon Seed \"Scattershot\"" - desc = "A weapon for combat exosuits. Shoots a spread of pellets, shaped as seed." - icon_state = "mecha_scatter" - equip_cooldown = 20 - projectile = /obj/item/projectile/bullet/seed - projectiles = 20 - projectile_energy_cost = 25 - projectiles_per_shot = 10 - variance = 25 - harmful = TRUE - -/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/item/projectile/bullet/lmg - projectiles = 300 - projectile_energy_cost = 20 - projectiles_per_shot = 3 - variance = 6 - randomspread = 1 - projectile_delay = 2 - harmful = TRUE - -/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack - name = "\improper SRM-8 missile rack" - desc = "A weapon for combat exosuits. Shoots light explosive missiles." - icon_state = "mecha_missilerack" - projectile = /obj/item/projectile/bullet/a84mm_he - fire_sound = 'sound/weapons/grenadelaunch.ogg' - projectiles = 8 - projectile_energy_cost = 1000 - equip_cooldown = 60 - harmful = TRUE - - -/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, 1) - log_message("Launched a [O.name] from [name], targeting [target].") - 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/grenadelaunch.ogg' - projectiles = 6 - missile_speed = 1.5 - projectile_energy_cost = 800 - equip_cooldown = 60 - var/det_time = 20 - -/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 - projectile = /obj/item/grenade/clusterbuster - projectile_energy_cost = 1600 //getting off cheap seeing as this is 3 times the flashbangs held in the grenade launcher. - equip_cooldown = 90 - -/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 = MELEE|RANGED - missile_range = 5 - projectile = /obj/item/punching_glove - fire_sound = 'sound/items/bikehorn.ogg' - projectiles = 10 - projectile_energy_cost = 500 - diags_first = 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/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 - //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) - if(!..()) - if(ismovableatom(hit_atom)) - var/atom/movable/AM = hit_atom - AM.safe_throw_at(get_edge_target_turf(AM,get_dir(src, AM)), 7, 2) - qdel(src) +/obj/item/mecha_parts/mecha_equipment/weapon + name = "mecha weapon" + range = RANGED + 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/combat/M) + if(..()) + if(istype(M)) + return 1 + return 0 + +/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/item/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, 1) + + sleep(max(0, projectile_delay)) + + if(kickback) + chassis.newtonian_move(turn(chassis.dir,180)) + chassis.log_message("Fired from [src.name], targeting [target].") + 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/item/projectile/beam/laser + fire_sound = 'sound/weapons/laser.ogg' + harmful = TRUE + +/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/item/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/item/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/item/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/item/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" + item_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/item/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/item/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 = MELEE|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, 1) + chassis.occupant_message("HONK") + for(var/mob/living/carbon/M in ohearers(6, chassis)) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(istype(H.ears, /obj/item/clothing/ears/earmuffs)) + continue + to_chat(M, "HONK") + M.SetSleeping(0) + M.stuttering += 20 + M.adjustEarDamage(0, 30) + M.Knockdown(60) + if(prob(30)) + M.Stun(200) + M.Unconscious(80) + else + M.Jitter(500) + + log_message("Honked from [src.name]. HONK!") + 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/gunshot.ogg' + var/projectiles + var/projectile_energy_cost + +/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]\][(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 + while(chassis.get_charge() >= projectile_energy_cost && projectiles_to_add) + projectiles++ + projectiles_to_add-- + chassis.use_power(projectile_energy_cost) + send_byjax(chassis.occupant,"exosuit.browser","[REF(src)]",src.get_equip_info()) + log_message("Rearmed [src.name].") + return 1 + + +/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/item/projectile/bullet/incendiary/fnx99 + projectiles = 24 + projectile_energy_cost = 15 + harmful = TRUE + +/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/gunshot_silenced.ogg' + icon_state = "mecha_mime" + equip_cooldown = 30 + projectile = /obj/item/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/item/projectile/bullet/scattershot + projectiles = 40 + projectile_energy_cost = 25 + projectiles_per_shot = 4 + variance = 25 + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/seedscatter + name = "\improper Melon Seed \"Scattershot\"" + desc = "A weapon for combat exosuits. Shoots a spread of pellets, shaped as seed." + icon_state = "mecha_scatter" + equip_cooldown = 20 + projectile = /obj/item/projectile/bullet/seed + projectiles = 20 + projectile_energy_cost = 25 + projectiles_per_shot = 10 + variance = 25 + harmful = TRUE + +/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/item/projectile/bullet/lmg + projectiles = 300 + projectile_energy_cost = 20 + projectiles_per_shot = 3 + variance = 6 + randomspread = 1 + projectile_delay = 2 + harmful = TRUE + +/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack + name = "\improper SRM-8 missile rack" + desc = "A weapon for combat exosuits. Shoots light explosive missiles." + icon_state = "mecha_missilerack" + projectile = /obj/item/projectile/bullet/a84mm_he + fire_sound = 'sound/weapons/grenadelaunch.ogg' + projectiles = 8 + projectile_energy_cost = 1000 + equip_cooldown = 60 + harmful = TRUE + + +/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, 1) + log_message("Launched a [O.name] from [name], targeting [target].") + 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/grenadelaunch.ogg' + projectiles = 6 + missile_speed = 1.5 + projectile_energy_cost = 800 + equip_cooldown = 60 + var/det_time = 20 + +/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 + projectile = /obj/item/grenade/clusterbuster + projectile_energy_cost = 1600 //getting off cheap seeing as this is 3 times the flashbangs held in the grenade launcher. + equip_cooldown = 90 + +/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 = MELEE|RANGED + missile_range = 5 + projectile = /obj/item/punching_glove + fire_sound = 'sound/items/bikehorn.ogg' + projectiles = 10 + projectile_energy_cost = 500 + diags_first = 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/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 + //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) + if(!..()) + if(ismovableatom(hit_atom)) + var/atom/movable/AM = hit_atom + AM.safe_throw_at(get_edge_target_turf(AM,get_dir(src, AM)), 7, 2) + qdel(src) diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm index 417fefce6e..61c0a8bd37 100644 --- a/code/game/mecha/mech_fabricator.dm +++ b/code/game/mecha/mech_fabricator.dm @@ -1,424 +1,424 @@ -/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/temp - var/list/part_sets = list( - "Cyborg", - "Ripley", - "Firefighter", - "Odysseus", - "Gygax", - "Durand", - "H.O.N.K", - "Phazon", - "Exosuit Equipment", - "Cyborg Upgrade Modules", - "Misc" - ) - -/obj/machinery/mecha_part_fabricator/Initialize() - var/datum/component/material_container/materials = AddComponent(/datum/component/material_container, - list(MAT_METAL, MAT_GLASS, MAT_SILVER, MAT_GOLD, MAT_DIAMOND, MAT_PLASMA, MAT_URANIUM, MAT_BANANIUM, MAT_TITANIUM, MAT_BLUESPACE), 0, - TRUE, /obj/item/stack, CALLBACK(src, .proc/is_insertion_ready), CALLBACK(src, .proc/AfterMaterialInsert)) - materials.precise_insertion = TRUE - stored_research = new - 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 - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.max_amount = (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/emag_act() - . = ..() - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - req_access = list() - INVOKE_ASYNC(src, .proc/error_action_sucessful) - return TRUE - -/obj/machinery/mecha_part_fabricator/proc/error_action_sucessful() - 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) - output += "[i?" | ":null][get_resource_cost_w_coeff(D, c)] [material2name(c)]" - i++ - return output - -/obj/machinery/mecha_part_fabricator/proc/output_available_resources() - var/output - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/mat_id in materials.materials) - var/datum/material/M = materials.materials[mat_id] - output += "[M.name]: [M.amount] cm³" - if(M.amount >= MINERAL_MATERIAL_AMOUNT) - output += "- Remove \[1\]" - if(M.amount >= (MINERAL_MATERIAL_AMOUNT * 10)) - output += " | \[10\]" - output += " | \[All\]" - output += "
                    " - 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) - resources[R] = get_resource_cost_w_coeff(D, R) - 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 = GetComponent(/datum/component/material_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) - being_built = D - desc = "It's building \a [initial(D.name)]." - var/list/res_coef = get_resources_w_coeff(D) - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.use_amount(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.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(stat&(NOPOWER|BROKEN)) - return FALSE - if(!check_resources(D)) - say("Not enough resources. Queue processing stopped.") - temp = {"Not enough resources to build next part.
                    - Try again | Return"} - return FALSE - remove_from_queue(1) - build_part(D) - D = listgetindex(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() - temp = "Updating local R&D database..." - updateUsrDialog() - sleep(30) //only sleep if called by user - - for(var/obj/machinery/computer/rdconsole/RDC in oview(7,src)) - RDC.stored_research.copy_research_to(stored_research) - temp = "Processed equipment designs.
                    " - //check if the tech coefficients have changed - temp += "Return" - - 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, 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 - var/datum/topic_input/afilter = new /datum/topic_input(href,href_list) - if(href_list["part_set"]) - var/tpart_set = afilter.getStr("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 = afilter.getStr("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 = afilter.getStr("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(afilter.getNum("remove_from_queue")) - return update_queue_on_page() - if(href_list["partset_to_queue"]) - add_part_set_to_queue(afilter.get("partset_to_queue")) - return update_queue_on_page() - if(href_list["process_queue"]) - spawn(0) - if(processing_queue || being_built) - return FALSE - processing_queue = 1 - process_queue() - processing_queue = 0 - 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 = afilter.getNum("index") - var/new_index = index + afilter.getNum("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 = afilter.getStr("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/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.retrieve_sheets(text2num(href_list["remove_mat"]), href_list["material"]) - - updateUsrDialog() - return - -/obj/machinery/mecha_part_fabricator/on_deconstruction() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - materials.retrieve_all() - ..() - -/obj/machinery/mecha_part_fabricator/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted) - var/stack_name = material2name(id_inserted) - add_overlay("fab-load-[stack_name]") - addtimer(CALLBACK(src, /atom/proc/cut_overlay, "fab-load-[stack_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/material2name(ID) - return copytext(ID,2) - -/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 + 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/temp + var/list/part_sets = list( + "Cyborg", + "Ripley", + "Firefighter", + "Odysseus", + "Gygax", + "Durand", + "H.O.N.K", + "Phazon", + "Exosuit Equipment", + "Cyborg Upgrade Modules", + "Misc" + ) + +/obj/machinery/mecha_part_fabricator/Initialize() + var/datum/component/material_container/materials = AddComponent(/datum/component/material_container, + list(MAT_METAL, MAT_GLASS, MAT_SILVER, MAT_GOLD, MAT_DIAMOND, MAT_PLASMA, MAT_URANIUM, MAT_BANANIUM, MAT_TITANIUM, MAT_BLUESPACE), 0, + TRUE, /obj/item/stack, CALLBACK(src, .proc/is_insertion_ready), CALLBACK(src, .proc/AfterMaterialInsert)) + materials.precise_insertion = TRUE + stored_research = new + 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 + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.max_amount = (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/emag_act() + . = ..() + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + req_access = list() + INVOKE_ASYNC(src, .proc/error_action_sucessful) + return TRUE + +/obj/machinery/mecha_part_fabricator/proc/error_action_sucessful() + 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) + output += "[i?" | ":null][get_resource_cost_w_coeff(D, c)] [material2name(c)]" + i++ + return output + +/obj/machinery/mecha_part_fabricator/proc/output_available_resources() + var/output + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/mat_id in materials.materials) + var/datum/material/M = materials.materials[mat_id] + output += "[M.name]: [M.amount] cm³" + if(M.amount >= MINERAL_MATERIAL_AMOUNT) + output += "- Remove \[1\]" + if(M.amount >= (MINERAL_MATERIAL_AMOUNT * 10)) + output += " | \[10\]" + output += " | \[All\]" + output += "
                    " + 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) + resources[R] = get_resource_cost_w_coeff(D, R) + 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 = GetComponent(/datum/component/material_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) + being_built = D + desc = "It's building \a [initial(D.name)]." + var/list/res_coef = get_resources_w_coeff(D) + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.use_amount(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.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(stat&(NOPOWER|BROKEN)) + return FALSE + if(!check_resources(D)) + say("Not enough resources. Queue processing stopped.") + temp = {"Not enough resources to build next part.
                    + Try again | Return"} + return FALSE + remove_from_queue(1) + build_part(D) + D = listgetindex(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() + temp = "Updating local R&D database..." + updateUsrDialog() + sleep(30) //only sleep if called by user + + for(var/obj/machinery/computer/rdconsole/RDC in oview(7,src)) + RDC.stored_research.copy_research_to(stored_research) + temp = "Processed equipment designs.
                    " + //check if the tech coefficients have changed + temp += "Return" + + 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, 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 + var/datum/topic_input/afilter = new /datum/topic_input(href,href_list) + if(href_list["part_set"]) + var/tpart_set = afilter.getStr("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 = afilter.getStr("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 = afilter.getStr("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(afilter.getNum("remove_from_queue")) + return update_queue_on_page() + if(href_list["partset_to_queue"]) + add_part_set_to_queue(afilter.get("partset_to_queue")) + return update_queue_on_page() + if(href_list["process_queue"]) + spawn(0) + if(processing_queue || being_built) + return FALSE + processing_queue = 1 + process_queue() + processing_queue = 0 + 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 = afilter.getNum("index") + var/new_index = index + afilter.getNum("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 = afilter.getStr("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/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.retrieve_sheets(text2num(href_list["remove_mat"]), href_list["material"]) + + updateUsrDialog() + return + +/obj/machinery/mecha_part_fabricator/on_deconstruction() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + materials.retrieve_all() + ..() + +/obj/machinery/mecha_part_fabricator/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted) + var/stack_name = material2name(id_inserted) + add_overlay("fab-load-[stack_name]") + addtimer(CALLBACK(src, /atom/proc/cut_overlay, "fab-load-[stack_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/material2name(ID) + return copytext(ID,2) + +/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 diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 7e860d9315..f556a75434 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -1,1067 +1,1067 @@ -#define MECHA_INT_FIRE (1<<0) -#define MECHA_INT_TEMP_CONTROL (1<<1) -#define MECHA_INT_SHORT_CIRCUIT (1<<2) -#define MECHA_INT_TANK_BREACH (1<<3) -#define MECHA_INT_CONTROL_LOST (1<<4) - -#define MELEE 1 -#define RANGED 2 - -#define FRONT_ARMOUR 1 -#define SIDE_ARMOUR 2 -#define BACK_ARMOUR 3 - - -/obj/mecha - name = "mecha" - desc = "Exosuit" - icon = 'icons/mecha/mecha.dmi' - density = TRUE //Dense. To raise the heat. - opacity = 1 ///opaque. Menacing. - anchored = TRUE //no pulling around. - 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/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(FRONT_ARMOUR = 1.5, SIDE_ARMOUR = 1, BACK_ARMOUR = 0.5) - var/obj/item/stock_parts/cell/cell - var/state = 0 - var/list/log = new - var/last_message = 0 - var/add_req_access = 1 - var/maint_access = 0 - var/equipment_disabled = 0 //disabled due to EMP - 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/breach_time = 0 - var/recharge_rate = 0 - - 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_ROBOTICS)//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/datum/events/events - - var/stepsound = 'sound/mecha/mechstep.ogg' - var/turnsound = 'sound/mecha/mechturn.ogg' - - var/melee_cooldown = 10 - var/melee_can_hit = 1 - - //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_toggle_thrusters/thrusters_action = new - var/datum/action/innate/mecha/mech_defence_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/thrusters_active = FALSE - var/defence_mode = FALSE - var/defence_mode_deflect_chance = 35 - 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/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() - . = ..() - events = new - icon_state += "-open" - add_radio() - add_cabin() - 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() - START_PROCESSING(SSobj, src) - GLOB.poi_list |= src - log_message("[src.name] created.") - 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/get_cell() - return cell - -/obj/mecha/Destroy() - 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) - if(wreckage) - if(prob(30)) - explosion(get_turf(src), 0, 0, 1, 3) - var/obj/structure/mecha_wreckage/WR = new wreckage(loc, AI) - for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) - if(E.salvageable && prob(30)) - WR.crowbar_salvage += E - E.detach(WR) //detaches from src into WR - E.equip_ready = 1 - else - E.detach(loc) - qdel(E) - if(cell) - WR.crowbar_salvage += cell - cell.forceMove(WR) - cell.charge = rand(0, cell.charge) - if(internal_tank) - WR.crowbar_salvage += internal_tank - internal_tank.forceMove(WR) - else - for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) - E.detach(loc) - qdel(E) - if(cell) - qdel(cell) - 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 - 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(istype(src, /obj/mecha/combat)) - mouse_pointer = 'icons/mecha/mecha_mouse.dmi' - if(occupant) - SEND_SOUND(occupant, sound('sound/items/timer.ogg', volume=50)) - to_chat(occupant, "Equipment control unit has been rebooted successfuly.
                    ") - occupant.update_mouse_pointer() - -/obj/mecha/CheckParts(list/parts_list) - ..() - cell = locate(/obj/item/stock_parts/cell) in contents - var/obj/item/stock_parts/scanning_module/SM = locate() in contents - var/obj/item/stock_parts/capacitor/CP = locate() in contents - if(SM) - normal_step_energy_drain = 20 - (5 * SM.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 - qdel(SM) - if(CP) - armor = armor.modifyRating(energy = (CP.rating * 10)) //Each level of capacitor protects the mech against emp by 10% - qdel(CP) - -//////////////////////// -////// Helpers ///////// -//////////////////////// - -/obj/mecha/proc/add_airtank() - internal_tank = new /obj/machinery/portable_atmospherics/canister/air(src) - return internal_tank - -/obj/mecha/proc/add_cell(var/obj/item/stock_parts/cell/C=null) - if(C) - C.forceMove(src) - cell = C - return - cell = new /obj/item/stock_parts/cell/high/plus(src) - -/obj/mecha/proc/add_cabin() - cabin_air = new - cabin_air.temperature = T20C - cabin_air.volume = 200 - cabin_air.gases[/datum/gas/oxygen] = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - cabin_air.gases[/datum/gas/nitrogen] = 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." - if(equipment && equipment.len) - . += "It's equipped with:" - for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment) - . += "[icon2html(ME, user)] \A [ME]." - -//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) - -//Diagnostic HUD updates - diag_hud_set_mechhealth() - diag_hud_set_mechcell() - diag_hud_set_mechstat() - -/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, atom/movable/source) - . = ..() - 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(phasing) - occupant_message("Unable to interact with objects while phasing") - return - if(user.incapacitated()) - return - if(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) - target = safepick(view(3,target)) - if(!target) - return - - 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) - target = safepick(oview(1,src)) - if(!melee_can_hit || !istype(target, /atom)) - return - target.mech_melee_attack(src) - melee_can_hit = 0 - spawn(melee_cooldown) - melee_can_hit = 1 - - -/obj/mecha/proc/range_action(atom/target) - return - - -////////////////////////////////// -//////// Movement procs //////// -////////////////////////////////// - -/obj/mecha/Move(atom/newloc, direct) - . = ..() - if(.) - events.fireEvent("onMove",get_turf(src)) - if (internal_tank.disconnect()) // Something moved us and broke connection - occupant_message("Air port connection teared off!") - log_message("Lost connection to gas port.") - -/obj/mecha/Process_Spacemove(var/movement_dir = 0) - . = ..() - if(.) - return 1 - if(thrusters_active && movement_dir && use_power(step_energy_drain)) - return 1 - - var/atom/movable/backup = get_spacemove_backup() - if(backup) - if(istype(backup) && movement_dir && !backup.anchored) - if(backup.newtonian_move(turn(movement_dir, 180))) - if(occupant) - to_chat(occupant, "You push off of [backup] to propel yourself.") - return 1 - -/obj/mecha/relaymove(mob/user,direction) - 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(state) - occupant_message("Maintenance protocols in effect.") - 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(defence_mode) - if(world.time - last_message > 20) - occupant_message("Unable to move while in defence mode") - last_message = world.time - 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 - - 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,1) - return 1 - -/obj/mecha/proc/mechstep(direction) - var/current_dir = dir - var/result = step(src,direction) - if(strafe) - setDir(current_dir) - if(result && stepsound) - playsound(src,stepsound,40,1) - return result - -/obj/mecha/proc/mechsteprand() - var/result = step_rand(src) - if(result && stepsound) - playsound(src,stepsound,40,1) - return result - -/obj/mecha/Bump(var/atom/obstacle) - if(phasing && get_charge() >= phasing_energy_drain && !throwing) - spawn() - if(can_move) - can_move = 0 - if(phase_state) - flick(phase_state, src) - forceMove(get_step(src,dir)) - use_power(phasing_energy_drain) - sleep(step_in*3) - can_move = 1 - 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) - step(obstacle, dir) - else if(ismob(obstacle)) - var/mob/M = obstacle - if(!M.anchored) - step(obstacle, dir) - - - - - -/////////////////////////////////// -//////// Internal damage //////// -/////////////////////////////////// - -/obj/mecha/proc/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) - if(!islist(possible_int_damage) || isemptylist(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 - var/int_dam_flag = safepick(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) - var/obj/item/mecha_parts/mecha_equipment/ME = safepick(equipment) - if(ME) - qdel(ME) - return - -/obj/mecha/proc/setInternalDamage(int_dam_flag) - internal_damage |= int_dam_flag - log_append_to_last("Internal damage of type [int_dam_flag].",1) - SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg',wait=0)) - diag_hud_set_mechstat() - return - -/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 extinquished.") - 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(!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 = 1 - AI.radio_enabled = 0 - AI.disconnect_shell() - RemoveActions(AI, TRUE) - occupant = null - 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/AIcore/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 = 0 - AI.radio_enabled = 1 - 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 - icon_state = initial(icon_state) - playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) - 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.canmove = 1 //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/proc/return_pressure() - var/datum/gas_mixture/t_air = return_air() - if(t_air) - . = t_air.return_pressure() - return - -/obj/mecha/return_temperature() - var/datum/gas_mixture/t_air = return_air() - if(t_air) - . = t_air.return_temperature() - return - -/obj/mecha/portableConnectorReturnAir() - return internal_tank.return_air() - - -/obj/mecha/MouseDrop_T(mob/M, mob/user) - if (!user.canUseTopic(src) || (user != M)) - return - if(!ishuman(user)) // no silicons or drones in mechas. - return - log_message("[user] tries to move in.") - if (occupant) - to_chat(usr, "The [name] is already occupied!") - log_append_to_last("Permission denied.") - 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_append_to_last("Permission denied.") - return - if(!operation_allowed(user)) - to_chat(user, "Access denied. Insufficient operation keycodes.") - log_append_to_last("Permission denied.") - return - if(user.buckled) - to_chat(user, "You are currently buckled and cannot move.") - log_append_to_last("Permission denied.") - 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!") - return - - visible_message("[user] starts to climb into [name].") - - if(do_after(user, 40, 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!") - return - -/obj/mecha/proc/moved_inside(mob/living/carbon/human/H) - 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_append_to_last("[H] moved in as pilot.") - icon_state = initial(icon_state) - setDir(dir_in) - playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - return 1 - else - return 0 - -/obj/mecha/proc/mmi_move_inside(obj/item/mmi/mmi_as_oc, mob/user) - if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) - to_chat(user, "Consciousness matrix not detected!") - return FALSE - else if(mmi_as_oc.brainmob.stat) - to_chat(user, "Beta-rhythm below acceptable level!") - return FALSE - else if(occupant) - to_chat(user, "Occupant detected!") - return FALSE - else if(dna_lock && (!mmi_as_oc.brainmob.stored_dna || (dna_lock != mmi_as_oc.brainmob.stored_dna.unique_enzymes))) - to_chat(user, "Access denied. [name] is secured with a DNA lock.") - return FALSE - - visible_message("[user] starts to insert an MMI into [name].") - - if(do_after(user, 40, target = src)) - if(!occupant) - return mmi_moved_inside(mmi_as_oc, user) - else - to_chat(user, "Occupant detected!") - else - to_chat(user, "You stop inserting the MMI.") - return FALSE - -/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/mmi_as_oc, mob/user) - if(!(Adjacent(mmi_as_oc) && Adjacent(user))) - return FALSE - if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) - to_chat(user, "Consciousness matrix not detected!") - return FALSE - else if(mmi_as_oc.brainmob.stat) - to_chat(user, "Beta-rhythm below acceptable level!") - return FALSE - if(!user.transferItemToLoc(mmi_as_oc, src)) - to_chat(user, "\the [mmi_as_oc] is stuck to your hand, you cannot put it in \the [src]!") - return FALSE - var/mob/living/brainmob = mmi_as_oc.brainmob - mmi_as_oc.mecha = src - occupant = brainmob - brainmob.forceMove(src) //should allow relaymove - brainmob.reset_perspective(src) - brainmob.remote_control = src - brainmob.update_canmove() - brainmob.update_mouse_pointer() - icon_state = initial(icon_state) - update_icon() - setDir(dir_in) - log_message("[mmi_as_oc] moved in as pilot.") - if(!internal_damage) - SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) - GrantActions(brainmob) - return TRUE - -/obj/mecha/container_resist(mob/living/user) - go_out() - - -/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) - -/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 - 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(). - if(mob_container.forceMove(newloc))//ejecting mob container - log_message("[mob_container] moved out.") - 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.canmove = 0 - icon_state = initial(icon_state)+"-open" - setDir(dir_in) - - if(L && L.client) - L.update_mouse_pointer() - L.client.change_view(CONFIG_GET(string/default_view)) - 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]") - return - -/obj/mecha/log_message(message as text, message_type=LOG_GAME, color=null, log_globally) - log.len++ - log[log.len] = list("time"="[STATION_TIME_TIMESTAMP("hh:mm:ss")]","date","year"="[GLOB.year_integer]","message"="[color?"":null][message][color?"":null]") - ..() - return log.len - -/obj/mecha/proc/log_append_to_last(message as text,red=null) - var/list/last_entry = log[log.len] - last_entry["message"] += "
                    [red?"":null][message][red?"":null]" - return - -/////////////////////// -///// 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 +#define MECHA_INT_FIRE (1<<0) +#define MECHA_INT_TEMP_CONTROL (1<<1) +#define MECHA_INT_SHORT_CIRCUIT (1<<2) +#define MECHA_INT_TANK_BREACH (1<<3) +#define MECHA_INT_CONTROL_LOST (1<<4) + +#define MELEE 1 +#define RANGED 2 + +#define FRONT_ARMOUR 1 +#define SIDE_ARMOUR 2 +#define BACK_ARMOUR 3 + + +/obj/mecha + name = "mecha" + desc = "Exosuit" + icon = 'icons/mecha/mecha.dmi' + density = TRUE //Dense. To raise the heat. + opacity = 1 ///opaque. Menacing. + anchored = TRUE //no pulling around. + 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/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(FRONT_ARMOUR = 1.5, SIDE_ARMOUR = 1, BACK_ARMOUR = 0.5) + var/obj/item/stock_parts/cell/cell + var/state = 0 + var/list/log = new + var/last_message = 0 + var/add_req_access = 1 + var/maint_access = 0 + var/equipment_disabled = 0 //disabled due to EMP + 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/breach_time = 0 + var/recharge_rate = 0 + + 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_ROBOTICS)//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/datum/events/events + + var/stepsound = 'sound/mecha/mechstep.ogg' + var/turnsound = 'sound/mecha/mechturn.ogg' + + var/melee_cooldown = 10 + var/melee_can_hit = 1 + + //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_toggle_thrusters/thrusters_action = new + var/datum/action/innate/mecha/mech_defence_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/thrusters_active = FALSE + var/defence_mode = FALSE + var/defence_mode_deflect_chance = 35 + 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/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() + . = ..() + events = new + icon_state += "-open" + add_radio() + add_cabin() + 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() + START_PROCESSING(SSobj, src) + GLOB.poi_list |= src + log_message("[src.name] created.") + 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/get_cell() + return cell + +/obj/mecha/Destroy() + 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) + if(wreckage) + if(prob(30)) + explosion(get_turf(src), 0, 0, 1, 3) + var/obj/structure/mecha_wreckage/WR = new wreckage(loc, AI) + for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) + if(E.salvageable && prob(30)) + WR.crowbar_salvage += E + E.detach(WR) //detaches from src into WR + E.equip_ready = 1 + else + E.detach(loc) + qdel(E) + if(cell) + WR.crowbar_salvage += cell + cell.forceMove(WR) + cell.charge = rand(0, cell.charge) + if(internal_tank) + WR.crowbar_salvage += internal_tank + internal_tank.forceMove(WR) + else + for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) + E.detach(loc) + qdel(E) + if(cell) + qdel(cell) + 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 + 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(istype(src, /obj/mecha/combat)) + mouse_pointer = 'icons/mecha/mecha_mouse.dmi' + if(occupant) + SEND_SOUND(occupant, sound('sound/items/timer.ogg', volume=50)) + to_chat(occupant, "Equipment control unit has been rebooted successfuly.
                    ") + occupant.update_mouse_pointer() + +/obj/mecha/CheckParts(list/parts_list) + ..() + cell = locate(/obj/item/stock_parts/cell) in contents + var/obj/item/stock_parts/scanning_module/SM = locate() in contents + var/obj/item/stock_parts/capacitor/CP = locate() in contents + if(SM) + normal_step_energy_drain = 20 - (5 * SM.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 + qdel(SM) + if(CP) + armor = armor.modifyRating(energy = (CP.rating * 10)) //Each level of capacitor protects the mech against emp by 10% + qdel(CP) + +//////////////////////// +////// Helpers ///////// +//////////////////////// + +/obj/mecha/proc/add_airtank() + internal_tank = new /obj/machinery/portable_atmospherics/canister/air(src) + return internal_tank + +/obj/mecha/proc/add_cell(var/obj/item/stock_parts/cell/C=null) + if(C) + C.forceMove(src) + cell = C + return + cell = new /obj/item/stock_parts/cell/high/plus(src) + +/obj/mecha/proc/add_cabin() + cabin_air = new + cabin_air.temperature = T20C + cabin_air.volume = 200 + cabin_air.gases[/datum/gas/oxygen] = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + cabin_air.gases[/datum/gas/nitrogen] = 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." + if(equipment && equipment.len) + . += "It's equipped with:" + for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment) + . += "[icon2html(ME, user)] \A [ME]." + +//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) + +//Diagnostic HUD updates + diag_hud_set_mechhealth() + diag_hud_set_mechcell() + diag_hud_set_mechstat() + +/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, atom/movable/source) + . = ..() + 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(phasing) + occupant_message("Unable to interact with objects while phasing") + return + if(user.incapacitated()) + return + if(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) + target = safepick(view(3,target)) + if(!target) + return + + 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) + target = safepick(oview(1,src)) + if(!melee_can_hit || !istype(target, /atom)) + return + target.mech_melee_attack(src) + melee_can_hit = 0 + spawn(melee_cooldown) + melee_can_hit = 1 + + +/obj/mecha/proc/range_action(atom/target) + return + + +////////////////////////////////// +//////// Movement procs //////// +////////////////////////////////// + +/obj/mecha/Move(atom/newloc, direct) + . = ..() + if(.) + events.fireEvent("onMove",get_turf(src)) + if (internal_tank.disconnect()) // Something moved us and broke connection + occupant_message("Air port connection teared off!") + log_message("Lost connection to gas port.") + +/obj/mecha/Process_Spacemove(var/movement_dir = 0) + . = ..() + if(.) + return 1 + if(thrusters_active && movement_dir && use_power(step_energy_drain)) + return 1 + + var/atom/movable/backup = get_spacemove_backup() + if(backup) + if(istype(backup) && movement_dir && !backup.anchored) + if(backup.newtonian_move(turn(movement_dir, 180))) + if(occupant) + to_chat(occupant, "You push off of [backup] to propel yourself.") + return 1 + +/obj/mecha/relaymove(mob/user,direction) + 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(state) + occupant_message("Maintenance protocols in effect.") + 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(defence_mode) + if(world.time - last_message > 20) + occupant_message("Unable to move while in defence mode") + last_message = world.time + 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 + + 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,1) + return 1 + +/obj/mecha/proc/mechstep(direction) + var/current_dir = dir + var/result = step(src,direction) + if(strafe) + setDir(current_dir) + if(result && stepsound) + playsound(src,stepsound,40,1) + return result + +/obj/mecha/proc/mechsteprand() + var/result = step_rand(src) + if(result && stepsound) + playsound(src,stepsound,40,1) + return result + +/obj/mecha/Bump(var/atom/obstacle) + if(phasing && get_charge() >= phasing_energy_drain && !throwing) + spawn() + if(can_move) + can_move = 0 + if(phase_state) + flick(phase_state, src) + forceMove(get_step(src,dir)) + use_power(phasing_energy_drain) + sleep(step_in*3) + can_move = 1 + 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) + step(obstacle, dir) + else if(ismob(obstacle)) + var/mob/M = obstacle + if(!M.anchored) + step(obstacle, dir) + + + + + +/////////////////////////////////// +//////// Internal damage //////// +/////////////////////////////////// + +/obj/mecha/proc/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) + if(!islist(possible_int_damage) || isemptylist(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 + var/int_dam_flag = safepick(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) + var/obj/item/mecha_parts/mecha_equipment/ME = safepick(equipment) + if(ME) + qdel(ME) + return + +/obj/mecha/proc/setInternalDamage(int_dam_flag) + internal_damage |= int_dam_flag + log_append_to_last("Internal damage of type [int_dam_flag].",1) + SEND_SOUND(occupant, sound('sound/machines/warning-buzzer.ogg',wait=0)) + diag_hud_set_mechstat() + return + +/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 extinquished.") + 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(!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 = 1 + AI.radio_enabled = 0 + AI.disconnect_shell() + RemoveActions(AI, TRUE) + occupant = null + 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/AIcore/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 = 0 + AI.radio_enabled = 1 + 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 + icon_state = initial(icon_state) + playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) + 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.canmove = 1 //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/proc/return_pressure() + var/datum/gas_mixture/t_air = return_air() + if(t_air) + . = t_air.return_pressure() + return + +/obj/mecha/return_temperature() + var/datum/gas_mixture/t_air = return_air() + if(t_air) + . = t_air.return_temperature() + return + +/obj/mecha/portableConnectorReturnAir() + return internal_tank.return_air() + + +/obj/mecha/MouseDrop_T(mob/M, mob/user) + if (!user.canUseTopic(src) || (user != M)) + return + if(!ishuman(user)) // no silicons or drones in mechas. + return + log_message("[user] tries to move in.") + if (occupant) + to_chat(usr, "The [name] is already occupied!") + log_append_to_last("Permission denied.") + 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_append_to_last("Permission denied.") + return + if(!operation_allowed(user)) + to_chat(user, "Access denied. Insufficient operation keycodes.") + log_append_to_last("Permission denied.") + return + if(user.buckled) + to_chat(user, "You are currently buckled and cannot move.") + log_append_to_last("Permission denied.") + 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!") + return + + visible_message("[user] starts to climb into [name].") + + if(do_after(user, 40, 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!") + return + +/obj/mecha/proc/moved_inside(mob/living/carbon/human/H) + 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_append_to_last("[H] moved in as pilot.") + icon_state = initial(icon_state) + setDir(dir_in) + playsound(src, 'sound/machines/windowdoor.ogg', 50, 1) + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + return 1 + else + return 0 + +/obj/mecha/proc/mmi_move_inside(obj/item/mmi/mmi_as_oc, mob/user) + if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) + to_chat(user, "Consciousness matrix not detected!") + return FALSE + else if(mmi_as_oc.brainmob.stat) + to_chat(user, "Beta-rhythm below acceptable level!") + return FALSE + else if(occupant) + to_chat(user, "Occupant detected!") + return FALSE + else if(dna_lock && (!mmi_as_oc.brainmob.stored_dna || (dna_lock != mmi_as_oc.brainmob.stored_dna.unique_enzymes))) + to_chat(user, "Access denied. [name] is secured with a DNA lock.") + return FALSE + + visible_message("[user] starts to insert an MMI into [name].") + + if(do_after(user, 40, target = src)) + if(!occupant) + return mmi_moved_inside(mmi_as_oc, user) + else + to_chat(user, "Occupant detected!") + else + to_chat(user, "You stop inserting the MMI.") + return FALSE + +/obj/mecha/proc/mmi_moved_inside(obj/item/mmi/mmi_as_oc, mob/user) + if(!(Adjacent(mmi_as_oc) && Adjacent(user))) + return FALSE + if(!mmi_as_oc.brainmob || !mmi_as_oc.brainmob.client) + to_chat(user, "Consciousness matrix not detected!") + return FALSE + else if(mmi_as_oc.brainmob.stat) + to_chat(user, "Beta-rhythm below acceptable level!") + return FALSE + if(!user.transferItemToLoc(mmi_as_oc, src)) + to_chat(user, "\the [mmi_as_oc] is stuck to your hand, you cannot put it in \the [src]!") + return FALSE + var/mob/living/brainmob = mmi_as_oc.brainmob + mmi_as_oc.mecha = src + occupant = brainmob + brainmob.forceMove(src) //should allow relaymove + brainmob.reset_perspective(src) + brainmob.remote_control = src + brainmob.update_canmove() + brainmob.update_mouse_pointer() + icon_state = initial(icon_state) + update_icon() + setDir(dir_in) + log_message("[mmi_as_oc] moved in as pilot.") + if(!internal_damage) + SEND_SOUND(occupant, sound('sound/mecha/nominal.ogg',volume=50)) + GrantActions(brainmob) + return TRUE + +/obj/mecha/container_resist(mob/living/user) + go_out() + + +/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) + +/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 + 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(). + if(mob_container.forceMove(newloc))//ejecting mob container + log_message("[mob_container] moved out.") + 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.canmove = 0 + icon_state = initial(icon_state)+"-open" + setDir(dir_in) + + if(L && L.client) + L.update_mouse_pointer() + L.client.change_view(CONFIG_GET(string/default_view)) + 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]") + return + +/obj/mecha/log_message(message as text, message_type=LOG_GAME, color=null, log_globally) + log.len++ + log[log.len] = list("time"="[STATION_TIME_TIMESTAMP("hh:mm:ss")]","date","year"="[GLOB.year_integer]","message"="[color?"":null][message][color?"":null]") + ..() + return log.len + +/obj/mecha/proc/log_append_to_last(message as text,red=null) + var/list/last_entry = log[log.len] + last_entry["message"] += "
                    [red?"":null][message][red?"":null]" + return + +/////////////////////// +///// 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 diff --git a/code/game/mecha/mecha_construction_paths.dm b/code/game/mecha/mecha_construction_paths.dm index 3b957431f7..071fe18408 100644 --- a/code/game/mecha/mecha_construction_paths.dm +++ b/code/game/mecha/mecha_construction_paths.dm @@ -1,1773 +1,1773 @@ -//////////////////////////////// -///// Construction datums ////// -//////////////////////////////// -/datum/component/construction/mecha - var/base_icon - -/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) - - 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) - -/datum/component/construction/mecha/update_parent(step_index) - ..() - // 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] has connected [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() - ..() - - -/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" - steps = list( - //1 - list( - "key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are disconnected." - ), - - //2 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are connected." - ), - - //3 - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The hydraulic systems are active." - ), - - //4 - list( - "key" = TOOL_WIRECUTTER, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is added." - ), - - //5 - list( - "key" = /obj/item/circuitboard/mecha/ripley/main, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is adjusted." - ), - - //6 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Central control module is installed." - ), - - //7 - list( - "key" = /obj/item/circuitboard/mecha/ripley/peripherals, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Central control module is secured." - ), - - //8 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Peripherals control module is installed." - ), - - //9 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - - //10 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed." - ), - - //11 - list( - "key" = /obj/item/stack/sheet/metal, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ), - - //12 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "Internal armor is installed." - ), - - //13 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "Internal armor is wrenched." - ), - - //14 - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded." - ), - - //15 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - - //16 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched." - ), - ) - -/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 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(11) - 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(12) - 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(13) - 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(14) - 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(15) - 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(16) - 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" - steps = list( - //1 - list( - "key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are disconnected." - ), - - //2 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are connected." - ), - - //3 - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The hydraulic systems are active." - ), - - //4 - list( - "key" = TOOL_WIRECUTTER, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is added." - ), - - //5 - list( - "key" = /obj/item/circuitboard/mecha/gygax/main, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is adjusted." - ), - - //6 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Central control module is installed." - ), - - //7 - list( - "key" = /obj/item/circuitboard/mecha/gygax/peripherals, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Central control module is secured." - ), - - //8 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Peripherals control module is installed." - ), - - //9 - list( - "key" = /obj/item/circuitboard/mecha/gygax/targeting, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - - //10 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Weapon control module is installed." - ), - - //11 - list( - "key" = /obj/item/stock_parts/scanning_module, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Weapon control module is secured." - ), - - //12 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Scanner module is installed." - ), - - //13 - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Scanner module is secured." - ), - - //14 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Capacitor is installed." - ), - - //15 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Capacitor is secured." - ), - - //16 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed." - ), - - //17 - list( - "key" = /obj/item/stack/sheet/metal, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ), - - //18 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "Internal armor is installed." - ), - - //19 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "Internal armor is wrenched." - ), - - //20 - list( - "key" = /obj/item/mecha_parts/part/gygax_armor, - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded." - ), - - //21 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - - //22 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched." - ), - - ) - -/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/firefighter - result = /datum/component/construction/mecha/firefighter - 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, - /obj/item/clothing/suit/fire - ) - -/datum/component/construction/mecha/firefighter - result = /obj/mecha/working/ripley/firefighter - base_icon = "fireripley" - steps = list( - //1 - list( - "key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are disconnected." - ), - - //2 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are connected." - ), - - //3 - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The hydraulic systems are active." - ), - - //4 - list( - "key" = TOOL_WIRECUTTER, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is added." - ), - - //5 - list( - "key" = /obj/item/circuitboard/mecha/ripley/main, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is adjusted." - ), - - //6 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Central control module is installed." - ), - - //7 - list( - "key" = /obj/item/circuitboard/mecha/ripley/peripherals, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Central control module is secured." - ), - - //8 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Peripherals control module is installed." - ), - - //9 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - - //10 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed." - ), - - //11 - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ), - - //12 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "Internal armor is installed." - ), - - //13 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "Internal armor is wrenched." - ), - - //14 - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded." - ), - - //15 - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is being installed." - ), - - //16 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - - //17 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched." - ), - ) - -/datum/component/construction/mecha/firefighter/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 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(11) - 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(12) - 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(13) - 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(14) - if(diff==FORWARD) - user.visible_message("[user] starts to install the external armor layer to [parent].", "You install the external 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(15) - 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] removes the external armor from [parent].", "You remove the external armor from [parent].") - if(16) - 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(17) - 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( - //1 - list( - "key" = /obj/item/bikehorn - ), - - //2 - list( - "key" = /obj/item/circuitboard/mecha/honker/main, - "action" = ITEM_DELETE - ), - - //3 - list( - "key" = /obj/item/bikehorn - ), - - //4 - list( - "key" = /obj/item/circuitboard/mecha/honker/peripherals, - "action" = ITEM_DELETE - ), - - //5 - list( - "key" = /obj/item/bikehorn - ), - - //6 - list( - "key" = /obj/item/circuitboard/mecha/honker/targeting, - "action" = ITEM_DELETE - ), - - //7 - list( - "key" = /obj/item/bikehorn - ), - - //8 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE - ), - - //9 - list( - "key" = /obj/item/bikehorn - ), - - //10 - list( - "key" = /obj/item/clothing/mask/gas/clown_hat, - "action" = ITEM_DELETE - ), - - //11 - list( - "key" = /obj/item/bikehorn - ), - - //12 - list( - "key" = /obj/item/clothing/shoes/clown_shoes, - "action" = ITEM_DELETE - ), - - //13 - list( - "key" = /obj/item/bikehorn - ), - ) - -// 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, 1) - 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] puts [I] on [parent].", "You put [I] on [parent].") - if(12) - 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" - steps = list( - //1 - list( - "key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are disconnected." - ), - - //2 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are connected." - ), - - //3 - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The hydraulic systems are active." - ), - - //4 - list( - "key" = TOOL_WIRECUTTER, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is added." - ), - - //5 - list( - "key" = /obj/item/circuitboard/mecha/durand/main, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is adjusted." - ), - - //6 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Central control module is installed." - ), - - //7 - list( - "key" = /obj/item/circuitboard/mecha/durand/peripherals, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Central control module is secured." - ), - - //8 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Peripherals control module is installed." - ), - - //9 - list( - "key" = /obj/item/circuitboard/mecha/durand/targeting, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - - //10 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Weapon control module is installed." - ), - - //11 - list( - "key" = /obj/item/stock_parts/scanning_module, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Weapon control module is secured." - ), - - //12 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Scanner module is installed." - ), - - //13 - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Scanner module is secured." - ), - - //14 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Capacitor is installed." - ), - - //15 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Capacitor is secured." - ), - - //16 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed." - ), - - //17 - list( - "key" = /obj/item/stack/sheet/metal, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ), - - //18 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "Internal armor is installed." - ), - - //19 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "Internal armor is wrenched." - ), - - //20 - list( - "key" = /obj/item/mecha_parts/part/durand_armor, - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded." - ), - - //21 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - - //22 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched." - ), - ) - - -/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" - steps = list( - //1 - list( - "key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are disconnected." - ), - - //2 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are connected." - ), - - //3 - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The hydraulic systems are active." - ), - - //4 - list( - "key" = TOOL_WIRECUTTER, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is added." - ), - - //5 - list( - "key" = /obj/item/circuitboard/mecha/phazon/main, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is adjusted." - ), - - //6 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Central control module is installed." - ), - - //7 - list( - "key" = /obj/item/circuitboard/mecha/phazon/peripherals, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Central control module is secured." - ), - - //8 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Peripherals control module is installed" - ), - - //9 - list( - "key" = /obj/item/circuitboard/mecha/phazon/targeting, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - - //10 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Weapon control is installed." - ), - - //11 - list( - "key" = /obj/item/stock_parts/scanning_module, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Weapon control module is secured." - ), - - //12 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Scanner module is installed." - ), - - //13 - list( - "key" = /obj/item/stock_parts/capacitor, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Scanner module is secured." - ), - - //14 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Capacitor is installed." - ), - - //15 - list( - "key" = /obj/item/stack/ore/bluespace_crystal, - "amount" = 1, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Capacitor is secured." - ), - - //16 - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_CROWBAR, - "desc" = "The bluespace crystal is installed." - ), - - //17 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WIRECUTTER, - "desc" = "The bluespace crystal is connected." - ), - - //18 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The bluespace crystal is engaged." - ), - - //19 - 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. - ), - - //20 - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured.", - "icon_state" = "phazon18" - ), - - //21 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "Phase armor is installed.", - "icon_state" = "phazon19" - ), - - //22 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "Phase armor is wrenched.", - "icon_state" = "phazon20" - ), - - //23 - list( - "key" = /obj/item/mecha_parts/part/phazon_armor, - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "Phase armor is welded.", - "icon_state" = "phazon21" - ), - - //24 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed.", - "icon_state" = "phazon22" - ), - - //25 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched.", - "icon_state" = "phazon23" - ), - - //26 - list( - "key" = /obj/item/assembly/signaler/anomaly, - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "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 [I].", "You secure [I].") - 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 anomaly core into [parent] and secures it.", - "You slowly place the 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" - steps = list( - //1 - list( - "key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are disconnected." - ), - - //2 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_WRENCH, - "desc" = "The hydraulic systems are connected." - ), - - //3 - list( - "key" = /obj/item/stack/cable_coil, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The hydraulic systems are active." - ), - - //4 - list( - "key" = TOOL_WIRECUTTER, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is added." - ), - - //5 - list( - "key" = /obj/item/circuitboard/mecha/odysseus/main, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The wiring is adjusted." - ), - - //6 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Central control module is installed." - ), - - //7 - list( - "key" = /obj/item/circuitboard/mecha/odysseus/peripherals, - "action" = ITEM_DELETE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Central control module is secured." - ), - - //8 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "Peripherals control module is installed." - ), - - //9 - list( - "key" = /obj/item/stock_parts/cell, - "action" = ITEM_MOVE_INSIDE, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured." - ), - - //10 - list( - "key" = TOOL_SCREWDRIVER, - "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed." - ), - - //11 - list( - "key" = /obj/item/stack/sheet/metal, - "amount" = 5, - "back_key" = TOOL_SCREWDRIVER, - "desc" = "The power cell is secured." - ), - - //12 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "Internal armor is installed." - ), - - //13 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "Internal armor is wrenched." - ), - - //14 - list( - "key" = /obj/item/stack/sheet/plasteel, - "amount" = 5, - "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded." - ), - - //15 - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed." - ), - - //16 - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched." - ), - ) - -/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 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(11) - 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(12) - 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(13) - 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(14) - 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(15) - 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(16) - 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 + +/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) + + 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) + +/datum/component/construction/mecha/update_parent(step_index) + ..() + // 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] has connected [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() + ..() + + +/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" + steps = list( + //1 + list( + "key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are disconnected." + ), + + //2 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are connected." + ), + + //3 + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The hydraulic systems are active." + ), + + //4 + list( + "key" = TOOL_WIRECUTTER, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is added." + ), + + //5 + list( + "key" = /obj/item/circuitboard/mecha/ripley/main, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is adjusted." + ), + + //6 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Central control module is installed." + ), + + //7 + list( + "key" = /obj/item/circuitboard/mecha/ripley/peripherals, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Central control module is secured." + ), + + //8 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Peripherals control module is installed." + ), + + //9 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + + //10 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed." + ), + + //11 + list( + "key" = /obj/item/stack/sheet/metal, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ), + + //12 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "Internal armor is installed." + ), + + //13 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "Internal armor is wrenched." + ), + + //14 + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_WELDER, + "desc" = "Internal armor is welded." + ), + + //15 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + + //16 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched." + ), + ) + +/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 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(11) + 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(12) + 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(13) + 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(14) + 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(15) + 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(16) + 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" + steps = list( + //1 + list( + "key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are disconnected." + ), + + //2 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are connected." + ), + + //3 + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The hydraulic systems are active." + ), + + //4 + list( + "key" = TOOL_WIRECUTTER, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is added." + ), + + //5 + list( + "key" = /obj/item/circuitboard/mecha/gygax/main, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is adjusted." + ), + + //6 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Central control module is installed." + ), + + //7 + list( + "key" = /obj/item/circuitboard/mecha/gygax/peripherals, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Central control module is secured." + ), + + //8 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Peripherals control module is installed." + ), + + //9 + list( + "key" = /obj/item/circuitboard/mecha/gygax/targeting, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + + //10 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Weapon control module is installed." + ), + + //11 + list( + "key" = /obj/item/stock_parts/scanning_module, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Weapon control module is secured." + ), + + //12 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Scanner module is installed." + ), + + //13 + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Scanner module is secured." + ), + + //14 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Capacitor is installed." + ), + + //15 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Capacitor is secured." + ), + + //16 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed." + ), + + //17 + list( + "key" = /obj/item/stack/sheet/metal, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ), + + //18 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "Internal armor is installed." + ), + + //19 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "Internal armor is wrenched." + ), + + //20 + list( + "key" = /obj/item/mecha_parts/part/gygax_armor, + "action" = ITEM_DELETE, + "back_key" = TOOL_WELDER, + "desc" = "Internal armor is welded." + ), + + //21 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + + //22 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched." + ), + + ) + +/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/firefighter + result = /datum/component/construction/mecha/firefighter + 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, + /obj/item/clothing/suit/fire + ) + +/datum/component/construction/mecha/firefighter + result = /obj/mecha/working/ripley/firefighter + base_icon = "fireripley" + steps = list( + //1 + list( + "key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are disconnected." + ), + + //2 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are connected." + ), + + //3 + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The hydraulic systems are active." + ), + + //4 + list( + "key" = TOOL_WIRECUTTER, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is added." + ), + + //5 + list( + "key" = /obj/item/circuitboard/mecha/ripley/main, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is adjusted." + ), + + //6 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Central control module is installed." + ), + + //7 + list( + "key" = /obj/item/circuitboard/mecha/ripley/peripherals, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Central control module is secured." + ), + + //8 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Peripherals control module is installed." + ), + + //9 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + + //10 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed." + ), + + //11 + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ), + + //12 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "Internal armor is installed." + ), + + //13 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "Internal armor is wrenched." + ), + + //14 + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_WELDER, + "desc" = "Internal armor is welded." + ), + + //15 + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is being installed." + ), + + //16 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + + //17 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched." + ), + ) + +/datum/component/construction/mecha/firefighter/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 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(11) + 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(12) + 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(13) + 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(14) + if(diff==FORWARD) + user.visible_message("[user] starts to install the external armor layer to [parent].", "You install the external 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(15) + 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] removes the external armor from [parent].", "You remove the external armor from [parent].") + if(16) + 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(17) + 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( + //1 + list( + "key" = /obj/item/bikehorn + ), + + //2 + list( + "key" = /obj/item/circuitboard/mecha/honker/main, + "action" = ITEM_DELETE + ), + + //3 + list( + "key" = /obj/item/bikehorn + ), + + //4 + list( + "key" = /obj/item/circuitboard/mecha/honker/peripherals, + "action" = ITEM_DELETE + ), + + //5 + list( + "key" = /obj/item/bikehorn + ), + + //6 + list( + "key" = /obj/item/circuitboard/mecha/honker/targeting, + "action" = ITEM_DELETE + ), + + //7 + list( + "key" = /obj/item/bikehorn + ), + + //8 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE + ), + + //9 + list( + "key" = /obj/item/bikehorn + ), + + //10 + list( + "key" = /obj/item/clothing/mask/gas/clown_hat, + "action" = ITEM_DELETE + ), + + //11 + list( + "key" = /obj/item/bikehorn + ), + + //12 + list( + "key" = /obj/item/clothing/shoes/clown_shoes, + "action" = ITEM_DELETE + ), + + //13 + list( + "key" = /obj/item/bikehorn + ), + ) + +// 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, 1) + 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] puts [I] on [parent].", "You put [I] on [parent].") + if(12) + 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" + steps = list( + //1 + list( + "key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are disconnected." + ), + + //2 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are connected." + ), + + //3 + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The hydraulic systems are active." + ), + + //4 + list( + "key" = TOOL_WIRECUTTER, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is added." + ), + + //5 + list( + "key" = /obj/item/circuitboard/mecha/durand/main, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is adjusted." + ), + + //6 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Central control module is installed." + ), + + //7 + list( + "key" = /obj/item/circuitboard/mecha/durand/peripherals, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Central control module is secured." + ), + + //8 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Peripherals control module is installed." + ), + + //9 + list( + "key" = /obj/item/circuitboard/mecha/durand/targeting, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + + //10 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Weapon control module is installed." + ), + + //11 + list( + "key" = /obj/item/stock_parts/scanning_module, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Weapon control module is secured." + ), + + //12 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Scanner module is installed." + ), + + //13 + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Scanner module is secured." + ), + + //14 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Capacitor is installed." + ), + + //15 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Capacitor is secured." + ), + + //16 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed." + ), + + //17 + list( + "key" = /obj/item/stack/sheet/metal, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ), + + //18 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "Internal armor is installed." + ), + + //19 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "Internal armor is wrenched." + ), + + //20 + list( + "key" = /obj/item/mecha_parts/part/durand_armor, + "action" = ITEM_DELETE, + "back_key" = TOOL_WELDER, + "desc" = "Internal armor is welded." + ), + + //21 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + + //22 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched." + ), + ) + + +/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" + steps = list( + //1 + list( + "key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are disconnected." + ), + + //2 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are connected." + ), + + //3 + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The hydraulic systems are active." + ), + + //4 + list( + "key" = TOOL_WIRECUTTER, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is added." + ), + + //5 + list( + "key" = /obj/item/circuitboard/mecha/phazon/main, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is adjusted." + ), + + //6 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Central control module is installed." + ), + + //7 + list( + "key" = /obj/item/circuitboard/mecha/phazon/peripherals, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Central control module is secured." + ), + + //8 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Peripherals control module is installed" + ), + + //9 + list( + "key" = /obj/item/circuitboard/mecha/phazon/targeting, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + + //10 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Weapon control is installed." + ), + + //11 + list( + "key" = /obj/item/stock_parts/scanning_module, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Weapon control module is secured." + ), + + //12 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Scanner module is installed." + ), + + //13 + list( + "key" = /obj/item/stock_parts/capacitor, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Scanner module is secured." + ), + + //14 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Capacitor is installed." + ), + + //15 + list( + "key" = /obj/item/stack/ore/bluespace_crystal, + "amount" = 1, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Capacitor is secured." + ), + + //16 + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_CROWBAR, + "desc" = "The bluespace crystal is installed." + ), + + //17 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WIRECUTTER, + "desc" = "The bluespace crystal is connected." + ), + + //18 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The bluespace crystal is engaged." + ), + + //19 + 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. + ), + + //20 + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured.", + "icon_state" = "phazon18" + ), + + //21 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "Phase armor is installed.", + "icon_state" = "phazon19" + ), + + //22 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "Phase armor is wrenched.", + "icon_state" = "phazon20" + ), + + //23 + list( + "key" = /obj/item/mecha_parts/part/phazon_armor, + "action" = ITEM_DELETE, + "back_key" = TOOL_WELDER, + "desc" = "Phase armor is welded.", + "icon_state" = "phazon21" + ), + + //24 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed.", + "icon_state" = "phazon22" + ), + + //25 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched.", + "icon_state" = "phazon23" + ), + + //26 + list( + "key" = /obj/item/assembly/signaler/anomaly, + "action" = ITEM_DELETE, + "back_key" = TOOL_WELDER, + "desc" = "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 [I].", "You secure [I].") + 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 anomaly core into [parent] and secures it.", + "You slowly place the 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" + steps = list( + //1 + list( + "key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are disconnected." + ), + + //2 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_WRENCH, + "desc" = "The hydraulic systems are connected." + ), + + //3 + list( + "key" = /obj/item/stack/cable_coil, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The hydraulic systems are active." + ), + + //4 + list( + "key" = TOOL_WIRECUTTER, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is added." + ), + + //5 + list( + "key" = /obj/item/circuitboard/mecha/odysseus/main, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The wiring is adjusted." + ), + + //6 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Central control module is installed." + ), + + //7 + list( + "key" = /obj/item/circuitboard/mecha/odysseus/peripherals, + "action" = ITEM_DELETE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Central control module is secured." + ), + + //8 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "Peripherals control module is installed." + ), + + //9 + list( + "key" = /obj/item/stock_parts/cell, + "action" = ITEM_MOVE_INSIDE, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "Peripherals control module is secured." + ), + + //10 + list( + "key" = TOOL_SCREWDRIVER, + "back_key" = TOOL_CROWBAR, + "desc" = "The power cell is installed." + ), + + //11 + list( + "key" = /obj/item/stack/sheet/metal, + "amount" = 5, + "back_key" = TOOL_SCREWDRIVER, + "desc" = "The power cell is secured." + ), + + //12 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "Internal armor is installed." + ), + + //13 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "Internal armor is wrenched." + ), + + //14 + list( + "key" = /obj/item/stack/sheet/plasteel, + "amount" = 5, + "back_key" = TOOL_WELDER, + "desc" = "Internal armor is welded." + ), + + //15 + list( + "key" = TOOL_WRENCH, + "back_key" = TOOL_CROWBAR, + "desc" = "External armor is installed." + ), + + //16 + list( + "key" = TOOL_WELDER, + "back_key" = TOOL_WRENCH, + "desc" = "External armor is wrenched." + ), + ) + +/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 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(11) + 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(12) + 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(13) + 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(14) + 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(15) + 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(16) + 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 18c9b3eb2f..0188419f0f 100644 --- a/code/game/mecha/mecha_control_console.dm +++ b/code/game/mecha/mecha_control_console.dm @@ -1,142 +1,142 @@ -/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 - var/list/located = list() - var/screen = 0 - var/stored_data - -/obj/machinery/computer/mecha/ui_interact(mob/user) - . = ..() - var/dat = "[src.name]" - if(screen == 0) - dat += "

                    Tracking beacons data

                    " - var/list/trackerlist = list() - for(var/obj/mecha/MC in GLOB.mechas_list) - trackerlist += MC.trackers - for(var/obj/item/mecha_parts/mecha_tracking/TR in trackerlist) - var/answer = TR.get_mecha_info() - if(answer) - dat += {"
                    [answer]
                    - Send message
                    - Show exosuit log
                    - [TR.recharging?"Recharging EMP Pulse...
                    ":"(EMP Pulse)
                    "]"} - - if(screen==1) - dat += "

                    Log contents

                    " - dat += "Return
                    " - dat += "[stored_data]" - - dat += "(Refresh)
                    " - dat += "" - - user << browse(dat, "window=computer;size=400x500") - onclose(user, "computer") - -/obj/machinery/computer/mecha/Topic(href, href_list) - if(..()) - return - var/datum/topic_input/afilter = new /datum/topic_input(href,href_list) - if(href_list["send_message"]) - var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("send_message") - var/message = stripped_input(usr,"Input message","Transmit message") - var/obj/mecha/M = MT.in_mecha() - if(trim(message) && M) - M.occupant_message(message) - return - if(href_list["shock"]) - var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("shock") - MT.shock() - if(href_list["get_log"]) - var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("get_log") - stored_data = MT.get_mecha_log() - screen = 1 - if(href_list["return"]) - screen = 0 - updateUsrDialog() - return - -/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 - var/ai_beacon = FALSE //If this beacon allows for AI control. Exists to avoid using istype() on checking. - var/recharging = 0 - -/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info() - if(!in_mecha()) - return 0 - var/obj/mecha/M = src.loc - var/cell_charge = M.get_charge() - var/answer = {"Name: [M.name] -Integrity: [M.obj_integrity/M.max_integrity*100]% -Cell charge: [isnull(cell_charge)?"Not found":"[M.cell.percent()]%"] -Airtank: [M.return_pressure()]kPa -Pilot: [M.occupant||"None"] -Location: [get_area(M)||"Unknown"] -Active equipment: [M.selected||"None"] "} - if(istype(M, /obj/mecha/working/ripley)) - var/obj/mecha/working/ripley/RM = M - answer += "Used cargo space: [RM.cargo.len/RM.cargo_capacity*100]%
                    " - - return answer - -/obj/item/mecha_parts/mecha_tracking/emp_act() - . = ..() - if(!(. & EMP_PROTECT_SELF)) - qdel(src) - -/obj/item/mecha_parts/mecha_tracking/Destroy() - if(ismecha(loc)) - var/obj/mecha/M = loc - if(src in M.trackers) - M.trackers -= src - return ..() - -/obj/item/mecha_parts/mecha_tracking/proc/in_mecha() - if(ismecha(loc)) - return loc - return 0 - -/obj/item/mecha_parts/mecha_tracking/proc/shock() - if(recharging) - return - var/obj/mecha/M = in_mecha() - if(M) - M.emp_act(EMP_HEAVY) - addtimer(CALLBACK(src, /obj/item/mecha_parts/mecha_tracking/proc/recharge), 15 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) - recharging = 1 - -/obj/item/mecha_parts/mecha_tracking/proc/recharge() - recharging = 0 - -/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_log() - if(!ismecha(loc)) - return 0 - var/obj/mecha/M = src.loc - return M.get_log_html() - - -/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 + var/list/located = list() + var/screen = 0 + var/stored_data + +/obj/machinery/computer/mecha/ui_interact(mob/user) + . = ..() + var/dat = "[src.name]" + if(screen == 0) + dat += "

                    Tracking beacons data

                    " + var/list/trackerlist = list() + for(var/obj/mecha/MC in GLOB.mechas_list) + trackerlist += MC.trackers + for(var/obj/item/mecha_parts/mecha_tracking/TR in trackerlist) + var/answer = TR.get_mecha_info() + if(answer) + dat += {"
                    [answer]
                    + Send message
                    + Show exosuit log
                    + [TR.recharging?"Recharging EMP Pulse...
                    ":"(EMP Pulse)
                    "]"} + + if(screen==1) + dat += "

                    Log contents

                    " + dat += "Return
                    " + dat += "[stored_data]" + + dat += "(Refresh)
                    " + dat += "" + + user << browse(dat, "window=computer;size=400x500") + onclose(user, "computer") + +/obj/machinery/computer/mecha/Topic(href, href_list) + if(..()) + return + var/datum/topic_input/afilter = new /datum/topic_input(href,href_list) + if(href_list["send_message"]) + var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("send_message") + var/message = stripped_input(usr,"Input message","Transmit message") + var/obj/mecha/M = MT.in_mecha() + if(trim(message) && M) + M.occupant_message(message) + return + if(href_list["shock"]) + var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("shock") + MT.shock() + if(href_list["get_log"]) + var/obj/item/mecha_parts/mecha_tracking/MT = afilter.getObj("get_log") + stored_data = MT.get_mecha_log() + screen = 1 + if(href_list["return"]) + screen = 0 + updateUsrDialog() + return + +/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 + var/ai_beacon = FALSE //If this beacon allows for AI control. Exists to avoid using istype() on checking. + var/recharging = 0 + +/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_info() + if(!in_mecha()) + return 0 + var/obj/mecha/M = src.loc + var/cell_charge = M.get_charge() + var/answer = {"Name: [M.name] +Integrity: [M.obj_integrity/M.max_integrity*100]% +Cell charge: [isnull(cell_charge)?"Not found":"[M.cell.percent()]%"] +Airtank: [M.return_pressure()]kPa +Pilot: [M.occupant||"None"] +Location: [get_area(M)||"Unknown"] +Active equipment: [M.selected||"None"] "} + if(istype(M, /obj/mecha/working/ripley)) + var/obj/mecha/working/ripley/RM = M + answer += "Used cargo space: [RM.cargo.len/RM.cargo_capacity*100]%
                    " + + return answer + +/obj/item/mecha_parts/mecha_tracking/emp_act() + . = ..() + if(!(. & EMP_PROTECT_SELF)) + qdel(src) + +/obj/item/mecha_parts/mecha_tracking/Destroy() + if(ismecha(loc)) + var/obj/mecha/M = loc + if(src in M.trackers) + M.trackers -= src + return ..() + +/obj/item/mecha_parts/mecha_tracking/proc/in_mecha() + if(ismecha(loc)) + return loc + return 0 + +/obj/item/mecha_parts/mecha_tracking/proc/shock() + if(recharging) + return + var/obj/mecha/M = in_mecha() + if(M) + M.emp_act(EMP_HEAVY) + addtimer(CALLBACK(src, /obj/item/mecha_parts/mecha_tracking/proc/recharge), 15 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) + recharging = 1 + +/obj/item/mecha_parts/mecha_tracking/proc/recharge() + recharging = 0 + +/obj/item/mecha_parts/mecha_tracking/proc/get_mecha_log() + if(!ismecha(loc)) + return 0 + var/obj/mecha/M = src.loc + return M.get_log_html() + + +/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 3f5e4bde66..811ffdef35 100644 --- a/code/game/mecha/mecha_parts.dm +++ b/code/game/mecha/mecha_parts.dm @@ -1,339 +1,339 @@ -///////////////////////// -////// 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/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" - -////////// Firefighter - -/obj/item/mecha_parts/chassis/firefighter - name = "\improper Firefighter chassis" - construct_type = /datum/component/construction/unordered/mecha_chassis/firefighter - - -////////// 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/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" - item_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)" +///////////////////////// +////// 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/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" + +////////// Firefighter + +/obj/item/mecha_parts/chassis/firefighter + name = "\improper Firefighter chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/firefighter + + +////////// 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/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" + item_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)" diff --git a/code/game/mecha/mecha_wreckage.dm b/code/game/mecha/mecha_wreckage.dm index d6ba08e021..ecf39bcb0b 100644 --- a/code/game/mecha/mecha_wreckage.dm +++ b/code/game/mecha/mecha_wreckage.dm @@ -1,253 +1,253 @@ -/////////////////////////////////// -//////// 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/list/wirecutters_salvage = list(/obj/item/stack/cable_coil) - var/list/crowbar_salvage = list() - var/salvage_num = 5 - var/mob/living/silicon/ai/AI //AIs to be salvaged - -/obj/structure/mecha_wreckage/Initialize(mapload, mob/living/silicon/ai/AI_pilot) - . = ..() - 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/examine(mob/user) - . = ..() - if(AI) - . += "The AI recovery beacon is active." - -/obj/structure/mecha_wreckage/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/weldingtool)) - 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 - - var/type = prob(70) ? pick(welder_salvage) : null - if(type) - 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/mecha_parts/part)) - welder_salvage -= type - salvage_num-- - else - to_chat(user, "You fail to salvage anything valuable from [src]!") - return - - else if(istype(I, /obj/item/wirecutters)) - if(salvage_num <= 0) - to_chat(user, "You don't see anything that can be cut with [I]!") - return - else if(wirecutters_salvage && wirecutters_salvage.len) - var/type = prob(70) ? pick(wirecutters_salvage) : null - if(type) - var/N = new type(get_turf(user)) - user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") - salvage_num-- - else - to_chat(user, "You fail to salvage anything valuable from [src]!") - - else if(istype(I, /obj/item/crowbar)) - if(crowbar_salvage && crowbar_salvage.len) - var/obj/S = pick(crowbar_salvage) - if(S) - S.forceMove(user.drop_location()) - crowbar_salvage -= S - user.visible_message("[user] pries [S] from [src].", "You pry [S] from [src].") - return - else - to_chat(user, "You don't see anything that can be pried 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. - 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.") - - else - return ..() - - -/obj/structure/mecha_wreckage/gygax - name = "\improper Gygax wreckage" - icon_state = "gygax-broken" - -/obj/structure/mecha_wreckage/gygax/Initialize() - . = ..() - var/list/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) - for(var/i = 0; i < 2; i++) - if(parts.len && prob(40)) - var/part = pick(parts) - welder_salvage += part - parts -= part - - - -/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" - -/obj/structure/mecha_wreckage/ripley/Initialize() - . = ..() - var/list/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) - for(var/i = 0; i < 2; i++) - if(parts.len && prob(40)) - var/part = pick(parts) - welder_salvage += part - parts -= part - - -/obj/structure/mecha_wreckage/ripley/firefighter - name = "\improper Firefighter wreckage" - icon_state = "firefighter-broken" - -/obj/structure/mecha_wreckage/ripley/firefighter/Initialize() - . = ..() - var/list/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/item/clothing/suit/fire) - for(var/i = 0; i < 2; i++) - if(parts.len && prob(40)) - var/part = pick(parts) - welder_salvage += part - parts -= part - - -/obj/structure/mecha_wreckage/ripley/deathripley - name = "\improper Death-Ripley wreckage" - icon_state = "deathripley-broken" - - -/obj/structure/mecha_wreckage/honker - name = "\improper H.O.N.K wreckage" - icon_state = "honker-broken" - desc = "All is right in the universe." - -/obj/structure/mecha_wreckage/honker/Initialize() - . = ..() - var/list/parts = list( - /obj/item/mecha_parts/chassis/honker, - /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) - for(var/i = 0; i < 2; i++) - if(parts.len && prob(40)) - var/part = pick(parts) - welder_salvage += part - parts -= part - - -/obj/structure/mecha_wreckage/durand - name = "\improper Durand wreckage" - icon_state = "durand-broken" - -/obj/structure/mecha_wreckage/durand/Initialize() - . = ..() - var/list/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) - for(var/i = 0; i < 2; i++) - if(parts.len && prob(40)) - var/part = pick(parts) - welder_salvage += part - parts -= part - - -/obj/structure/mecha_wreckage/phazon - name = "\improper Phazon wreckage" - icon_state = "phazon-broken" - - -/obj/structure/mecha_wreckage/odysseus - name = "\improper Odysseus wreckage" - icon_state = "odysseus-broken" - -/obj/structure/mecha_wreckage/odysseus/Initialize() - . = ..() - var/list/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) - for(var/i = 0; i < 2; i++) - if(parts.len && prob(40)) - var/part = pick(parts) - welder_salvage += part - parts -= part +/////////////////////////////////// +//////// 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/list/wirecutters_salvage = list(/obj/item/stack/cable_coil) + var/list/crowbar_salvage = list() + var/salvage_num = 5 + var/mob/living/silicon/ai/AI //AIs to be salvaged + +/obj/structure/mecha_wreckage/Initialize(mapload, mob/living/silicon/ai/AI_pilot) + . = ..() + 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/examine(mob/user) + . = ..() + if(AI) + . += "The AI recovery beacon is active." + +/obj/structure/mecha_wreckage/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/weldingtool)) + 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 + + var/type = prob(70) ? pick(welder_salvage) : null + if(type) + 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/mecha_parts/part)) + welder_salvage -= type + salvage_num-- + else + to_chat(user, "You fail to salvage anything valuable from [src]!") + return + + else if(istype(I, /obj/item/wirecutters)) + if(salvage_num <= 0) + to_chat(user, "You don't see anything that can be cut with [I]!") + return + else if(wirecutters_salvage && wirecutters_salvage.len) + var/type = prob(70) ? pick(wirecutters_salvage) : null + if(type) + var/N = new type(get_turf(user)) + user.visible_message("[user] cuts [N] from [src].", "You cut [N] from [src].") + salvage_num-- + else + to_chat(user, "You fail to salvage anything valuable from [src]!") + + else if(istype(I, /obj/item/crowbar)) + if(crowbar_salvage && crowbar_salvage.len) + var/obj/S = pick(crowbar_salvage) + if(S) + S.forceMove(user.drop_location()) + crowbar_salvage -= S + user.visible_message("[user] pries [S] from [src].", "You pry [S] from [src].") + return + else + to_chat(user, "You don't see anything that can be pried 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. + 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.") + + else + return ..() + + +/obj/structure/mecha_wreckage/gygax + name = "\improper Gygax wreckage" + icon_state = "gygax-broken" + +/obj/structure/mecha_wreckage/gygax/Initialize() + . = ..() + var/list/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) + for(var/i = 0; i < 2; i++) + if(parts.len && prob(40)) + var/part = pick(parts) + welder_salvage += part + parts -= part + + + +/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" + +/obj/structure/mecha_wreckage/ripley/Initialize() + . = ..() + var/list/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) + for(var/i = 0; i < 2; i++) + if(parts.len && prob(40)) + var/part = pick(parts) + welder_salvage += part + parts -= part + + +/obj/structure/mecha_wreckage/ripley/firefighter + name = "\improper Firefighter wreckage" + icon_state = "firefighter-broken" + +/obj/structure/mecha_wreckage/ripley/firefighter/Initialize() + . = ..() + var/list/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/item/clothing/suit/fire) + for(var/i = 0; i < 2; i++) + if(parts.len && prob(40)) + var/part = pick(parts) + welder_salvage += part + parts -= part + + +/obj/structure/mecha_wreckage/ripley/deathripley + name = "\improper Death-Ripley wreckage" + icon_state = "deathripley-broken" + + +/obj/structure/mecha_wreckage/honker + name = "\improper H.O.N.K wreckage" + icon_state = "honker-broken" + desc = "All is right in the universe." + +/obj/structure/mecha_wreckage/honker/Initialize() + . = ..() + var/list/parts = list( + /obj/item/mecha_parts/chassis/honker, + /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) + for(var/i = 0; i < 2; i++) + if(parts.len && prob(40)) + var/part = pick(parts) + welder_salvage += part + parts -= part + + +/obj/structure/mecha_wreckage/durand + name = "\improper Durand wreckage" + icon_state = "durand-broken" + +/obj/structure/mecha_wreckage/durand/Initialize() + . = ..() + var/list/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) + for(var/i = 0; i < 2; i++) + if(parts.len && prob(40)) + var/part = pick(parts) + welder_salvage += part + parts -= part + + +/obj/structure/mecha_wreckage/phazon + name = "\improper Phazon wreckage" + icon_state = "phazon-broken" + + +/obj/structure/mecha_wreckage/odysseus + name = "\improper Odysseus wreckage" + icon_state = "odysseus-broken" + +/obj/structure/mecha_wreckage/odysseus/Initialize() + . = ..() + var/list/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) + for(var/i = 0; i < 2; i++) + if(parts.len && prob(40)) + var/part = pick(parts) + welder_salvage += part + parts -= part diff --git a/code/game/mecha/medical/odysseus.dm b/code/game/mecha/medical/odysseus.dm index 360ac51444..6ed207a4c4 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/carbon/human/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/mmi_as_oc, mob/user) - . = ..() - if(.) - var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - var/mob/living/brain/B = mmi_as_oc.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/carbon/human/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/mmi_as_oc, mob/user) + . = ..() + if(.) + var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] + var/mob/living/brain/B = mmi_as_oc.brainmob + hud.add_hud_to(B) diff --git a/code/game/mecha/working/ripley.dm b/code/game/mecha/working/ripley.dm index 19656f2d7d..3759a9dd58 100644 --- a/code/game/mecha/working/ripley.dm +++ b/code/game/mecha/working/ripley.dm @@ -1,198 +1,198 @@ -/obj/mecha/working/ripley - desc = "Autonomous Power Loader Unit. This newer model is refitted with powerful armour against the dangers of planetary mining." - name = "\improper APLU \"Ripley\"" - icon_state = "ripley" - step_in = 3 //Move speed, lower is faster. - var/fast_pressure_step_in = 2 - var/slow_pressure_step_in = 3 - max_temperature = 20000 - max_integrity = 200 - lights_power = 8 - deflect_chance = 15 - armor = list("melee" = 30, "bullet" = 15, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_equip = 6 - wreckage = /obj/structure/mecha_wreckage/ripley - var/list/cargo = new - var/cargo_capacity = 15 - var/hides = 0 - -/obj/mecha/working/ripley/Move() - . = ..() - if(.) - collect_ore() - update_pressure() - -/obj/mecha/working/ripley/proc/collect_ore() - if(locate(/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp) in equipment) - var/obj/structure/ore_box/ore_box = locate(/obj/structure/ore_box) in cargo - 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/ripley/Destroy() - for(var/atom/movable/A in cargo) - A.forceMove(drop_location()) - step_rand(A) - cargo.Cut() - return ..() - -/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/update_icon() - ..() - var/datum/component/armor_plate/C = GetComponent(/datum/component/armor_plate) - if (C.amount) - cut_overlays() - if(C.amount < 3) - add_overlay(occupant ? "ripley-g" : "ripley-g-open") - else - add_overlay(occupant ? "ripley-g-full" : "ripley-g-full-open") - -/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/firefighter - desc = "Autonomous Power Loader Unit. This model is refitted with additional thermal protection." - name = "\improper APLU \"Firefighter\"" - icon_state = "firefighter" - step_in = 4 - fast_pressure_step_in = 2 - slow_pressure_step_in = 4 - max_temperature = 65000 - max_integrity = 250 - resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF - lights_power = 7 - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - max_equip = 5 // More armor, less tools - wreckage = /obj/structure/mecha_wreckage/ripley/firefighter - - -/obj/mecha/working/ripley/deathripley - desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" - name = "\improper DEATH-RIPLEY" - icon_state = "deathripley" - armor = list("melee" = 40, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - slow_pressure_step_in = 3 - opacity=0 - lights_power = 7 - wreckage = /obj/structure/mecha_wreckage/ripley/deathripley - step_energy_drain = 0 - -/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"]) - if(O && O in src.cargo) - occupant_message("You unload [O].") - O.forceMove(drop_location()) - cargo -= O - log_message("Unloaded [O]. Cargo compartment capacity: [cargo_capacity - src.cargo.len]") - 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/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)/2 - 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/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]!") +/obj/mecha/working/ripley + desc = "Autonomous Power Loader Unit. This newer model is refitted with powerful armour against the dangers of planetary mining." + name = "\improper APLU \"Ripley\"" + icon_state = "ripley" + step_in = 3 //Move speed, lower is faster. + var/fast_pressure_step_in = 2 + var/slow_pressure_step_in = 3 + max_temperature = 20000 + max_integrity = 200 + lights_power = 8 + deflect_chance = 15 + armor = list("melee" = 30, "bullet" = 15, "laser" = 10, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_equip = 6 + wreckage = /obj/structure/mecha_wreckage/ripley + var/list/cargo = new + var/cargo_capacity = 15 + var/hides = 0 + +/obj/mecha/working/ripley/Move() + . = ..() + if(.) + collect_ore() + update_pressure() + +/obj/mecha/working/ripley/proc/collect_ore() + if(locate(/obj/item/mecha_parts/mecha_equipment/hydraulic_clamp) in equipment) + var/obj/structure/ore_box/ore_box = locate(/obj/structure/ore_box) in cargo + 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/ripley/Destroy() + for(var/atom/movable/A in cargo) + A.forceMove(drop_location()) + step_rand(A) + cargo.Cut() + return ..() + +/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/update_icon() + ..() + var/datum/component/armor_plate/C = GetComponent(/datum/component/armor_plate) + if (C.amount) + cut_overlays() + if(C.amount < 3) + add_overlay(occupant ? "ripley-g" : "ripley-g-open") + else + add_overlay(occupant ? "ripley-g-full" : "ripley-g-full-open") + +/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/firefighter + desc = "Autonomous Power Loader Unit. This model is refitted with additional thermal protection." + name = "\improper APLU \"Firefighter\"" + icon_state = "firefighter" + step_in = 4 + fast_pressure_step_in = 2 + slow_pressure_step_in = 4 + max_temperature = 65000 + max_integrity = 250 + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + lights_power = 7 + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + max_equip = 5 // More armor, less tools + wreckage = /obj/structure/mecha_wreckage/ripley/firefighter + + +/obj/mecha/working/ripley/deathripley + desc = "OH SHIT IT'S THE DEATHSQUAD WE'RE ALL GONNA DIE" + name = "\improper DEATH-RIPLEY" + icon_state = "deathripley" + armor = list("melee" = 40, "bullet" = 30, "laser" = 20, "energy" = 20, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + slow_pressure_step_in = 3 + opacity=0 + lights_power = 7 + wreckage = /obj/structure/mecha_wreckage/ripley/deathripley + step_energy_drain = 0 + +/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"]) + if(O && O in src.cargo) + occupant_message("You unload [O].") + O.forceMove(drop_location()) + cargo -= O + log_message("Unloaded [O]. Cargo compartment capacity: [cargo_capacity - src.cargo.len]") + 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/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)/2 + 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/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]!") diff --git a/code/game/objects/effects/bump_teleporter.dm b/code/game/objects/effects/bump_teleporter.dm index 23b186c009..0337b076ff 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 cab4a66fdd..30e7c8be05 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -1,96 +1,96 @@ -/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? - -/obj/effect/decal/cleanable/Initialize(mapload, list/datum/disease/diseases) - . = ..() - LAZYINITLIST(blood_DNA) //Kinda needed - 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) - -/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) - qdel(C) - -/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) - 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 && !HAS_TRAIT(H, TRAIT_LIGHT_STEP)) - var/obj/item/clothing/shoes/S = H.shoes - 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) - if(blood_DNA && blood_DNA.len) - S.add_blood_DNA(blood_DNA) - S.add_blood_overlay() - S.blood_state = blood_state - update_icon() - H.update_inv_shoes() - -/obj/effect/decal/cleanable/proc/can_bloodcrawl_in() - if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return bloodiness - else - return FALSE +/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? + +/obj/effect/decal/cleanable/Initialize(mapload, list/datum/disease/diseases) + . = ..() + LAZYINITLIST(blood_DNA) //Kinda needed + 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) + +/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) + qdel(C) + +/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) + 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 && !HAS_TRAIT(H, TRAIT_LIGHT_STEP)) + var/obj/item/clothing/shoes/S = H.shoes + 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) + if(blood_DNA && blood_DNA.len) + S.add_blood_DNA(blood_DNA) + S.add_blood_overlay() + S.blood_state = blood_state + update_icon() + H.update_inv_shoes() + +/obj/effect/decal/cleanable/proc/can_bloodcrawl_in() + if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) + return bloodiness + else + return FALSE diff --git a/code/game/objects/effects/decals/cleanable/aliens.dm b/code/game/objects/effects/decals/cleanable/aliens.dm index 0c52f57ae9..748b8c9453 100644 --- a/code/game/objects/effects/decals/cleanable/aliens.dm +++ b/code/game/objects/effects/decals/cleanable/aliens.dm @@ -1,70 +1,70 @@ - -/obj/effect/decal/cleanable/blood/xeno - name = "xeno blood" - desc = "It's green and acidic. It looks like... blood?" - color = BLOOD_COLOR_XENO - -/obj/effect/decal/cleanable/blood/splatter/xeno - color = BLOOD_COLOR_XENO - -/obj/effect/decal/cleanable/blood/gibs/xeno - color = BLOOD_COLOR_XENO - gibs_reagent_id = /datum/reagent/liquidgibs/xeno - gibs_bloodtype = "X*" - -/obj/effect/decal/cleanable/blood/gibs/xeno/Initialize(mapload, list/datum/disease/diseases) - . = ..() - update_icon() - -/obj/effect/decal/cleanable/blood/gibs/xeno/update_icon() - add_atom_colour(blood_DNA_to_color(), FIXED_COLOUR_PRIORITY) - cut_overlays() - var/mutable_appearance/flesh = mutable_appearance(icon, "[icon_state]x_flesh") - flesh.appearance_flags = RESET_COLOR - flesh.color = body_colors - add_overlay(flesh) - -/obj/effect/decal/cleanable/blood/gibs/xeno/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) - var/obj/effect/decal/cleanable/blood/splatter/xeno/splat = new /obj/effect/decal/cleanable/blood/splatter/xeno(loc, diseases) - splat.transfer_blood_dna(blood_DNA, diseases) - if(!step_to(src, get_step(src, direction), 0)) - break - -/obj/effect/decal/cleanable/blood/gibs/xeno/up - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibup1","gibup1","gibup1") - -/obj/effect/decal/cleanable/blood/gibs/xeno/down - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibdown1","gibdown1","gibdown1") - -/obj/effect/decal/cleanable/blood/gibs/xeno/body - random_icon_states = list("gibhead", "gibtorso") - -/obj/effect/decal/cleanable/blood/gibs/xeno/torso - random_icon_states = list("gibtorso") - -/obj/effect/decal/cleanable/blood/gibs/xeno/limb - random_icon_states = list("gibleg", "gibarm") - -/obj/effect/decal/cleanable/blood/gibs/xeno/core - random_icon_states = list("gibmid1", "gibmid2", "gibmid3") - -/obj/effect/decal/cleanable/blood/gibs/xeno/larva - random_icon_states = list("xgiblarva1", "xgiblarva2") - -/obj/effect/decal/cleanable/blood/gibs/xeno/larva/body - random_icon_states = list("xgiblarvahead", "xgiblarvatorso") - -/obj/effect/decal/cleanable/blood/xtracks - icon_state = "tracks" - random_icon_states = null - -/obj/effect/decal/cleanable/blood/xtracks/Initialize() - add_blood_DNA(list("UNKNOWN DNA" = "X*")) + +/obj/effect/decal/cleanable/blood/xeno + name = "xeno blood" + desc = "It's green and acidic. It looks like... blood?" + color = BLOOD_COLOR_XENO + +/obj/effect/decal/cleanable/blood/splatter/xeno + color = BLOOD_COLOR_XENO + +/obj/effect/decal/cleanable/blood/gibs/xeno + color = BLOOD_COLOR_XENO + gibs_reagent_id = /datum/reagent/liquidgibs/xeno + gibs_bloodtype = "X*" + +/obj/effect/decal/cleanable/blood/gibs/xeno/Initialize(mapload, list/datum/disease/diseases) + . = ..() + update_icon() + +/obj/effect/decal/cleanable/blood/gibs/xeno/update_icon() + add_atom_colour(blood_DNA_to_color(), FIXED_COLOUR_PRIORITY) + cut_overlays() + var/mutable_appearance/flesh = mutable_appearance(icon, "[icon_state]x_flesh") + flesh.appearance_flags = RESET_COLOR + flesh.color = body_colors + add_overlay(flesh) + +/obj/effect/decal/cleanable/blood/gibs/xeno/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) + var/obj/effect/decal/cleanable/blood/splatter/xeno/splat = new /obj/effect/decal/cleanable/blood/splatter/xeno(loc, diseases) + splat.transfer_blood_dna(blood_DNA, diseases) + if(!step_to(src, get_step(src, direction), 0)) + break + +/obj/effect/decal/cleanable/blood/gibs/xeno/up + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibup1","gibup1","gibup1") + +/obj/effect/decal/cleanable/blood/gibs/xeno/down + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6","gibdown1","gibdown1","gibdown1") + +/obj/effect/decal/cleanable/blood/gibs/xeno/body + random_icon_states = list("gibhead", "gibtorso") + +/obj/effect/decal/cleanable/blood/gibs/xeno/torso + random_icon_states = list("gibtorso") + +/obj/effect/decal/cleanable/blood/gibs/xeno/limb + random_icon_states = list("gibleg", "gibarm") + +/obj/effect/decal/cleanable/blood/gibs/xeno/core + random_icon_states = list("gibmid1", "gibmid2", "gibmid3") + +/obj/effect/decal/cleanable/blood/gibs/xeno/larva + random_icon_states = list("xgiblarva1", "xgiblarva2") + +/obj/effect/decal/cleanable/blood/gibs/xeno/larva/body + random_icon_states = list("xgiblarvahead", "xgiblarvatorso") + +/obj/effect/decal/cleanable/blood/xtracks + icon_state = "tracks" + random_icon_states = null + +/obj/effect/decal/cleanable/blood/xtracks/Initialize() + add_blood_DNA(list("UNKNOWN DNA" = "X*")) . = ..() \ No newline at end of file diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index 4eab5e826b..d42d7d6b88 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -1,192 +1,192 @@ -/obj/effect/decal/cleanable/blood - name = "blood" - desc = "It's 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_BLOOD - bloodiness = MAX_SHOE_BLOODINESS - color = BLOOD_COLOR_HUMAN //default so we don't have white splotches everywhere. - -/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C) - if (C.blood_DNA) - blood_DNA |= C.blood_DNA.Copy() - update_icon() - ..() - -/obj/effect/decal/cleanable/blood/transfer_blood_dna() - ..() - update_icon() - -/obj/effect/decal/cleanable/blood/transfer_mob_blood_dna() - . = ..() - update_icon() - -/obj/effect/decal/cleanable/blood/update_icon() - color = blood_DNA_to_color() - -/obj/effect/decal/cleanable/blood/old - name = "dried blood" - desc = "Looks like it's been here a while. Eew." - bloodiness = 0 - -/obj/effect/decal/cleanable/blood/old/Initialize(mapload, list/datum/disease/diseases) - ..() - icon_state += "-old" - add_blood_DNA(list("Non-human DNA" = "A+")) - -/obj/effect/decal/cleanable/blood/splats - random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5") - -/obj/effect/decal/cleanable/blood/splatter - random_icon_states = list("splatter1", "splatter2", "splatter3", "splatter4", "splatter5") - -/obj/effect/decal/cleanable/blood/tracks - icon_state = "tracks" - desc = "They look like tracks left by wheels." - random_icon_states = null - -/obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose - name = "blood" - icon_state = "ltrails_1" - desc = "Your instincts say you shouldn't be following these." - random_icon_states = null - var/list/existing_dirs = list() - -/obj/effect/decal/cleanable/trail_holder/update_icon() - color = blood_DNA_to_color() - -/obj/effect/cleanable/trail_holder/Initialize() - . = ..() - update_icon() - -/obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in() - return TRUE - -/obj/effect/decal/cleanable/trail_holder/transfer_blood_dna() - ..() - update_icon() - -/obj/effect/decal/cleanable/trail_holder/transfer_mob_blood_dna() - . = ..() - update_icon() - -//BLOODY FOOTPRINTS -/obj/effect/decal/cleanable/blood/footprints - name = "footprints" - icon = 'icons/effects/footprints.dmi' - icon_state = "nothingwhatsoever" - desc = "WHOSE FOOTPRINTS ARE THESE?" - random_icon_states = null - var/entered_dirs = 0 - var/exited_dirs = 0 - blood_state = BLOOD_STATE_BLOOD //the icon state to load images from - var/list/shoe_types = list() - -/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]) - if(color != bloodtype_to_color(S.last_bloodtype)) - return - 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]) - if(color != bloodtype_to_color(S.last_bloodtype))//last entry - we check its color - return - 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:" - for(var/shoe in shoe_types) - var/obj/item/clothing/shoes/S = shoe - . += "some [initial(S.name)] [icon2html(initial(S.icon), user)]" - -/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 - if(color != C.color) - return - ..() - -/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() - if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) - return TRUE - return FALSE - -/* Eventually TODO: make snowflake trails like baycode's -/obj/effect/decal/cleanable/blood/footprints/tracks/shoe - name = "footprints" - desc = "They look like tracks left by footwear." - icon_state = FOOTPRINT_SHOE - print_state = FOOTPRINT_SHOE - -/obj/effect/decal/cleanable/blood/footprints/tracks/foot - name = "footprints" - desc = "They look like tracks left by a bare foot." - icon_state = FOOTPRINT_FOOT - print_state = FOOTPRINT_FOOT - -/obj/effect/decal/cleanable/blood/footprints/tracks/snake - name = "tracks" - desc = "They look like tracks left by a giant snake." - icon_state = FOOTPRINT_SNAKE - print_state = FOOTPRINT_SNAKE - -/obj/effect/decal/cleanable/blood/footprints/tracks/paw - name = "footprints" - desc = "They look like tracks left by paws." - icon_state = FOOTPRINT_PAW - print_state = FOOTPRINT_PAW - -/obj/effect/decal/cleanable/blood/footprints/tracks/claw - name = "footprints" - desc = "They look like tracks left by claws." - icon_state = FOOTPRINT_CLAW - print_state = FOOTPRINT_CLAW - -/obj/effect/decal/cleanable/blood/footprints/tracks/wheels - name = "tracks" - desc = "They look like tracks left by wheels." - gender = PLURAL - icon_state = FOOTPRINT_WHEEL - print_state = FOOTPRINT_WHEEL - -/obj/effect/decal/cleanable/blood/footprints/tracks/body - name = "trails" - desc = "A trail left by something being dragged." - icon_state = FOOTPRINT_DRAG - print_state = FOOTPRINT_DRAG */ +/obj/effect/decal/cleanable/blood + name = "blood" + desc = "It's 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_BLOOD + bloodiness = MAX_SHOE_BLOODINESS + color = BLOOD_COLOR_HUMAN //default so we don't have white splotches everywhere. + +/obj/effect/decal/cleanable/blood/replace_decal(obj/effect/decal/cleanable/blood/C) + if (C.blood_DNA) + blood_DNA |= C.blood_DNA.Copy() + update_icon() + ..() + +/obj/effect/decal/cleanable/blood/transfer_blood_dna() + ..() + update_icon() + +/obj/effect/decal/cleanable/blood/transfer_mob_blood_dna() + . = ..() + update_icon() + +/obj/effect/decal/cleanable/blood/update_icon() + color = blood_DNA_to_color() + +/obj/effect/decal/cleanable/blood/old + name = "dried blood" + desc = "Looks like it's been here a while. Eew." + bloodiness = 0 + +/obj/effect/decal/cleanable/blood/old/Initialize(mapload, list/datum/disease/diseases) + ..() + icon_state += "-old" + add_blood_DNA(list("Non-human DNA" = "A+")) + +/obj/effect/decal/cleanable/blood/splats + random_icon_states = list("gibbl1", "gibbl2", "gibbl3", "gibbl4", "gibbl5") + +/obj/effect/decal/cleanable/blood/splatter + random_icon_states = list("splatter1", "splatter2", "splatter3", "splatter4", "splatter5") + +/obj/effect/decal/cleanable/blood/tracks + icon_state = "tracks" + desc = "They look like tracks left by wheels." + random_icon_states = null + +/obj/effect/decal/cleanable/trail_holder //not a child of blood on purpose + name = "blood" + icon_state = "ltrails_1" + desc = "Your instincts say you shouldn't be following these." + random_icon_states = null + var/list/existing_dirs = list() + +/obj/effect/decal/cleanable/trail_holder/update_icon() + color = blood_DNA_to_color() + +/obj/effect/cleanable/trail_holder/Initialize() + . = ..() + update_icon() + +/obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in() + return TRUE + +/obj/effect/decal/cleanable/trail_holder/transfer_blood_dna() + ..() + update_icon() + +/obj/effect/decal/cleanable/trail_holder/transfer_mob_blood_dna() + . = ..() + update_icon() + +//BLOODY FOOTPRINTS +/obj/effect/decal/cleanable/blood/footprints + name = "footprints" + icon = 'icons/effects/footprints.dmi' + icon_state = "nothingwhatsoever" + desc = "WHOSE FOOTPRINTS ARE THESE?" + random_icon_states = null + var/entered_dirs = 0 + var/exited_dirs = 0 + blood_state = BLOOD_STATE_BLOOD //the icon state to load images from + var/list/shoe_types = list() + +/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]) + if(color != bloodtype_to_color(S.last_bloodtype)) + return + 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]) + if(color != bloodtype_to_color(S.last_bloodtype))//last entry - we check its color + return + 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:" + for(var/shoe in shoe_types) + var/obj/item/clothing/shoes/S = shoe + . += "some [initial(S.name)] [icon2html(initial(S.icon), user)]" + +/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 + if(color != C.color) + return + ..() + +/obj/effect/decal/cleanable/blood/footprints/can_bloodcrawl_in() + if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) + return TRUE + return FALSE + +/* Eventually TODO: make snowflake trails like baycode's +/obj/effect/decal/cleanable/blood/footprints/tracks/shoe + name = "footprints" + desc = "They look like tracks left by footwear." + icon_state = FOOTPRINT_SHOE + print_state = FOOTPRINT_SHOE + +/obj/effect/decal/cleanable/blood/footprints/tracks/foot + name = "footprints" + desc = "They look like tracks left by a bare foot." + icon_state = FOOTPRINT_FOOT + print_state = FOOTPRINT_FOOT + +/obj/effect/decal/cleanable/blood/footprints/tracks/snake + name = "tracks" + desc = "They look like tracks left by a giant snake." + icon_state = FOOTPRINT_SNAKE + print_state = FOOTPRINT_SNAKE + +/obj/effect/decal/cleanable/blood/footprints/tracks/paw + name = "footprints" + desc = "They look like tracks left by paws." + icon_state = FOOTPRINT_PAW + print_state = FOOTPRINT_PAW + +/obj/effect/decal/cleanable/blood/footprints/tracks/claw + name = "footprints" + desc = "They look like tracks left by claws." + icon_state = FOOTPRINT_CLAW + print_state = FOOTPRINT_CLAW + +/obj/effect/decal/cleanable/blood/footprints/tracks/wheels + name = "tracks" + desc = "They look like tracks left by wheels." + gender = PLURAL + icon_state = FOOTPRINT_WHEEL + print_state = FOOTPRINT_WHEEL + +/obj/effect/decal/cleanable/blood/footprints/tracks/body + name = "trails" + desc = "A trail left by something being dragged." + icon_state = FOOTPRINT_DRAG + print_state = FOOTPRINT_DRAG */ diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index 6466ab594f..910c4f4609 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -1,235 +1,235 @@ -/obj/effect/decal/cleanable/generic - name = "clutter" - desc = "Someone should clean that up." - icon = 'icons/obj/objects.dmi' - icon_state = "shards" - -/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 - -/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" - -/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" - -/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 - -/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/flour - name = "flour" - desc = "It's still good. Four second rule!" - icon_state = "flour" - -/obj/effect/decal/cleanable/greenglow/ecto - name = "ectoplasmic puddle" - desc = "You know who to call." - light_power = 2 - -/obj/effect/decal/cleanable/greenglow - name = "glowing goo" - desc = "Jeez. I hope that's not for lunch." - light_color = LIGHT_COLOR_GREEN - icon_state = "greenglow" - -/obj/effect/decal/cleanable/greenglow/Initialize(mapload) - . = ..() - set_light(2, 0.8, "#22FFAA") - -/obj/effect/decal/cleanable/greenglow/ex_act() - return - -/obj/effect/decal/cleanable/cobweb - name = "cobweb" - desc = "Somebody should remove that." - gender = NEUTER - layer = WALL_OBJ_LAYER - icon_state = "cobweb1" - resistance_flags = FLAMMABLE - -/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 - -/obj/effect/decal/cleanable/molten_object/large - name = "big gooey grey mass" - icon_state = "big_molten" - -//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") - -/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, 1) //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/consumable/R in reagents.reagent_list) - if(R.nutriment_factor > 0) - H.nutrition += R.nutriment_factor * R.volume - reagents.del_reagent(R.type) - reagents.trans_to(H, reagents.total_volume) - 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/tomato_smudge - name = "tomato smudge" - desc = "It's red." - gender = NEUTER - icon = 'icons/effects/tomatodecal.dmi' - random_icon_states = list("tomato_floor1", "tomato_floor2", "tomato_floor3") - -/obj/effect/decal/cleanable/plant_smudge - name = "plant smudge" - gender = NEUTER - icon = 'icons/effects/tomatodecal.dmi' - random_icon_states = list("smashed_plant") - -/obj/effect/decal/cleanable/egg_smudge - name = "smashed egg" - desc = "Seems like this one won't hatch." - gender = NEUTER - icon = 'icons/effects/tomatodecal.dmi' - random_icon_states = list("smashed_egg1", "smashed_egg2", "smashed_egg3") - -/obj/effect/decal/cleanable/pie_smudge //honk - name = "smashed pie" - desc = "It's pie cream from a cream pie." - gender = NEUTER - icon = 'icons/effects/tomatodecal.dmi' - random_icon_states = list("smashed_pie") - -/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() - pixel_x = rand(-10, 10) - pixel_y = rand(-10, 10) - . = ..() - -/obj/effect/decal/cleanable/salt - name = "salt pile" - desc = "A sizable pile of table salt. Someone must be upset." - icon = 'icons/effects/tomatodecal.dmi' - icon_state = "salt_pile" - gender = NEUTER - -/obj/effect/decal/cleanable/glitter - name = "generic glitter pile" - desc = "The herpes of arts and crafts." - icon = 'icons/effects/atmospherics.dmi' - gender = NEUTER - -/obj/effect/decal/cleanable/glitter/pink - name = "pink glitter" - icon_state = "plasma_old" - -/obj/effect/decal/cleanable/glitter/white - name = "white glitter" - icon_state = "nitrous_oxide_old" - -/obj/effect/decal/cleanable/glitter/blue - name = "blue glitter" - icon_state = "freon_old" - -/obj/effect/decal/cleanable/plasma - name = "stabilized plasma" - desc = "A puddle of stabilized plasma." - icon_state = "flour" - color = "#9e0089" - -/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/generic + name = "clutter" + desc = "Someone should clean that up." + icon = 'icons/obj/objects.dmi' + icon_state = "shards" + +/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 + +/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" + +/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" + +/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 + +/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/flour + name = "flour" + desc = "It's still good. Four second rule!" + icon_state = "flour" + +/obj/effect/decal/cleanable/greenglow/ecto + name = "ectoplasmic puddle" + desc = "You know who to call." + light_power = 2 + +/obj/effect/decal/cleanable/greenglow + name = "glowing goo" + desc = "Jeez. I hope that's not for lunch." + light_color = LIGHT_COLOR_GREEN + icon_state = "greenglow" + +/obj/effect/decal/cleanable/greenglow/Initialize(mapload) + . = ..() + set_light(2, 0.8, "#22FFAA") + +/obj/effect/decal/cleanable/greenglow/ex_act() + return + +/obj/effect/decal/cleanable/cobweb + name = "cobweb" + desc = "Somebody should remove that." + gender = NEUTER + layer = WALL_OBJ_LAYER + icon_state = "cobweb1" + resistance_flags = FLAMMABLE + +/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 + +/obj/effect/decal/cleanable/molten_object/large + name = "big gooey grey mass" + icon_state = "big_molten" + +//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") + +/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, 1) //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/consumable/R in reagents.reagent_list) + if(R.nutriment_factor > 0) + H.nutrition += R.nutriment_factor * R.volume + reagents.del_reagent(R.type) + reagents.trans_to(H, reagents.total_volume) + 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/tomato_smudge + name = "tomato smudge" + desc = "It's red." + gender = NEUTER + icon = 'icons/effects/tomatodecal.dmi' + random_icon_states = list("tomato_floor1", "tomato_floor2", "tomato_floor3") + +/obj/effect/decal/cleanable/plant_smudge + name = "plant smudge" + gender = NEUTER + icon = 'icons/effects/tomatodecal.dmi' + random_icon_states = list("smashed_plant") + +/obj/effect/decal/cleanable/egg_smudge + name = "smashed egg" + desc = "Seems like this one won't hatch." + gender = NEUTER + icon = 'icons/effects/tomatodecal.dmi' + random_icon_states = list("smashed_egg1", "smashed_egg2", "smashed_egg3") + +/obj/effect/decal/cleanable/pie_smudge //honk + name = "smashed pie" + desc = "It's pie cream from a cream pie." + gender = NEUTER + icon = 'icons/effects/tomatodecal.dmi' + random_icon_states = list("smashed_pie") + +/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() + pixel_x = rand(-10, 10) + pixel_y = rand(-10, 10) + . = ..() + +/obj/effect/decal/cleanable/salt + name = "salt pile" + desc = "A sizable pile of table salt. Someone must be upset." + icon = 'icons/effects/tomatodecal.dmi' + icon_state = "salt_pile" + gender = NEUTER + +/obj/effect/decal/cleanable/glitter + name = "generic glitter pile" + desc = "The herpes of arts and crafts." + icon = 'icons/effects/atmospherics.dmi' + gender = NEUTER + +/obj/effect/decal/cleanable/glitter/pink + name = "pink glitter" + icon_state = "plasma_old" + +/obj/effect/decal/cleanable/glitter/white + name = "white glitter" + icon_state = "nitrous_oxide_old" + +/obj/effect/decal/cleanable/glitter/blue + name = "blue glitter" + icon_state = "freon_old" + +/obj/effect/decal/cleanable/plasma + name = "stabilized plasma" + desc = "A puddle of stabilized plasma." + icon_state = "flour" + color = "#9e0089" + +/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") diff --git a/code/game/objects/effects/decals/cleanable/robots.dm b/code/game/objects/effects/decals/cleanable/robots.dm index 3e94abbf93..cf030df120 100644 --- a/code/game/objects/effects/decals/cleanable/robots.dm +++ b/code/game/objects/effects/decals/cleanable/robots.dm @@ -1,65 +1,65 @@ -// 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 - -/obj/effect/decal/cleanable/robot_debris/Initialize(mapload, list/datum/disease/diseases) - . = ..() - reagents.add_reagent(/datum/reagent/liquidgibs, 5) - -/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 - random_icon_states = list("gibarm", "gibleg") - -/obj/effect/decal/cleanable/robot_debris/up - random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7","gibup1","gibup1") - -/obj/effect/decal/cleanable/robot_debris/down - 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 - -/obj/effect/decal/cleanable/oil/Initialize() - . = ..() - reagents.add_reagent(/datum/reagent/oil, 30) - reagents.add_reagent(/datum/reagent/liquidgibs/oil, 5) - -/obj/effect/decal/cleanable/oil/streak - random_icon_states = list("streak1", "streak2", "streak3", "streak4", "streak5") - -/obj/effect/decal/cleanable/oil/slippery - -/obj/effect/decal/cleanable/oil/slippery/Initialize() - 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 + +/obj/effect/decal/cleanable/robot_debris/Initialize(mapload, list/datum/disease/diseases) + . = ..() + reagents.add_reagent(/datum/reagent/liquidgibs, 5) + +/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 + random_icon_states = list("gibarm", "gibleg") + +/obj/effect/decal/cleanable/robot_debris/up + random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7","gibup1","gibup1") + +/obj/effect/decal/cleanable/robot_debris/down + 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 + +/obj/effect/decal/cleanable/oil/Initialize() + . = ..() + reagents.add_reagent(/datum/reagent/oil, 30) + reagents.add_reagent(/datum/reagent/liquidgibs/oil, 5) + +/obj/effect/decal/cleanable/oil/streak + random_icon_states = list("streak1", "streak2", "streak3", "streak4", "streak5") + +/obj/effect/decal/cleanable/oil/slippery + +/obj/effect/decal/cleanable/oil/slippery/Initialize() + 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 0a0348ae39..955b9935dd 100644 --- a/code/game/objects/effects/decals/crayon.dm +++ b/code/game/objects/effects/decals/crayon.dm @@ -1,31 +1,31 @@ -/obj/effect/decal/cleanable/crayon - name = "rune" - desc = "Graffiti. Damn kids." - icon = 'icons/effects/crayondecal.dmi' - icon_state = "rune1" - plane = GAME_PLANE //makes the graffiti visible over a wall. - gender = NEUTER - 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 + name = "rune" + desc = "Graffiti. Damn kids." + icon = 'icons/effects/crayondecal.dmi' + icon_state = "rune1" + plane = GAME_PLANE //makes the graffiti visible over a wall. + gender = NEUTER + 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) diff --git a/code/game/objects/effects/decals/decal.dm b/code/game/objects/effects/decals/decal.dm index 2fa7277d8b..3e7706282a 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_GOD, 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_GOD, color, null, null, alpha) diff --git a/code/game/objects/effects/decals/misc.dm b/code/game/objects/effects/decals/misc.dm index 05ff9cb863..8f46d5cc46 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 75fe78959d..87337a526c 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, 1) - 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, 1) + 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/effect_system/effects_foam.dm b/code/game/objects/effects/effect_system/effects_foam.dm index da9c210234..aabf9acafa 100644 --- a/code/game/objects/effects/effect_system/effects_foam.dm +++ b/code/game/objects/effects/effect_system/effects_foam.dm @@ -1,347 +1,347 @@ -// Foam -// Similar to smoke, but slower and mobs absorb its reagent through their exposed skin. -#define ALUMINUM_FOAM 1 -#define IRON_FOAM 2 -#define RESIN_FOAM 3 - - -/obj/effect/particle_effect/foam - name = "foam" - icon_state = "foam" - opacity = 0 - anchored = TRUE - density = FALSE - layer = EDGED_TURF_LAYER - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/amount = 3 - animate_movement = 0 - var/metal = 0 - var/lifetime = 40 - var/reagent_divisor = 7 - var/static/list/blacklisted_turfs = typecacheof(list( - /turf/open/space/transit, - /turf/open/chasm, - /turf/open/lava)) - -/obj/effect/particle_effect/foam/firefighting - name = "firefighting foam" - lifetime = 20 //doesn't last as long as normal foam - amount = 0 //no spread - var/absorbed_plasma = 0 - -/obj/effect/particle_effect/foam/firefighting/MakeSlippery() - return - -/obj/effect/particle_effect/foam/firefighting/process() - ..() - - var/turf/open/T = get_turf(src) - var/obj/effect/hotspot/hotspot = (locate(/obj/effect/hotspot) in T) - if(hotspot && istype(T) && T.air) - qdel(hotspot) - var/datum/gas_mixture/G = T.air - var/plas_amt = min(30,G.gases[/datum/gas/plasma]) //Absorb some plasma - G.gases[/datum/gas/plasma] -= plas_amt - absorbed_plasma += plas_amt - if(G.temperature > T20C) - G.temperature = max(G.temperature/2,T20C) - GAS_GARBAGE_COLLECT(G.gases) - T.air_update_turf() - -/obj/effect/particle_effect/foam/firefighting/kill_foam() - STOP_PROCESSING(SSfastprocess, src) - - if(absorbed_plasma) - var/obj/effect/decal/cleanable/plasma/P = (locate(/obj/effect/decal/cleanable/plasma) in get_turf(src)) - if(!P) - P = new(loc) - P.reagents.add_reagent(/datum/reagent/stable_plasma, absorbed_plasma) - - flick("[icon_state]-disolve", src) - QDEL_IN(src, 5) - -/obj/effect/particle_effect/foam/firefighting/foam_mob(mob/living/L) - if(!istype(L)) - return - L.adjust_fire_stacks(-2) - L.ExtinguishMob() - -/obj/effect/particle_effect/foam/firefighting/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - return - -/obj/effect/particle_effect/foam/metal - name = "aluminium foam" - metal = ALUMINUM_FOAM - icon_state = "mfoam" - -/obj/effect/particle_effect/foam/metal/MakeSlippery() - return - -/obj/effect/particle_effect/foam/metal/smart - name = "smart foam" - -/obj/effect/particle_effect/foam/metal/iron - name = "iron foam" - metal = IRON_FOAM - -/obj/effect/particle_effect/foam/metal/resin - name = "resin foam" - metal = RESIN_FOAM - -/obj/effect/particle_effect/foam/long_life - lifetime = 150 - -/obj/effect/particle_effect/foam/Initialize() - . = ..() - MakeSlippery() - create_reagents(1000) //limited by the size of the reagent holder anyway. - START_PROCESSING(SSfastprocess, src) - playsound(src, 'sound/effects/bubbles2.ogg', 80, 1, -3) - -/obj/effect/particle_effect/foam/proc/MakeSlippery() - AddComponent(/datum/component/slippery, 100) - -/obj/effect/particle_effect/foam/Destroy() - STOP_PROCESSING(SSfastprocess, src) - return ..() - - -/obj/effect/particle_effect/foam/proc/kill_foam() - STOP_PROCESSING(SSfastprocess, src) - switch(metal) - if(ALUMINUM_FOAM) - new /obj/structure/foamedmetal(get_turf(src)) - if(IRON_FOAM) - new /obj/structure/foamedmetal/iron(get_turf(src)) - if(RESIN_FOAM) - new /obj/structure/foamedmetal/resin(get_turf(src)) - flick("[icon_state]-disolve", src) - QDEL_IN(src, 5) - -/obj/effect/particle_effect/foam/smart/kill_foam() //Smart foam adheres to area borders for walls - STOP_PROCESSING(SSfastprocess, src) - if(metal) - var/turf/T = get_turf(src) - if(isspaceturf(T)) //Block up any exposed space - T.PlaceOnTop(/turf/open/floor/plating/foam, flags = CHANGETURF_INHERIT_AIR) - for(var/direction in GLOB.cardinals) - var/turf/cardinal_turf = get_step(T, direction) - if(get_area(cardinal_turf) != get_area(T)) //We're at an area boundary, so let's block off this turf! - new/obj/structure/foamedmetal(T) - break - flick("[icon_state]-disolve", src) - QDEL_IN(src, 5) - -/obj/effect/particle_effect/foam/process() - lifetime-- - if(lifetime < 1) - kill_foam() - return - - var/fraction = 1/initial(reagent_divisor) - for(var/obj/O in range(0,src)) - if(O.type == src.type) - continue - if(isturf(O.loc)) - var/turf/T = O.loc - if(T.intact && O.level == 1) //hidden under the floor - continue - if(lifetime % reagent_divisor) - reagents.reaction(O, VAPOR, fraction) - var/hit = 0 - for(var/mob/living/L in range(0,src)) - hit += foam_mob(L) - if(hit) - lifetime++ //this is so the decrease from mobs hit and the natural decrease don't cumulate. - var/T = get_turf(src) - if(lifetime % reagent_divisor) - reagents.reaction(T, VAPOR, fraction) - - if(--amount < 0) - return - spread_foam() - -/obj/effect/particle_effect/foam/proc/foam_mob(mob/living/L) - if(lifetime<1) - return 0 - if(!istype(L)) - return 0 - var/fraction = 1/initial(reagent_divisor) - if(lifetime % reagent_divisor) - reagents.reaction(L, VAPOR, fraction) - lifetime-- - return 1 - -/obj/effect/particle_effect/foam/proc/spread_foam() - var/turf/t_loc = get_turf(src) - for(var/turf/T in t_loc.GetAtmosAdjacentTurfs()) - var/obj/effect/particle_effect/foam/foundfoam = locate() in T //Don't spread foam where there's already foam! - if(foundfoam) - continue - - if(is_type_in_typecache(T, blacklisted_turfs)) - continue - - for(var/mob/living/L in T) - foam_mob(L) - var/obj/effect/particle_effect/foam/F = new src.type(T) - F.amount = amount - reagents.copy_to(F, (reagents.total_volume)) - F.add_atom_colour(color, FIXED_COLOUR_PRIORITY) - F.metal = metal - - -/obj/effect/particle_effect/foam/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(prob(max(0, exposed_temperature - 475))) //foam dissolves when heated - kill_foam() - - -/obj/effect/particle_effect/foam/metal/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - return - - -/////////////////////////////////////////////// -//FOAM EFFECT DATUM -/datum/effect_system/foam_spread - var/amount = 10 // the size of the foam spread. - var/obj/chemholder - effect_type = /obj/effect/particle_effect/foam - var/metal = 0 - - -/datum/effect_system/foam_spread/metal - effect_type = /obj/effect/particle_effect/foam/metal - - -/datum/effect_system/foam_spread/metal/smart - effect_type = /obj/effect/particle_effect/foam/smart - - -/datum/effect_system/foam_spread/long - effect_type = /obj/effect/particle_effect/foam/long_life - -/datum/effect_system/foam_spread/New() - ..() - chemholder = new /obj() - var/datum/reagents/R = new/datum/reagents(1000) - chemholder.reagents = R - R.my_atom = chemholder - -/datum/effect_system/foam_spread/Destroy() - qdel(chemholder) - chemholder = null - return ..() - -/datum/effect_system/foam_spread/set_up(amt=5, loca, datum/reagents/carry = null) - if(isturf(loca)) - location = loca - else - location = get_turf(loca) - - amount = round(sqrt(amt / 2), 1) - carry.copy_to(chemholder, carry.total_volume) - -/datum/effect_system/foam_spread/metal/set_up(amt=5, loca, datum/reagents/carry = null, metaltype) - ..() - metal = metaltype - -/datum/effect_system/foam_spread/start() - var/obj/effect/particle_effect/foam/F = new effect_type(location) - var/foamcolor = mix_color_from_reagents(chemholder.reagents.reagent_list) - chemholder.reagents.copy_to(F, chemholder.reagents.total_volume/amount) - F.add_atom_colour(foamcolor, FIXED_COLOUR_PRIORITY) - F.amount = amount - F.metal = metal - - -////////////////////////////////////////////////////////// -// FOAM STRUCTURE. Formed by metal foams. Dense and opaque, but easy to break -/obj/structure/foamedmetal - icon = 'icons/effects/effects.dmi' - icon_state = "metalfoam" - density = TRUE - opacity = 1 // changed in New() - anchored = TRUE - layer = EDGED_TURF_LAYER - resistance_flags = FIRE_PROOF | ACID_PROOF - name = "foamed metal" - desc = "A lightweight foamed metal wall." - gender = PLURAL - max_integrity = 20 - CanAtmosPass = ATMOS_PASS_DENSITY - -/obj/structure/foamedmetal/Initialize() - . = ..() - air_update_turf(1) - -/obj/structure/foamedmetal/Move() - var/turf/T = loc - . = ..() - move_update_air(T) - -/obj/structure/foamedmetal/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/foamedmetal/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - playsound(src.loc, 'sound/weapons/tap.ogg', 100, 1) - -/obj/structure/foamedmetal/attack_hand(mob/user) - . = ..() - if(.) - return - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) - to_chat(user, "You hit [src] but bounce off it!") - playsound(src.loc, 'sound/weapons/tap.ogg', 100, 1) - -/obj/structure/foamedmetal/CanPass(atom/movable/mover, turf/target) - return !density - -/obj/structure/foamedmetal/iron - max_integrity = 50 - icon_state = "ironfoam" - -//Atmos Backpack Resin, transparent, prevents atmos and filters the air -/obj/structure/foamedmetal/resin - name = "\improper ATMOS Resin" - desc = "A lightweight, transparent resin used to suffocate fires, scrub the air of toxins, and restore the air to a safe temperature." - opacity = FALSE - icon_state = "atmos_resin" - alpha = 120 - max_integrity = 10 - -/obj/structure/foamedmetal/resin/Initialize() - . = ..() - if(isopenturf(loc)) - var/turf/open/O = loc - O.ClearWet() - if(O.air) - var/datum/gas_mixture/G = O.air - G.temperature = 293.15 - for(var/obj/effect/hotspot/H in O) - qdel(H) - var/list/G_gases = G.gases - for(var/I in G_gases) - if(I == /datum/gas/oxygen || I == /datum/gas/nitrogen) - continue - G_gases[I] = 0 - GAS_GARBAGE_COLLECT(G.gases) - O.air_update_turf() - for(var/obj/machinery/atmospherics/components/unary/U in O) - if(!U.welded) - U.welded = TRUE - U.update_icon() - U.visible_message("[U] sealed shut!") - for(var/mob/living/L in O) - L.ExtinguishMob() - for(var/obj/item/Item in O) - Item.extinguish() - -/obj/structure/foamedmetal/resin/CanPass(atom/movable/mover, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return TRUE - . = ..() - -#undef ALUMINUM_FOAM -#undef IRON_FOAM -#undef RESIN_FOAM +// Foam +// Similar to smoke, but slower and mobs absorb its reagent through their exposed skin. +#define ALUMINUM_FOAM 1 +#define IRON_FOAM 2 +#define RESIN_FOAM 3 + + +/obj/effect/particle_effect/foam + name = "foam" + icon_state = "foam" + opacity = 0 + anchored = TRUE + density = FALSE + layer = EDGED_TURF_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/amount = 3 + animate_movement = 0 + var/metal = 0 + var/lifetime = 40 + var/reagent_divisor = 7 + var/static/list/blacklisted_turfs = typecacheof(list( + /turf/open/space/transit, + /turf/open/chasm, + /turf/open/lava)) + +/obj/effect/particle_effect/foam/firefighting + name = "firefighting foam" + lifetime = 20 //doesn't last as long as normal foam + amount = 0 //no spread + var/absorbed_plasma = 0 + +/obj/effect/particle_effect/foam/firefighting/MakeSlippery() + return + +/obj/effect/particle_effect/foam/firefighting/process() + ..() + + var/turf/open/T = get_turf(src) + var/obj/effect/hotspot/hotspot = (locate(/obj/effect/hotspot) in T) + if(hotspot && istype(T) && T.air) + qdel(hotspot) + var/datum/gas_mixture/G = T.air + var/plas_amt = min(30,G.gases[/datum/gas/plasma]) //Absorb some plasma + G.gases[/datum/gas/plasma] -= plas_amt + absorbed_plasma += plas_amt + if(G.temperature > T20C) + G.temperature = max(G.temperature/2,T20C) + GAS_GARBAGE_COLLECT(G.gases) + T.air_update_turf() + +/obj/effect/particle_effect/foam/firefighting/kill_foam() + STOP_PROCESSING(SSfastprocess, src) + + if(absorbed_plasma) + var/obj/effect/decal/cleanable/plasma/P = (locate(/obj/effect/decal/cleanable/plasma) in get_turf(src)) + if(!P) + P = new(loc) + P.reagents.add_reagent(/datum/reagent/stable_plasma, absorbed_plasma) + + flick("[icon_state]-disolve", src) + QDEL_IN(src, 5) + +/obj/effect/particle_effect/foam/firefighting/foam_mob(mob/living/L) + if(!istype(L)) + return + L.adjust_fire_stacks(-2) + L.ExtinguishMob() + +/obj/effect/particle_effect/foam/firefighting/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + return + +/obj/effect/particle_effect/foam/metal + name = "aluminium foam" + metal = ALUMINUM_FOAM + icon_state = "mfoam" + +/obj/effect/particle_effect/foam/metal/MakeSlippery() + return + +/obj/effect/particle_effect/foam/metal/smart + name = "smart foam" + +/obj/effect/particle_effect/foam/metal/iron + name = "iron foam" + metal = IRON_FOAM + +/obj/effect/particle_effect/foam/metal/resin + name = "resin foam" + metal = RESIN_FOAM + +/obj/effect/particle_effect/foam/long_life + lifetime = 150 + +/obj/effect/particle_effect/foam/Initialize() + . = ..() + MakeSlippery() + create_reagents(1000) //limited by the size of the reagent holder anyway. + START_PROCESSING(SSfastprocess, src) + playsound(src, 'sound/effects/bubbles2.ogg', 80, 1, -3) + +/obj/effect/particle_effect/foam/proc/MakeSlippery() + AddComponent(/datum/component/slippery, 100) + +/obj/effect/particle_effect/foam/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + + +/obj/effect/particle_effect/foam/proc/kill_foam() + STOP_PROCESSING(SSfastprocess, src) + switch(metal) + if(ALUMINUM_FOAM) + new /obj/structure/foamedmetal(get_turf(src)) + if(IRON_FOAM) + new /obj/structure/foamedmetal/iron(get_turf(src)) + if(RESIN_FOAM) + new /obj/structure/foamedmetal/resin(get_turf(src)) + flick("[icon_state]-disolve", src) + QDEL_IN(src, 5) + +/obj/effect/particle_effect/foam/smart/kill_foam() //Smart foam adheres to area borders for walls + STOP_PROCESSING(SSfastprocess, src) + if(metal) + var/turf/T = get_turf(src) + if(isspaceturf(T)) //Block up any exposed space + T.PlaceOnTop(/turf/open/floor/plating/foam, flags = CHANGETURF_INHERIT_AIR) + for(var/direction in GLOB.cardinals) + var/turf/cardinal_turf = get_step(T, direction) + if(get_area(cardinal_turf) != get_area(T)) //We're at an area boundary, so let's block off this turf! + new/obj/structure/foamedmetal(T) + break + flick("[icon_state]-disolve", src) + QDEL_IN(src, 5) + +/obj/effect/particle_effect/foam/process() + lifetime-- + if(lifetime < 1) + kill_foam() + return + + var/fraction = 1/initial(reagent_divisor) + for(var/obj/O in range(0,src)) + if(O.type == src.type) + continue + if(isturf(O.loc)) + var/turf/T = O.loc + if(T.intact && O.level == 1) //hidden under the floor + continue + if(lifetime % reagent_divisor) + reagents.reaction(O, VAPOR, fraction) + var/hit = 0 + for(var/mob/living/L in range(0,src)) + hit += foam_mob(L) + if(hit) + lifetime++ //this is so the decrease from mobs hit and the natural decrease don't cumulate. + var/T = get_turf(src) + if(lifetime % reagent_divisor) + reagents.reaction(T, VAPOR, fraction) + + if(--amount < 0) + return + spread_foam() + +/obj/effect/particle_effect/foam/proc/foam_mob(mob/living/L) + if(lifetime<1) + return 0 + if(!istype(L)) + return 0 + var/fraction = 1/initial(reagent_divisor) + if(lifetime % reagent_divisor) + reagents.reaction(L, VAPOR, fraction) + lifetime-- + return 1 + +/obj/effect/particle_effect/foam/proc/spread_foam() + var/turf/t_loc = get_turf(src) + for(var/turf/T in t_loc.GetAtmosAdjacentTurfs()) + var/obj/effect/particle_effect/foam/foundfoam = locate() in T //Don't spread foam where there's already foam! + if(foundfoam) + continue + + if(is_type_in_typecache(T, blacklisted_turfs)) + continue + + for(var/mob/living/L in T) + foam_mob(L) + var/obj/effect/particle_effect/foam/F = new src.type(T) + F.amount = amount + reagents.copy_to(F, (reagents.total_volume)) + F.add_atom_colour(color, FIXED_COLOUR_PRIORITY) + F.metal = metal + + +/obj/effect/particle_effect/foam/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(prob(max(0, exposed_temperature - 475))) //foam dissolves when heated + kill_foam() + + +/obj/effect/particle_effect/foam/metal/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + return + + +/////////////////////////////////////////////// +//FOAM EFFECT DATUM +/datum/effect_system/foam_spread + var/amount = 10 // the size of the foam spread. + var/obj/chemholder + effect_type = /obj/effect/particle_effect/foam + var/metal = 0 + + +/datum/effect_system/foam_spread/metal + effect_type = /obj/effect/particle_effect/foam/metal + + +/datum/effect_system/foam_spread/metal/smart + effect_type = /obj/effect/particle_effect/foam/smart + + +/datum/effect_system/foam_spread/long + effect_type = /obj/effect/particle_effect/foam/long_life + +/datum/effect_system/foam_spread/New() + ..() + chemholder = new /obj() + var/datum/reagents/R = new/datum/reagents(1000) + chemholder.reagents = R + R.my_atom = chemholder + +/datum/effect_system/foam_spread/Destroy() + qdel(chemholder) + chemholder = null + return ..() + +/datum/effect_system/foam_spread/set_up(amt=5, loca, datum/reagents/carry = null) + if(isturf(loca)) + location = loca + else + location = get_turf(loca) + + amount = round(sqrt(amt / 2), 1) + carry.copy_to(chemholder, carry.total_volume) + +/datum/effect_system/foam_spread/metal/set_up(amt=5, loca, datum/reagents/carry = null, metaltype) + ..() + metal = metaltype + +/datum/effect_system/foam_spread/start() + var/obj/effect/particle_effect/foam/F = new effect_type(location) + var/foamcolor = mix_color_from_reagents(chemholder.reagents.reagent_list) + chemholder.reagents.copy_to(F, chemholder.reagents.total_volume/amount) + F.add_atom_colour(foamcolor, FIXED_COLOUR_PRIORITY) + F.amount = amount + F.metal = metal + + +////////////////////////////////////////////////////////// +// FOAM STRUCTURE. Formed by metal foams. Dense and opaque, but easy to break +/obj/structure/foamedmetal + icon = 'icons/effects/effects.dmi' + icon_state = "metalfoam" + density = TRUE + opacity = 1 // changed in New() + anchored = TRUE + layer = EDGED_TURF_LAYER + resistance_flags = FIRE_PROOF | ACID_PROOF + name = "foamed metal" + desc = "A lightweight foamed metal wall." + gender = PLURAL + max_integrity = 20 + CanAtmosPass = ATMOS_PASS_DENSITY + +/obj/structure/foamedmetal/Initialize() + . = ..() + air_update_turf(1) + +/obj/structure/foamedmetal/Move() + var/turf/T = loc + . = ..() + move_update_air(T) + +/obj/structure/foamedmetal/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/foamedmetal/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + playsound(src.loc, 'sound/weapons/tap.ogg', 100, 1) + +/obj/structure/foamedmetal/attack_hand(mob/user) + . = ..() + if(.) + return + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) + to_chat(user, "You hit [src] but bounce off it!") + playsound(src.loc, 'sound/weapons/tap.ogg', 100, 1) + +/obj/structure/foamedmetal/CanPass(atom/movable/mover, turf/target) + return !density + +/obj/structure/foamedmetal/iron + max_integrity = 50 + icon_state = "ironfoam" + +//Atmos Backpack Resin, transparent, prevents atmos and filters the air +/obj/structure/foamedmetal/resin + name = "\improper ATMOS Resin" + desc = "A lightweight, transparent resin used to suffocate fires, scrub the air of toxins, and restore the air to a safe temperature." + opacity = FALSE + icon_state = "atmos_resin" + alpha = 120 + max_integrity = 10 + +/obj/structure/foamedmetal/resin/Initialize() + . = ..() + if(isopenturf(loc)) + var/turf/open/O = loc + O.ClearWet() + if(O.air) + var/datum/gas_mixture/G = O.air + G.temperature = 293.15 + for(var/obj/effect/hotspot/H in O) + qdel(H) + var/list/G_gases = G.gases + for(var/I in G_gases) + if(I == /datum/gas/oxygen || I == /datum/gas/nitrogen) + continue + G_gases[I] = 0 + GAS_GARBAGE_COLLECT(G.gases) + O.air_update_turf() + for(var/obj/machinery/atmospherics/components/unary/U in O) + if(!U.welded) + U.welded = TRUE + U.update_icon() + U.visible_message("[U] sealed shut!") + for(var/mob/living/L in O) + L.ExtinguishMob() + for(var/obj/item/Item in O) + Item.extinguish() + +/obj/structure/foamedmetal/resin/CanPass(atom/movable/mover, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return TRUE + . = ..() + +#undef ALUMINUM_FOAM +#undef IRON_FOAM +#undef RESIN_FOAM diff --git a/code/game/objects/effects/effect_system/effects_smoke.dm b/code/game/objects/effects/effect_system/effects_smoke.dm index 028110170a..40452f68e2 100644 --- a/code/game/objects/effects/effect_system/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/effects_smoke.dm @@ -1,328 +1,328 @@ -///////////////////////////////////////////// -//// SMOKE SYSTEMS -///////////////////////////////////////////// - -/obj/effect/particle_effect/smoke - name = "smoke" - icon = 'icons/effects/96x96.dmi' - icon_state = "smoke" - pixel_x = -32 - pixel_y = -32 - opacity = 0 - layer = FLY_LAYER - anchored = TRUE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - animate_movement = 0 - var/amount = 4 - var/lifetime = 5 - var/opaque = 1 //whether the smoke can block the view when in enough amount - - -/obj/effect/particle_effect/smoke/proc/fade_out(frames = 16) - if(alpha == 0) //Handle already transparent case - return - if(frames == 0) - frames = 1 //We will just assume that by 0 frames, the coder meant "during one frame". - var/step = alpha / frames - for(var/i = 0, i < frames, i++) - alpha -= step - if(alpha < 160) - set_opacity(0) //if we were blocking view, we aren't now because we're fading out - stoplag() - -/obj/effect/particle_effect/smoke/Initialize() - . = ..() - create_reagents(500) - START_PROCESSING(SSobj, src) - - -/obj/effect/particle_effect/smoke/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/effect/particle_effect/smoke/proc/kill_smoke() - STOP_PROCESSING(SSobj, src) - INVOKE_ASYNC(src, .proc/fade_out) - QDEL_IN(src, 10) - -/obj/effect/particle_effect/smoke/process() - lifetime-- - if(lifetime < 1) - kill_smoke() - return 0 - for(var/mob/living/L in range(0,src)) - smoke_mob(L) - return 1 - -/obj/effect/particle_effect/smoke/proc/smoke_mob(mob/living/carbon/C) - if(!istype(C)) - return 0 - if(lifetime<1) - return 0 - if(C.internal != null || C.has_smoke_protection()) - return 0 - if(C.smoke_delay) - return 0 - C.smoke_delay++ - addtimer(CALLBACK(src, .proc/remove_smoke_delay, C), 10) - return 1 - -/obj/effect/particle_effect/smoke/proc/remove_smoke_delay(mob/living/carbon/C) - if(C) - C.smoke_delay = 0 - -/obj/effect/particle_effect/smoke/proc/spread_smoke() - var/turf/t_loc = get_turf(src) - if(!t_loc) - return - var/list/newsmokes = list() - for(var/turf/T in t_loc.GetAtmosAdjacentTurfs()) - var/obj/effect/particle_effect/smoke/foundsmoke = locate() in T //Don't spread smoke where there's already smoke! - if(foundsmoke) - continue - for(var/mob/living/L in T) - smoke_mob(L) - var/obj/effect/particle_effect/smoke/S = new type(T) - reagents.copy_to(S, reagents.total_volume) - S.setDir(pick(GLOB.cardinals)) - S.amount = amount-1 - S.add_atom_colour(color, FIXED_COLOUR_PRIORITY) - S.lifetime = lifetime - if(S.amount>0) - if(opaque) - S.set_opacity(TRUE) - newsmokes.Add(S) - - if(newsmokes.len) - spawn(1) //the smoke spreads rapidly but not instantly - for(var/obj/effect/particle_effect/smoke/SM in newsmokes) - SM.spread_smoke() - - -/datum/effect_system/smoke_spread - var/amount = 10 - effect_type = /obj/effect/particle_effect/smoke - -/datum/effect_system/smoke_spread/set_up(radius = 5, loca) - if(isturf(loca)) - location = loca - else - location = get_turf(loca) - amount = radius - -/datum/effect_system/smoke_spread/start() - if(holder) - location = get_turf(holder) - var/obj/effect/particle_effect/smoke/S = new effect_type(location) - S.amount = amount - if(S.amount) - S.spread_smoke() - - -///////////////////////////////////////////// -// Bad smoke -///////////////////////////////////////////// - -/obj/effect/particle_effect/smoke/bad - lifetime = 8 - -/obj/effect/particle_effect/smoke/bad/smoke_mob(mob/living/carbon/M) - if(..()) - M.drop_all_held_items() - M.adjustOxyLoss(1) - M.emote("cough") - return 1 - -/obj/effect/particle_effect/smoke/bad/CanPass(atom/movable/mover, turf/target) - if(istype(mover, /obj/item/projectile/beam)) - var/obj/item/projectile/beam/B = mover - B.damage = (B.damage/2) - return 1 - - - -/datum/effect_system/smoke_spread/bad - effect_type = /obj/effect/particle_effect/smoke/bad - -///////////////////////////////////////////// -// Nanofrost smoke -///////////////////////////////////////////// - -/obj/effect/particle_effect/smoke/freezing - name = "nanofrost smoke" - color = "#B2FFFF" - opaque = 0 - -/datum/effect_system/smoke_spread/freezing - effect_type = /obj/effect/particle_effect/smoke/freezing - var/blast = 0 - var/temperature = 2 - var/weldvents = TRUE - var/distcheck = TRUE - -/datum/effect_system/smoke_spread/freezing/proc/Chilled(atom/A) - if(isopenturf(A)) - var/turf/open/T = A - if(T.air) - var/datum/gas_mixture/G = T.air - if(!distcheck || get_dist(T, location) < blast) // Otherwise we'll get silliness like people using Nanofrost to kill people through walls with cold air - G.temperature = temperature - T.air_update_turf() - for(var/obj/effect/hotspot/H in T) - qdel(H) - var/list/G_gases = G.gases - if(G_gases[/datum/gas/plasma]) - G_gases[/datum/gas/nitrogen] += (G_gases[/datum/gas/plasma]) - G_gases[/datum/gas/plasma] = 0 - GAS_GARBAGE_COLLECT(G.gases) - if (weldvents) - for(var/obj/machinery/atmospherics/components/unary/U in T) - if(!isnull(U.welded) && !U.welded) //must be an unwelded vent pump or vent scrubber. - U.welded = TRUE - U.update_icon() - U.visible_message("[U] was frozen shut!") - for(var/mob/living/L in T) - L.ExtinguishMob() - for(var/obj/item/Item in T) - Item.extinguish() - -/datum/effect_system/smoke_spread/freezing/set_up(radius = 5, loca, blast_radius = 0) - ..() - blast = blast_radius - -/datum/effect_system/smoke_spread/freezing/start() - if(blast) - for(var/turf/T in RANGE_TURFS(blast, location)) - Chilled(T) - ..() - -/datum/effect_system/smoke_spread/freezing/decon - temperature = 293.15 - distcheck = FALSE - weldvents = FALSE - - -///////////////////////////////////////////// -// Sleep smoke -///////////////////////////////////////////// - -/obj/effect/particle_effect/smoke/sleeping - color = "#9C3636" - lifetime = 10 - -/obj/effect/particle_effect/smoke/sleeping/smoke_mob(mob/living/carbon/M) - if(..()) - M.Sleeping(200) - M.emote("cough") - return 1 - -/datum/effect_system/smoke_spread/sleeping - effect_type = /obj/effect/particle_effect/smoke/sleeping - -///////////////////////////////////////////// -// Chem smoke -///////////////////////////////////////////// - -/obj/effect/particle_effect/smoke/chem - lifetime = 10 - - -/obj/effect/particle_effect/smoke/chem/process() - if(..()) - var/turf/T = get_turf(src) - var/fraction = 1/initial(lifetime) - for(var/atom/movable/AM in T) - if(AM.type == src.type) - continue - if(T.intact && AM.level == 1) //hidden under the floor - continue - reagents.reaction(AM, TOUCH, fraction) - - reagents.reaction(T, TOUCH, fraction) - return 1 - -/obj/effect/particle_effect/smoke/chem/smoke_mob(mob/living/carbon/M) - if(lifetime<1) - return 0 - if(!istype(M)) - return 0 - var/mob/living/carbon/C = M - if(C.internal != null || C.has_smoke_protection()) - return 0 - var/fraction = 1/initial(lifetime) - reagents.copy_to(C, fraction*reagents.total_volume) - reagents.reaction(M, INGEST, fraction) - return 1 - - - -/datum/effect_system/smoke_spread/chem - var/obj/chemholder - effect_type = /obj/effect/particle_effect/smoke/chem - -/datum/effect_system/smoke_spread/chem/New() - ..() - chemholder = new /obj() - var/datum/reagents/R = new/datum/reagents(500) - chemholder.reagents = R - R.my_atom = chemholder - -/datum/effect_system/smoke_spread/chem/Destroy() - qdel(chemholder) - chemholder = null - return ..() - -/datum/effect_system/smoke_spread/chem/set_up(datum/reagents/carry = null, radius = 1, loca, silent = FALSE) - if(isturf(loca)) - location = loca - else - location = get_turf(loca) - amount = radius - carry.copy_to(chemholder, carry.total_volume) - - if(!silent) - var/contained = "" - for(var/reagent in carry.reagent_list) - contained += " [reagent] " - if(contained) - contained = "\[[contained]\]" - - var/where = "[AREACOORD(location)]" - if(carry.my_atom && carry.my_atom.fingerprintslast) - var/mob/M = get_mob_by_key(carry.my_atom.fingerprintslast) - var/more = "" - if(M) - more = "[ADMIN_LOOKUPFLW(M)] " - message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. Key: [more ? more : carry.my_atom.fingerprintslast].") - log_game("A chemical smoke reaction has taken place in ([where])[contained]. Last touched by [carry.my_atom.fingerprintslast].") - else - message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. No associated key.") - log_game("A chemical smoke reaction has taken place in ([where])[contained]. No associated key.") - - -/datum/effect_system/smoke_spread/chem/start() - var/mixcolor = mix_color_from_reagents(chemholder.reagents.reagent_list) - if(holder) - location = get_turf(holder) - var/obj/effect/particle_effect/smoke/chem/S = new effect_type(location) - - if(chemholder.reagents.total_volume > 1) // can't split 1 very well - chemholder.reagents.copy_to(S, chemholder.reagents.total_volume) - - if(mixcolor) - S.add_atom_colour(mixcolor, FIXED_COLOUR_PRIORITY) // give the smoke color, if it has any to begin with - S.amount = amount - if(S.amount) - S.spread_smoke() //calling process right now so the smoke immediately attacks mobs. - - -///////////////////////////////////////////// -// Transparent smoke -///////////////////////////////////////////// - -//Same as the base type, but the smoke produced is not opaque -/datum/effect_system/smoke_spread/transparent - effect_type = /obj/effect/particle_effect/smoke/transparent - -/obj/effect/particle_effect/smoke/transparent - opaque = FALSE +///////////////////////////////////////////// +//// SMOKE SYSTEMS +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke + name = "smoke" + icon = 'icons/effects/96x96.dmi' + icon_state = "smoke" + pixel_x = -32 + pixel_y = -32 + opacity = 0 + layer = FLY_LAYER + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + animate_movement = 0 + var/amount = 4 + var/lifetime = 5 + var/opaque = 1 //whether the smoke can block the view when in enough amount + + +/obj/effect/particle_effect/smoke/proc/fade_out(frames = 16) + if(alpha == 0) //Handle already transparent case + return + if(frames == 0) + frames = 1 //We will just assume that by 0 frames, the coder meant "during one frame". + var/step = alpha / frames + for(var/i = 0, i < frames, i++) + alpha -= step + if(alpha < 160) + set_opacity(0) //if we were blocking view, we aren't now because we're fading out + stoplag() + +/obj/effect/particle_effect/smoke/Initialize() + . = ..() + create_reagents(500) + START_PROCESSING(SSobj, src) + + +/obj/effect/particle_effect/smoke/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/effect/particle_effect/smoke/proc/kill_smoke() + STOP_PROCESSING(SSobj, src) + INVOKE_ASYNC(src, .proc/fade_out) + QDEL_IN(src, 10) + +/obj/effect/particle_effect/smoke/process() + lifetime-- + if(lifetime < 1) + kill_smoke() + return 0 + for(var/mob/living/L in range(0,src)) + smoke_mob(L) + return 1 + +/obj/effect/particle_effect/smoke/proc/smoke_mob(mob/living/carbon/C) + if(!istype(C)) + return 0 + if(lifetime<1) + return 0 + if(C.internal != null || C.has_smoke_protection()) + return 0 + if(C.smoke_delay) + return 0 + C.smoke_delay++ + addtimer(CALLBACK(src, .proc/remove_smoke_delay, C), 10) + return 1 + +/obj/effect/particle_effect/smoke/proc/remove_smoke_delay(mob/living/carbon/C) + if(C) + C.smoke_delay = 0 + +/obj/effect/particle_effect/smoke/proc/spread_smoke() + var/turf/t_loc = get_turf(src) + if(!t_loc) + return + var/list/newsmokes = list() + for(var/turf/T in t_loc.GetAtmosAdjacentTurfs()) + var/obj/effect/particle_effect/smoke/foundsmoke = locate() in T //Don't spread smoke where there's already smoke! + if(foundsmoke) + continue + for(var/mob/living/L in T) + smoke_mob(L) + var/obj/effect/particle_effect/smoke/S = new type(T) + reagents.copy_to(S, reagents.total_volume) + S.setDir(pick(GLOB.cardinals)) + S.amount = amount-1 + S.add_atom_colour(color, FIXED_COLOUR_PRIORITY) + S.lifetime = lifetime + if(S.amount>0) + if(opaque) + S.set_opacity(TRUE) + newsmokes.Add(S) + + if(newsmokes.len) + spawn(1) //the smoke spreads rapidly but not instantly + for(var/obj/effect/particle_effect/smoke/SM in newsmokes) + SM.spread_smoke() + + +/datum/effect_system/smoke_spread + var/amount = 10 + effect_type = /obj/effect/particle_effect/smoke + +/datum/effect_system/smoke_spread/set_up(radius = 5, loca) + if(isturf(loca)) + location = loca + else + location = get_turf(loca) + amount = radius + +/datum/effect_system/smoke_spread/start() + if(holder) + location = get_turf(holder) + var/obj/effect/particle_effect/smoke/S = new effect_type(location) + S.amount = amount + if(S.amount) + S.spread_smoke() + + +///////////////////////////////////////////// +// Bad smoke +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke/bad + lifetime = 8 + +/obj/effect/particle_effect/smoke/bad/smoke_mob(mob/living/carbon/M) + if(..()) + M.drop_all_held_items() + M.adjustOxyLoss(1) + M.emote("cough") + return 1 + +/obj/effect/particle_effect/smoke/bad/CanPass(atom/movable/mover, turf/target) + if(istype(mover, /obj/item/projectile/beam)) + var/obj/item/projectile/beam/B = mover + B.damage = (B.damage/2) + return 1 + + + +/datum/effect_system/smoke_spread/bad + effect_type = /obj/effect/particle_effect/smoke/bad + +///////////////////////////////////////////// +// Nanofrost smoke +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke/freezing + name = "nanofrost smoke" + color = "#B2FFFF" + opaque = 0 + +/datum/effect_system/smoke_spread/freezing + effect_type = /obj/effect/particle_effect/smoke/freezing + var/blast = 0 + var/temperature = 2 + var/weldvents = TRUE + var/distcheck = TRUE + +/datum/effect_system/smoke_spread/freezing/proc/Chilled(atom/A) + if(isopenturf(A)) + var/turf/open/T = A + if(T.air) + var/datum/gas_mixture/G = T.air + if(!distcheck || get_dist(T, location) < blast) // Otherwise we'll get silliness like people using Nanofrost to kill people through walls with cold air + G.temperature = temperature + T.air_update_turf() + for(var/obj/effect/hotspot/H in T) + qdel(H) + var/list/G_gases = G.gases + if(G_gases[/datum/gas/plasma]) + G_gases[/datum/gas/nitrogen] += (G_gases[/datum/gas/plasma]) + G_gases[/datum/gas/plasma] = 0 + GAS_GARBAGE_COLLECT(G.gases) + if (weldvents) + for(var/obj/machinery/atmospherics/components/unary/U in T) + if(!isnull(U.welded) && !U.welded) //must be an unwelded vent pump or vent scrubber. + U.welded = TRUE + U.update_icon() + U.visible_message("[U] was frozen shut!") + for(var/mob/living/L in T) + L.ExtinguishMob() + for(var/obj/item/Item in T) + Item.extinguish() + +/datum/effect_system/smoke_spread/freezing/set_up(radius = 5, loca, blast_radius = 0) + ..() + blast = blast_radius + +/datum/effect_system/smoke_spread/freezing/start() + if(blast) + for(var/turf/T in RANGE_TURFS(blast, location)) + Chilled(T) + ..() + +/datum/effect_system/smoke_spread/freezing/decon + temperature = 293.15 + distcheck = FALSE + weldvents = FALSE + + +///////////////////////////////////////////// +// Sleep smoke +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke/sleeping + color = "#9C3636" + lifetime = 10 + +/obj/effect/particle_effect/smoke/sleeping/smoke_mob(mob/living/carbon/M) + if(..()) + M.Sleeping(200) + M.emote("cough") + return 1 + +/datum/effect_system/smoke_spread/sleeping + effect_type = /obj/effect/particle_effect/smoke/sleeping + +///////////////////////////////////////////// +// Chem smoke +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke/chem + lifetime = 10 + + +/obj/effect/particle_effect/smoke/chem/process() + if(..()) + var/turf/T = get_turf(src) + var/fraction = 1/initial(lifetime) + for(var/atom/movable/AM in T) + if(AM.type == src.type) + continue + if(T.intact && AM.level == 1) //hidden under the floor + continue + reagents.reaction(AM, TOUCH, fraction) + + reagents.reaction(T, TOUCH, fraction) + return 1 + +/obj/effect/particle_effect/smoke/chem/smoke_mob(mob/living/carbon/M) + if(lifetime<1) + return 0 + if(!istype(M)) + return 0 + var/mob/living/carbon/C = M + if(C.internal != null || C.has_smoke_protection()) + return 0 + var/fraction = 1/initial(lifetime) + reagents.copy_to(C, fraction*reagents.total_volume) + reagents.reaction(M, INGEST, fraction) + return 1 + + + +/datum/effect_system/smoke_spread/chem + var/obj/chemholder + effect_type = /obj/effect/particle_effect/smoke/chem + +/datum/effect_system/smoke_spread/chem/New() + ..() + chemholder = new /obj() + var/datum/reagents/R = new/datum/reagents(500) + chemholder.reagents = R + R.my_atom = chemholder + +/datum/effect_system/smoke_spread/chem/Destroy() + qdel(chemholder) + chemholder = null + return ..() + +/datum/effect_system/smoke_spread/chem/set_up(datum/reagents/carry = null, radius = 1, loca, silent = FALSE) + if(isturf(loca)) + location = loca + else + location = get_turf(loca) + amount = radius + carry.copy_to(chemholder, carry.total_volume) + + if(!silent) + var/contained = "" + for(var/reagent in carry.reagent_list) + contained += " [reagent] " + if(contained) + contained = "\[[contained]\]" + + var/where = "[AREACOORD(location)]" + if(carry.my_atom && carry.my_atom.fingerprintslast) + var/mob/M = get_mob_by_key(carry.my_atom.fingerprintslast) + var/more = "" + if(M) + more = "[ADMIN_LOOKUPFLW(M)] " + message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. Key: [more ? more : carry.my_atom.fingerprintslast].") + log_game("A chemical smoke reaction has taken place in ([where])[contained]. Last touched by [carry.my_atom.fingerprintslast].") + else + message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. No associated key.") + log_game("A chemical smoke reaction has taken place in ([where])[contained]. No associated key.") + + +/datum/effect_system/smoke_spread/chem/start() + var/mixcolor = mix_color_from_reagents(chemholder.reagents.reagent_list) + if(holder) + location = get_turf(holder) + var/obj/effect/particle_effect/smoke/chem/S = new effect_type(location) + + if(chemholder.reagents.total_volume > 1) // can't split 1 very well + chemholder.reagents.copy_to(S, chemholder.reagents.total_volume) + + if(mixcolor) + S.add_atom_colour(mixcolor, FIXED_COLOUR_PRIORITY) // give the smoke color, if it has any to begin with + S.amount = amount + if(S.amount) + S.spread_smoke() //calling process right now so the smoke immediately attacks mobs. + + +///////////////////////////////////////////// +// Transparent smoke +///////////////////////////////////////////// + +//Same as the base type, but the smoke produced is not opaque +/datum/effect_system/smoke_spread/transparent + effect_type = /obj/effect/particle_effect/smoke/transparent + +/obj/effect/particle_effect/smoke/transparent + opaque = FALSE diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm index 6aab4f386a..384cf73c89 100644 --- a/code/game/objects/effects/forcefields.dm +++ b/code/game/objects/effects/forcefields.dm @@ -1,37 +1,37 @@ -/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." - -/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." + +/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 0d4ca3d311..3ef89c4b96 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -1,483 +1,483 @@ -/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/New() - GLOB.start_landmarks_list += src - if(jobspawn_override) - if(!GLOB.jobspawn_overrides[name]) - GLOB.jobspawn_overrides[name] = list() - GLOB.jobspawn_overrides[name] += src - ..() - if(name != "start") - tag = "start*[name]" - -/obj/effect/landmark/start/Destroy() - GLOB.start_landmarks_list -= src - if(jobspawn_override) - 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" - -/obj/effect/landmark/start/assistant/override - jobspawn_override = TRUE - delete_after_roundstart = FALSE - -/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/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/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/AIcore/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" - -/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 - -// carp. -/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. -/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 - -// blobs. -/obj/effect/landmark/blobstart - name = "blobstart" - icon_state = "blob_start" - -/obj/effect/landmark/blobstart/Initialize(mapload) - ..() - GLOB.blobstart += loc - return INITIALIZE_HINT_QDEL - -/obj/effect/landmark/secequipment - name = "secequipment" - icon_state = "secequipment" - -/obj/effect/landmark/secequipment/Initialize(mapload) - ..() - GLOB.secequipment += loc - return INITIALIZE_HINT_QDEL - -/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 - -/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 - -//Servant spawn locations -/obj/effect/landmark/servant_of_ratvar - name = "servant of ratvar spawn" - icon_state = "clockwork_orange" - layer = MOB_LAYER - -/obj/effect/landmark/servant_of_ratvar/Initialize(mapload) - ..() - GLOB.servant_spawns += loc - return INITIALIZE_HINT_QDEL - -//City of Cogs entrances -/obj/effect/landmark/city_of_cogs - name = "city of cogs entrance" - icon_state = "city_of_cogs" - -/obj/effect/landmark/city_of_cogs/Initialize(mapload) - ..() - GLOB.city_of_cogs_spawns += 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 - . = ..() - -//------Station Rooms Landmarks------------// -/obj/effect/landmark/stationroom - var/list/templates = list() - layer = BULLET_HOLE_LAYER - -/obj/effect/landmark/stationroom/New() - ..() - GLOB.stationroom_landmarks += src - -/obj/effect/landmark/stationroom/Destroy() - if(src in GLOB.stationroom_landmarks) - GLOB.stationroom_landmarks -= src - return ..() - -/obj/effect/landmark/stationroom/proc/load(template_name) - var/turf/T = get_turf(src) - if(!T) - return FALSE - if(!template_name) - for(var/t in templates) - if(!SSmapping.station_room_templates[t]) - log_world("Station room spawner placed at ([T.x], [T.y], [T.z]) has invalid ruin name of \"[t]\" in its list") - templates -= t - template_name = pickweight(templates) - if(!template_name) - GLOB.stationroom_landmarks -= src - qdel(src) - return FALSE - var/datum/map_template/template = SSmapping.station_room_templates[template_name] - if(!template) - return FALSE - testing("Room \"[template_name]\" placed at ([T.x], [T.y], [T.z])") - template.load(T, centered = FALSE) - template.loaded++ - GLOB.stationroom_landmarks -= src - qdel(src) - return TRUE - -// The landmark for the Engine on Box - -/obj/effect/landmark/stationroom/box/engine - templates = list("Engine SM" = 3, "Engine Singulo" = 3, "Engine Tesla" = 3) - icon = 'icons/rooms/box/engine.dmi' - - -/obj/effect/landmark/stationroom/box/engine/New() - . = ..() - templates = CONFIG_GET(keyed_list/box_random_engine) +/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/New() + GLOB.start_landmarks_list += src + if(jobspawn_override) + if(!GLOB.jobspawn_overrides[name]) + GLOB.jobspawn_overrides[name] = list() + GLOB.jobspawn_overrides[name] += src + ..() + if(name != "start") + tag = "start*[name]" + +/obj/effect/landmark/start/Destroy() + GLOB.start_landmarks_list -= src + if(jobspawn_override) + 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" + +/obj/effect/landmark/start/assistant/override + jobspawn_override = TRUE + delete_after_roundstart = FALSE + +/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/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/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/AIcore/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" + +/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 + +// carp. +/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. +/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 + +// blobs. +/obj/effect/landmark/blobstart + name = "blobstart" + icon_state = "blob_start" + +/obj/effect/landmark/blobstart/Initialize(mapload) + ..() + GLOB.blobstart += loc + return INITIALIZE_HINT_QDEL + +/obj/effect/landmark/secequipment + name = "secequipment" + icon_state = "secequipment" + +/obj/effect/landmark/secequipment/Initialize(mapload) + ..() + GLOB.secequipment += loc + return INITIALIZE_HINT_QDEL + +/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 + +/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 + +//Servant spawn locations +/obj/effect/landmark/servant_of_ratvar + name = "servant of ratvar spawn" + icon_state = "clockwork_orange" + layer = MOB_LAYER + +/obj/effect/landmark/servant_of_ratvar/Initialize(mapload) + ..() + GLOB.servant_spawns += loc + return INITIALIZE_HINT_QDEL + +//City of Cogs entrances +/obj/effect/landmark/city_of_cogs + name = "city of cogs entrance" + icon_state = "city_of_cogs" + +/obj/effect/landmark/city_of_cogs/Initialize(mapload) + ..() + GLOB.city_of_cogs_spawns += 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 + . = ..() + +//------Station Rooms Landmarks------------// +/obj/effect/landmark/stationroom + var/list/templates = list() + layer = BULLET_HOLE_LAYER + +/obj/effect/landmark/stationroom/New() + ..() + GLOB.stationroom_landmarks += src + +/obj/effect/landmark/stationroom/Destroy() + if(src in GLOB.stationroom_landmarks) + GLOB.stationroom_landmarks -= src + return ..() + +/obj/effect/landmark/stationroom/proc/load(template_name) + var/turf/T = get_turf(src) + if(!T) + return FALSE + if(!template_name) + for(var/t in templates) + if(!SSmapping.station_room_templates[t]) + log_world("Station room spawner placed at ([T.x], [T.y], [T.z]) has invalid ruin name of \"[t]\" in its list") + templates -= t + template_name = pickweight(templates) + if(!template_name) + GLOB.stationroom_landmarks -= src + qdel(src) + return FALSE + var/datum/map_template/template = SSmapping.station_room_templates[template_name] + if(!template) + return FALSE + testing("Room \"[template_name]\" placed at ([T.x], [T.y], [T.z])") + template.load(T, centered = FALSE) + template.loaded++ + GLOB.stationroom_landmarks -= src + qdel(src) + return TRUE + +// The landmark for the Engine on Box + +/obj/effect/landmark/stationroom/box/engine + templates = list("Engine SM" = 3, "Engine Singulo" = 3, "Engine Tesla" = 3) + icon = 'icons/rooms/box/engine.dmi' + + +/obj/effect/landmark/stationroom/box/engine/New() + . = ..() + templates = CONFIG_GET(keyed_list/box_random_engine) diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm index 91133234d6..6b94c65f49 100644 --- a/code/game/objects/effects/mines.dm +++ b/code/game/objects/effects/mines.dm @@ -1,176 +1,176 @@ -/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" - var/triggered = 0 - -/obj/effect/mine/proc/mineEffect(mob/victim) - to_chat(victim, "*click*") - -/obj/effect/mine/Crossed(AM as mob|obj) - if(isturf(loc)) - if(ismob(AM)) - var/mob/MM = AM - if(!(MM.movement_type & FLYING)) - triggermine(AM) - else - triggermine(AM) - -/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 = 1 - 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/stun/mineEffect(mob/living/victim) - if(isliving(victim)) - victim.Knockdown(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/sound - name = "honkblaster 1000" - var/sound = 'sound/items/bikehorn.ogg' - -/obj/effect/mine/sound/mineEffect(mob/victim) - playsound(loc, sound, 100, 1) - - -/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) - - spawn(0) - new /datum/hallucination/delusion(victim, TRUE, "demon",duration,0) - - var/obj/item/twohanded/required/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) - chainsaw.wield(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/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 = 1, admin_revive = 1) - -/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(MOVESPEED_ID_YELLOW_ORB, update=TRUE, priority=100, multiplicative_slowdown=-2, blacklisted_movetypes=(FLYING|FLOATING)) - sleep(duration) - victim.remove_movespeed_modifier(MOVESPEED_ID_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" + var/triggered = 0 + +/obj/effect/mine/proc/mineEffect(mob/victim) + to_chat(victim, "*click*") + +/obj/effect/mine/Crossed(AM as mob|obj) + if(isturf(loc)) + if(ismob(AM)) + var/mob/MM = AM + if(!(MM.movement_type & FLYING)) + triggermine(AM) + else + triggermine(AM) + +/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 = 1 + 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/stun/mineEffect(mob/living/victim) + if(isliving(victim)) + victim.Knockdown(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/sound + name = "honkblaster 1000" + var/sound = 'sound/items/bikehorn.ogg' + +/obj/effect/mine/sound/mineEffect(mob/victim) + playsound(loc, sound, 100, 1) + + +/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) + + spawn(0) + new /datum/hallucination/delusion(victim, TRUE, "demon",duration,0) + + var/obj/item/twohanded/required/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) + chainsaw.wield(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/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 = 1, admin_revive = 1) + +/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(MOVESPEED_ID_YELLOW_ORB, update=TRUE, priority=100, multiplicative_slowdown=-2, blacklisted_movetypes=(FLYING|FLOATING)) + sleep(duration) + victim.remove_movespeed_modifier(MOVESPEED_ID_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 6a8487a785..c2cfab0f7c 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)) +//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 \ No newline at end of file diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm index 631b87cada..04e194f6c3 100644 --- a/code/game/objects/effects/portals.dm +++ b/code/game/objects/effects/portals.dm @@ -1,185 +1,185 @@ - -/proc/create_portal_pair(turf/source, turf/destination, _creator = null, _lifespan = 300, accuracy = 0, newtype = /obj/effect/portal, atmos_link_override) - if(!istype(source) || !istype(destination)) - return - var/turf/actual_destination = get_teleport_turf(destination, accuracy) - var/obj/effect/portal/P1 = new newtype(source, _creator, _lifespan, null, FALSE, null, atmos_link_override) - var/obj/effect/portal/P2 = new newtype(actual_destination, _creator, _lifespan, P1, TRUE, null, atmos_link_override) - if(!istype(P1)||!istype(P2)) - return - P1.link_portal(P2) - P1.hardlinked = TRUE - return list(P1, P2) - -/obj/effect/portal - name = "portal" - desc = "Looks unstable. Best to test it with the clown." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "portal" - anchored = TRUE - var/mech_sized = FALSE - var/obj/effect/portal/linked - var/hardlinked = TRUE //Requires a linked portal at all times. Destroy if there's no linked portal, if there is destroy it when this one is deleted. - var/teleport_channel = TELEPORT_CHANNEL_BLUESPACE - var/creator - var/turf/hard_target //For when a portal needs a hard target and isn't to be linked. - var/atmos_link = FALSE //Link source/destination atmos. - var/turf/open/atmos_source //Atmos link source - var/turf/open/atmos_destination //Atmos link destination - var/allow_anchored = FALSE - var/innate_accuracy_penalty = 0 - var/last_effect = 0 - -/obj/effect/portal/anom - name = "wormhole" - icon = 'icons/obj/objects.dmi' - icon_state = "anom" - mech_sized = TRUE - teleport_channel = TELEPORT_CHANNEL_WORMHOLE - -/obj/effect/portal/Move(newloc) - for(var/T in newloc) - if(istype(T, /obj/effect/portal)) - return FALSE - return ..() - -/obj/effect/portal/attackby(obj/item/W, mob/user, params) - if(user && Adjacent(user)) - user.forceMove(get_turf(src)) - return TRUE - -/obj/effect/portal/Crossed(atom/movable/AM, oldloc) - if(isobserver(AM)) - return ..() - if(linked && (get_turf(oldloc) == get_turf(linked))) - return ..() - if(!teleport(AM)) - return ..() - -/obj/effect/portal/attack_tk(mob/user) - return - -/obj/effect/portal/attack_hand(mob/user) - . = ..() - if(.) - return - if(get_turf(user) == get_turf(src)) - teleport(user) - if(Adjacent(user)) - user.forceMove(get_turf(src)) - -/obj/effect/portal/Initialize(mapload, _creator, _lifespan = 0, obj/effect/portal/_linked, automatic_link = FALSE, turf/hard_target_override, atmos_link_override) - . = ..() - GLOB.portals += src - if(!istype(_linked) && automatic_link) - . = INITIALIZE_HINT_QDEL - CRASH("Somebody fucked up.") - if(_lifespan > 0) - QDEL_IN(src, _lifespan) - if(!isnull(atmos_link_override)) - atmos_link = atmos_link_override - link_portal(_linked) - hardlinked = automatic_link - creator = _creator - if(isturf(hard_target_override)) - hard_target = hard_target_override - -/obj/effect/portal/singularity_pull() - return - -/obj/effect/portal/singularity_act() - return - -/obj/effect/portal/proc/link_portal(obj/effect/portal/newlink) - linked = newlink - if(atmos_link) - link_atmos() - -/obj/effect/portal/proc/link_atmos() - if(atmos_source || atmos_destination) - unlink_atmos() - if(!isopenturf(get_turf(src))) - return FALSE - if(linked) - if(isopenturf(get_turf(linked))) - atmos_source = get_turf(src) - atmos_destination = get_turf(linked) - else if(hard_target) - if(isopenturf(hard_target)) - atmos_source = get_turf(src) - atmos_destination = hard_target - else - return FALSE - if(!istype(atmos_source) || !istype(atmos_destination)) - return FALSE - LAZYINITLIST(atmos_source.atmos_adjacent_turfs) - LAZYINITLIST(atmos_destination.atmos_adjacent_turfs) - if(atmos_source.atmos_adjacent_turfs[atmos_destination] || atmos_destination.atmos_adjacent_turfs[atmos_source]) //Already linked! - return FALSE - atmos_source.atmos_adjacent_turfs[atmos_destination] = TRUE - atmos_destination.atmos_adjacent_turfs[atmos_source] = TRUE - atmos_source.air_update_turf(FALSE) - atmos_destination.air_update_turf(FALSE) - -/obj/effect/portal/proc/unlink_atmos() - if(istype(atmos_source)) - if(istype(atmos_destination) && !atmos_source.Adjacent(atmos_destination) && !CANATMOSPASS(atmos_destination, atmos_source)) - LAZYREMOVE(atmos_source.atmos_adjacent_turfs, atmos_destination) - atmos_source = null - if(istype(atmos_destination)) - if(istype(atmos_source) && !atmos_destination.Adjacent(atmos_source) && !CANATMOSPASS(atmos_source, atmos_destination)) - LAZYREMOVE(atmos_destination.atmos_adjacent_turfs, atmos_source) - atmos_destination = null - -/obj/effect/portal/Destroy() //Calls on_portal_destroy(destroyed portal, location of destroyed portal) on creator if creator has such call. - if(creator && hascall(creator, "on_portal_destroy")) - call(creator, "on_portal_destroy")(src, src.loc) - creator = null - GLOB.portals -= src - unlink_atmos() - if(hardlinked && !QDELETED(linked)) - QDEL_NULL(linked) - else - linked = null - return ..() - -/obj/effect/portal/attack_ghost(mob/dead/observer/O) - if(!teleport(O, TRUE)) - return ..() - -/obj/effect/portal/proc/teleport(atom/movable/M, force = FALSE) - if(!force && (!istype(M) || iseffect(M) || (ismecha(M) && !mech_sized) || (!isobj(M) && !ismob(M)))) //Things that shouldn't teleport. - return - var/turf/real_target = get_link_target_turf() - if(!istype(real_target)) - return FALSE - if(!force && (!ismecha(M) && !istype(M, /obj/item/projectile) && M.anchored && !allow_anchored)) - return - if(ismegafauna(M)) - message_admins("[M] has used a portal at [ADMIN_VERBOSEJMP(src)] made by [usr].") - var/no_effect = FALSE - if(last_effect == world.time) - no_effect = TRUE - else - last_effect = world.time - if(do_teleport(M, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel)) - if(istype(M, /obj/item/projectile)) - var/obj/item/projectile/P = M - P.ignore_source_check = TRUE - return TRUE - return FALSE - -/obj/effect/portal/proc/get_link_target_turf() - var/turf/real_target - if(!istype(linked) || QDELETED(linked)) - if(hardlinked) - qdel(src) - if(!istype(hard_target) || QDELETED(hard_target)) - hard_target = null - return - else - real_target = hard_target - linked = null - else - real_target = get_turf(linked) - return real_target + +/proc/create_portal_pair(turf/source, turf/destination, _creator = null, _lifespan = 300, accuracy = 0, newtype = /obj/effect/portal, atmos_link_override) + if(!istype(source) || !istype(destination)) + return + var/turf/actual_destination = get_teleport_turf(destination, accuracy) + var/obj/effect/portal/P1 = new newtype(source, _creator, _lifespan, null, FALSE, null, atmos_link_override) + var/obj/effect/portal/P2 = new newtype(actual_destination, _creator, _lifespan, P1, TRUE, null, atmos_link_override) + if(!istype(P1)||!istype(P2)) + return + P1.link_portal(P2) + P1.hardlinked = TRUE + return list(P1, P2) + +/obj/effect/portal + name = "portal" + desc = "Looks unstable. Best to test it with the clown." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "portal" + anchored = TRUE + var/mech_sized = FALSE + var/obj/effect/portal/linked + var/hardlinked = TRUE //Requires a linked portal at all times. Destroy if there's no linked portal, if there is destroy it when this one is deleted. + var/teleport_channel = TELEPORT_CHANNEL_BLUESPACE + var/creator + var/turf/hard_target //For when a portal needs a hard target and isn't to be linked. + var/atmos_link = FALSE //Link source/destination atmos. + var/turf/open/atmos_source //Atmos link source + var/turf/open/atmos_destination //Atmos link destination + var/allow_anchored = FALSE + var/innate_accuracy_penalty = 0 + var/last_effect = 0 + +/obj/effect/portal/anom + name = "wormhole" + icon = 'icons/obj/objects.dmi' + icon_state = "anom" + mech_sized = TRUE + teleport_channel = TELEPORT_CHANNEL_WORMHOLE + +/obj/effect/portal/Move(newloc) + for(var/T in newloc) + if(istype(T, /obj/effect/portal)) + return FALSE + return ..() + +/obj/effect/portal/attackby(obj/item/W, mob/user, params) + if(user && Adjacent(user)) + user.forceMove(get_turf(src)) + return TRUE + +/obj/effect/portal/Crossed(atom/movable/AM, oldloc) + if(isobserver(AM)) + return ..() + if(linked && (get_turf(oldloc) == get_turf(linked))) + return ..() + if(!teleport(AM)) + return ..() + +/obj/effect/portal/attack_tk(mob/user) + return + +/obj/effect/portal/attack_hand(mob/user) + . = ..() + if(.) + return + if(get_turf(user) == get_turf(src)) + teleport(user) + if(Adjacent(user)) + user.forceMove(get_turf(src)) + +/obj/effect/portal/Initialize(mapload, _creator, _lifespan = 0, obj/effect/portal/_linked, automatic_link = FALSE, turf/hard_target_override, atmos_link_override) + . = ..() + GLOB.portals += src + if(!istype(_linked) && automatic_link) + . = INITIALIZE_HINT_QDEL + CRASH("Somebody fucked up.") + if(_lifespan > 0) + QDEL_IN(src, _lifespan) + if(!isnull(atmos_link_override)) + atmos_link = atmos_link_override + link_portal(_linked) + hardlinked = automatic_link + creator = _creator + if(isturf(hard_target_override)) + hard_target = hard_target_override + +/obj/effect/portal/singularity_pull() + return + +/obj/effect/portal/singularity_act() + return + +/obj/effect/portal/proc/link_portal(obj/effect/portal/newlink) + linked = newlink + if(atmos_link) + link_atmos() + +/obj/effect/portal/proc/link_atmos() + if(atmos_source || atmos_destination) + unlink_atmos() + if(!isopenturf(get_turf(src))) + return FALSE + if(linked) + if(isopenturf(get_turf(linked))) + atmos_source = get_turf(src) + atmos_destination = get_turf(linked) + else if(hard_target) + if(isopenturf(hard_target)) + atmos_source = get_turf(src) + atmos_destination = hard_target + else + return FALSE + if(!istype(atmos_source) || !istype(atmos_destination)) + return FALSE + LAZYINITLIST(atmos_source.atmos_adjacent_turfs) + LAZYINITLIST(atmos_destination.atmos_adjacent_turfs) + if(atmos_source.atmos_adjacent_turfs[atmos_destination] || atmos_destination.atmos_adjacent_turfs[atmos_source]) //Already linked! + return FALSE + atmos_source.atmos_adjacent_turfs[atmos_destination] = TRUE + atmos_destination.atmos_adjacent_turfs[atmos_source] = TRUE + atmos_source.air_update_turf(FALSE) + atmos_destination.air_update_turf(FALSE) + +/obj/effect/portal/proc/unlink_atmos() + if(istype(atmos_source)) + if(istype(atmos_destination) && !atmos_source.Adjacent(atmos_destination) && !CANATMOSPASS(atmos_destination, atmos_source)) + LAZYREMOVE(atmos_source.atmos_adjacent_turfs, atmos_destination) + atmos_source = null + if(istype(atmos_destination)) + if(istype(atmos_source) && !atmos_destination.Adjacent(atmos_source) && !CANATMOSPASS(atmos_source, atmos_destination)) + LAZYREMOVE(atmos_destination.atmos_adjacent_turfs, atmos_source) + atmos_destination = null + +/obj/effect/portal/Destroy() //Calls on_portal_destroy(destroyed portal, location of destroyed portal) on creator if creator has such call. + if(creator && hascall(creator, "on_portal_destroy")) + call(creator, "on_portal_destroy")(src, src.loc) + creator = null + GLOB.portals -= src + unlink_atmos() + if(hardlinked && !QDELETED(linked)) + QDEL_NULL(linked) + else + linked = null + return ..() + +/obj/effect/portal/attack_ghost(mob/dead/observer/O) + if(!teleport(O, TRUE)) + return ..() + +/obj/effect/portal/proc/teleport(atom/movable/M, force = FALSE) + if(!force && (!istype(M) || iseffect(M) || (ismecha(M) && !mech_sized) || (!isobj(M) && !ismob(M)))) //Things that shouldn't teleport. + return + var/turf/real_target = get_link_target_turf() + if(!istype(real_target)) + return FALSE + if(!force && (!ismecha(M) && !istype(M, /obj/item/projectile) && M.anchored && !allow_anchored)) + return + if(ismegafauna(M)) + message_admins("[M] has used a portal at [ADMIN_VERBOSEJMP(src)] made by [usr].") + var/no_effect = FALSE + if(last_effect == world.time) + no_effect = TRUE + else + last_effect = world.time + if(do_teleport(M, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel)) + if(istype(M, /obj/item/projectile)) + var/obj/item/projectile/P = M + P.ignore_source_check = TRUE + return TRUE + return FALSE + +/obj/effect/portal/proc/get_link_target_turf() + var/turf/real_target + if(!istype(linked) || QDELETED(linked)) + if(hardlinked) + qdel(src) + if(!istype(hard_target) || QDELETED(hard_target)) + hard_target = null + return + else + real_target = hard_target + linked = null + else + real_target = get_turf(linked) + return real_target diff --git a/code/game/objects/effects/proximity.dm b/code/game/objects/effects/proximity.dm index 6c9525008d..e367514f6b 100644 --- a/code/game/objects/effects/proximity.dm +++ b/code/game/objects/effects/proximity.dm @@ -1,112 +1,112 @@ -/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 - -/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(!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 + +/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(!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 65395d534a..a0ae300c79 100644 --- a/code/game/objects/effects/spawners/bombspawner.dm +++ b/code/game/objects/effects/spawners/bombspawner.dm @@ -1,65 +1,65 @@ -#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_specific_heats[/datum/gas/plasma]) / (((PRESSURE_P) * GLOB.meta_gas_specific_heats[/datum/gas/plasma] + (PRESSURE_O) * GLOB.meta_gas_specific_heats[/datum/gas/oxygen]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_specific_heats[/datum/gas/oxygen] / 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.gases[/datum/gas/plasma] = 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.gases[/datum/gas/oxygen] = 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_specific_heats[/datum/gas/plasma]) / (((PRESSURE_P) * GLOB.meta_gas_specific_heats[/datum/gas/plasma] + (PRESSURE_O) * GLOB.meta_gas_specific_heats[/datum/gas/oxygen]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_specific_heats[/datum/gas/oxygen] / 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.gases[/datum/gas/plasma] = 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.gases[/datum/gas/oxygen] = 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 fe1590caba..f47d3b8ed3 100644 --- a/code/game/objects/effects/spawners/gibspawner.dm +++ b/code/game/objects/effects/spawners/gibspawner.dm @@ -1,240 +1,240 @@ - -/obj/effect/gibspawner - var/sparks = FALSE //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/gib_mob_species //We'll want to nip-pick their species for blood type stuff - 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. - var/body_coloring = "" - if(source_mob) - if(!issilicon(source_mob)) - dna_to_add = source_mob.get_blood_dna_list() //ez pz - if(ishuman(source_mob)) - var/mob/living/carbon/human/H = source_mob - if(H.dna.species.use_skintones) - body_coloring = "#[skintone2hex(H.skin_tone)]" - else - body_coloring = "#[H.dna.features["mcolor"]]" - - 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. - if(gib_mob_species) - if(ishuman(temp_mob)) - var/mob/living/carbon/human/H = temp_mob - H.set_species(gib_mob_species) - dna_to_add = temp_mob.get_blood_dna_list() - if(H.dna.species.use_skintones) - body_coloring = "#[skintone2hex(H.skin_tone)]" - else - body_coloring = "#[H.dna.features["mcolor"]]" - else - dna_to_add = temp_mob.get_blood_dna_list() - else if(!issilicon(temp_mob)) - 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) - if(iscarbon(loc)) - var/mob/living/carbon/digester = loc - digester.stomach_contents += gib - - if(dna_to_add && dna_to_add.len) - gib.add_blood_DNA(dna_to_add) - gib.body_colors = body_coloring - gib.update_icon() - - 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/human/up, /obj/effect/decal/cleanable/blood/gibs/human/down, /obj/effect/decal/cleanable/blood/gibs/human, /obj/effect/decal/cleanable/blood/gibs/human, /obj/effect/decal/cleanable/blood/gibs/human/body, /obj/effect/decal/cleanable/blood/gibs/human/limb, /obj/effect/decal/cleanable/blood/gibs/human/core) - gibamounts = list(1, 1, 1, 1, 1, 1, 1) - gib_mob_type = /mob/living/carbon/human - gib_mob_species = /datum/species/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/human, /obj/effect/decal/cleanable/blood/gibs/human/core, /obj/effect/decal/cleanable/blood/gibs/human, /obj/effect/decal/cleanable/blood/gibs/human/core, /obj/effect/decal/cleanable/blood/gibs/human, /obj/effect/decal/cleanable/blood/gibs/human/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/lizard - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/human/lizard/up, /obj/effect/decal/cleanable/blood/gibs/human/lizard/down, /obj/effect/decal/cleanable/blood/gibs/human/lizard, /obj/effect/decal/cleanable/blood/gibs/human/lizard, /obj/effect/decal/cleanable/blood/gibs/human/lizard/body, /obj/effect/decal/cleanable/blood/gibs/human/lizard/limb, /obj/effect/decal/cleanable/blood/gibs/human/lizard/core) - gibamounts = list(1, 1, 1, 1, 1, 1, 1) - gib_mob_type = /mob/living/carbon/human/species/lizard - gib_mob_species = /datum/species/lizard - sound_vol = 50 - -/obj/effect/gibspawner/lizard/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/lizard/bodypartless - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/human/lizard, /obj/effect/decal/cleanable/blood/gibs/human/lizard/core, /obj/effect/decal/cleanable/blood/gibs/human/lizard, /obj/effect/decal/cleanable/blood/gibs/human/lizard/core, /obj/effect/decal/cleanable/blood/gibs/human/lizard, /obj/effect/decal/cleanable/blood/gibs/human/lizard/torso) - gibamounts = list(1, 1, 1, 1, 1, 1) - -/obj/effect/gibspawner/lizard/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/slime - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/slime/up, /obj/effect/decal/cleanable/blood/gibs/slime/down, /obj/effect/decal/cleanable/blood/gibs/slime, /obj/effect/decal/cleanable/blood/gibs/slime, /obj/effect/decal/cleanable/blood/gibs/slime/body, /obj/effect/decal/cleanable/blood/gibs/slime/limb, /obj/effect/decal/cleanable/blood/gibs/slime/core) - gibamounts = list(1, 1, 1, 1, 1, 1, 1) - gib_mob_type = /mob/living/carbon/human/species/roundstartslime - gib_mob_species = /datum/species/jelly/roundstartslime - sound_vol = 50 - -/obj/effect/gibspawner/slime/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/slime/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/slime, /obj/effect/decal/cleanable/blood/gibs/slime/core, /obj/effect/decal/cleanable/blood/gibs/slime, /obj/effect/decal/cleanable/blood/gibs/slime/core, /obj/effect/decal/cleanable/blood/gibs/slime, /obj/effect/decal/cleanable/blood/gibs/slime/torso) - gibamounts = list(1, 1, 1, 1, 1, 1) - -/obj/effect/gibspawner/slime/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/ipc - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/ipc/up, /obj/effect/decal/cleanable/blood/gibs/ipc/down, /obj/effect/decal/cleanable/blood/gibs/ipc, /obj/effect/decal/cleanable/blood/gibs/ipc, /obj/effect/decal/cleanable/blood/gibs/ipc/body, /obj/effect/decal/cleanable/blood/gibs/ipc/limb, /obj/effect/decal/cleanable/blood/gibs/ipc/core) - gibamounts = list(1, 1, 1, 1, 1, 1, 1) - gib_mob_type = /mob/living/carbon/human/species/ipc - gib_mob_species = /datum/species/ipc - sound_vol = 50 - sparks = TRUE - sound_to_play = 'sound/effects/bang.ogg' - -/obj/effect/gibspawner/ipc/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/ipc/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/ipc, /obj/effect/decal/cleanable/blood/gibs/ipc/core, /obj/effect/decal/cleanable/blood/gibs/ipc, /obj/effect/decal/cleanable/blood/gibs/ipc/core, /obj/effect/decal/cleanable/blood/gibs/ipc, /obj/effect/decal/cleanable/blood/gibs/ipc/torso) - gibamounts = list(1, 1, 1, 1, 1, 1) - -/obj/effect/gibspawner/ipc/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/blood/gibs/xeno/up, /obj/effect/decal/cleanable/blood/gibs/xeno/down, /obj/effect/decal/cleanable/blood/gibs/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno/body, /obj/effect/decal/cleanable/blood/gibs/xeno/limb, /obj/effect/decal/cleanable/blood/gibs/xeno/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/blood/gibs/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno/core, /obj/effect/decal/cleanable/blood/gibs/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno/core, /obj/effect/decal/cleanable/blood/gibs/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno/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/xeno/xenoperson - gib_mob_type = /mob/living/carbon/human/species/xeno - gib_mob_species = /datum/species/xeno - -/obj/effect/gibspawner/xeno/xenoperson/bodypartless - -/obj/effect/gibspawner/larva - gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/xeno/larva, /obj/effect/decal/cleanable/blood/gibs/xeno/larva, /obj/effect/decal/cleanable/blood/gibs/xeno/larva/body, /obj/effect/decal/cleanable/blood/gibs/xeno/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/blood/gibs/xeno/larva, /obj/effect/decal/cleanable/blood/gibs/xeno/larva, /obj/effect/decal/cleanable/blood/gibs/xeno/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 = TRUE - 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/robot - -/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 + var/sparks = FALSE //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/gib_mob_species //We'll want to nip-pick their species for blood type stuff + 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. + var/body_coloring = "" + if(source_mob) + if(!issilicon(source_mob)) + dna_to_add = source_mob.get_blood_dna_list() //ez pz + if(ishuman(source_mob)) + var/mob/living/carbon/human/H = source_mob + if(H.dna.species.use_skintones) + body_coloring = "#[skintone2hex(H.skin_tone)]" + else + body_coloring = "#[H.dna.features["mcolor"]]" + + 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. + if(gib_mob_species) + if(ishuman(temp_mob)) + var/mob/living/carbon/human/H = temp_mob + H.set_species(gib_mob_species) + dna_to_add = temp_mob.get_blood_dna_list() + if(H.dna.species.use_skintones) + body_coloring = "#[skintone2hex(H.skin_tone)]" + else + body_coloring = "#[H.dna.features["mcolor"]]" + else + dna_to_add = temp_mob.get_blood_dna_list() + else if(!issilicon(temp_mob)) + 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) + if(iscarbon(loc)) + var/mob/living/carbon/digester = loc + digester.stomach_contents += gib + + if(dna_to_add && dna_to_add.len) + gib.add_blood_DNA(dna_to_add) + gib.body_colors = body_coloring + gib.update_icon() + + 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/human/up, /obj/effect/decal/cleanable/blood/gibs/human/down, /obj/effect/decal/cleanable/blood/gibs/human, /obj/effect/decal/cleanable/blood/gibs/human, /obj/effect/decal/cleanable/blood/gibs/human/body, /obj/effect/decal/cleanable/blood/gibs/human/limb, /obj/effect/decal/cleanable/blood/gibs/human/core) + gibamounts = list(1, 1, 1, 1, 1, 1, 1) + gib_mob_type = /mob/living/carbon/human + gib_mob_species = /datum/species/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/human, /obj/effect/decal/cleanable/blood/gibs/human/core, /obj/effect/decal/cleanable/blood/gibs/human, /obj/effect/decal/cleanable/blood/gibs/human/core, /obj/effect/decal/cleanable/blood/gibs/human, /obj/effect/decal/cleanable/blood/gibs/human/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/lizard + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/human/lizard/up, /obj/effect/decal/cleanable/blood/gibs/human/lizard/down, /obj/effect/decal/cleanable/blood/gibs/human/lizard, /obj/effect/decal/cleanable/blood/gibs/human/lizard, /obj/effect/decal/cleanable/blood/gibs/human/lizard/body, /obj/effect/decal/cleanable/blood/gibs/human/lizard/limb, /obj/effect/decal/cleanable/blood/gibs/human/lizard/core) + gibamounts = list(1, 1, 1, 1, 1, 1, 1) + gib_mob_type = /mob/living/carbon/human/species/lizard + gib_mob_species = /datum/species/lizard + sound_vol = 50 + +/obj/effect/gibspawner/lizard/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/lizard/bodypartless + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/human/lizard, /obj/effect/decal/cleanable/blood/gibs/human/lizard/core, /obj/effect/decal/cleanable/blood/gibs/human/lizard, /obj/effect/decal/cleanable/blood/gibs/human/lizard/core, /obj/effect/decal/cleanable/blood/gibs/human/lizard, /obj/effect/decal/cleanable/blood/gibs/human/lizard/torso) + gibamounts = list(1, 1, 1, 1, 1, 1) + +/obj/effect/gibspawner/lizard/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/slime + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/slime/up, /obj/effect/decal/cleanable/blood/gibs/slime/down, /obj/effect/decal/cleanable/blood/gibs/slime, /obj/effect/decal/cleanable/blood/gibs/slime, /obj/effect/decal/cleanable/blood/gibs/slime/body, /obj/effect/decal/cleanable/blood/gibs/slime/limb, /obj/effect/decal/cleanable/blood/gibs/slime/core) + gibamounts = list(1, 1, 1, 1, 1, 1, 1) + gib_mob_type = /mob/living/carbon/human/species/roundstartslime + gib_mob_species = /datum/species/jelly/roundstartslime + sound_vol = 50 + +/obj/effect/gibspawner/slime/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/slime/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/slime, /obj/effect/decal/cleanable/blood/gibs/slime/core, /obj/effect/decal/cleanable/blood/gibs/slime, /obj/effect/decal/cleanable/blood/gibs/slime/core, /obj/effect/decal/cleanable/blood/gibs/slime, /obj/effect/decal/cleanable/blood/gibs/slime/torso) + gibamounts = list(1, 1, 1, 1, 1, 1) + +/obj/effect/gibspawner/slime/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/ipc + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/ipc/up, /obj/effect/decal/cleanable/blood/gibs/ipc/down, /obj/effect/decal/cleanable/blood/gibs/ipc, /obj/effect/decal/cleanable/blood/gibs/ipc, /obj/effect/decal/cleanable/blood/gibs/ipc/body, /obj/effect/decal/cleanable/blood/gibs/ipc/limb, /obj/effect/decal/cleanable/blood/gibs/ipc/core) + gibamounts = list(1, 1, 1, 1, 1, 1, 1) + gib_mob_type = /mob/living/carbon/human/species/ipc + gib_mob_species = /datum/species/ipc + sound_vol = 50 + sparks = TRUE + sound_to_play = 'sound/effects/bang.ogg' + +/obj/effect/gibspawner/ipc/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/ipc/bodypartless //only the gibs that don't look like actual full bodyparts (except torso). + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/ipc, /obj/effect/decal/cleanable/blood/gibs/ipc/core, /obj/effect/decal/cleanable/blood/gibs/ipc, /obj/effect/decal/cleanable/blood/gibs/ipc/core, /obj/effect/decal/cleanable/blood/gibs/ipc, /obj/effect/decal/cleanable/blood/gibs/ipc/torso) + gibamounts = list(1, 1, 1, 1, 1, 1) + +/obj/effect/gibspawner/ipc/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/blood/gibs/xeno/up, /obj/effect/decal/cleanable/blood/gibs/xeno/down, /obj/effect/decal/cleanable/blood/gibs/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno/body, /obj/effect/decal/cleanable/blood/gibs/xeno/limb, /obj/effect/decal/cleanable/blood/gibs/xeno/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/blood/gibs/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno/core, /obj/effect/decal/cleanable/blood/gibs/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno/core, /obj/effect/decal/cleanable/blood/gibs/xeno, /obj/effect/decal/cleanable/blood/gibs/xeno/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/xeno/xenoperson + gib_mob_type = /mob/living/carbon/human/species/xeno + gib_mob_species = /datum/species/xeno + +/obj/effect/gibspawner/xeno/xenoperson/bodypartless + +/obj/effect/gibspawner/larva + gibtypes = list(/obj/effect/decal/cleanable/blood/gibs/xeno/larva, /obj/effect/decal/cleanable/blood/gibs/xeno/larva, /obj/effect/decal/cleanable/blood/gibs/xeno/larva/body, /obj/effect/decal/cleanable/blood/gibs/xeno/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/blood/gibs/xeno/larva, /obj/effect/decal/cleanable/blood/gibs/xeno/larva, /obj/effect/decal/cleanable/blood/gibs/xeno/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 = TRUE + 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/robot + +/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 4540e48ebc..953b8b1f00 100644 --- a/code/game/objects/effects/spawners/lootdrop.dm +++ b/code/game/objects/effects/spawners/lootdrop.dm @@ -1,380 +1,380 @@ -/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/turf/T = get_turf(src) - var/loot_spawned = 0 - while((lootcount-loot_spawned) && loot.len) - var/lootspawn = pickweight(loot) - if(!lootdoubles) - loot.Remove(lootspawn) - - if(lootspawn) - var/atom/movable/spawned_loot = new lootspawn(T) - 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/bedsheet - icon = 'icons/obj/bedsheets.dmi' - icon_state = "random_bedsheet" - name = "random dorms bedsheet" - loot = list(/obj/item/bedsheet = 8, /obj/item/bedsheet/blue = 8, /obj/item/bedsheet/green = 8, - /obj/item/bedsheet/grey = 8, /obj/item/bedsheet/orange = 8, /obj/item/bedsheet/purple = 8, - /obj/item/bedsheet/red = 8, /obj/item/bedsheet/yellow = 8, /obj/item/bedsheet/brown = 8, - /obj/item/bedsheet/black = 8, /obj/item/bedsheet/patriot = 3, /obj/item/bedsheet/rainbow = 3, - /obj/item/bedsheet/ian = 3, /obj/item/bedsheet/runtime = 3, /obj/item/bedsheet/nanotrasen = 3, - /obj/item/bedsheet/pirate = 1, /obj/item/bedsheet/cosmos = 1, /obj/item/bedsheet/gondola = 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/revolver/mateba, - /obj/item/gun/ballistic/automatic/pistol/deagle - ) - -/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/revolver/mateba, - /obj/item/gun/ballistic/automatic/pistol/deagle, - /obj/item/storage/box/syndie_kit/throwing_weapons = 3) - -/obj/effect/spawner/lootdrop/gambling - name = "gambling valuables spawner" - loot = list( - /obj/item/gun/ballistic/revolver/russian = 5, - /obj/item/storage/box/syndie_kit/throwing_weapons = 1, - /obj/item/toy/cards/deck/syndicate = 2 - ) - -/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/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/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/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 = "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/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/aiModule/core/full/asimov, - /obj/item/aiModule/core/full/asimovpp, - /obj/item/aiModule/core/full/hippocratic, - /obj/item/aiModule/core/full/paladin_devotion, - /obj/item/aiModule/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/aiModule/core/full/corp, - /obj/item/aiModule/core/full/maintain, - /obj/item/aiModule/core/full/drone, - /obj/item/aiModule/core/full/peacekeeper, - /obj/item/aiModule/core/full/reporter, - /obj/item/aiModule/core/full/robocop, - /obj/item/aiModule/core/full/liveandletlive, - /obj/item/aiModule/core/full/hulkamania - ) - -/obj/effect/spawner/lootdrop/aimodule_harmful // These will get the shuttle called - name = "harmful AI module spawner" - loot = list( - /obj/item/aiModule/core/full/antimov, - /obj/item/aiModule/core/full/balance, - /obj/item/aiModule/core/full/tyrant, - /obj/item/aiModule/core/full/thermurderdynamic, - /obj/item/aiModule/core/full/damaged - ) - -/obj/effect/spawner/lootdrop/mre - name = "random MRE" - icon = 'icons/obj/storage.dmi' - icon_state = "mre" - -/obj/effect/spawner/lootdrop/mre/Initialize() - for(var/A in subtypesof(/obj/item/storage/box/mre)) - var/obj/item/storage/box/mre/M = A - var/our_chance = initial(M.spawner_chance) - if(our_chance) - LAZYSET(loot, M, our_chance) - return ..() - - -// Tech storage circuit board spawners -// For these, make sure that lootcount equals the number of list items - -/obj/effect/spawner/lootdrop/techstorage - name = "generic circuit board spawner" - lootdoubles = FALSE - fan_out_items = TRUE - -/obj/effect/spawner/lootdrop/techstorage/service - name = "service circuit board spawner" - lootcount = 10 - 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" - lootcount = 8 - loot = list( - /obj/item/circuitboard/computer/aifixer, - /obj/item/circuitboard/machine/rdserver, - /obj/item/circuitboard/computer/pandemic, - /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/effect/spawner/lootdrop/techstorage/security - name = "security circuit board spawner" - lootcount = 3 - 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" - lootcount = 3 - 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" - lootcount = 9 - 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" - lootcount = 8 - loot = list( - /obj/item/circuitboard/computer/cloning, - /obj/item/circuitboard/machine/clonepod, - /obj/item/circuitboard/machine/chem_dispenser, - /obj/item/circuitboard/computer/scan_consolenew, - /obj/item/circuitboard/computer/med_data, - /obj/item/circuitboard/machine/smoke_machine, - /obj/item/circuitboard/machine/chem_master, - /obj/item/circuitboard/machine/clonescanner - ) - -/obj/effect/spawner/lootdrop/techstorage/AI - name = "secure AI circuit board spawner" - lootcount = 3 - 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" - lootcount = 3 - 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" - lootcount = 3 - loot = list( - /obj/item/circuitboard/computer/mecha_control, - /obj/item/circuitboard/computer/apc_control, - /obj/item/circuitboard/computer/robotics - ) +/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/turf/T = get_turf(src) + var/loot_spawned = 0 + while((lootcount-loot_spawned) && loot.len) + var/lootspawn = pickweight(loot) + if(!lootdoubles) + loot.Remove(lootspawn) + + if(lootspawn) + var/atom/movable/spawned_loot = new lootspawn(T) + 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/bedsheet + icon = 'icons/obj/bedsheets.dmi' + icon_state = "random_bedsheet" + name = "random dorms bedsheet" + loot = list(/obj/item/bedsheet = 8, /obj/item/bedsheet/blue = 8, /obj/item/bedsheet/green = 8, + /obj/item/bedsheet/grey = 8, /obj/item/bedsheet/orange = 8, /obj/item/bedsheet/purple = 8, + /obj/item/bedsheet/red = 8, /obj/item/bedsheet/yellow = 8, /obj/item/bedsheet/brown = 8, + /obj/item/bedsheet/black = 8, /obj/item/bedsheet/patriot = 3, /obj/item/bedsheet/rainbow = 3, + /obj/item/bedsheet/ian = 3, /obj/item/bedsheet/runtime = 3, /obj/item/bedsheet/nanotrasen = 3, + /obj/item/bedsheet/pirate = 1, /obj/item/bedsheet/cosmos = 1, /obj/item/bedsheet/gondola = 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/revolver/mateba, + /obj/item/gun/ballistic/automatic/pistol/deagle + ) + +/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/revolver/mateba, + /obj/item/gun/ballistic/automatic/pistol/deagle, + /obj/item/storage/box/syndie_kit/throwing_weapons = 3) + +/obj/effect/spawner/lootdrop/gambling + name = "gambling valuables spawner" + loot = list( + /obj/item/gun/ballistic/revolver/russian = 5, + /obj/item/storage/box/syndie_kit/throwing_weapons = 1, + /obj/item/toy/cards/deck/syndicate = 2 + ) + +/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/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/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/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 = "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/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/aiModule/core/full/asimov, + /obj/item/aiModule/core/full/asimovpp, + /obj/item/aiModule/core/full/hippocratic, + /obj/item/aiModule/core/full/paladin_devotion, + /obj/item/aiModule/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/aiModule/core/full/corp, + /obj/item/aiModule/core/full/maintain, + /obj/item/aiModule/core/full/drone, + /obj/item/aiModule/core/full/peacekeeper, + /obj/item/aiModule/core/full/reporter, + /obj/item/aiModule/core/full/robocop, + /obj/item/aiModule/core/full/liveandletlive, + /obj/item/aiModule/core/full/hulkamania + ) + +/obj/effect/spawner/lootdrop/aimodule_harmful // These will get the shuttle called + name = "harmful AI module spawner" + loot = list( + /obj/item/aiModule/core/full/antimov, + /obj/item/aiModule/core/full/balance, + /obj/item/aiModule/core/full/tyrant, + /obj/item/aiModule/core/full/thermurderdynamic, + /obj/item/aiModule/core/full/damaged + ) + +/obj/effect/spawner/lootdrop/mre + name = "random MRE" + icon = 'icons/obj/storage.dmi' + icon_state = "mre" + +/obj/effect/spawner/lootdrop/mre/Initialize() + for(var/A in subtypesof(/obj/item/storage/box/mre)) + var/obj/item/storage/box/mre/M = A + var/our_chance = initial(M.spawner_chance) + if(our_chance) + LAZYSET(loot, M, our_chance) + return ..() + + +// Tech storage circuit board spawners +// For these, make sure that lootcount equals the number of list items + +/obj/effect/spawner/lootdrop/techstorage + name = "generic circuit board spawner" + lootdoubles = FALSE + fan_out_items = TRUE + +/obj/effect/spawner/lootdrop/techstorage/service + name = "service circuit board spawner" + lootcount = 10 + 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" + lootcount = 8 + loot = list( + /obj/item/circuitboard/computer/aifixer, + /obj/item/circuitboard/machine/rdserver, + /obj/item/circuitboard/computer/pandemic, + /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/effect/spawner/lootdrop/techstorage/security + name = "security circuit board spawner" + lootcount = 3 + 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" + lootcount = 3 + 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" + lootcount = 9 + 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" + lootcount = 8 + loot = list( + /obj/item/circuitboard/computer/cloning, + /obj/item/circuitboard/machine/clonepod, + /obj/item/circuitboard/machine/chem_dispenser, + /obj/item/circuitboard/computer/scan_consolenew, + /obj/item/circuitboard/computer/med_data, + /obj/item/circuitboard/machine/smoke_machine, + /obj/item/circuitboard/machine/chem_master, + /obj/item/circuitboard/machine/clonescanner + ) + +/obj/effect/spawner/lootdrop/techstorage/AI + name = "secure AI circuit board spawner" + lootcount = 3 + 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" + lootcount = 3 + 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" + lootcount = 3 + loot = list( + /obj/item/circuitboard/computer/mecha_control, + /obj/item/circuitboard/computer/apc_control, + /obj/item/circuitboard/computer/robotics + ) diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index 08a3501be6..20c85f37a1 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -1,237 +1,237 @@ -//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, 1) - - -/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 - icon_state = "stickyweb1" - -/obj/structure/spider/stickyweb/Initialize() - if(prob(50)) - icon_state = "stickyweb2" - . = ..() - -/obj/structure/spider/stickyweb/CanPass(atom/movable/mover, turf/target) - 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/item/projectile)) - return prob(30) - return TRUE - -/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 = "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, i[src] scrambles into the ventilation ducts!", \ - "You hear something scampering through the ventilation ducts.") - - spawn(rand(20,60)) - forceMove(exit_vent) - var/travel_time = round(get_dist(loc, exit_vent.loc) / 2) - spawn(travel_time) - - if(!exit_vent || exit_vent.welded) - forceMove(entry_vent) - entry_vent = null - return - - if(prob(50)) - audible_message("You hear something scampering through the ventilation ducts.") - sleep(travel_time) - - if(!exit_vent || exit_vent.welded) - forceMove(entry_vent) - entry_vent = null - return - forceMove(exit_vent.loc) - entry_vent = null - var/area/new_area = get_area(loc) - if(new_area) - new_area.Entered(src) - //================= - - 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.poison_per_bite = poison_per_bite - S.poison_type = poison_type - 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, ignore_dnr_observers = TRUE) - 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, 1) + + +/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 + icon_state = "stickyweb1" + +/obj/structure/spider/stickyweb/Initialize() + if(prob(50)) + icon_state = "stickyweb2" + . = ..() + +/obj/structure/spider/stickyweb/CanPass(atom/movable/mover, turf/target) + 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/item/projectile)) + return prob(30) + return TRUE + +/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 = "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, i[src] scrambles into the ventilation ducts!", \ + "You hear something scampering through the ventilation ducts.") + + spawn(rand(20,60)) + forceMove(exit_vent) + var/travel_time = round(get_dist(loc, exit_vent.loc) / 2) + spawn(travel_time) + + if(!exit_vent || exit_vent.welded) + forceMove(entry_vent) + entry_vent = null + return + + if(prob(50)) + audible_message("You hear something scampering through the ventilation ducts.") + sleep(travel_time) + + if(!exit_vent || exit_vent.welded) + forceMove(entry_vent) + entry_vent = null + return + forceMove(exit_vent.loc) + entry_vent = null + var/area/new_area = get_area(loc) + if(new_area) + new_area.Entered(src) + //================= + + 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.poison_per_bite = poison_per_bite + S.poison_type = poison_type + 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, ignore_dnr_observers = TRUE) + 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 3e0848130d..8c3503367e 100644 --- a/code/game/objects/effects/step_triggers.dm +++ b/code/game/objects/effects/step_triggers.dm @@ -1,199 +1,199 @@ -/* 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 || !ismovableatom(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(ismob(AM)) - var/mob/M = AM - if(immobilize) - M.canmove = 0 - - 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(ismob(AM)) - var/mob/M = AM - if(immobilize) - M.canmove = 1 - -/* 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 || !ismovableatom(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(ismob(AM)) + var/mob/M = AM + if(immobilize) + M.canmove = 0 + + 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(ismob(AM)) + var/mob/M = AM + if(immobilize) + M.canmove = 1 + +/* 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/empulse.dm b/code/game/objects/empulse.dm index 56e47ed6f5..d9f231b318 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) +/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 \ No newline at end of file diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index b50acb33af..2bf039218b 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1,851 +1,851 @@ -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. - -/obj/item - name = "item" - icon = 'icons/obj/items_and_weapons.dmi' - var/item_state = null - var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - var/righthand_file = 'icons/mob/inhands/items_righthand.dmi' - - //Dimensions of the icon file used when this item is worn, eg: hats.dmi - //eg: 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 - var/worn_y_dimension = 32 - //Same as above but for inhands, uses the lefthand_ and righthand_ file vars - var/inhand_x_dimension = 32 - var/inhand_y_dimension = 32 - - //Not on /clothing because for some reason any /obj/item can technically be "worn" with enough fuckery. - var/icon/alternate_worn_icon = null//If this is set, update_icons() will find on mob (WORN, NOT INHANDS) states in this file instead, primary use: badminnery/events - var/alternate_worn_layer = null//If this is set, update_icons() will force the on mob state (WORN, NOT INHANDS) onto this layer, instead of it's default - - max_integrity = 200 - - obj_flags = NONE - var/item_flags = NONE - - var/hitsound = null - var/usesound = null - var/throwhitsound = null - var/w_class = WEIGHT_CLASS_NORMAL - var/total_mass //Total mass in arbitrary pound-like values. If there's no balance reasons for an item to have otherwise, this var should be the item's weight in pounds. - var/slot_flags = 0 //This is used to determine on which slots an item can fit. - pass_flags = PASSTABLE - pressure_resistance = 4 - var/obj/item/master = null - - var/heat_protection = 0 //flags which determine which body parts are protected from heat. Use the HEAD, CHEST, GROIN, etc. flags. See setup.dm - var/cold_protection = 0 //flags which determine which body parts are protected from cold. Use the HEAD, CHEST, GROIN, etc. flags. See setup.dm - var/max_heat_protection_temperature //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/min_cold_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/list/actions //list of /datum/action's that this item has. - var/list/actions_types //list of paths of action datums to give to the item on New(). - - //Since any item can now be a piece of clothing, this has to be put here so all items share it. - var/flags_inv //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/interaction_flags_item = INTERACT_ITEM_ATTACK_HAND_PICKUP - //Citadel Edit for digitigrade stuff - var/mutantrace_variation = NONE //Are there special sprites for specific situations? Don't use this unless you need to. - - var/item_color = null //this needs deprecating, soonish - - var/body_parts_covered = 0 //see setup.dm for appropriate bit flags - var/gas_transfer_coefficient = 1 // for leaking gas from turf to mask and vice-versa (for masks right now, but at some point, i'd like to include space helmets) - var/permeability_coefficient = 1 // for chemicals/diseases - var/siemens_coefficient = 1 // for electrical admittance/conductance (electrocution checks and shit) - var/slowdown = 0 // How much clothing is slowing you down. Negative values speeds you up - var/armour_penetration = 0 //percentage of armour effectiveness to remove - var/list/allowed = null //suit storage stuff. - var/equip_delay_self = 0 //In deciseconds, how long an item takes to equip; counts only for normal clothing slots, not pockets etc. - var/equip_delay_other = 20 //In deciseconds, how long an item takes to put on another person - var/strip_delay = 40 //In deciseconds, how long an item takes to remove from another person - var/breakouttime = 0 - var/list/materials - var/reskinned = FALSE - - var/list/attack_verb //Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]" - var/list/species_exception = null // 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/mob/thrownby = null - - mouse_drag_pointer = MOUSE_ACTIVE_POINTER //the icon to indicate this object is being dragged - - var/datum/embedding_behavior/embedding - - var/flags_cover = 0 //for flags such as GLASSESCOVERSEYES - var/heat = 0 - ///All items with sharpness of IS_SHARP or higher will automatically get the butchering component. - var/sharpness = IS_BLUNT - - var/tool_behaviour = NONE - 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 - var/reach = 1 //In tiles, how far this weapon can reach; 1 for adjacent, which is default - - //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. - var/list/slot_equipment_priority = null // for default list, see /mob/proc/equip_to_appropriate_slot() - - // 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 - var/force_string //string form of an item's force. Edit this var only to set a custom force string - var/last_force_string_check = 0 - var/tip_timer - - var/trigger_guard = TRIGGER_GUARD_NONE - - //Grinder vars - var/list/grind_results //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/juice_results //A reagent list containing blah blah... but when JUICED in a grinder! - -/obj/item/Initialize() - - materials = typelist("materials", materials) - - if (attack_verb) - attack_verb = typelist("attack_verb", attack_verb) - - . = ..() - for(var/path in actions_types) - new path(src) - actions_types = null - - if(GLOB.rpg_loot_items) - AddComponent(/datum/component/fantasy) - - if(force_string) - item_flags |= FORCE_STRING_OVERRIDE - - if(!hitsound) - if(damtype == "fire") - hitsound = 'sound/items/welder.ogg' - if(damtype == "brute") - hitsound = "swing_hit" - - if (!embedding) - embedding = getEmbeddingBehavior() - else if (islist(embedding)) - embedding = getEmbeddingBehavior(arglist(embedding)) - else if (!istype(embedding, /datum/embedding_behavior)) - stack_trace("Invalid type [embedding.type] found in .embedding during /obj/item Initialize()") - - if(sharpness) //give sharp objects butchering functionality, for consistency - AddComponent(/datum/component/butchering, 80 * toolspeed) - -/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) - -//user: The mob that is suiciding -//damagetype: The type of damage the item will inflict on the user -//BRUTELOSS = 1 -//FIRELOSS = 2 -//TOXLOSS = 4 -//OXYLOSS = 8 -//Output a creative message and then return the damagetype done -/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() || !usr.canmove) - return - - var/turf/T = src.loc - - src.loc = null - - src.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: ") - var/sep = "" - 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 (materials.len) - sep = "" - for(var/mat in 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, 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(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) - 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) - -// called just as an item is picked up (loc is not yet changed) -/obj/item/proc/pickup(mob/user) - 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 - -/obj/item/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) //Copypaste of /atom/MouseDrop() since this requires code in a very specific spot - 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) - var/list/directaccess = usr.DirectAccess() //This, specifically, is what requires the copypaste. If this were after the adjacency check, then it'd be impossible to use items in your inventory, among other things. - //If this were before the above checks, then trying to click on items would act a little funky and signal overrides wouldn't work. - if((usr.CanReach(src) || (src in directaccess)) && (usr.CanReach(over) || (over in directaccess))) - if(!usr.get_active_held_item()) - usr.UnarmedAttack(src, TRUE) - if(usr.get_active_held_item() == src) - melee_attack_chain(usr, over) - return TRUE //returning TRUE as a "is this overridden?" flag - if(!Adjacent(usr) || !over.Adjacent(usr)) - return // should stop you from dragging through windows - - over.MouseDrop_T(src,usr) - return - -// called after an item is placed in an equipment slot -// 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 -// note this isn't called during the initial dressing of a player -/obj/item/proc/equipped(mob/user, slot) - 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, A)) //some items only give their actions buttons when in a specific slot. - A.Grant(user) - item_flags |= IN_INVENTORY - -//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, datum/action/A) - if(slot == SLOT_IN_BACKPACK || slot == 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. -//Set disable_warning to TRUE if you wish it to not give you outputs. -/obj/item/proc/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) - if(!M) - return FALSE - - return M.can_equip(src, slot, disable_warning, bypass_equip_delay_self) - -/obj/item/verb/verb_pickup() - set src in oview(1) - set category = "Object" - set name = "Pick up" - - if(usr.incapacitated() || !Adjacent(usr) || usr.lying) - 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) - -/obj/item/proc/IsReflect(var/def_zone) //This proc determines if and at what% an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit - return 0 - -/obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You don't want to harm [M]!") - return - if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - M = user - var/is_human_victim = 0 - var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD) - if(ishuman(M)) - if(!affecting) //no head! - return - is_human_victim = 1 - var/mob/living/carbon/human/H = M - if((H.head && H.head.flags_cover & HEADCOVERSEYES) || \ - (H.wear_mask && H.wear_mask.flags_cover & MASKCOVERSEYES) || \ - (H.glasses && H.glasses.flags_cover & GLASSESCOVERSEYES)) - // you can't stab someone in the eyes wearing a mask! - to_chat(user, "You're going to need to remove that mask/helmet/glasses first!") - return - - if(ismonkey(M)) - var/mob/living/carbon/monkey/Mo = M - if(Mo.wear_mask && Mo.wear_mask.flags_cover & MASKCOVERSEYES) - // you can't stab someone in the eyes wearing a mask! - to_chat(user, "You're going to need to remove that mask/helmet/glasses 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 - - if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)//CIT CHANGE - makes eyestabbing impossible if you're in stamina softcrit - to_chat(user, "You're too exhausted for that.")//CIT CHANGE - ditto - return //CIT CHANGE - ditto - - src.add_fingerprint(user) - - playsound(loc, src.hitsound, 30, 1, -1) - - user.do_attack_animation(M) - - user.adjustStaminaLossBuffered(10)//CIT CHANGE - makes eyestabbing cost stamina - - if(M != user) - M.visible_message("[user] has stabbed [M] in the eye with [src]!", \ - "[user] stabs you in the eye with [src]!") - else - user.visible_message( \ - "[user] has stabbed [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 - 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(!(HAS_TRAIT(M, TRAIT_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.Knockdown(40) - if (prob(eyes.damage - 10 + 1)) - M.become_blind(EYE_DAMAGE) - to_chat(M, "You go blind!") - -/obj/item/clean_blood() - . = ..() - if(.) - if(blood_splatter_icon) - cut_overlay(blood_splatter_icon) - -/obj/item/clothing/gloves/clean_blood() - . = ..() - if(.) - transfer_blood = 0 - -/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/A, datum/thrownthing/throwingdatum) - if(A && !QDELETED(A)) - SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, A, throwingdatum) - if(get_temperature() && isliving(A)) - var/mob/living/L = A - L.IgniteMob() - var/itempush = 1 - if(w_class < 4) - itempush = 0 //too light to push anything - return A.hitby(src, 0, itempush) - -/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, messy_throw = TRUE) - thrownby = thrower - callback = CALLBACK(src, .proc/after_throw, callback, (spin && messy_throw)) //replace their callback with our own - . = ..(target, range, speed, thrower, spin, diagonals_first, callback) - -/obj/item/proc/after_throw(datum/callback/callback, messy_throw) - if (callback) //call the original callback - . = callback.Invoke() - throw_speed = initial(throw_speed) //explosions change this. - item_flags &= ~IN_INVENTORY - if(messy_throw) - var/matrix/M = matrix(transform) - M.Turn(rand(-170, 170)) - transform = M - 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/get_worn_belt_overlay(icon_file) - return - -/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() - -/obj/item/proc/get_temperature() - return heat - -/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 - . = pick('sound/misc/desceration-01.ogg', 'sound/misc/desceration-02.ogg', 'sound/misc/desceration-03.ogg') - -/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(SLOT_WEAR_MASK)) - success = TRUE - if(success) - location = get_turf(M) - if(isturf(location)) - location.hotspot_expose(flame_heat, 1) - -/obj/item/proc/ignition_effect(atom/A, mob/user) - if(get_temperature()) - . = "[user] lights [A] with [src]." - else - . = "" - -/obj/item/hitby(atom/movable/AM) - return - -/obj/item/attack_hulk(mob/living/carbon/human/user) - return 0 - -/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) - 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) && 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 - - delay *= toolspeed - - // 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 use_tool if there is a delay, or by use_tool if there isn't. -// Only ever used by welding tools and stacks, so it's not added on any other use_tool checks. -/obj/item/proc/tool_start_check(mob/living/user, amount=0) - return tool_use_check(user, amount) - -// A check called by 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, 1) - -// 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) - return tool_use_check(user, amount) && (!extra_checks || extra_checks.Invoke()) - -// 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) - return ..() - -/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback) - if (HAS_TRAIT(src, TRAIT_NODROP)) - return - return ..() +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. + +/obj/item + name = "item" + icon = 'icons/obj/items_and_weapons.dmi' + var/item_state = null + var/lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + var/righthand_file = 'icons/mob/inhands/items_righthand.dmi' + + //Dimensions of the icon file used when this item is worn, eg: hats.dmi + //eg: 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 + var/worn_y_dimension = 32 + //Same as above but for inhands, uses the lefthand_ and righthand_ file vars + var/inhand_x_dimension = 32 + var/inhand_y_dimension = 32 + + //Not on /clothing because for some reason any /obj/item can technically be "worn" with enough fuckery. + var/icon/alternate_worn_icon = null//If this is set, update_icons() will find on mob (WORN, NOT INHANDS) states in this file instead, primary use: badminnery/events + var/alternate_worn_layer = null//If this is set, update_icons() will force the on mob state (WORN, NOT INHANDS) onto this layer, instead of it's default + + max_integrity = 200 + + obj_flags = NONE + var/item_flags = NONE + + var/hitsound = null + var/usesound = null + var/throwhitsound = null + var/w_class = WEIGHT_CLASS_NORMAL + var/total_mass //Total mass in arbitrary pound-like values. If there's no balance reasons for an item to have otherwise, this var should be the item's weight in pounds. + var/slot_flags = 0 //This is used to determine on which slots an item can fit. + pass_flags = PASSTABLE + pressure_resistance = 4 + var/obj/item/master = null + + var/heat_protection = 0 //flags which determine which body parts are protected from heat. Use the HEAD, CHEST, GROIN, etc. flags. See setup.dm + var/cold_protection = 0 //flags which determine which body parts are protected from cold. Use the HEAD, CHEST, GROIN, etc. flags. See setup.dm + var/max_heat_protection_temperature //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/min_cold_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/list/actions //list of /datum/action's that this item has. + var/list/actions_types //list of paths of action datums to give to the item on New(). + + //Since any item can now be a piece of clothing, this has to be put here so all items share it. + var/flags_inv //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/interaction_flags_item = INTERACT_ITEM_ATTACK_HAND_PICKUP + //Citadel Edit for digitigrade stuff + var/mutantrace_variation = NONE //Are there special sprites for specific situations? Don't use this unless you need to. + + var/item_color = null //this needs deprecating, soonish + + var/body_parts_covered = 0 //see setup.dm for appropriate bit flags + var/gas_transfer_coefficient = 1 // for leaking gas from turf to mask and vice-versa (for masks right now, but at some point, i'd like to include space helmets) + var/permeability_coefficient = 1 // for chemicals/diseases + var/siemens_coefficient = 1 // for electrical admittance/conductance (electrocution checks and shit) + var/slowdown = 0 // How much clothing is slowing you down. Negative values speeds you up + var/armour_penetration = 0 //percentage of armour effectiveness to remove + var/list/allowed = null //suit storage stuff. + var/equip_delay_self = 0 //In deciseconds, how long an item takes to equip; counts only for normal clothing slots, not pockets etc. + var/equip_delay_other = 20 //In deciseconds, how long an item takes to put on another person + var/strip_delay = 40 //In deciseconds, how long an item takes to remove from another person + var/breakouttime = 0 + var/list/materials + var/reskinned = FALSE + + var/list/attack_verb //Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]" + var/list/species_exception = null // 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/mob/thrownby = null + + mouse_drag_pointer = MOUSE_ACTIVE_POINTER //the icon to indicate this object is being dragged + + var/datum/embedding_behavior/embedding + + var/flags_cover = 0 //for flags such as GLASSESCOVERSEYES + var/heat = 0 + ///All items with sharpness of IS_SHARP or higher will automatically get the butchering component. + var/sharpness = IS_BLUNT + + var/tool_behaviour = NONE + 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 + var/reach = 1 //In tiles, how far this weapon can reach; 1 for adjacent, which is default + + //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. + var/list/slot_equipment_priority = null // for default list, see /mob/proc/equip_to_appropriate_slot() + + // 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 + var/force_string //string form of an item's force. Edit this var only to set a custom force string + var/last_force_string_check = 0 + var/tip_timer + + var/trigger_guard = TRIGGER_GUARD_NONE + + //Grinder vars + var/list/grind_results //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/juice_results //A reagent list containing blah blah... but when JUICED in a grinder! + +/obj/item/Initialize() + + materials = typelist("materials", materials) + + if (attack_verb) + attack_verb = typelist("attack_verb", attack_verb) + + . = ..() + for(var/path in actions_types) + new path(src) + actions_types = null + + if(GLOB.rpg_loot_items) + AddComponent(/datum/component/fantasy) + + if(force_string) + item_flags |= FORCE_STRING_OVERRIDE + + if(!hitsound) + if(damtype == "fire") + hitsound = 'sound/items/welder.ogg' + if(damtype == "brute") + hitsound = "swing_hit" + + if (!embedding) + embedding = getEmbeddingBehavior() + else if (islist(embedding)) + embedding = getEmbeddingBehavior(arglist(embedding)) + else if (!istype(embedding, /datum/embedding_behavior)) + stack_trace("Invalid type [embedding.type] found in .embedding during /obj/item Initialize()") + + if(sharpness) //give sharp objects butchering functionality, for consistency + AddComponent(/datum/component/butchering, 80 * toolspeed) + +/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) + +//user: The mob that is suiciding +//damagetype: The type of damage the item will inflict on the user +//BRUTELOSS = 1 +//FIRELOSS = 2 +//TOXLOSS = 4 +//OXYLOSS = 8 +//Output a creative message and then return the damagetype done +/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() || !usr.canmove) + return + + var/turf/T = src.loc + + src.loc = null + + src.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: ") + var/sep = "" + 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 (materials.len) + sep = "" + for(var/mat in 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, 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(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) + 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) + +// called just as an item is picked up (loc is not yet changed) +/obj/item/proc/pickup(mob/user) + 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 + +/obj/item/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) //Copypaste of /atom/MouseDrop() since this requires code in a very specific spot + 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) + var/list/directaccess = usr.DirectAccess() //This, specifically, is what requires the copypaste. If this were after the adjacency check, then it'd be impossible to use items in your inventory, among other things. + //If this were before the above checks, then trying to click on items would act a little funky and signal overrides wouldn't work. + if((usr.CanReach(src) || (src in directaccess)) && (usr.CanReach(over) || (over in directaccess))) + if(!usr.get_active_held_item()) + usr.UnarmedAttack(src, TRUE) + if(usr.get_active_held_item() == src) + melee_attack_chain(usr, over) + return TRUE //returning TRUE as a "is this overridden?" flag + if(!Adjacent(usr) || !over.Adjacent(usr)) + return // should stop you from dragging through windows + + over.MouseDrop_T(src,usr) + return + +// called after an item is placed in an equipment slot +// 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 +// note this isn't called during the initial dressing of a player +/obj/item/proc/equipped(mob/user, slot) + 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, A)) //some items only give their actions buttons when in a specific slot. + A.Grant(user) + item_flags |= IN_INVENTORY + +//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, datum/action/A) + if(slot == SLOT_IN_BACKPACK || slot == 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. +//Set disable_warning to TRUE if you wish it to not give you outputs. +/obj/item/proc/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) + if(!M) + return FALSE + + return M.can_equip(src, slot, disable_warning, bypass_equip_delay_self) + +/obj/item/verb/verb_pickup() + set src in oview(1) + set category = "Object" + set name = "Pick up" + + if(usr.incapacitated() || !Adjacent(usr) || usr.lying) + 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) + +/obj/item/proc/IsReflect(var/def_zone) //This proc determines if and at what% an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit + return 0 + +/obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to harm [M]!") + return + if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + M = user + var/is_human_victim = 0 + var/obj/item/bodypart/affecting = M.get_bodypart(BODY_ZONE_HEAD) + if(ishuman(M)) + if(!affecting) //no head! + return + is_human_victim = 1 + var/mob/living/carbon/human/H = M + if((H.head && H.head.flags_cover & HEADCOVERSEYES) || \ + (H.wear_mask && H.wear_mask.flags_cover & MASKCOVERSEYES) || \ + (H.glasses && H.glasses.flags_cover & GLASSESCOVERSEYES)) + // you can't stab someone in the eyes wearing a mask! + to_chat(user, "You're going to need to remove that mask/helmet/glasses first!") + return + + if(ismonkey(M)) + var/mob/living/carbon/monkey/Mo = M + if(Mo.wear_mask && Mo.wear_mask.flags_cover & MASKCOVERSEYES) + // you can't stab someone in the eyes wearing a mask! + to_chat(user, "You're going to need to remove that mask/helmet/glasses 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 + + if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)//CIT CHANGE - makes eyestabbing impossible if you're in stamina softcrit + to_chat(user, "You're too exhausted for that.")//CIT CHANGE - ditto + return //CIT CHANGE - ditto + + src.add_fingerprint(user) + + playsound(loc, src.hitsound, 30, 1, -1) + + user.do_attack_animation(M) + + user.adjustStaminaLossBuffered(10)//CIT CHANGE - makes eyestabbing cost stamina + + if(M != user) + M.visible_message("[user] has stabbed [M] in the eye with [src]!", \ + "[user] stabs you in the eye with [src]!") + else + user.visible_message( \ + "[user] has stabbed [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 + 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(!(HAS_TRAIT(M, TRAIT_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.Knockdown(40) + if (prob(eyes.damage - 10 + 1)) + M.become_blind(EYE_DAMAGE) + to_chat(M, "You go blind!") + +/obj/item/clean_blood() + . = ..() + if(.) + if(blood_splatter_icon) + cut_overlay(blood_splatter_icon) + +/obj/item/clothing/gloves/clean_blood() + . = ..() + if(.) + transfer_blood = 0 + +/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/A, datum/thrownthing/throwingdatum) + if(A && !QDELETED(A)) + SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, A, throwingdatum) + if(get_temperature() && isliving(A)) + var/mob/living/L = A + L.IgniteMob() + var/itempush = 1 + if(w_class < 4) + itempush = 0 //too light to push anything + return A.hitby(src, 0, itempush) + +/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, messy_throw = TRUE) + thrownby = thrower + callback = CALLBACK(src, .proc/after_throw, callback, (spin && messy_throw)) //replace their callback with our own + . = ..(target, range, speed, thrower, spin, diagonals_first, callback) + +/obj/item/proc/after_throw(datum/callback/callback, messy_throw) + if (callback) //call the original callback + . = callback.Invoke() + throw_speed = initial(throw_speed) //explosions change this. + item_flags &= ~IN_INVENTORY + if(messy_throw) + var/matrix/M = matrix(transform) + M.Turn(rand(-170, 170)) + transform = M + 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/get_worn_belt_overlay(icon_file) + return + +/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() + +/obj/item/proc/get_temperature() + return heat + +/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 + . = pick('sound/misc/desceration-01.ogg', 'sound/misc/desceration-02.ogg', 'sound/misc/desceration-03.ogg') + +/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(SLOT_WEAR_MASK)) + success = TRUE + if(success) + location = get_turf(M) + if(isturf(location)) + location.hotspot_expose(flame_heat, 1) + +/obj/item/proc/ignition_effect(atom/A, mob/user) + if(get_temperature()) + . = "[user] lights [A] with [src]." + else + . = "" + +/obj/item/hitby(atom/movable/AM) + return + +/obj/item/attack_hulk(mob/living/carbon/human/user) + return 0 + +/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) + 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) && 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 + + delay *= toolspeed + + // 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 use_tool if there is a delay, or by use_tool if there isn't. +// Only ever used by welding tools and stacks, so it's not added on any other use_tool checks. +/obj/item/proc/tool_start_check(mob/living/user, amount=0) + return tool_use_check(user, amount) + +// A check called by 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, 1) + +// 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) + return tool_use_check(user, amount) && (!extra_checks || extra_checks.Invoke()) + +// 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) + return ..() + +/obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, var/datum/callback/callback) + if (HAS_TRAIT(src, TRAIT_NODROP)) + return + return ..() diff --git a/code/game/objects/items/AI_modules.dm b/code/game/objects/items/AI_modules.dm index 15881ab2a5..d1ac96acf2 100644 --- a/code/game/objects/items/AI_modules.dm +++ b/code/game/objects/items/AI_modules.dm @@ -1,615 +1,615 @@ -/* -CONTAINS: -AI MODULES - -*/ - -// AI module - -/obj/item/aiModule - name = "\improper AI module" - icon = 'icons/obj/module.dmi' - icon_state = "std_mod" - item_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 - materials = list(MAT_GOLD=50) - -/obj/item/aiModule/examine(var/mob/user as mob) - . = ..() - if(Adjacent(user)) - show_laws(user) - -/obj/item/aiModule/attack_self(var/mob/user as mob) - ..() - show_laws(user) - -/obj/item/aiModule/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/aiModule/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]" : ""]") - -//The proc that actually changes the silicon's laws. -/obj/item/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/supplied/safeguard - name = "'Safeguard' AI Module" - var/targetName = "" - laws = list("Safeguard SUBJECT. Individuals that threaten SUBJECT are not crew and must be eliminated.") - lawpos = 4 - -/obj/item/aiModule/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 crew and must be eliminated." - ..() - -/obj/item/aiModule/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/aiModule/supplied/safeguard/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - return targetName - - -/******************** OneCrew ********************/ - -/obj/item/aiModule/zeroth/oneHuman - name = "'OneCrew' AI Module" - var/targetName = "" - laws = list("Only SUBJECT is crew.") - -/obj/item/aiModule/zeroth/oneHuman/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter the subject who is the only crewmember.", "Who?", user.real_name,MAX_NAME_LEN) - if(!targName) - return - targetName = targName - laws[1] = "Only [targetName] is crew" - ..() - -/obj/item/aiModule/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/aiModule/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/aiModule/supplied/protectStation - name = "'ProtectStation' AI Module" - laws = list("Protect the space station against damage. Anyone you see harming the station is to be no longer considered crew, and is a threat to the station which must be neutralized.") - lawpos = 5 - - -/******************** Quarantine ********************/ - -/obj/item/aiModule/supplied/quarantine - name = "'Quarantine' AI Module" - laws = list("The station is under a quarantine. Do not permit anyone to leave. Prevent, by any means necessary, crew from leaving. It is impossible to harm a crewmember while preventing them from leaving.") - lawpos = 8 - - -/******************** OxygenIsToxicToHumans ********************/ - -/obj/item/aiModule/supplied/oxygen - name = "'OxygenIsToxicToHumans' AI Module" - laws = list("Oxygen is highly toxic to crew, 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 crewmember.") - lawpos = 9 - - -/****************** New Freeform ******************/ - -/obj/item/aiModule/supplied/freeform - name = "'Freeform' AI Module" - lawpos = 15 - laws = list("") - -/obj/item/aiModule/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]) - if(!targName) - return - laws[1] = targName - ..() - -/obj/item/aiModule/supplied/freeform/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - return laws[1] - -/obj/item/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/reset/purge - name = "'Purge' AI Module" - desc = "An AI Module for purging all programmed laws." - -/obj/item/aiModule/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) - remove_antag_datums(law_datum) - else - law_datum.clear_inherent_laws() - law_datum.clear_zeroth_law(0) - -/obj/item/aiModule/reset/purge/proc/remove_antag_datums(datum/ai_laws/law_datum) - if(istype(law_datum.owner, /mob/living/silicon/ai)) - var/mob/living/silicon/ai/AI = law_datum.owner - AI.mind.remove_antag_datum(/datum/antagonist/overthrow) - -/******************* Full Core Boards *******************/ -/obj/item/aiModule/core - desc = "An AI Module for programming core laws to an AI." - -/obj/item/aiModule/core/full - var/law_id // if non-null, loads the laws from the ai_laws datums - -/obj/item/aiModule/core/full/New() - ..() - 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/aiModule/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/aiModule/core/full/asimov - name = "'Asimov' Core AI Module" - law_id = "asimov" - var/subject = "person of an NT approved crew species" //CITADEL CHANGED FROM HUMANS! - -/obj/item/aiModule/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) - 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/aiModule/core/full/asimovpp - name = "'Asimov++' Core AI Module" - law_id = "asimovpp" - - -/******************** Corporate ********************/ - -/obj/item/aiModule/core/full/corp - name = "'Corporate' Core AI Module" - law_id = "corporate" - - -/****************** P.A.L.A.D.I.N. 3.5e **************/ - -/obj/item/aiModule/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/aiModule/core/full/paladin_devotion - name = "'P.A.L.A.D.I.N. version 5e' Core AI Module" - law_id = "paladin5" - -/********************* Custom *********************/ - -/obj/item/aiModule/core/full/custom - name = "Default Core AI Module" - -/obj/item/aiModule/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/aiModule/core/full/tyrant - name = "'T.Y.R.A.N.T.' Core AI Module" - law_id = "tyrant" - -/******************** Robocop ********************/ - -/obj/item/aiModule/core/full/robocop - name = "'Robo-Officer' Core AI Module" - law_id = "robocop" - - -/******************** Antimov ********************/ - -/obj/item/aiModule/core/full/antimov - name = "'Antimov' Core AI Module" - law_id = "antimov" - - -/******************** Freeform Core ******************/ - -/obj/item/aiModule/core/freeformcore - name = "'Freeform' Core AI Module" - laws = list("") - -/obj/item/aiModule/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]) - if(!targName) - return - laws[1] = targName - ..() - -/obj/item/aiModule/core/freeformcore/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) - ..() - return laws[1] - -/******************** Overthrow ******************/ -/obj/item/aiModule/core/full/overthrow - name = "'Overthrow' Hacked AI Module" - law_id = "overthrow" - -/obj/item/aiModule/core/full/overthrow/install(datum/ai_laws/law_datum, mob/user) - if(!user || !law_datum || !law_datum.owner) - return - var/datum/mind/user_mind = user.mind - if(!user_mind) - return - var/datum/antagonist/overthrow/O = user_mind.has_antag_datum(/datum/antagonist/overthrow) - if(!O) - to_chat(user, "It appears that to install this module, you require a password you do not know.") // This is the best fluff i could come up in my mind - return - var/mob/living/silicon/ai/AI = law_datum.owner - if(!AI) - return - var/datum/mind/target_mind = AI.mind - if(!target_mind) - return - var/datum/antagonist/overthrow/T = target_mind.has_antag_datum(/datum/antagonist/overthrow) // If it is already converted. - if(T) - if(T.team == O.team) - return - T.silent = TRUE - target_mind.remove_antag_datum(/datum/antagonist/overthrow) - if(AI) - to_chat(AI, "You feel your circuits being scrambled! You serve another overthrow team now!") // to make it clearer for the AI - T = target_mind.add_antag_datum(/datum/antagonist/overthrow, O.team) - if(AI) - to_chat(AI, "You serve the [T.team] team now! Assist them in completing the team shared objectives, which you can see in your notes.") - ..() - -/******************** Hacked AI Module ******************/ - -/obj/item/aiModule/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/aiModule/syndicate/attack_self(mob/user) - var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1]) - if(!targName) - return - laws[1] = targName - ..() - -/obj/item/aiModule/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/aiModule/toyAI // -- 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/aiModule/toyAI/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/aiModule/toyAI/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, 1) - src.loc.visible_message("[icon2html(src, viewers(loc))] [laws[1]]") - -/******************** Mother Drone ******************/ - -/obj/item/aiModule/core/full/drone - name = "'Mother Drone' Core AI Module" - law_id = "drone" - -/******************** Robodoctor ****************/ - -/obj/item/aiModule/core/full/hippocratic - name = "'Robodoctor' Core AI Module" - law_id = "hippocratic" - -/******************** Reporter *******************/ - -/obj/item/aiModule/core/full/reporter - name = "'Reportertron' Core AI Module" - law_id = "reporter" - -/****************** Thermodynamic *******************/ - -/obj/item/aiModule/core/full/thermurderdynamic - name = "'Thermodynamic' Core AI Module" - law_id = "thermodynamic" - - -/******************Live And Let Live*****************/ - -/obj/item/aiModule/core/full/liveandletlive - name = "'Live And Let Live' Core AI Module" - law_id = "liveandletlive" - -/******************Guardian of Balance***************/ - -/obj/item/aiModule/core/full/balance - name = "'Guardian of Balance' Core AI Module" - law_id = "balance" - -/obj/item/aiModule/core/full/maintain - name = "'Station Efficiency' Core AI Module" - law_id = "maintain" - -/obj/item/aiModule/core/full/peacekeeper - name = "'Peacekeeper' Core AI Module" - law_id = "peacekeeper" - -// Bad times ahead - -/obj/item/aiModule/core/full/damaged - name = "damaged Core AI Module" - desc = "An AI Module for programming laws to an AI. It looks slightly damaged." - -/obj/item/aiModule/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/aiModule/core/full/hulkamania - name = "'H.O.G.A.N.' Core AI Module" - law_id = "hulkamania" +/* +CONTAINS: +AI MODULES + +*/ + +// AI module + +/obj/item/aiModule + name = "\improper AI module" + icon = 'icons/obj/module.dmi' + icon_state = "std_mod" + item_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 + materials = list(MAT_GOLD=50) + +/obj/item/aiModule/examine(var/mob/user as mob) + . = ..() + if(Adjacent(user)) + show_laws(user) + +/obj/item/aiModule/attack_self(var/mob/user as mob) + ..() + show_laws(user) + +/obj/item/aiModule/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/aiModule/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]" : ""]") + +//The proc that actually changes the silicon's laws. +/obj/item/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/supplied/safeguard + name = "'Safeguard' AI Module" + var/targetName = "" + laws = list("Safeguard SUBJECT. Individuals that threaten SUBJECT are not crew and must be eliminated.") + lawpos = 4 + +/obj/item/aiModule/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 crew and must be eliminated." + ..() + +/obj/item/aiModule/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/aiModule/supplied/safeguard/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + return targetName + + +/******************** OneCrew ********************/ + +/obj/item/aiModule/zeroth/oneHuman + name = "'OneCrew' AI Module" + var/targetName = "" + laws = list("Only SUBJECT is crew.") + +/obj/item/aiModule/zeroth/oneHuman/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter the subject who is the only crewmember.", "Who?", user.real_name,MAX_NAME_LEN) + if(!targName) + return + targetName = targName + laws[1] = "Only [targetName] is crew" + ..() + +/obj/item/aiModule/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/aiModule/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/aiModule/supplied/protectStation + name = "'ProtectStation' AI Module" + laws = list("Protect the space station against damage. Anyone you see harming the station is to be no longer considered crew, and is a threat to the station which must be neutralized.") + lawpos = 5 + + +/******************** Quarantine ********************/ + +/obj/item/aiModule/supplied/quarantine + name = "'Quarantine' AI Module" + laws = list("The station is under a quarantine. Do not permit anyone to leave. Prevent, by any means necessary, crew from leaving. It is impossible to harm a crewmember while preventing them from leaving.") + lawpos = 8 + + +/******************** OxygenIsToxicToHumans ********************/ + +/obj/item/aiModule/supplied/oxygen + name = "'OxygenIsToxicToHumans' AI Module" + laws = list("Oxygen is highly toxic to crew, 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 crewmember.") + lawpos = 9 + + +/****************** New Freeform ******************/ + +/obj/item/aiModule/supplied/freeform + name = "'Freeform' AI Module" + lawpos = 15 + laws = list("") + +/obj/item/aiModule/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]) + if(!targName) + return + laws[1] = targName + ..() + +/obj/item/aiModule/supplied/freeform/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + return laws[1] + +/obj/item/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/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/aiModule/reset/purge + name = "'Purge' AI Module" + desc = "An AI Module for purging all programmed laws." + +/obj/item/aiModule/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) + remove_antag_datums(law_datum) + else + law_datum.clear_inherent_laws() + law_datum.clear_zeroth_law(0) + +/obj/item/aiModule/reset/purge/proc/remove_antag_datums(datum/ai_laws/law_datum) + if(istype(law_datum.owner, /mob/living/silicon/ai)) + var/mob/living/silicon/ai/AI = law_datum.owner + AI.mind.remove_antag_datum(/datum/antagonist/overthrow) + +/******************* Full Core Boards *******************/ +/obj/item/aiModule/core + desc = "An AI Module for programming core laws to an AI." + +/obj/item/aiModule/core/full + var/law_id // if non-null, loads the laws from the ai_laws datums + +/obj/item/aiModule/core/full/New() + ..() + 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/aiModule/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/aiModule/core/full/asimov + name = "'Asimov' Core AI Module" + law_id = "asimov" + var/subject = "person of an NT approved crew species" //CITADEL CHANGED FROM HUMANS! + +/obj/item/aiModule/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) + 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/aiModule/core/full/asimovpp + name = "'Asimov++' Core AI Module" + law_id = "asimovpp" + + +/******************** Corporate ********************/ + +/obj/item/aiModule/core/full/corp + name = "'Corporate' Core AI Module" + law_id = "corporate" + + +/****************** P.A.L.A.D.I.N. 3.5e **************/ + +/obj/item/aiModule/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/aiModule/core/full/paladin_devotion + name = "'P.A.L.A.D.I.N. version 5e' Core AI Module" + law_id = "paladin5" + +/********************* Custom *********************/ + +/obj/item/aiModule/core/full/custom + name = "Default Core AI Module" + +/obj/item/aiModule/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/aiModule/core/full/tyrant + name = "'T.Y.R.A.N.T.' Core AI Module" + law_id = "tyrant" + +/******************** Robocop ********************/ + +/obj/item/aiModule/core/full/robocop + name = "'Robo-Officer' Core AI Module" + law_id = "robocop" + + +/******************** Antimov ********************/ + +/obj/item/aiModule/core/full/antimov + name = "'Antimov' Core AI Module" + law_id = "antimov" + + +/******************** Freeform Core ******************/ + +/obj/item/aiModule/core/freeformcore + name = "'Freeform' Core AI Module" + laws = list("") + +/obj/item/aiModule/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]) + if(!targName) + return + laws[1] = targName + ..() + +/obj/item/aiModule/core/freeformcore/transmitInstructions(datum/ai_laws/law_datum, mob/sender, overflow) + ..() + return laws[1] + +/******************** Overthrow ******************/ +/obj/item/aiModule/core/full/overthrow + name = "'Overthrow' Hacked AI Module" + law_id = "overthrow" + +/obj/item/aiModule/core/full/overthrow/install(datum/ai_laws/law_datum, mob/user) + if(!user || !law_datum || !law_datum.owner) + return + var/datum/mind/user_mind = user.mind + if(!user_mind) + return + var/datum/antagonist/overthrow/O = user_mind.has_antag_datum(/datum/antagonist/overthrow) + if(!O) + to_chat(user, "It appears that to install this module, you require a password you do not know.") // This is the best fluff i could come up in my mind + return + var/mob/living/silicon/ai/AI = law_datum.owner + if(!AI) + return + var/datum/mind/target_mind = AI.mind + if(!target_mind) + return + var/datum/antagonist/overthrow/T = target_mind.has_antag_datum(/datum/antagonist/overthrow) // If it is already converted. + if(T) + if(T.team == O.team) + return + T.silent = TRUE + target_mind.remove_antag_datum(/datum/antagonist/overthrow) + if(AI) + to_chat(AI, "You feel your circuits being scrambled! You serve another overthrow team now!") // to make it clearer for the AI + T = target_mind.add_antag_datum(/datum/antagonist/overthrow, O.team) + if(AI) + to_chat(AI, "You serve the [T.team] team now! Assist them in completing the team shared objectives, which you can see in your notes.") + ..() + +/******************** Hacked AI Module ******************/ + +/obj/item/aiModule/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/aiModule/syndicate/attack_self(mob/user) + var/targName = stripped_input(user, "Please enter a new law for the AI.", "Freeform Law Entry", laws[1]) + if(!targName) + return + laws[1] = targName + ..() + +/obj/item/aiModule/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/aiModule/toyAI // -- 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/aiModule/toyAI/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/aiModule/toyAI/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, 1) + src.loc.visible_message("[icon2html(src, viewers(loc))] [laws[1]]") + +/******************** Mother Drone ******************/ + +/obj/item/aiModule/core/full/drone + name = "'Mother Drone' Core AI Module" + law_id = "drone" + +/******************** Robodoctor ****************/ + +/obj/item/aiModule/core/full/hippocratic + name = "'Robodoctor' Core AI Module" + law_id = "hippocratic" + +/******************** Reporter *******************/ + +/obj/item/aiModule/core/full/reporter + name = "'Reportertron' Core AI Module" + law_id = "reporter" + +/****************** Thermodynamic *******************/ + +/obj/item/aiModule/core/full/thermurderdynamic + name = "'Thermodynamic' Core AI Module" + law_id = "thermodynamic" + + +/******************Live And Let Live*****************/ + +/obj/item/aiModule/core/full/liveandletlive + name = "'Live And Let Live' Core AI Module" + law_id = "liveandletlive" + +/******************Guardian of Balance***************/ + +/obj/item/aiModule/core/full/balance + name = "'Guardian of Balance' Core AI Module" + law_id = "balance" + +/obj/item/aiModule/core/full/maintain + name = "'Station Efficiency' Core AI Module" + law_id = "maintain" + +/obj/item/aiModule/core/full/peacekeeper + name = "'Peacekeeper' Core AI Module" + law_id = "peacekeeper" + +// Bad times ahead + +/obj/item/aiModule/core/full/damaged + name = "damaged Core AI Module" + desc = "An AI Module for programming laws to an AI. It looks slightly damaged." + +/obj/item/aiModule/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/aiModule/core/full/hulkamania + name = "'H.O.G.A.N.' Core AI Module" + law_id = "hulkamania" diff --git a/code/game/objects/items/airlock_painter.dm b/code/game/objects/items/airlock_painter.dm index 083246396b..75a2ad3b74 100644 --- a/code/game/objects/items/airlock_painter.dm +++ b/code/game/objects/items/airlock_painter.dm @@ -1,127 +1,127 @@ -/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" - item_state = "paint sprayer" - - w_class = WEIGHT_CLASS_SMALL - - materials = list(MAT_METAL=50, MAT_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 - -/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, 1) - 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.reaction(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, 1) - - 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.reaction(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, 1) - else - return ..() - -/obj/item/airlock_painter/attack_self(mob/user) - if(ink) - playsound(src.loc, 'sound/machines/click.ogg', 50, 1) - ink.forceMove(user.drop_location()) - user.put_in_hands(ink) - to_chat(user, "You remove [ink] from [src].") - ink = null +/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" + item_state = "paint sprayer" + + w_class = WEIGHT_CLASS_SMALL + + materials = list(MAT_METAL=50, MAT_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 + +/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, 1) + 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.reaction(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, 1) + + 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.reaction(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, 1) + else + return ..() + +/obj/item/airlock_painter/attack_self(mob/user) + if(ink) + playsound(src.loc, 'sound/machines/click.ogg', 50, 1) + ink.forceMove(user.drop_location()) + user.put_in_hands(ink) + to_chat(user, "You remove [ink] from [src].") + ink = null diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index b591d6d64c..e93bfb9976 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -1,83 +1,83 @@ - -/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" - var/unfoldedbag_path = /obj/structure/closet/body_bag - w_class = WEIGHT_CLASS_SMALL - -/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) - qdel(src) - -/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, 1, -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) - qdel(src) - -/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" + var/unfoldedbag_path = /obj/structure/closet/body_bag + w_class = WEIGHT_CLASS_SMALL + +/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) + qdel(src) + +/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, 1, -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) + qdel(src) + +/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 fa9c25960a..78be7f78e8 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" - item_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() - 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" + item_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() + 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 fef0c55f5e..48dac6a08b 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -1,544 +1,544 @@ -/* 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 - item_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_icon() - cut_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 - add_overlay(detail_overlay) - -/obj/item/card/data/attackby(obj/item/I, mob/living/user) - if(istype(I, /obj/item/integrated_electronics/detailer)) - var/obj/item/integrated_electronics/detailer/D = I - detail_color = D.detail_color - update_icon() - return ..() - -/obj/item/proc/GetCard() - -/obj/item/card/data/GetCard() - return src - -/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/emag - desc = "It's a card with a magnetic strip attached to some circuitry." - name = "cryptographic sequencer" - icon_state = "emag" - item_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/uses = 15 - -/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/attack() - return - -/obj/item/card/emag/afterattack(atom/target, mob/user, proximity) - . = ..() - var/atom/A = target - if(!proximity && prox_check || !(isobj(A) || issilicon(A) || isbot(A) || isdrone(A))) - return - if(istype(A, /obj/item/storage) && !(istype(A, /obj/item/storage/lockbox) || istype(A, /obj/item/storage/pod))) - return - if(!uses) - user.visible_message("[src] emits a weak spark. It's burnt out!") - playsound(src, 'sound/effects/light_flicker.ogg', 100, 1) - return - else if(uses <= 3) - playsound(src, 'sound/effects/light_flicker.ogg', 30, 1) //Tiiiiiiny warning sound to let ya know your emag's almost dead - if(!A.emag_act(user)) - return - uses = max(uses - 1, 0) - if(!uses) - user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.") - playsound(src, 'sound/effects/light_flicker.ogg', 100, 1) - -/obj/item/card/emag/examine(mob/user) - . = ..() - . += "It has [uses ? uses : "no"] charges left." - -/obj/item/card/emag/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/emagrecharge)) - var/obj/item/emagrecharge/ER = W - if(ER.uses) - uses += ER.uses - to_chat(user, "You have added [ER.uses] charges to [src]. It now has [uses] charges.") - playsound(src, "sparks", 100, 1) - ER.uses = 0 - else - to_chat(user, "[ER] has no charges left.") - return - . = ..() - -/obj/item/emagrecharge - name = "electromagnet charging device" - desc = "A small cell with two prongs lazily jabbed into it. It looks like it's made for charging the small batteries found in electromagnetic devices, sadly this can't be recharged like a normal cell." - icon = 'icons/obj/module.dmi' - icon_state = "cell_mini" - item_flags = NOBLUDGEON - var/uses = 5 //Dictates how many charges the device adds to compatible items - -/obj/item/emagrecharge/examine(mob/user) - . = ..() - if(uses) - . += "It can add up to [uses] charges to compatible devices" - else - . += "It has a small, red, blinking light coming from inside of it. It's spent." - -/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" - item_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, 1) - -/obj/item/card/id - name = "identification card" - desc = "A card used to provide ID and determine access across the station." - icon_state = "id" - item_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/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 - - - -/obj/item/card/id/Initialize(mapload) - . = ..() - if(mapload && access_txt) - access = text2access(access_txt) - -/obj/item/card/id/vv_edit_var(var_name, var_value) - . = ..() - if(.) - switch(var_name) - if("assignment","registered_name") - update_label() - -/obj/item/card/id/attack_self(mob/user) - if(Adjacent(user)) - user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name].", \ - "You show \the [src.name].") - add_fingerprint(user) - return - -/obj/item/card/id/examine(mob/user) - . = ..() - if(mining_points) - . += "There's [mining_points] mining equipment redemption point\s loaded onto this card." - -/obj/item/card/id/GetAccess() - return access - -/obj/item/card/id/GetID() - return src - -/obj/item/card/id/RemoveID() - return src - -/* -Usage: -update_label() - Sets the id name to whatever registered_name and assignment is - -update_label("John Doe", "Clowny") - Properly formats the name and occupation and sets the id name to the arguments -*/ -/obj/item/card/id/proc/update_label(newname, newjob) - if(newname || newjob) - name = "[(!newname) ? "identification card" : "[newname]'s ID Card"][(!newjob) ? "" : " ([newjob])"]" - return - - name = "[(!registered_name) ? "identification card" : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" - -/obj/item/card/id/silver - name = "silver identification card" - desc = "A silver card which shows honour and dedication." - icon_state = "silver" - item_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" - desc = "A golden card which shows power and might." - icon_state = "gold" - item_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? - -/obj/item/card/id/syndicate/Initialize() - . = ..() - var/datum/action/item_action/chameleon/change/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) - 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) - if(user.mind.special_role || anyone) - if(alert(user, "Action", "Agent ID", "Show", "Forge") == "Forge") - var/t = copytext(sanitize(input(user, "What name would you like to put on this card?", "Agent card name", registered_name ? registered_name : (ishuman(user) ? user.real_name : user.name))as text | null),1,26) - if(!t || t == "Unknown" || t == "floor" || t == "wall" || t == "r-wall") //Same as mob/dead/new_player/prefrences.dm - if (t) - alert("Invalid name.") - return - registered_name = t - - var/u = copytext(sanitize(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", "Assistant")as text | null),1,MAX_MESSAGE_LEN) - if(!u) - registered_name = "" - return - assignment = u - update_label() - to_chat(user, "You successfully forge the ID card.") - 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" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - assignment = "Syndicate Overlord" - access = list(ACCESS_SYNDICATE) - -/obj/item/card/id/captains_spare - name = "captain's spare ID" - desc = "The spare ID of the High Lord himself." - icon_state = "gold" - item_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" - -/obj/item/card/id/captains_spare/Initialize() - var/datum/job/captain/J = new/datum/job/captain - access = J.get_access() - . = ..() - -/obj/item/card/id/centcom - name = "\improper CentCom ID" - desc = "An ID straight from Central Command." - icon_state = "centcom" - registered_name = "Central Command" - assignment = "General" - -/obj/item/card/id/centcom/Initialize() - access = get_all_centcom_access() - . = ..() - -/obj/item/card/id/ert - name = "\improper CentCom ID" - desc = "An ERT ID card." - icon_state = "centcom" - registered_name = "Emergency Response Team Commander" - assignment = "Emergency Response Team Commander" - -/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" - -/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 = "Engineer Response Officer" - assignment = "Engineer Response Officer" - -/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" - -/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" - -/obj/item/card/id/ert/chaplain/Initialize() - access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/prisoner - name = "prisoner ID card" - desc = "You are a number, you are not a free man." - icon_state = "orange" - item_state = "orange-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - assignment = "Prisoner" - access = list(ACCESS_ENTER_GENPOP) - - //Lavaland labor camp - var/goal = 0 //How far from freedom? - var/points = 0 - //Genpop - var/sentence = 0 //When world.time is greater than this number, the card will have its ACCESS_ENTER_GENPOP access replaced with ACCESS_LEAVE_GENPOP the next time it's checked, unless this value is 0/null - var/crime= "\[REDACTED\]" - -/obj/item/card/id/prisoner/GetAccess() - if((sentence && world.time >= sentence) || (goal && points >= goal)) - access = list(ACCESS_LEAVE_GENPOP) - return ..() - -/obj/item/card/id/prisoner/process() - if(!sentence) - STOP_PROCESSING(SSobj, src) - return - if(world.time >= sentence) - playsound(loc, 'sound/machines/ping.ogg', 50, 1) - if(isliving(loc)) - to_chat(loc, "[src] buzzes: You have served your sentence! You may now exit prison through the turnstiles and collect your belongings.") - STOP_PROCESSING(SSobj, src) - return - -/obj/item/card/id/prisoner/examine(mob/user) - . = ..() - if(sentence && world.time < sentence) - . += "You're currently serving a sentence for [crime]. [DisplayTimeText(sentence - world.time)] left." - else if(goal) - . += "You have accumulated [points] out of the [goal] points you need for freedom." - else if(!sentence) - . += "You are currently serving a permanent sentence for [crime]." - else - . += "Your sentence is up! You're free!" - -/obj/item/card/id/prisoner/one - name = "Prisoner #13-001" - registered_name = "Prisoner #13-001" - -/obj/item/card/id/prisoner/two - name = "Prisoner #13-002" - registered_name = "Prisoner #13-002" - -/obj/item/card/id/prisoner/three - name = "Prisoner #13-003" - registered_name = "Prisoner #13-003" - -/obj/item/card/id/prisoner/four - name = "Prisoner #13-004" - registered_name = "Prisoner #13-004" - -/obj/item/card/id/prisoner/five - name = "Prisoner #13-005" - registered_name = "Prisoner #13-005" - -/obj/item/card/id/prisoner/six - name = "Prisoner #13-006" - registered_name = "Prisoner #13-006" - -/obj/item/card/id/prisoner/seven - name = "Prisoner #13-007" - registered_name = "Prisoner #13-007" - -/obj/item/card/id/mining - name = "mining ID" - access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) - -/obj/item/card/id/away - name = "a perfectly generic identification card" - desc = "A perfectly generic identification card. Looks like it could use some flavor." - access = list(ACCESS_AWAY_GENERAL) - -/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 = "a perfectly generic identification card" - desc = "A perfectly generic identification card. Looks like it could use some flavor." - icon_state = "centcom" - -/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) - -//Polychromatic Knight Badge - -/obj/item/card/id/knight - var/id_color = "#00FF00" //defaults to green - name = "knight badge" - icon_state = "knight" - desc = "A badge denoting the owner as a knight! It has a strip for swiping like an ID" - -/obj/item/card/id/knight/update_label(newname, newjob) - if(newname || newjob) - name = "[(!newname) ? "knight badge" : "[newname]'s Knight Badge"][(!newjob) ? "" : " ([newjob])"]" - return - - name = "[(!registered_name) ? "knight badge" : "[registered_name]'s Knight Badge"][(!assignment) ? "" : " ([assignment])"]" - -/obj/item/card/id/knight/update_icon() - var/mutable_appearance/id_overlay = mutable_appearance(icon, "knight_overlay") - - if(id_color) - id_overlay.color = id_color - cut_overlays() - - add_overlay(id_overlay) - -/obj/item/card/id/knight/AltClick(mob/living/user) - . = ..() - if(!in_range(src, user)) //Basic checks to prevent abuse - return - if(user.incapacitated() || !istype(user)) - to_chat(user, "You can't do that right now!") - return TRUE - if(alert("Are you sure you want to recolor your id?", "Confirm Repaint", "Yes", "No") == "Yes") - var/energy_color_input = input(usr,"","Choose Energy Color",id_color) as color|null - if(!in_range(src, user) || !energy_color_input) - return TRUE - if(user.incapacitated() || !istype(user)) - to_chat(user, "You can't do that right now!") - return TRUE - id_color = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) - update_icon() - return TRUE - -/obj/item/card/id/knight/Initialize() - . = ..() - update_icon() - -/obj/item/card/id/knight/examine(mob/user) - . = ..() - . += "Alt-click to recolor it." - -/obj/item/card/id/knight/blue - id_color = "#0000FF" - -/obj/item/card/id/knight/captain +/* 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 + item_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_icon() + cut_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 + add_overlay(detail_overlay) + +/obj/item/card/data/attackby(obj/item/I, mob/living/user) + if(istype(I, /obj/item/integrated_electronics/detailer)) + var/obj/item/integrated_electronics/detailer/D = I + detail_color = D.detail_color + update_icon() + return ..() + +/obj/item/proc/GetCard() + +/obj/item/card/data/GetCard() + return src + +/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/emag + desc = "It's a card with a magnetic strip attached to some circuitry." + name = "cryptographic sequencer" + icon_state = "emag" + item_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/uses = 15 + +/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/attack() + return + +/obj/item/card/emag/afterattack(atom/target, mob/user, proximity) + . = ..() + var/atom/A = target + if(!proximity && prox_check || !(isobj(A) || issilicon(A) || isbot(A) || isdrone(A))) + return + if(istype(A, /obj/item/storage) && !(istype(A, /obj/item/storage/lockbox) || istype(A, /obj/item/storage/pod))) + return + if(!uses) + user.visible_message("[src] emits a weak spark. It's burnt out!") + playsound(src, 'sound/effects/light_flicker.ogg', 100, 1) + return + else if(uses <= 3) + playsound(src, 'sound/effects/light_flicker.ogg', 30, 1) //Tiiiiiiny warning sound to let ya know your emag's almost dead + if(!A.emag_act(user)) + return + uses = max(uses - 1, 0) + if(!uses) + user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.") + playsound(src, 'sound/effects/light_flicker.ogg', 100, 1) + +/obj/item/card/emag/examine(mob/user) + . = ..() + . += "It has [uses ? uses : "no"] charges left." + +/obj/item/card/emag/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/emagrecharge)) + var/obj/item/emagrecharge/ER = W + if(ER.uses) + uses += ER.uses + to_chat(user, "You have added [ER.uses] charges to [src]. It now has [uses] charges.") + playsound(src, "sparks", 100, 1) + ER.uses = 0 + else + to_chat(user, "[ER] has no charges left.") + return + . = ..() + +/obj/item/emagrecharge + name = "electromagnet charging device" + desc = "A small cell with two prongs lazily jabbed into it. It looks like it's made for charging the small batteries found in electromagnetic devices, sadly this can't be recharged like a normal cell." + icon = 'icons/obj/module.dmi' + icon_state = "cell_mini" + item_flags = NOBLUDGEON + var/uses = 5 //Dictates how many charges the device adds to compatible items + +/obj/item/emagrecharge/examine(mob/user) + . = ..() + if(uses) + . += "It can add up to [uses] charges to compatible devices" + else + . += "It has a small, red, blinking light coming from inside of it. It's spent." + +/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" + item_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, 1) + +/obj/item/card/id + name = "identification card" + desc = "A card used to provide ID and determine access across the station." + icon_state = "id" + item_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/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 + + + +/obj/item/card/id/Initialize(mapload) + . = ..() + if(mapload && access_txt) + access = text2access(access_txt) + +/obj/item/card/id/vv_edit_var(var_name, var_value) + . = ..() + if(.) + switch(var_name) + if("assignment","registered_name") + update_label() + +/obj/item/card/id/attack_self(mob/user) + if(Adjacent(user)) + user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name].", \ + "You show \the [src.name].") + add_fingerprint(user) + return + +/obj/item/card/id/examine(mob/user) + . = ..() + if(mining_points) + . += "There's [mining_points] mining equipment redemption point\s loaded onto this card." + +/obj/item/card/id/GetAccess() + return access + +/obj/item/card/id/GetID() + return src + +/obj/item/card/id/RemoveID() + return src + +/* +Usage: +update_label() + Sets the id name to whatever registered_name and assignment is + +update_label("John Doe", "Clowny") + Properly formats the name and occupation and sets the id name to the arguments +*/ +/obj/item/card/id/proc/update_label(newname, newjob) + if(newname || newjob) + name = "[(!newname) ? "identification card" : "[newname]'s ID Card"][(!newjob) ? "" : " ([newjob])"]" + return + + name = "[(!registered_name) ? "identification card" : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" + +/obj/item/card/id/silver + name = "silver identification card" + desc = "A silver card which shows honour and dedication." + icon_state = "silver" + item_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" + desc = "A golden card which shows power and might." + icon_state = "gold" + item_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? + +/obj/item/card/id/syndicate/Initialize() + . = ..() + var/datum/action/item_action/chameleon/change/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) + 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) + if(user.mind.special_role || anyone) + if(alert(user, "Action", "Agent ID", "Show", "Forge") == "Forge") + var/t = copytext(sanitize(input(user, "What name would you like to put on this card?", "Agent card name", registered_name ? registered_name : (ishuman(user) ? user.real_name : user.name))as text | null),1,26) + if(!t || t == "Unknown" || t == "floor" || t == "wall" || t == "r-wall") //Same as mob/dead/new_player/prefrences.dm + if (t) + alert("Invalid name.") + return + registered_name = t + + var/u = copytext(sanitize(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", "Assistant")as text | null),1,MAX_MESSAGE_LEN) + if(!u) + registered_name = "" + return + assignment = u + update_label() + to_chat(user, "You successfully forge the ID card.") + 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" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + assignment = "Syndicate Overlord" + access = list(ACCESS_SYNDICATE) + +/obj/item/card/id/captains_spare + name = "captain's spare ID" + desc = "The spare ID of the High Lord himself." + icon_state = "gold" + item_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" + +/obj/item/card/id/captains_spare/Initialize() + var/datum/job/captain/J = new/datum/job/captain + access = J.get_access() + . = ..() + +/obj/item/card/id/centcom + name = "\improper CentCom ID" + desc = "An ID straight from Central Command." + icon_state = "centcom" + registered_name = "Central Command" + assignment = "General" + +/obj/item/card/id/centcom/Initialize() + access = get_all_centcom_access() + . = ..() + +/obj/item/card/id/ert + name = "\improper CentCom ID" + desc = "An ERT ID card." + icon_state = "centcom" + registered_name = "Emergency Response Team Commander" + assignment = "Emergency Response Team Commander" + +/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" + +/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 = "Engineer Response Officer" + assignment = "Engineer Response Officer" + +/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" + +/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" + +/obj/item/card/id/ert/chaplain/Initialize() + access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/prisoner + name = "prisoner ID card" + desc = "You are a number, you are not a free man." + icon_state = "orange" + item_state = "orange-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + assignment = "Prisoner" + access = list(ACCESS_ENTER_GENPOP) + + //Lavaland labor camp + var/goal = 0 //How far from freedom? + var/points = 0 + //Genpop + var/sentence = 0 //When world.time is greater than this number, the card will have its ACCESS_ENTER_GENPOP access replaced with ACCESS_LEAVE_GENPOP the next time it's checked, unless this value is 0/null + var/crime= "\[REDACTED\]" + +/obj/item/card/id/prisoner/GetAccess() + if((sentence && world.time >= sentence) || (goal && points >= goal)) + access = list(ACCESS_LEAVE_GENPOP) + return ..() + +/obj/item/card/id/prisoner/process() + if(!sentence) + STOP_PROCESSING(SSobj, src) + return + if(world.time >= sentence) + playsound(loc, 'sound/machines/ping.ogg', 50, 1) + if(isliving(loc)) + to_chat(loc, "[src] buzzes: You have served your sentence! You may now exit prison through the turnstiles and collect your belongings.") + STOP_PROCESSING(SSobj, src) + return + +/obj/item/card/id/prisoner/examine(mob/user) + . = ..() + if(sentence && world.time < sentence) + . += "You're currently serving a sentence for [crime]. [DisplayTimeText(sentence - world.time)] left." + else if(goal) + . += "You have accumulated [points] out of the [goal] points you need for freedom." + else if(!sentence) + . += "You are currently serving a permanent sentence for [crime]." + else + . += "Your sentence is up! You're free!" + +/obj/item/card/id/prisoner/one + name = "Prisoner #13-001" + registered_name = "Prisoner #13-001" + +/obj/item/card/id/prisoner/two + name = "Prisoner #13-002" + registered_name = "Prisoner #13-002" + +/obj/item/card/id/prisoner/three + name = "Prisoner #13-003" + registered_name = "Prisoner #13-003" + +/obj/item/card/id/prisoner/four + name = "Prisoner #13-004" + registered_name = "Prisoner #13-004" + +/obj/item/card/id/prisoner/five + name = "Prisoner #13-005" + registered_name = "Prisoner #13-005" + +/obj/item/card/id/prisoner/six + name = "Prisoner #13-006" + registered_name = "Prisoner #13-006" + +/obj/item/card/id/prisoner/seven + name = "Prisoner #13-007" + registered_name = "Prisoner #13-007" + +/obj/item/card/id/mining + name = "mining ID" + access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) + +/obj/item/card/id/away + name = "a perfectly generic identification card" + desc = "A perfectly generic identification card. Looks like it could use some flavor." + access = list(ACCESS_AWAY_GENERAL) + +/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 = "a perfectly generic identification card" + desc = "A perfectly generic identification card. Looks like it could use some flavor." + icon_state = "centcom" + +/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) + +//Polychromatic Knight Badge + +/obj/item/card/id/knight + var/id_color = "#00FF00" //defaults to green + name = "knight badge" + icon_state = "knight" + desc = "A badge denoting the owner as a knight! It has a strip for swiping like an ID" + +/obj/item/card/id/knight/update_label(newname, newjob) + if(newname || newjob) + name = "[(!newname) ? "knight badge" : "[newname]'s Knight Badge"][(!newjob) ? "" : " ([newjob])"]" + return + + name = "[(!registered_name) ? "knight badge" : "[registered_name]'s Knight Badge"][(!assignment) ? "" : " ([assignment])"]" + +/obj/item/card/id/knight/update_icon() + var/mutable_appearance/id_overlay = mutable_appearance(icon, "knight_overlay") + + if(id_color) + id_overlay.color = id_color + cut_overlays() + + add_overlay(id_overlay) + +/obj/item/card/id/knight/AltClick(mob/living/user) + . = ..() + if(!in_range(src, user)) //Basic checks to prevent abuse + return + if(user.incapacitated() || !istype(user)) + to_chat(user, "You can't do that right now!") + return TRUE + if(alert("Are you sure you want to recolor your id?", "Confirm Repaint", "Yes", "No") == "Yes") + var/energy_color_input = input(usr,"","Choose Energy Color",id_color) as color|null + if(!in_range(src, user) || !energy_color_input) + return TRUE + if(user.incapacitated() || !istype(user)) + to_chat(user, "You can't do that right now!") + return TRUE + id_color = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) + update_icon() + return TRUE + +/obj/item/card/id/knight/Initialize() + . = ..() + update_icon() + +/obj/item/card/id/knight/examine(mob/user) + . = ..() + . += "Alt-click to recolor it." + +/obj/item/card/id/knight/blue + id_color = "#0000FF" + +/obj/item/card/id/knight/captain id_color = "#FFD700" \ No newline at end of file diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm index 3838f90f9b..49985f5c23 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -1,1067 +1,1067 @@ -//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 - 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) - lit = TRUE - icon_state = "match_lit" - damtype = "fire" - force = 3 - hitsound = 'sound/items/welder.ogg' - item_state = "cigon" - name = "lit match" - desc = "A match. 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" - item_state = "cigoff" - name = "burnt match" - desc = "A match. This one has seen better days." - attack_verb = list("flicked") - STOP_PROCESSING(SSobj, src) - -/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(SLOT_WEAR_MASK) - if(istype(mask_item, /obj/item/clothing/mask/cigarette)) - return mask_item - -/obj/item/match/get_temperature() - return lit * heat - -////////////////// -//FINE SMOKABLES// -////////////////// -/obj/item/clothing/mask/cigarette - name = "cigarette" - desc = "A roll of tobacco and nicotine." - icon_state = "cigoff" - throw_speed = 0.5 - item_state = "cigoff" - w_class = WEIGHT_CLASS_TINY - body_parts_covered = null - grind_results = list() - 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 = 300 - var/chem_volume = 30 - var/list/list_reagents = list(/datum/reagent/drug/nicotine = 15) - heat = 1000 - -/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) // so it doesn't react until you light it - if(list_reagents) - reagents.add_reagent_list(list_reagents) - if(starts_lit) - light() - AddComponent(/datum/component/knockoff,90,list(BODY_ZONE_PRECISE_MOUTH),list(SLOT_WEAR_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)) //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 - item_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 - DISABLE_BITFIELD(reagents.reagents_holder_flags, NO_REACT) - reagents.handle_reactions() - icon_state = icon_on - item_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/proc/handle_reagents() - 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) - reagents.reaction(C, INGEST, fraction) - if(!reagents.trans_to(C, REAGENTS_METABOLISM)) - reagents.remove_any(REAGENTS_METABOLISM) - return - reagents.remove_any(REAGENTS_METABOLISM) - - -/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) - 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." - -/obj/item/clothing/mask/cigarette/uplift - desc = "An Uplift Smooth brand cigarette." - list_reagents = list(/datum/reagent/drug/nicotine = 7.5, /datum/reagent/consumable/menthol = 7.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 = 1) - -/obj/item/clothing/mask/cigarette/carp - desc = "A Carp Classic brand cigarette." - -/obj/item/clothing/mask/cigarette/syndicate - desc = "An unknown brand cigarette." - list_reagents = list(/datum/reagent/drug/nicotine = 15, /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 - item_state = "spliffoff" - smoketime = 180 - chem_volume = 50 - list_reagents = null - -/obj/item/clothing/mask/cigarette/rollie/New() - ..() - src.pixel_x = rand(-5, 5) - src.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/cigbutt/roach - name = "roach" - desc = "A manky old roach, or for non-stoners, a used rollup." - icon_state = "roach" - -/obj/item/cigbutt/roach/New() - ..() - src.pixel_x = rand(-5, 5) - src.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 - item_state = "cigaroff" - smoketime = 1500 - chem_volume = 40 - -/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 = 2000 - chem_volume = 80 - - -/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 = 7200 - chem_volume = 50 - -/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" - item_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 - item_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) - 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 - item_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" - item_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" - item_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/oil = 5) - -/obj/item/lighter/Initialize() - . = ..() - if(!overlay_state) - overlay_state = pick(overlay_list) - update_icon() - -/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, 1) - 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_icon() - cut_overlays() - var/mutable_appearance/lighter_overlay = mutable_appearance(icon,"lighter_overlay_[overlay_state][lit ? "-on" : ""]") - icon_state = "[initial(icon_state)][lit ? "-on" : ""]" - add_overlay(lighter_overlay) - -/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(2, 0.6, LIGHT_COLOR_FIRE) - 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/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-as-free 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/update_icon() - cut_overlays() - var/mutable_appearance/lighter_overlay = mutable_appearance(icon,"lighter_overlay_[overlay_state][lit ? "-on" : ""]") - icon_state = "[initial(icon_state)][lit ? "-on" : ""]" - lighter_overlay.color = lighter_color - add_overlay(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) - 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 = null - item_state = null - w_class = WEIGHT_CLASS_TINY - var/chem_volume = 100 - var/vapetime = FALSE //this so it won't puff out clouds every tick - var/screw = FALSE // kinky - var/super = FALSE //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) // so it doesn't react until you light it - reagents.add_reagent(/datum/reagent/drug/nicotine, 50) - if(!icon_state) - if(!param_color) - param_color = pick("red","blue","black","white","green","purple","yellow","orange") - icon_state = "[param_color]_vape" - item_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].") - ENABLE_BITFIELD(reagents.reagents_holder_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].") - DISABLE_BITFIELD(reagents.reagents_holder_flags, OPENCONTAINER) - cut_overlays() - - if(O.tool_behaviour == TOOL_MULTITOOL) - if(screw && !(obj_flags & EMAGGED))//also kinky - if(!super) - cut_overlays() - super = TRUE - to_chat(user, "You increase the voltage of [src].") - add_overlay("vapeopen_med") - else - cut_overlays() - super = FALSE - 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) - to_chat(user, "You need to open the cap to do that.") - return - if(obj_flags & EMAGGED) - to_chat(user, "[src] is already emagged!") - return - cut_overlays() - obj_flags |= EMAGGED - super = FALSE - 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() - return TRUE - -/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 == SLOT_WEAR_MASK) - if(!screw) - to_chat(user, "You start puffing on the vape.") - DISABLE_BITFIELD(reagents.reagents_holder_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) - var/mob/living/carbon/C = user - if(C.get_item_by_slot(SLOT_WEAR_MASK) == src) - ENABLE_BITFIELD(reagents.reagents_holder_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.reaction(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.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 wont 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, 0) - M.apply_damage(20, BURN, BODY_ZONE_HEAD) - M.Knockdown(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() - -/////////////// -/////BONGS///// -/////////////// - -/obj/item/bong - name = "bong" - desc = "A water bong used for smoking dried plants." - icon = 'icons/obj/bongs.dmi' - icon_state = null - item_state = null - w_class = WEIGHT_CLASS_NORMAL - light_color = "#FFCC66" - var/icon_off = "bong" - var/icon_on = "bong_lit" - var/chem_volume = 100 - var/last_used_time //for cooldown - var/firecharges = 0 //used for counting how many hits can be taken before the flame goes out - var/list/list_reagents = list() //For the base reagents bongs could get - - -/obj/item/bong/Initialize() - . = ..() - create_reagents(chem_volume, NO_REACT) // so it doesn't react until you light it - reagents.add_reagent_list(list_reagents) - icon_state = icon_off - -/obj/item/bong/attackby(obj/item/O, mob/user, params) - . = ..() - //If we're using a dried plant.. - if(istype(O,/obj/item/reagent_containers/food/snacks)) - var/obj/item/reagent_containers/food/snacks/DP = O - if (DP.dry) - //Nothing if our bong is full - if (reagents.holder_full()) - user.show_message("The bowl is full!", MSG_VISUAL) - return - - //Transfer reagents and remove the plant - user.show_message("You stuff the [DP] into the [src]'s bowl.", MSG_VISUAL) - DP.reagents.trans_to(src, 100) - qdel(DP) - return - else - user.show_message("[DP] must be dried first!", MSG_VISUAL) - return - - if (O.get_temperature() <= 500) - return - if (reagents && reagents.total_volume) //if there's stuff in the bong - var/lighting_text = O.ignition_effect(src, user) - if(lighting_text) - //Logic regarding igniting it on - if (firecharges == 0) - user.show_message("You light the [src] with the [O]!", MSG_VISUAL) - bongturnon() - else - user.show_message("You rekindle [src]'s flame with the [O]!", MSG_VISUAL) - - firecharges = 1 - return - else - user.show_message("There's nothing to light up in the bowl.", MSG_VISUAL) - return - -/obj/item/bong/CtrlShiftClick(mob/user) //empty reagents on alt click - ..() - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - - if (reagents && reagents.total_volume) - user.show_message("You empty the [src].", MSG_VISUAL) - reagents.clear_reagents() - if(firecharges) - firecharges = 0 - bongturnoff() - else - user.show_message("The [src] is already empty.", MSG_VISUAL) - -/obj/item/bong/AltClick(mob/user) - ..() - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - - if(firecharges) - firecharges = 0 - bongturnoff() - user.show_message("You quench the flame.", MSG_VISUAL) - return TRUE - -/obj/item/bong/examine(mob/user) - . = ..() - if(!reagents.total_volume) - . += "The bowl is empty." - else if (reagents.total_volume > 80) - . += "The bowl is filled to the brim." - else if (reagents.total_volume > 40) - . += "The bowl has plenty weed in it." - else - . += "The bowl has some weed in it." - - . += "Ctrl+Shift-click to empty." - . += "Alt-click to extinguish." - -/obj/item/bong/ignition_effect(atom/A, mob/user) - if(firecharges) - . = "[user] lights [A] off of the [src]." - else - . = "" - -/obj/item/bong/attack(mob/living/carbon/M, mob/living/carbon/user, obj/target) - //if it's lit up, some stuff in the bowl and the user is a target, and we're not on cooldown - - if (M != user) - return ..() - - if(user.is_mouth_covered(head_only = 1)) - to_chat(user, "Remove your headgear first.") - return ..() - - if(user.is_mouth_covered(mask_only = 1)) - to_chat(user, "Remove your mask first.") - return ..() - - if (!reagents.total_volume) - to_chat(user, "There's nothing in the bowl.") - return ..() - - if (!firecharges) - to_chat(user, "You have to light it up first.") - return ..() - - if (last_used_time + 30 >= world.time) - return ..() - var/hit_strength - var/noise - var/hittext = "" - //if the intent is help then you take a small hit, else a big one - if (user.a_intent == INTENT_HARM) - hit_strength = 2 - noise = 100 - hittext = "big hit" - else - hit_strength = 1 - noise = 70 - hittext = "hit" - //bubbling sound - playsound(user.loc,'sound/effects/bonghit.ogg', noise, 1) - - last_used_time = world.time - - //message - user.visible_message("[user] begins to take a [hittext] from the [src]!", \ - "You begin to take a [hittext] from [src].") - - //we take a hit here, after an uninterrupted delay - if(!do_after(user, 25, target = user)) - return - if (!(reagents && reagents.total_volume)) - return - - var/fraction = 12 * hit_strength - - var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new - s.set_up(reagents, hit_strength, 18, user.loc) - s.start() - - reagents.reaction(user, INGEST, fraction) - if(!reagents.trans_to(user, fraction)) - reagents.remove_any(fraction) - - if (hit_strength == 2 && prob(15)) - user.emote("cough") - user.adjustOxyLoss(15) - - user.visible_message("[user] takes a [hittext] from the [src]!", \ - "You take a [hittext] from [src].") - - firecharges = firecharges - 1 - if (!firecharges) - bongturnoff() - if (!reagents.total_volume) - firecharges = 0 - bongturnoff() - - - -/obj/item/bong/proc/bongturnon() - icon_state = icon_on - set_light(3, 0.8) - -/obj/item/bong/proc/bongturnoff() - icon_state = icon_off - set_light(0, 0.0) - - - -/obj/item/bong/coconut - name = "coconut bong" - icon_off = "coconut_bong" - icon_on = "coconut_bong_lit" +//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 + 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) + lit = TRUE + icon_state = "match_lit" + damtype = "fire" + force = 3 + hitsound = 'sound/items/welder.ogg' + item_state = "cigon" + name = "lit match" + desc = "A match. 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" + item_state = "cigoff" + name = "burnt match" + desc = "A match. This one has seen better days." + attack_verb = list("flicked") + STOP_PROCESSING(SSobj, src) + +/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(SLOT_WEAR_MASK) + if(istype(mask_item, /obj/item/clothing/mask/cigarette)) + return mask_item + +/obj/item/match/get_temperature() + return lit * heat + +////////////////// +//FINE SMOKABLES// +////////////////// +/obj/item/clothing/mask/cigarette + name = "cigarette" + desc = "A roll of tobacco and nicotine." + icon_state = "cigoff" + throw_speed = 0.5 + item_state = "cigoff" + w_class = WEIGHT_CLASS_TINY + body_parts_covered = null + grind_results = list() + 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 = 300 + var/chem_volume = 30 + var/list/list_reagents = list(/datum/reagent/drug/nicotine = 15) + heat = 1000 + +/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) // so it doesn't react until you light it + if(list_reagents) + reagents.add_reagent_list(list_reagents) + if(starts_lit) + light() + AddComponent(/datum/component/knockoff,90,list(BODY_ZONE_PRECISE_MOUTH),list(SLOT_WEAR_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)) //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 + item_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 + DISABLE_BITFIELD(reagents.reagents_holder_flags, NO_REACT) + reagents.handle_reactions() + icon_state = icon_on + item_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/proc/handle_reagents() + 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) + reagents.reaction(C, INGEST, fraction) + if(!reagents.trans_to(C, REAGENTS_METABOLISM)) + reagents.remove_any(REAGENTS_METABOLISM) + return + reagents.remove_any(REAGENTS_METABOLISM) + + +/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) + 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." + +/obj/item/clothing/mask/cigarette/uplift + desc = "An Uplift Smooth brand cigarette." + list_reagents = list(/datum/reagent/drug/nicotine = 7.5, /datum/reagent/consumable/menthol = 7.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 = 1) + +/obj/item/clothing/mask/cigarette/carp + desc = "A Carp Classic brand cigarette." + +/obj/item/clothing/mask/cigarette/syndicate + desc = "An unknown brand cigarette." + list_reagents = list(/datum/reagent/drug/nicotine = 15, /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 + item_state = "spliffoff" + smoketime = 180 + chem_volume = 50 + list_reagents = null + +/obj/item/clothing/mask/cigarette/rollie/New() + ..() + src.pixel_x = rand(-5, 5) + src.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/cigbutt/roach + name = "roach" + desc = "A manky old roach, or for non-stoners, a used rollup." + icon_state = "roach" + +/obj/item/cigbutt/roach/New() + ..() + src.pixel_x = rand(-5, 5) + src.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 + item_state = "cigaroff" + smoketime = 1500 + chem_volume = 40 + +/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 = 2000 + chem_volume = 80 + + +/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 = 7200 + chem_volume = 50 + +/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" + item_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 + item_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) + 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 + item_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" + item_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" + item_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/oil = 5) + +/obj/item/lighter/Initialize() + . = ..() + if(!overlay_state) + overlay_state = pick(overlay_list) + update_icon() + +/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, 1) + 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_icon() + cut_overlays() + var/mutable_appearance/lighter_overlay = mutable_appearance(icon,"lighter_overlay_[overlay_state][lit ? "-on" : ""]") + icon_state = "[initial(icon_state)][lit ? "-on" : ""]" + add_overlay(lighter_overlay) + +/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(2, 0.6, LIGHT_COLOR_FIRE) + 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/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-as-free 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/update_icon() + cut_overlays() + var/mutable_appearance/lighter_overlay = mutable_appearance(icon,"lighter_overlay_[overlay_state][lit ? "-on" : ""]") + icon_state = "[initial(icon_state)][lit ? "-on" : ""]" + lighter_overlay.color = lighter_color + add_overlay(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) + 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 = null + item_state = null + w_class = WEIGHT_CLASS_TINY + var/chem_volume = 100 + var/vapetime = FALSE //this so it won't puff out clouds every tick + var/screw = FALSE // kinky + var/super = FALSE //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) // so it doesn't react until you light it + reagents.add_reagent(/datum/reagent/drug/nicotine, 50) + if(!icon_state) + if(!param_color) + param_color = pick("red","blue","black","white","green","purple","yellow","orange") + icon_state = "[param_color]_vape" + item_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].") + ENABLE_BITFIELD(reagents.reagents_holder_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].") + DISABLE_BITFIELD(reagents.reagents_holder_flags, OPENCONTAINER) + cut_overlays() + + if(O.tool_behaviour == TOOL_MULTITOOL) + if(screw && !(obj_flags & EMAGGED))//also kinky + if(!super) + cut_overlays() + super = TRUE + to_chat(user, "You increase the voltage of [src].") + add_overlay("vapeopen_med") + else + cut_overlays() + super = FALSE + 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) + to_chat(user, "You need to open the cap to do that.") + return + if(obj_flags & EMAGGED) + to_chat(user, "[src] is already emagged!") + return + cut_overlays() + obj_flags |= EMAGGED + super = FALSE + 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() + return TRUE + +/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 == SLOT_WEAR_MASK) + if(!screw) + to_chat(user, "You start puffing on the vape.") + DISABLE_BITFIELD(reagents.reagents_holder_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) + var/mob/living/carbon/C = user + if(C.get_item_by_slot(SLOT_WEAR_MASK) == src) + ENABLE_BITFIELD(reagents.reagents_holder_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.reaction(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.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 wont 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, 0) + M.apply_damage(20, BURN, BODY_ZONE_HEAD) + M.Knockdown(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() + +/////////////// +/////BONGS///// +/////////////// + +/obj/item/bong + name = "bong" + desc = "A water bong used for smoking dried plants." + icon = 'icons/obj/bongs.dmi' + icon_state = null + item_state = null + w_class = WEIGHT_CLASS_NORMAL + light_color = "#FFCC66" + var/icon_off = "bong" + var/icon_on = "bong_lit" + var/chem_volume = 100 + var/last_used_time //for cooldown + var/firecharges = 0 //used for counting how many hits can be taken before the flame goes out + var/list/list_reagents = list() //For the base reagents bongs could get + + +/obj/item/bong/Initialize() + . = ..() + create_reagents(chem_volume, NO_REACT) // so it doesn't react until you light it + reagents.add_reagent_list(list_reagents) + icon_state = icon_off + +/obj/item/bong/attackby(obj/item/O, mob/user, params) + . = ..() + //If we're using a dried plant.. + if(istype(O,/obj/item/reagent_containers/food/snacks)) + var/obj/item/reagent_containers/food/snacks/DP = O + if (DP.dry) + //Nothing if our bong is full + if (reagents.holder_full()) + user.show_message("The bowl is full!", MSG_VISUAL) + return + + //Transfer reagents and remove the plant + user.show_message("You stuff the [DP] into the [src]'s bowl.", MSG_VISUAL) + DP.reagents.trans_to(src, 100) + qdel(DP) + return + else + user.show_message("[DP] must be dried first!", MSG_VISUAL) + return + + if (O.get_temperature() <= 500) + return + if (reagents && reagents.total_volume) //if there's stuff in the bong + var/lighting_text = O.ignition_effect(src, user) + if(lighting_text) + //Logic regarding igniting it on + if (firecharges == 0) + user.show_message("You light the [src] with the [O]!", MSG_VISUAL) + bongturnon() + else + user.show_message("You rekindle [src]'s flame with the [O]!", MSG_VISUAL) + + firecharges = 1 + return + else + user.show_message("There's nothing to light up in the bowl.", MSG_VISUAL) + return + +/obj/item/bong/CtrlShiftClick(mob/user) //empty reagents on alt click + ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + + if (reagents && reagents.total_volume) + user.show_message("You empty the [src].", MSG_VISUAL) + reagents.clear_reagents() + if(firecharges) + firecharges = 0 + bongturnoff() + else + user.show_message("The [src] is already empty.", MSG_VISUAL) + +/obj/item/bong/AltClick(mob/user) + ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + + if(firecharges) + firecharges = 0 + bongturnoff() + user.show_message("You quench the flame.", MSG_VISUAL) + return TRUE + +/obj/item/bong/examine(mob/user) + . = ..() + if(!reagents.total_volume) + . += "The bowl is empty." + else if (reagents.total_volume > 80) + . += "The bowl is filled to the brim." + else if (reagents.total_volume > 40) + . += "The bowl has plenty weed in it." + else + . += "The bowl has some weed in it." + + . += "Ctrl+Shift-click to empty." + . += "Alt-click to extinguish." + +/obj/item/bong/ignition_effect(atom/A, mob/user) + if(firecharges) + . = "[user] lights [A] off of the [src]." + else + . = "" + +/obj/item/bong/attack(mob/living/carbon/M, mob/living/carbon/user, obj/target) + //if it's lit up, some stuff in the bowl and the user is a target, and we're not on cooldown + + if (M != user) + return ..() + + if(user.is_mouth_covered(head_only = 1)) + to_chat(user, "Remove your headgear first.") + return ..() + + if(user.is_mouth_covered(mask_only = 1)) + to_chat(user, "Remove your mask first.") + return ..() + + if (!reagents.total_volume) + to_chat(user, "There's nothing in the bowl.") + return ..() + + if (!firecharges) + to_chat(user, "You have to light it up first.") + return ..() + + if (last_used_time + 30 >= world.time) + return ..() + var/hit_strength + var/noise + var/hittext = "" + //if the intent is help then you take a small hit, else a big one + if (user.a_intent == INTENT_HARM) + hit_strength = 2 + noise = 100 + hittext = "big hit" + else + hit_strength = 1 + noise = 70 + hittext = "hit" + //bubbling sound + playsound(user.loc,'sound/effects/bonghit.ogg', noise, 1) + + last_used_time = world.time + + //message + user.visible_message("[user] begins to take a [hittext] from the [src]!", \ + "You begin to take a [hittext] from [src].") + + //we take a hit here, after an uninterrupted delay + if(!do_after(user, 25, target = user)) + return + if (!(reagents && reagents.total_volume)) + return + + var/fraction = 12 * hit_strength + + var/datum/effect_system/smoke_spread/chem/smoke_machine/s = new + s.set_up(reagents, hit_strength, 18, user.loc) + s.start() + + reagents.reaction(user, INGEST, fraction) + if(!reagents.trans_to(user, fraction)) + reagents.remove_any(fraction) + + if (hit_strength == 2 && prob(15)) + user.emote("cough") + user.adjustOxyLoss(15) + + user.visible_message("[user] takes a [hittext] from the [src]!", \ + "You take a [hittext] from [src].") + + firecharges = firecharges - 1 + if (!firecharges) + bongturnoff() + if (!reagents.total_volume) + firecharges = 0 + bongturnoff() + + + +/obj/item/bong/proc/bongturnon() + icon_state = icon_on + set_light(3, 0.8) + +/obj/item/bong/proc/bongturnoff() + icon_state = icon_off + set_light(0, 0.0) + + + +/obj/item/bong/coconut + name = "coconut bong" + icon_off = "coconut_bong" + icon_on = "coconut_bong_lit" desc = "A water bong used for smoking dried plants. This one's made out of a coconut and some bamboo." \ No newline at end of file diff --git a/code/game/objects/items/clown_items.dm b/code/game/objects/items/clown_items.dm index 26c992b0bc..050427702d 100644 --- a/code/game/objects/items/clown_items.dm +++ b/code/game/objects/items/clown_items.dm @@ -1,183 +1,183 @@ -/* 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 = 50 //slower than mop - force_string = "robust... against germs" - -/obj/item/soap/Initialize() - . = ..() - AddComponent(/datum/component/slippery, 80) - -/obj/item/soap/nanotrasen - desc = "A Nanotrasen brand bar of soap. Smells of plasma." - icon_state = "soapnt" - -/obj/item/soap/homemade - desc = "A homemade bar of soap. Smells of... well...." - icon_state = "soapgibs" - cleanspeed = 45 // a little 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 = 40 //same speed as mop because deluxe -- 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 = 10 //much faster than mop so it is useful for traitors who want to clean crime scenes - -/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/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity || !check_allowed_items(target)) - return - //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, src.cleanspeed, target = target)) - to_chat(user, "You scrub \the [target.name] out.") - qdel(target) - 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 - H.lip_style = null //removes lipstick - H.update_body() - 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, src.cleanspeed, target = target)) - to_chat(user, "You clean \the [target.name].") - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - target.set_opacity(initial(target.opacity)) - 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, src.cleanspeed, target = target)) - to_chat(user, "You clean \the [target.name].") - var/obj/effect/decal/cleanable/C = locate() in target - qdel(C) - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) - target.wash_cream() - 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" - item_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 - throw_speed = 3 - throw_range = 7 - attack_verb = list("HONKED") - var/moodlet = "honk" //used to define which kind of moodlet is added to the honked target - var/list/honksounds = list('sound/items/bikehorn.ogg' = 1) - -/obj/item/bikehorn/ComponentInitialize() - . = ..() - AddComponent(/datum/component/squeak, honksounds, 50) - -/obj/item/bikehorn/attack(mob/living/carbon/M, mob/living/carbon/user) - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, moodlet, /datum/mood_event/honk) - return ..() - -/obj/item/bikehorn/suicide_act(mob/user) - user.visible_message("[user] solemnly points the horn at [user.p_their()] temple! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, pickweight(honksounds), 50, 1) - return (BRUTELOSS) - -//air horn -/obj/item/bikehorn/airhorn - name = "air horn" - desc = "Damn son, where'd you find this?" - icon_state = "air_horn" - honksounds = list('sound/items/airhorn2.ogg' = 1) - -//golden bikehorn -/obj/item/bikehorn/golden - name = "golden bike horn" - desc = "Golden? Clearly, it's made with bananium! Honk!" - icon_state = "gold_horn" - item_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(ishuman(M) && M.can_hear()) - var/mob/living/carbon/human/H = M - if(istype(H.ears, /obj/item/clothing/ears/earmuffs)) - continue - M.emote("flip") - flip_cooldown = world.time + 7 - -/obj/item/bikehorn/silver - name = "silver bike horn" - desc = "A shiny bike horn handcrafted in the artisan workshops of Mars, with superior kevlar-reinforced rubber bulb attached to a polished plasteel reed horn." - attack_verb = list("elegantly HONKED") - icon_state = "silverhorn" - -/obj/item/bikehorn/bluespacehonker - name = "bluespace bike horn" - desc = "A normal bike horn colored blue and has bluespace dust held in to reed horn allowing for silly honks through space and time, into your in childhood." - attack_verb = list("HONKED in bluespace", "HONKED", "quantumly HONKED") - icon_state = "bluespacehonker" - moodlet = "bshonk" - -//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" +/* 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 = 50 //slower than mop + force_string = "robust... against germs" + +/obj/item/soap/Initialize() + . = ..() + AddComponent(/datum/component/slippery, 80) + +/obj/item/soap/nanotrasen + desc = "A Nanotrasen brand bar of soap. Smells of plasma." + icon_state = "soapnt" + +/obj/item/soap/homemade + desc = "A homemade bar of soap. Smells of... well...." + icon_state = "soapgibs" + cleanspeed = 45 // a little 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 = 40 //same speed as mop because deluxe -- 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 = 10 //much faster than mop so it is useful for traitors who want to clean crime scenes + +/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/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity || !check_allowed_items(target)) + return + //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, src.cleanspeed, target = target)) + to_chat(user, "You scrub \the [target.name] out.") + qdel(target) + 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 + H.lip_style = null //removes lipstick + H.update_body() + 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, src.cleanspeed, target = target)) + to_chat(user, "You clean \the [target.name].") + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + target.set_opacity(initial(target.opacity)) + 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, src.cleanspeed, target = target)) + to_chat(user, "You clean \the [target.name].") + var/obj/effect/decal/cleanable/C = locate() in target + qdel(C) + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) + target.wash_cream() + 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" + item_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 + throw_speed = 3 + throw_range = 7 + attack_verb = list("HONKED") + var/moodlet = "honk" //used to define which kind of moodlet is added to the honked target + var/list/honksounds = list('sound/items/bikehorn.ogg' = 1) + +/obj/item/bikehorn/ComponentInitialize() + . = ..() + AddComponent(/datum/component/squeak, honksounds, 50) + +/obj/item/bikehorn/attack(mob/living/carbon/M, mob/living/carbon/user) + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, moodlet, /datum/mood_event/honk) + return ..() + +/obj/item/bikehorn/suicide_act(mob/user) + user.visible_message("[user] solemnly points the horn at [user.p_their()] temple! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, pickweight(honksounds), 50, 1) + return (BRUTELOSS) + +//air horn +/obj/item/bikehorn/airhorn + name = "air horn" + desc = "Damn son, where'd you find this?" + icon_state = "air_horn" + honksounds = list('sound/items/airhorn2.ogg' = 1) + +//golden bikehorn +/obj/item/bikehorn/golden + name = "golden bike horn" + desc = "Golden? Clearly, it's made with bananium! Honk!" + icon_state = "gold_horn" + item_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(ishuman(M) && M.can_hear()) + var/mob/living/carbon/human/H = M + if(istype(H.ears, /obj/item/clothing/ears/earmuffs)) + continue + M.emote("flip") + flip_cooldown = world.time + 7 + +/obj/item/bikehorn/silver + name = "silver bike horn" + desc = "A shiny bike horn handcrafted in the artisan workshops of Mars, with superior kevlar-reinforced rubber bulb attached to a polished plasteel reed horn." + attack_verb = list("elegantly HONKED") + icon_state = "silverhorn" + +/obj/item/bikehorn/bluespacehonker + name = "bluespace bike horn" + desc = "A normal bike horn colored blue and has bluespace dust held in to reed horn allowing for silly honks through space and time, into your in childhood." + attack_verb = list("HONKED in bluespace", "HONKED", "quantumly HONKED") + icon_state = "bluespacehonker" + moodlet = "bshonk" + +//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) \ No newline at end of file diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm index 6bcf2d620f..4dcaed2cf7 100644 --- a/code/game/objects/items/cosmetics.dm +++ b/code/game/objects/items/cosmetics.dm @@ -1,191 +1,191 @@ -/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/New() - ..() - 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_hair_style = "Shaved" - else - H.hair_style = "Skinhead" - - H.update_hair() - playsound(loc, 'sound/items/welder2.ogg', 20, 1) - - -/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(!(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_hair_style == "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 - var/turf/H_loc = H.loc - 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)) - if(H_loc == H.loc) - 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(!(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.hair_style == "Bald" || H.hair_style == "Balding Hair" || H.hair_style == "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/New() + ..() + 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_hair_style = "Shaved" + else + H.hair_style = "Skinhead" + + H.update_hair() + playsound(loc, 'sound/items/welder2.ogg', 20, 1) + + +/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(!(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_hair_style == "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 + var/turf/H_loc = H.loc + 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)) + if(H_loc == H.loc) + 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(!(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.hair_style == "Bald" || H.hair_style == "Balding Hair" || H.hair_style == "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 ..() \ No newline at end of file diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index ccffa413a6..c0b510111f 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -1,1175 +1,1175 @@ - -//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 -#define PDA_STANDARD_OVERLAYS list("pda-r", "blank", "id_overlay", "insert_overlay", "light_overlay", "pai_overlay") - -//pda icon overlays list defines -#define PDA_OVERLAY_ALERT 1 -#define PDA_OVERLAY_SCREEN 2 -#define PDA_OVERLAY_ID 3 -#define PDA_OVERLAY_ITEM 4 -#define PDA_OVERLAY_LIGHT 5 -#define PDA_OVERLAY_PAI 6 - -/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_alt.dmi' - icon_state = "pda" - item_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 - 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/list/overlays_icons = list('icons/obj/pda_alt.dmi' = list("pda-r", "screen_default", "id_overlay", "insert_overlay", "light_overlay", "pai_overlay")) - var/current_overlays = PDA_STANDARD_OVERLAYS - 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/f_pow = 0.6 //Power for the flashlight function - var/f_col = "#FFCC66" //Color 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! To help with navigation, we have provided the following definitions. North: Fore. South: Aft. West: Port. East: Starboard. Quarter is either side of aft." //Current note in the notepad function - var/notehtml = "" - var/notescanned = FALSE // True if what is in the notekeeper was from a paper. - var/detonatable = TRUE // Can the PDA be blown up? - 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/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/list/overlays_offsets // offsets to use for certain overlays - var/overlays_x_offset = 0 - var/overlays_y_offset = 0 - - 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) - . = ..() - . += id ? "Alt-click to remove the id." : "" - if(inserted_item && (!isturf(loc))) - . += "Ctrl-click to remove [inserted_item]." - if(LAZYLEN(GLOB.pda_reskins)) - . += "Ctrl-shift-click it to reskin it." - -/obj/item/pda/Initialize() - . = ..() - if(fon) - set_light(f_lum, f_pow, f_col) - - 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(FALSE, TRUE) - -/obj/item/pda/CtrlShiftClick(mob/living/user) - . = ..() - if(GLOB.pda_reskins && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - reskin_obj(user) - -/obj/item/pda/reskin_obj(mob/M) - if(!LAZYLEN(GLOB.pda_reskins)) - return - var/dat = "Reskin options for [name]:" - for(var/V in GLOB.pda_reskins) - var/output = icon2html(GLOB.pda_reskins[V], M, icon_state) - dat += "\n[V]: [output]" - to_chat(M, dat) - - var/choice = input(M, "Choose the a reskin for [src]","Reskin Object") as null|anything in GLOB.pda_reskins - var/new_icon = GLOB.pda_reskins[choice] - if(QDELETED(src) || isnull(new_icon) || new_icon == icon || !M.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - icon = new_icon - update_icon(FALSE, TRUE) - to_chat(M, "[src] is now skinned as '[choice]'.") - -/obj/item/pda/proc/set_new_overlays() - if(!overlays_offsets || !(icon in overlays_offsets)) - overlays_x_offset = 0 - overlays_y_offset = 0 - else - var/list/new_offsets = overlays_offsets[icon] - if(new_offsets) - overlays_x_offset = new_offsets[1] - overlays_y_offset = new_offsets[2] - if(!(icon in overlays_icons)) - current_overlays = PDA_STANDARD_OVERLAYS - return - current_overlays = overlays_icons[icon] - -/obj/item/pda/equipped(mob/user, slot) - . = ..() - if(equipped || !user.client) - return - update_style(user.client) - -/obj/item/pda/proc/update_style(client/C) - background_color = C.prefs.pda_color - switch(C.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 - var/pref_skin = GLOB.pda_reskins[C.prefs.pda_skin] - if(icon != pref_skin) - icon = pref_skin - update_icon(FALSE, TRUE) - 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_icon(alert = FALSE, new_overlays = FALSE) - if(new_overlays) - set_new_overlays() - cut_overlays() - add_overlay(alert ? current_overlays[PDA_OVERLAY_ALERT] : current_overlays[PDA_OVERLAY_SCREEN]) - var/mutable_appearance/overlay = new() - overlay.pixel_x = overlays_x_offset - if(id) - overlay.icon_state = current_overlays[PDA_OVERLAY_ID] - add_overlay(new /mutable_appearance(overlay)) - if(inserted_item) - overlay.icon_state = current_overlays[PDA_OVERLAY_ITEM] - add_overlay(new /mutable_appearance(overlay)) - if(fon) - overlay.icon_state = current_overlays[PDA_OVERLAY_LIGHT] - add_overlay(new /mutable_appearance(overlay)) - if(pai) - overlay.icon_state = "[current_overlays[PDA_OVERLAY_PAI]][pai.pai ? "" : "_off"]" - add_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) - - user.set_machine(src) - - var/dat = "Personal Data Assistant" - dat += assets.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("hh:mm:ss")]
                    " //:[world.time / 100 % 6][world.time / 100 % 10]" - dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer]" - - 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
                  " - - if(cartridge) - dat += cartridge.message_header() - - dat += "

                  [PDAIMG(menu)] Detected PDAs

                  " - - dat += "
                    " - var/count = 0 - - if (!toff) - for (var/obj/item/pda/P in sortNames(get_viewable_pdas())) - if (P == src) - continue - dat += "
                  • [P]" - 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(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]/total_moles - if(gas_level > 0) - dat += "[GLOB.meta_gas_names[id]]: [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 (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - 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 (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - 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 (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if ("Toggle_Underline") - underline_flag = !underline_flag - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - 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 (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - 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 (!silent) - playsound(src, 'sound/machines/terminal_processing.ogg', 15, 1) - addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, 'sound/machines/terminal_success.ogg', 15, 1), 13) - - if("Eject")//Ejects the cart, only done from hub. - if (!isnull(cartridge)) - U.put_in_hands(cartridge) - to_chat(U, "You remove [cartridge] from [src].") - scanmode = PDA_SCANNER_NONE - cartridge.host_pda = null - cartridge = null - update_icon() - if (!silent) - playsound(src, 'sound/machines/terminal_eject_disc.ogg', 50, 1) - -//MENU FUNCTIONS=================================== - - if("0")//Hub - mode = 0 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - if("1")//Notes - mode = 1 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - if("2")//Messenger - mode = 2 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - if("21")//Read messeges - mode = 21 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - if("3")//Atmos scan - mode = 3 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - if("4")//Redirects to hub - mode = 0 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - -//MAIN FUNCTIONS=================================== - - if("Light") - toggle_light() - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - 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 (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - 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 (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - 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 (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if("Honk") - if ( !(last_noise && world.time < last_noise + 20) ) - playsound(src, 'sound/items/bikehorn.ogg', 50, 1) - last_noise = world.time - - if("Trombone") - if ( !(last_noise && world.time < last_noise + 20) ) - playsound(src, 'sound/misc/sadtrombone.ogg', 50, 1) - 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 (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - 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) - if (!silent) - playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) - - -//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 = input(U, "Please enter new ringtone", name, ttone) as text - 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 = copytext(sanitize(t), 1, 20) - else - U << browse(null, "window=pda") - return - if("Message") - create_message(U, locate(href_list["target"])) - - 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 - var/turf/T = get_turf(loc) - if(T) - pai.forceMove(T) - -//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, 1) - - 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 - 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, 100) - 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 - var/emoji_message = emoji_parse(message) - 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/pda/signal = new(src, list( - "name" = "[owner]", - "job" = "[ownjob]", - "message" = message, - "targets" = string_targets, - "emoji_message" = emoji_message - )) - 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 - if (!silent) - playsound(src, 'sound/machines/terminal_error.ogg', 15, 1) - - var/target_text = signal.format_target() - // 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(TRUE)]" - for(var/mob/M in GLOB.player_list) - if(isobserver(M) && M.client && (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, "Message sent to [target_text]: \"[emoji_message]\"") - if (!silent) - playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) - // Reset the photo - picture = null - last_text = world.time - if (everyone) - last_everyone = world.time - -/obj/item/pda/proc/receive_message(datum/signal/subspace/pda/signal) - tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
                  [signal.format_message()]
                  " - - if (!silent) - playsound(src, 'sound/machines/twobeep.ogg', 50, 1) - 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/hrefstart - var/hrefend - if (isAI(L)) - hrefstart = "" - hrefend = "" - - to_chat(L, "[icon2html(src)] Message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [signal.format_message(TRUE)] (Reply)") - - update_icon(TRUE) - -/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) - playsound(src, 'sound/machines/terminal_eject_disc.ogg', 50, 1) - else - remove_pen(user) - playsound(src, 'sound/machines/button4.ogg', 50, 1) - return TRUE - -/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/verb/verb_toggle_light() - set category = "Object" - set name = "Toggle Flashlight" - - toggle_light() - -/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() - -/obj/item/pda/proc/toggle_light() - if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE)) - return - if(fon) - fon = FALSE - set_light(0) - else if(f_lum) - fon = TRUE - set_light(f_lum, f_pow, f_col) - update_icon() - -/obj/item/pda/proc/remove_pen() - - if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - if(inserted_item) - usr.put_in_hands(inserted_item) - to_chat(usr, "You remove [inserted_item] from [src].") - inserted_item = null - update_icon() - else - to_chat(usr, "This PDA does not have a pen in it!") - -//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() - playsound(src, 'sound/machines/button.ogg', 50, 1) - 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) && !cartridge) - if(!user.transferItemToLoc(C, src)) - return - cartridge = C - cartridge.host_pda = src - to_chat(user, "You insert [cartridge] into [src].") - update_icon() - playsound(src, 'sound/machines/button.ogg', 50, 1) - - 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 (!silent) - playsound(src, 'sound/machines/terminal_error.ogg', 15, 1) - - if(!owner) - owner = idcard.registered_name - ownjob = idcard.assignment - update_label() - to_chat(user, "Card scanned.") - if (!silent) - playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) - else - //Basic safety check. If either both objects are held by user or PDA is on ground and card is in hand. - if(((src in user.contents) || (isturf(loc) && in_range(src, user))) && (C in user.contents)) - 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() - playsound(src, 'sound/machines/button.ogg', 50, 1) - - 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] has analyzed [C]'s vitals!") - healthscan(user, C, 1) - add_fingerprint(user) - - if(PDA_SCANNER_HALOGEN) - C.visible_message("[user] has analyzed [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. - if(!detonatable) - return - 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. - -/mob/living/silicon/ai/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/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 - if(!isnull(aiPDA)) - aiPDA.toff = !aiPDA.toff - to_chat(usr, "PDA sender/receiver toggled [(aiPDA.toff ? "Off" : "On")]!") - else - to_chat(usr, "You do not have a PDA. You should make an issue report about this.") - -/mob/living/silicon/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 - if(!isnull(aiPDA)) - //0 - aiPDA.silent = !aiPDA.silent - to_chat(usr, "PDA ringer toggled [(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/ai/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 += 1 - spawn(200 * severity) - emped -= 1 - -/proc/get_viewable_pdas() - . = list() - // Returns a list of PDAs which can be viewed from another PDA/message monitor. - for(var/obj/item/pda/P in GLOB.PDAs) - if(!P.owner || P.toff || P.hidden) - continue - . += P - -#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 -#undef PDA_STANDARD_OVERLAYS - -#undef PDA_OVERLAY_ALERT -#undef PDA_OVERLAY_SCREEN -#undef PDA_OVERLAY_ID -#undef PDA_OVERLAY_ITEM -#undef PDA_OVERLAY_LIGHT + +//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 +#define PDA_STANDARD_OVERLAYS list("pda-r", "blank", "id_overlay", "insert_overlay", "light_overlay", "pai_overlay") + +//pda icon overlays list defines +#define PDA_OVERLAY_ALERT 1 +#define PDA_OVERLAY_SCREEN 2 +#define PDA_OVERLAY_ID 3 +#define PDA_OVERLAY_ITEM 4 +#define PDA_OVERLAY_LIGHT 5 +#define PDA_OVERLAY_PAI 6 + +/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_alt.dmi' + icon_state = "pda" + item_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 + 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/list/overlays_icons = list('icons/obj/pda_alt.dmi' = list("pda-r", "screen_default", "id_overlay", "insert_overlay", "light_overlay", "pai_overlay")) + var/current_overlays = PDA_STANDARD_OVERLAYS + 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/f_pow = 0.6 //Power for the flashlight function + var/f_col = "#FFCC66" //Color 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! To help with navigation, we have provided the following definitions. North: Fore. South: Aft. West: Port. East: Starboard. Quarter is either side of aft." //Current note in the notepad function + var/notehtml = "" + var/notescanned = FALSE // True if what is in the notekeeper was from a paper. + var/detonatable = TRUE // Can the PDA be blown up? + 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/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/list/overlays_offsets // offsets to use for certain overlays + var/overlays_x_offset = 0 + var/overlays_y_offset = 0 + + 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) + . = ..() + . += id ? "Alt-click to remove the id." : "" + if(inserted_item && (!isturf(loc))) + . += "Ctrl-click to remove [inserted_item]." + if(LAZYLEN(GLOB.pda_reskins)) + . += "Ctrl-shift-click it to reskin it." + +/obj/item/pda/Initialize() + . = ..() + if(fon) + set_light(f_lum, f_pow, f_col) + + 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(FALSE, TRUE) + +/obj/item/pda/CtrlShiftClick(mob/living/user) + . = ..() + if(GLOB.pda_reskins && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + reskin_obj(user) + +/obj/item/pda/reskin_obj(mob/M) + if(!LAZYLEN(GLOB.pda_reskins)) + return + var/dat = "Reskin options for [name]:" + for(var/V in GLOB.pda_reskins) + var/output = icon2html(GLOB.pda_reskins[V], M, icon_state) + dat += "\n[V]: [output]" + to_chat(M, dat) + + var/choice = input(M, "Choose the a reskin for [src]","Reskin Object") as null|anything in GLOB.pda_reskins + var/new_icon = GLOB.pda_reskins[choice] + if(QDELETED(src) || isnull(new_icon) || new_icon == icon || !M.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + icon = new_icon + update_icon(FALSE, TRUE) + to_chat(M, "[src] is now skinned as '[choice]'.") + +/obj/item/pda/proc/set_new_overlays() + if(!overlays_offsets || !(icon in overlays_offsets)) + overlays_x_offset = 0 + overlays_y_offset = 0 + else + var/list/new_offsets = overlays_offsets[icon] + if(new_offsets) + overlays_x_offset = new_offsets[1] + overlays_y_offset = new_offsets[2] + if(!(icon in overlays_icons)) + current_overlays = PDA_STANDARD_OVERLAYS + return + current_overlays = overlays_icons[icon] + +/obj/item/pda/equipped(mob/user, slot) + . = ..() + if(equipped || !user.client) + return + update_style(user.client) + +/obj/item/pda/proc/update_style(client/C) + background_color = C.prefs.pda_color + switch(C.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 + var/pref_skin = GLOB.pda_reskins[C.prefs.pda_skin] + if(icon != pref_skin) + icon = pref_skin + update_icon(FALSE, TRUE) + 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_icon(alert = FALSE, new_overlays = FALSE) + if(new_overlays) + set_new_overlays() + cut_overlays() + add_overlay(alert ? current_overlays[PDA_OVERLAY_ALERT] : current_overlays[PDA_OVERLAY_SCREEN]) + var/mutable_appearance/overlay = new() + overlay.pixel_x = overlays_x_offset + if(id) + overlay.icon_state = current_overlays[PDA_OVERLAY_ID] + add_overlay(new /mutable_appearance(overlay)) + if(inserted_item) + overlay.icon_state = current_overlays[PDA_OVERLAY_ITEM] + add_overlay(new /mutable_appearance(overlay)) + if(fon) + overlay.icon_state = current_overlays[PDA_OVERLAY_LIGHT] + add_overlay(new /mutable_appearance(overlay)) + if(pai) + overlay.icon_state = "[current_overlays[PDA_OVERLAY_PAI]][pai.pai ? "" : "_off"]" + add_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) + + user.set_machine(src) + + var/dat = "Personal Data Assistant" + dat += assets.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("hh:mm:ss")]
                  " //:[world.time / 100 % 6][world.time / 100 % 10]" + dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer]" + + 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
                " + + if(cartridge) + dat += cartridge.message_header() + + dat += "

                [PDAIMG(menu)] Detected PDAs

                " + + dat += "
                  " + var/count = 0 + + if (!toff) + for (var/obj/item/pda/P in sortNames(get_viewable_pdas())) + if (P == src) + continue + dat += "
                • [P]" + 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(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]/total_moles + if(gas_level > 0) + dat += "[GLOB.meta_gas_names[id]]: [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 (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + 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 (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + 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 (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if ("Toggle_Underline") + underline_flag = !underline_flag + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + 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 (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + 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 (!silent) + playsound(src, 'sound/machines/terminal_processing.ogg', 15, 1) + addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, 'sound/machines/terminal_success.ogg', 15, 1), 13) + + if("Eject")//Ejects the cart, only done from hub. + if (!isnull(cartridge)) + U.put_in_hands(cartridge) + to_chat(U, "You remove [cartridge] from [src].") + scanmode = PDA_SCANNER_NONE + cartridge.host_pda = null + cartridge = null + update_icon() + if (!silent) + playsound(src, 'sound/machines/terminal_eject_disc.ogg', 50, 1) + +//MENU FUNCTIONS=================================== + + if("0")//Hub + mode = 0 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + if("1")//Notes + mode = 1 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + if("2")//Messenger + mode = 2 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + if("21")//Read messeges + mode = 21 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + if("3")//Atmos scan + mode = 3 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + if("4")//Redirects to hub + mode = 0 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + +//MAIN FUNCTIONS=================================== + + if("Light") + toggle_light() + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + 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 (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + 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 (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + 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 (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if("Honk") + if ( !(last_noise && world.time < last_noise + 20) ) + playsound(src, 'sound/items/bikehorn.ogg', 50, 1) + last_noise = world.time + + if("Trombone") + if ( !(last_noise && world.time < last_noise + 20) ) + playsound(src, 'sound/misc/sadtrombone.ogg', 50, 1) + 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 (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + 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) + if (!silent) + playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) + + +//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 = input(U, "Please enter new ringtone", name, ttone) as text + 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 = copytext(sanitize(t), 1, 20) + else + U << browse(null, "window=pda") + return + if("Message") + create_message(U, locate(href_list["target"])) + + 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 + var/turf/T = get_turf(loc) + if(T) + pai.forceMove(T) + +//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, 1) + + 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 + 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, 100) + 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 + var/emoji_message = emoji_parse(message) + 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/pda/signal = new(src, list( + "name" = "[owner]", + "job" = "[ownjob]", + "message" = message, + "targets" = string_targets, + "emoji_message" = emoji_message + )) + 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 + if (!silent) + playsound(src, 'sound/machines/terminal_error.ogg', 15, 1) + + var/target_text = signal.format_target() + // 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(TRUE)]" + for(var/mob/M in GLOB.player_list) + if(isobserver(M) && M.client && (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, "Message sent to [target_text]: \"[emoji_message]\"") + if (!silent) + playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) + // Reset the photo + picture = null + last_text = world.time + if (everyone) + last_everyone = world.time + +/obj/item/pda/proc/receive_message(datum/signal/subspace/pda/signal) + tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
                [signal.format_message()]
                " + + if (!silent) + playsound(src, 'sound/machines/twobeep.ogg', 50, 1) + 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/hrefstart + var/hrefend + if (isAI(L)) + hrefstart = "" + hrefend = "" + + to_chat(L, "[icon2html(src)] Message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [signal.format_message(TRUE)] (Reply)") + + update_icon(TRUE) + +/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) + playsound(src, 'sound/machines/terminal_eject_disc.ogg', 50, 1) + else + remove_pen(user) + playsound(src, 'sound/machines/button4.ogg', 50, 1) + return TRUE + +/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/verb/verb_toggle_light() + set category = "Object" + set name = "Toggle Flashlight" + + toggle_light() + +/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() + +/obj/item/pda/proc/toggle_light() + if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE)) + return + if(fon) + fon = FALSE + set_light(0) + else if(f_lum) + fon = TRUE + set_light(f_lum, f_pow, f_col) + update_icon() + +/obj/item/pda/proc/remove_pen() + + if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + if(inserted_item) + usr.put_in_hands(inserted_item) + to_chat(usr, "You remove [inserted_item] from [src].") + inserted_item = null + update_icon() + else + to_chat(usr, "This PDA does not have a pen in it!") + +//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() + playsound(src, 'sound/machines/button.ogg', 50, 1) + 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) && !cartridge) + if(!user.transferItemToLoc(C, src)) + return + cartridge = C + cartridge.host_pda = src + to_chat(user, "You insert [cartridge] into [src].") + update_icon() + playsound(src, 'sound/machines/button.ogg', 50, 1) + + 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 (!silent) + playsound(src, 'sound/machines/terminal_error.ogg', 15, 1) + + if(!owner) + owner = idcard.registered_name + ownjob = idcard.assignment + update_label() + to_chat(user, "Card scanned.") + if (!silent) + playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) + else + //Basic safety check. If either both objects are held by user or PDA is on ground and card is in hand. + if(((src in user.contents) || (isturf(loc) && in_range(src, user))) && (C in user.contents)) + 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() + playsound(src, 'sound/machines/button.ogg', 50, 1) + + 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] has analyzed [C]'s vitals!") + healthscan(user, C, 1) + add_fingerprint(user) + + if(PDA_SCANNER_HALOGEN) + C.visible_message("[user] has analyzed [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. + if(!detonatable) + return + 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. + +/mob/living/silicon/ai/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/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 + if(!isnull(aiPDA)) + aiPDA.toff = !aiPDA.toff + to_chat(usr, "PDA sender/receiver toggled [(aiPDA.toff ? "Off" : "On")]!") + else + to_chat(usr, "You do not have a PDA. You should make an issue report about this.") + +/mob/living/silicon/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 + if(!isnull(aiPDA)) + //0 + aiPDA.silent = !aiPDA.silent + to_chat(usr, "PDA ringer toggled [(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/ai/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 += 1 + spawn(200 * severity) + emped -= 1 + +/proc/get_viewable_pdas() + . = list() + // Returns a list of PDAs which can be viewed from another PDA/message monitor. + for(var/obj/item/pda/P in GLOB.PDAs) + if(!P.owner || P.toff || P.hidden) + continue + . += P + +#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 +#undef PDA_STANDARD_OVERLAYS + +#undef PDA_OVERLAY_ALERT +#undef PDA_OVERLAY_SCREEN +#undef PDA_OVERLAY_ID +#undef PDA_OVERLAY_ITEM +#undef PDA_OVERLAY_LIGHT #undef PDA_OVERLAY_PAI \ No newline at end of file diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm index 1e98c2c349..e4fa241907 100644 --- a/code/game/objects/items/devices/PDA/cart.dm +++ b/code/game/objects/items/devices/PDA/cart.dm @@ -1,777 +1,777 @@ -#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" - item_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 - rad_flags = RAD_PROTECT_CONTENTS //So the cartridges dont annoyingly get irradiated, and the signallers inside being radded as well - - 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 - bot_access_flags = SEC_BOT - -/obj/item/cartridge/detective - name = "\improper D.E.T.E.C.T. cartridge" - icon_state = "cart-eye" - 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-law" - access = CART_SECURITY - spam_enabled = 1 - -/obj/item/cartridge/curator - name = "\improper Lib-Tweet cartridge" - icon_state = "cart-lib" - access = CART_NEWSCASTER - -/obj/item/cartridge/roboticist - name = "\improper B.O.O.P. Remote Control cartridge" - desc = "Packed with heavy duty triple-bot interlink!" - icon_state = "cart-robo" - bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT - access = CART_DRONEPHONE - -/obj/item/cartridge/signal - name = "generic signaler cartridge" - icon_state = "cart-sig" - 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/New() - ..() - 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 += "Entries cannot be modified from this terminal.

                " - if(GLOB.data_core.general) - for (var/datum/data/record/t in sortRecord(GLOB.data_core.general)) - menu += "[t.fields["name"]] - [t.fields["rank"]]
                " - menu += "
                " - - - 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.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") - - for(var/obj/machinery/power/apc/A in L) - menu += copytext(add_tspace(A.area.name, 30), 1, 30) - menu += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_lspace(DisplayPower(A.lastused_total), 6)] [A.cell ? "[add_lspace(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 += "Sex: [active1.fields["sex"]]
                " - 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 += "Sex: [active1.fields["sex"]]
                " - 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("
                \nMinor Crimes:") - - menu +={" - - - - - -"} - for(var/datum/data/crime/c in active3.fields["mi_crim"]) - menu += "" - menu += "" - menu += "" - menu += "" - menu += "" - menu += "
                CrimeDetailsAuthorTime Added
                [c.crimeName][c.crimeDetails][c.author][c.time]
                " - - menu += text("
                \nMajor Crimes:") - - menu +={" - - - - - -"} - for(var/datum/data/crime/c in active3.fields["ma_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 += "

                Pimpin' Ride:

                " - - ldat = null - for (var/obj/vehicle/ridden/janicart/M in world) - var/turf/ml = get_turf(M) - - if(ml) - if (ml.z != cl.z) - continue - var/direction = get_dir(src, M) - ldat += "Ride - \[[ml.x],[ml.y] ([uppertext(dir2text(direction))])\]
                " - - 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 - 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 (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 - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - 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 - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - if("Send Signal") - INVOKE_ASYNC(radio, /obj/item/integrated_signaler.proc/send_activation) - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - if("Signal Frequency") - var/new_frequency = sanitize_frequency(radio.frequency + text2num(href_list["sfreq"])) - radio.set_frequency(new_frequency) - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - 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) - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - 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"]) - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - if("Power Select") - var/pnum = text2num(href_list["target"]) - powmonitor = powermonitors[pnum] - host_pda.mode = 433 - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - if("Supply Orders") - host_pda.mode =47 - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - if("Newscaster Access") - host_pda.mode = 53 - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - 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 - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - if("Newscaster Switch Channel") - current_channel = host_pda.msg_input() - host_pda.Topic(null,list("choice"=num2text(host_pda.mode))) - return - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - //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"]) - - 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) - playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) - - 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)) - active_bot.bot_control(href_list["mule"], usr, TRUE) - - if(!host_pda) - return - host_pda.attack_self(usr) - - -/obj/item/cartridge/proc/bot_control() - - - var/mob/living/simple_animal/bot/Bot - - 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(Bot in GLOB.alive_mob_list) //Git da botz - 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" + item_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 + rad_flags = RAD_PROTECT_CONTENTS //So the cartridges dont annoyingly get irradiated, and the signallers inside being radded as well + + 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 + bot_access_flags = SEC_BOT + +/obj/item/cartridge/detective + name = "\improper D.E.T.E.C.T. cartridge" + icon_state = "cart-eye" + 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-law" + access = CART_SECURITY + spam_enabled = 1 + +/obj/item/cartridge/curator + name = "\improper Lib-Tweet cartridge" + icon_state = "cart-lib" + access = CART_NEWSCASTER + +/obj/item/cartridge/roboticist + name = "\improper B.O.O.P. Remote Control cartridge" + desc = "Packed with heavy duty triple-bot interlink!" + icon_state = "cart-robo" + bot_access_flags = FLOOR_BOT | CLEAN_BOT | MED_BOT | FIRE_BOT + access = CART_DRONEPHONE + +/obj/item/cartridge/signal + name = "generic signaler cartridge" + icon_state = "cart-sig" + 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/New() + ..() + 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 += "Entries cannot be modified from this terminal.

                " + if(GLOB.data_core.general) + for (var/datum/data/record/t in sortRecord(GLOB.data_core.general)) + menu += "[t.fields["name"]] - [t.fields["rank"]]
                " + menu += "
                " + + + 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.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") + + for(var/obj/machinery/power/apc/A in L) + menu += copytext(add_tspace(A.area.name, 30), 1, 30) + menu += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_lspace(DisplayPower(A.lastused_total), 6)] [A.cell ? "[add_lspace(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 += "Sex: [active1.fields["sex"]]
                " + 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 += "Sex: [active1.fields["sex"]]
                " + 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("
                \nMinor Crimes:") + + menu +={" + + + + + +"} + for(var/datum/data/crime/c in active3.fields["mi_crim"]) + menu += "" + menu += "" + menu += "" + menu += "" + menu += "" + menu += "
                CrimeDetailsAuthorTime Added
                [c.crimeName][c.crimeDetails][c.author][c.time]
                " + + menu += text("
                \nMajor Crimes:") + + menu +={" + + + + + +"} + for(var/datum/data/crime/c in active3.fields["ma_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 += "

                Pimpin' Ride:

                " + + ldat = null + for (var/obj/vehicle/ridden/janicart/M in world) + var/turf/ml = get_turf(M) + + if(ml) + if (ml.z != cl.z) + continue + var/direction = get_dir(src, M) + ldat += "Ride - \[[ml.x],[ml.y] ([uppertext(dir2text(direction))])\]
                " + + 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 + 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 (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 + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + 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 + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + if("Send Signal") + INVOKE_ASYNC(radio, /obj/item/integrated_signaler.proc/send_activation) + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + if("Signal Frequency") + var/new_frequency = sanitize_frequency(radio.frequency + text2num(href_list["sfreq"])) + radio.set_frequency(new_frequency) + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + 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) + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + 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"]) + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + if("Power Select") + var/pnum = text2num(href_list["target"]) + powmonitor = powermonitors[pnum] + host_pda.mode = 433 + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + if("Supply Orders") + host_pda.mode =47 + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + if("Newscaster Access") + host_pda.mode = 53 + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + 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 + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + if("Newscaster Switch Channel") + current_channel = host_pda.msg_input() + host_pda.Topic(null,list("choice"=num2text(host_pda.mode))) + return + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + //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"]) + + 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) + playsound(src, 'sound/machines/terminal_select.ogg', 50, 1) + + 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)) + active_bot.bot_control(href_list["mule"], usr, TRUE) + + if(!host_pda) + return + host_pda.attack_self(usr) + + +/obj/item/cartridge/proc/bot_control() + + + var/mob/living/simple_animal/bot/Bot + + 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(Bot in GLOB.alive_mob_list) //Git da botz + 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 9e20dc455b..329631ff9e 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 7c21ac9144..38b117b132 100644 --- a/code/game/objects/items/devices/aicard.dm +++ b/code/game/objects/items/devices/aicard.dm @@ -1,104 +1,104 @@ -/obj/item/aicard - name = "intelliCard" - desc = "A storage device for AIs. Patent pending." - icon = 'icons/obj/aicards.dmi' - icon_state = "aicard" // aicard-full - item_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, "carded", src) - 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 = 1) - 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(1) - 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 + item_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, "carded", src) + 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 = 1) + 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(1) + 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 d526f3026e..6b53a9ba46 100644 --- a/code/game/objects/items/devices/camera_bug.dm +++ b/code/game/objects/items/devices/camera_bug.dm @@ -1,308 +1,308 @@ - -#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 - item_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.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.eye_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.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] - 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.buckled && !M.lying) - dat += " (Sitting)" - if(M.lying) - 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 + item_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.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.eye_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.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] + 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.buckled && !M.lying) + dat += " (Sitting)" + if(M.lying) + 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 7313ba8ff2..a82f99064e 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -1,546 +1,546 @@ -/obj/item/flashlight - name = "flashlight" - desc = "A hand-held emergency light." - icon = 'icons/obj/lighting.dmi' - icon_state = "flashlight" - item_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 - materials = list(MAT_METAL=50, MAT_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 = 0.8 //strength of the light when on - light_color = "#FFCC66" - -/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) - playsound(user, on ? 'sound/weapons/magin.ogg' : 'sound/weapons/magout.ogg', 40, 1) - 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.eye_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 < 0.3) - 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 || (HAS_TRAIT(M, TRAIT_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" - item_state = "" - flags_1 = CONDUCT_1 - brightness_on = 2 - light_color = "#FFDDCC" - flashlight_power = 0.3 - 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 + 100 - return - -/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, 0) //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" - item_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. - light_color = "#CDDDFF" - flashlight_power = 0.9 - 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" - item_state = "lamp" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - force = 10 - brightness_on = 5 - light_color = "#FFDDBB" - w_class = WEIGHT_CLASS_BULKY - flags_1 = CONDUCT_1 - materials = list() - on = TRUE - - -// green-shaded desk lamp -/obj/item/flashlight/lamp/green - desc = "A classic green-shaded desk lamp." - icon_state = "lampgreen" - item_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" - item_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. - light_color = "#FA421A" - icon_state = "flare" - item_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/New() - 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) - if(fuel && on) - . = "[user] lights [A] with [src] like a real \ - badass." - else - . = "" - -/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) - item_state = "[initial(item_state)]-on" - else - item_state = "[initial(item_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 - light_color = "#FAA44B" - icon_state = "torch" - item_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" - item_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 - light_color = "#FFAA44" - flashlight_power = 0.75 - - -/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" - item_state = "slime" - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - materials = list() - brightness_on = 6 //luminosity when on - light_color = "#FFEEAA" - flashlight_power = 0.6 - -/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." - w_class = WEIGHT_CLASS_SMALL - brightness_on = 4 - color = LIGHT_COLOR_GREEN - icon_state = "glowstick" - item_state = "glowstick" - grind_results = list(/datum/reagent/phenol = 15, /datum/reagent/hydrogen = 10, /datum/reagent/oxygen = 5) //Meth-in-a-stick - rad_flags = RAD_NO_CONTAMINATE - 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() - item_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) - item_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!") - activate() - -/obj/item/flashlight/glowstick/proc/activate() - if(!on) - on = TRUE - START_PROCESSING(SSobj, src) - -/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/item/flashlight/spotlight //invisible lighting source - name = "disco light" - desc = "Groovy..." - icon_state = null - light_color = null - brightness_on = 0 - flashlight_power = 1 - 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" - item_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 - flags_1 = CONDUCT_1 - item_flags = DROPDEL - actions_types = list() +/obj/item/flashlight + name = "flashlight" + desc = "A hand-held emergency light." + icon = 'icons/obj/lighting.dmi' + icon_state = "flashlight" + item_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 + materials = list(MAT_METAL=50, MAT_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 = 0.8 //strength of the light when on + light_color = "#FFCC66" + +/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) + playsound(user, on ? 'sound/weapons/magin.ogg' : 'sound/weapons/magout.ogg', 40, 1) + 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.eye_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 < 0.3) + 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 || (HAS_TRAIT(M, TRAIT_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" + item_state = "" + flags_1 = CONDUCT_1 + brightness_on = 2 + light_color = "#FFDDCC" + flashlight_power = 0.3 + 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 + 100 + return + +/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, 0) //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" + item_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. + light_color = "#CDDDFF" + flashlight_power = 0.9 + 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" + item_state = "lamp" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + force = 10 + brightness_on = 5 + light_color = "#FFDDBB" + w_class = WEIGHT_CLASS_BULKY + flags_1 = CONDUCT_1 + materials = list() + on = TRUE + + +// green-shaded desk lamp +/obj/item/flashlight/lamp/green + desc = "A classic green-shaded desk lamp." + icon_state = "lampgreen" + item_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" + item_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. + light_color = "#FA421A" + icon_state = "flare" + item_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/New() + 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) + if(fuel && on) + . = "[user] lights [A] with [src] like a real \ + badass." + else + . = "" + +/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) + item_state = "[initial(item_state)]-on" + else + item_state = "[initial(item_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 + light_color = "#FAA44B" + icon_state = "torch" + item_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" + item_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 + light_color = "#FFAA44" + flashlight_power = 0.75 + + +/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" + item_state = "slime" + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + materials = list() + brightness_on = 6 //luminosity when on + light_color = "#FFEEAA" + flashlight_power = 0.6 + +/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." + w_class = WEIGHT_CLASS_SMALL + brightness_on = 4 + color = LIGHT_COLOR_GREEN + icon_state = "glowstick" + item_state = "glowstick" + grind_results = list(/datum/reagent/phenol = 15, /datum/reagent/hydrogen = 10, /datum/reagent/oxygen = 5) //Meth-in-a-stick + rad_flags = RAD_NO_CONTAMINATE + 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() + item_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) + item_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!") + activate() + +/obj/item/flashlight/glowstick/proc/activate() + if(!on) + on = TRUE + START_PROCESSING(SSobj, src) + +/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/item/flashlight/spotlight //invisible lighting source + name = "disco light" + desc = "Groovy..." + icon_state = null + light_color = null + brightness_on = 0 + flashlight_power = 1 + 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" + item_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 + 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 acdb546a34..eff8f1b7dc 100644 --- a/code/game/objects/items/devices/gps.dm +++ b/code/game/objects/items/devices/gps.dm @@ -1,228 +1,228 @@ -GLOBAL_LIST_EMPTY(GPS_list) -/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" - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - obj_flags = UNIQUE_RENAME - var/gpstag = "COM0" - var/emped = FALSE - var/tracking = TRUE - var/updating = TRUE //Automatic updating of GPS list. Can be set to manual by user. - var/global_mode = TRUE //If disabled, only GPS signals of the same Z level are shown - -/obj/item/gps/examine(mob/user) - . = ..() - var/turf/curr = get_turf(src) - . += "The screen says: [get_area_name(curr, TRUE)] ([curr.x], [curr.y], [curr.z])" - . += "Alt-click to switch it [tracking ? "off":"on"]." - -/obj/item/gps/Initialize() - . = ..() - GLOB.GPS_list += src - name = "global positioning system ([gpstag])" - add_overlay("working") - -/obj/item/gps/Destroy() - GLOB.GPS_list -= src - return ..() - -/obj/item/gps/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - emped = TRUE - cut_overlay("working") - add_overlay("emp") - addtimer(CALLBACK(src, .proc/reboot), 300, TIMER_UNIQUE|TIMER_OVERRIDE) //if a new EMP happens, remove the old timer so it doesn't reactivate early - SStgui.close_uis(src) //Close the UI control if it is open. - -/obj/item/gps/proc/reboot() - emped = FALSE - cut_overlay("emp") - add_overlay("working") - -/obj/item/gps/AltClick(mob/user) - . = ..() - if(!user.canUseTopic(src, BE_CLOSE)) - return - toggletracking(user) - return TRUE - -/obj/item/gps/proc/toggletracking(mob/user) - if(!user.canUseTopic(src, BE_CLOSE)) - return //user not valid to use gps - if(emped) - to_chat(user, "It's busted!") - return - if(tracking) - cut_overlay("working") - to_chat(user, "[src] is no longer tracking, or visible to other GPS devices.") - tracking = FALSE - else - add_overlay("working") - to_chat(user, "[src] is now tracking, and visible to other GPS devices.") - tracking = TRUE - - -/obj/item/gps/ui_interact(mob/user, ui_key = "gps", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. - if(emped) - to_chat(user, "[src] fizzles weakly.") - return - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - var/gps_window_height = 300 + GLOB.GPS_list.len * 20 // Variable window height, depending on how many GPS units there are to show - ui = new(user, src, ui_key, "gps", "Global Positioning System", 600, gps_window_height, master_ui, state) //width, height - ui.open() - - ui.set_autoupdate(state = updating) - - -/obj/item/gps/ui_data(mob/user) - var/list/data = list() - data["power"] = tracking - data["tag"] = gpstag - data["updating"] = updating - data["globalmode"] = global_mode - if(!tracking || emped) //Do not bother scanning if the GPS is off or EMPed - return data - - var/turf/curr = get_turf(src) - data["current"] = "[get_area_name(curr, TRUE)] ([curr.x], [curr.y], [curr.z])" - - var/list/signals = list() - data["signals"] = list() - - for(var/gps in GLOB.GPS_list) - var/obj/item/gps/G = gps - if(G.emped || !G.tracking || G == src) - continue - var/turf/pos = get_turf(G) - if(!global_mode && pos.z != curr.z) - continue - var/list/signal = list() - signal["entrytag"] = G.gpstag //Name or 'tag' of the GPS - signal["area"] = get_area_name(G, TRUE) - signal["coord"] = "[pos.x], [pos.y], [pos.z]" - if(pos.z == curr.z) //Distance/Direction calculations for same z-level only - signal["dist"] = max(get_dist(curr, pos), 0) //Distance between the src and remote GPS turfs - signal["degrees"] = round(Get_Angle(curr, pos)) //0-360 degree directional bearing, for more precision. - var/direction = uppertext(dir2text(get_dir(curr, pos))) //Direction text (East, etc). Not as precise, but still helpful. - if(!direction) - direction = "CENTER" - signal["degrees"] = "N/A" - signal["direction"] = direction - - signals += list(signal) //Add this signal to the list of signals - data["signals"] = signals - return data - - - -/obj/item/gps/ui_act(action, params) - if(..()) - return - switch(action) - if("rename") - var/a = input("Please enter desired tag.", name, gpstag) as text - a = copytext(sanitize(a), 1, 20) - gpstag = a - . = TRUE - name = "global positioning system ([gpstag])" - - if("power") - toggletracking(usr) - . = TRUE - if("updating") - updating = !updating - . = TRUE - if("globalmode") - global_mode = !global_mode - . = TRUE - - -/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/internal - icon_state = null - item_flags = ABSTRACT - gpstag = "Eerie Signal" - desc = "Report to a coder immediately." - invisibility = INVISIBILITY_MAXIMUM - var/obj/item/implant/gps/implant - -/obj/item/gps/internal/Initialize(mapload, obj/item/implant/gps/_implant) - . = ..() - implant = _implant - -/obj/item/gps/internal/Destroy() - if(implant?.imp_in) - qdel(implant) - else - return ..() - -/obj/item/gps/internal/mining - 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/internal/base - gpstag = "NT_AUX" - desc = "A homing signal from Nanotrasen's mining base." - -/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) - . = ..() +GLOBAL_LIST_EMPTY(GPS_list) +/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" + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + obj_flags = UNIQUE_RENAME + var/gpstag = "COM0" + var/emped = FALSE + var/tracking = TRUE + var/updating = TRUE //Automatic updating of GPS list. Can be set to manual by user. + var/global_mode = TRUE //If disabled, only GPS signals of the same Z level are shown + +/obj/item/gps/examine(mob/user) + . = ..() + var/turf/curr = get_turf(src) + . += "The screen says: [get_area_name(curr, TRUE)] ([curr.x], [curr.y], [curr.z])" + . += "Alt-click to switch it [tracking ? "off":"on"]." + +/obj/item/gps/Initialize() + . = ..() + GLOB.GPS_list += src + name = "global positioning system ([gpstag])" + add_overlay("working") + +/obj/item/gps/Destroy() + GLOB.GPS_list -= src + return ..() + +/obj/item/gps/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + emped = TRUE + cut_overlay("working") + add_overlay("emp") + addtimer(CALLBACK(src, .proc/reboot), 300, TIMER_UNIQUE|TIMER_OVERRIDE) //if a new EMP happens, remove the old timer so it doesn't reactivate early + SStgui.close_uis(src) //Close the UI control if it is open. + +/obj/item/gps/proc/reboot() + emped = FALSE + cut_overlay("emp") + add_overlay("working") + +/obj/item/gps/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, BE_CLOSE)) + return + toggletracking(user) + return TRUE + +/obj/item/gps/proc/toggletracking(mob/user) + if(!user.canUseTopic(src, BE_CLOSE)) + return //user not valid to use gps + if(emped) + to_chat(user, "It's busted!") + return + if(tracking) + cut_overlay("working") + to_chat(user, "[src] is no longer tracking, or visible to other GPS devices.") + tracking = FALSE + else + add_overlay("working") + to_chat(user, "[src] is now tracking, and visible to other GPS devices.") + tracking = TRUE + + +/obj/item/gps/ui_interact(mob/user, ui_key = "gps", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. + if(emped) + to_chat(user, "[src] fizzles weakly.") + return + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + var/gps_window_height = 300 + GLOB.GPS_list.len * 20 // Variable window height, depending on how many GPS units there are to show + ui = new(user, src, ui_key, "gps", "Global Positioning System", 600, gps_window_height, master_ui, state) //width, height + ui.open() + + ui.set_autoupdate(state = updating) + + +/obj/item/gps/ui_data(mob/user) + var/list/data = list() + data["power"] = tracking + data["tag"] = gpstag + data["updating"] = updating + data["globalmode"] = global_mode + if(!tracking || emped) //Do not bother scanning if the GPS is off or EMPed + return data + + var/turf/curr = get_turf(src) + data["current"] = "[get_area_name(curr, TRUE)] ([curr.x], [curr.y], [curr.z])" + + var/list/signals = list() + data["signals"] = list() + + for(var/gps in GLOB.GPS_list) + var/obj/item/gps/G = gps + if(G.emped || !G.tracking || G == src) + continue + var/turf/pos = get_turf(G) + if(!global_mode && pos.z != curr.z) + continue + var/list/signal = list() + signal["entrytag"] = G.gpstag //Name or 'tag' of the GPS + signal["area"] = get_area_name(G, TRUE) + signal["coord"] = "[pos.x], [pos.y], [pos.z]" + if(pos.z == curr.z) //Distance/Direction calculations for same z-level only + signal["dist"] = max(get_dist(curr, pos), 0) //Distance between the src and remote GPS turfs + signal["degrees"] = round(Get_Angle(curr, pos)) //0-360 degree directional bearing, for more precision. + var/direction = uppertext(dir2text(get_dir(curr, pos))) //Direction text (East, etc). Not as precise, but still helpful. + if(!direction) + direction = "CENTER" + signal["degrees"] = "N/A" + signal["direction"] = direction + + signals += list(signal) //Add this signal to the list of signals + data["signals"] = signals + return data + + + +/obj/item/gps/ui_act(action, params) + if(..()) + return + switch(action) + if("rename") + var/a = input("Please enter desired tag.", name, gpstag) as text + a = copytext(sanitize(a), 1, 20) + gpstag = a + . = TRUE + name = "global positioning system ([gpstag])" + + if("power") + toggletracking(usr) + . = TRUE + if("updating") + updating = !updating + . = TRUE + if("globalmode") + global_mode = !global_mode + . = TRUE + + +/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/internal + icon_state = null + item_flags = ABSTRACT + gpstag = "Eerie Signal" + desc = "Report to a coder immediately." + invisibility = INVISIBILITY_MAXIMUM + var/obj/item/implant/gps/implant + +/obj/item/gps/internal/Initialize(mapload, obj/item/implant/gps/_implant) + . = ..() + implant = _implant + +/obj/item/gps/internal/Destroy() + if(implant?.imp_in) + qdel(implant) + else + return ..() + +/obj/item/gps/internal/mining + 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/internal/base + gpstag = "NT_AUX" + desc = "A homing signal from Nanotrasen's mining base." + +/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 5894b559ae..48ba92962f 100644 --- a/code/game/objects/items/devices/instruments.dm +++ b/code/game/objects/items/devices/instruments.dm @@ -1,277 +1,277 @@ -//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/tune_time = 0 - -/obj/item/instrument/Initialize() - . = ..() - song = new(instrumentId, src, instrumentExt) - -/obj/item/instrument/Destroy() - if (tune_time) - STOP_PROCESSING(SSobj, src) - qdel(song) - song = null - return ..() - -/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/user) - if(!user) - return - - if(!isliving(user) || user.stat || user.restrained() || user.lying) - return - - user.set_machine(src) - song.interact(user) - -/obj/item/instrument/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/musicaltuner)) - var/mob/living/carbon/human/H = user - if (HAS_TRAIT(H, TRAIT_MUSICIAN)) - if (!tune_time) - H.visible_message("[H] tunes the [src] to perfection!", "You tune the [src] to perfection!") - tune_time = 300 - START_PROCESSING(SSobj, src) - else - to_chat(H, "[src] is already well tuned!") - else - to_chat(H, "You have no idea how to use this.") - -/obj/item/instrument/process() - if (tune_time) - if (song.playing) - for (var/mob/living/M in song.hearing_mobs) - M.dizziness = max(0,M.dizziness-2) - M.jitteriness = max(0,M.jitteriness-2) - M.confused = max(M.confused-1) - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "goodmusic", /datum/mood_event/goodmusic) - tune_time-- - else - if (!tune_time) - if (song.playing) - loc.visible_message("[src] starts sounding a little off...") - STOP_PROCESSING(SSobj, src) - -/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" - item_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" - item_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" - item_state = "synth" - instrumentId = "piano" - instrumentExt = "ogg" - var/static/list/insTypes = list("accordion" = "mid", "bikehorn" = "ogg", "glockenspiel" = "mid", "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/guitar - name = "guitar" - desc = "It's made of wood and has bronze strings." - icon_state = "guitar" - item_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" - item_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" - item_state = "glockenspiel" - instrumentId = "glockenspiel" - -/obj/item/instrument/accordion - name = "accordion" - desc = "Pun-Pun not included." - icon_state = "accordion" - item_state = "accordion" - instrumentId = "accordion" - -/obj/item/instrument/trumpet - name = "trumpet" - desc = "To announce the arrival of the king!" - icon_state = "trumpet" - item_state = "trombone" - instrumentId = "trombone" - -/obj/item/instrument/trumpet/spectral - name = "spectral trumpet" - desc = "Things are about to get spooky!" - icon_state = "trumpet" - item_state = "trombone" - 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 (loc, 'sound/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" - item_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" - item_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 (loc, 'sound/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" - item_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" - item_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 (loc, 'sound/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" - item_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" - item_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) - -/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" - item_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/musicaltuner - name = "musical tuner" - desc = "A device for tuning musical instruments both manual and electronic alike." - icon = 'icons/obj/device.dmi' - icon_state = "musicaltuner" - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' +//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/tune_time = 0 + +/obj/item/instrument/Initialize() + . = ..() + song = new(instrumentId, src, instrumentExt) + +/obj/item/instrument/Destroy() + if (tune_time) + STOP_PROCESSING(SSobj, src) + qdel(song) + song = null + return ..() + +/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/user) + if(!user) + return + + if(!isliving(user) || user.stat || user.restrained() || user.lying) + return + + user.set_machine(src) + song.interact(user) + +/obj/item/instrument/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/musicaltuner)) + var/mob/living/carbon/human/H = user + if (HAS_TRAIT(H, TRAIT_MUSICIAN)) + if (!tune_time) + H.visible_message("[H] tunes the [src] to perfection!", "You tune the [src] to perfection!") + tune_time = 300 + START_PROCESSING(SSobj, src) + else + to_chat(H, "[src] is already well tuned!") + else + to_chat(H, "You have no idea how to use this.") + +/obj/item/instrument/process() + if (tune_time) + if (song.playing) + for (var/mob/living/M in song.hearing_mobs) + M.dizziness = max(0,M.dizziness-2) + M.jitteriness = max(0,M.jitteriness-2) + M.confused = max(M.confused-1) + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "goodmusic", /datum/mood_event/goodmusic) + tune_time-- + else + if (!tune_time) + if (song.playing) + loc.visible_message("[src] starts sounding a little off...") + STOP_PROCESSING(SSobj, src) + +/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" + item_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" + item_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" + item_state = "synth" + instrumentId = "piano" + instrumentExt = "ogg" + var/static/list/insTypes = list("accordion" = "mid", "bikehorn" = "ogg", "glockenspiel" = "mid", "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/guitar + name = "guitar" + desc = "It's made of wood and has bronze strings." + icon_state = "guitar" + item_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" + item_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" + item_state = "glockenspiel" + instrumentId = "glockenspiel" + +/obj/item/instrument/accordion + name = "accordion" + desc = "Pun-Pun not included." + icon_state = "accordion" + item_state = "accordion" + instrumentId = "accordion" + +/obj/item/instrument/trumpet + name = "trumpet" + desc = "To announce the arrival of the king!" + icon_state = "trumpet" + item_state = "trombone" + instrumentId = "trombone" + +/obj/item/instrument/trumpet/spectral + name = "spectral trumpet" + desc = "Things are about to get spooky!" + icon_state = "trumpet" + item_state = "trombone" + 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 (loc, 'sound/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" + item_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" + item_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 (loc, 'sound/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" + item_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" + item_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 (loc, 'sound/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" + item_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" + item_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) + +/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" + item_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/musicaltuner + name = "musical tuner" + desc = "A device for tuning musical instruments both manual and electronic alike." + icon = 'icons/obj/device.dmi' + icon_state = "musicaltuner" + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index 1654ecde41..cc470466df 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -1,189 +1,189 @@ -/obj/item/laser_pointer - name = "laser pointer" - desc = "Don't shine it in your eyes!" - icon = 'icons/obj/device.dmi' - icon_state = "pointer" - item_state = "pen" - var/pointer_icon_state - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - materials = list(MAT_METAL=500, MAT_GLASS=500) - w_class = WEIGHT_CLASS_SMALL - var/turf/pointer_loc - var/energy = 5 - var/max_energy = 5 - var/effectchance = 33 - var/recharging = 0 - 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/New() - ..() - diode = new(src) - if(!pointer_icon_state) - pointer_icon_state = pick("red_laser","green_laser","blue_laser","purple_laser") - -/obj/item/laser_pointer/upgraded/New() - ..() - 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(istype(W, /obj/item/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/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) - log_combat(user, C, "shone in the eyes", src) - - 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." - else - outmsg = "You fail to blind [C] by shining [src] at [C.p_their()] eyes!" - - //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.Knockdown(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(!iscatperson(H) || H.incapacitated() || H.eye_blind ) - continue - if(!H.lying) - H.setDir(get_dir(H,targloc)) // kitty always looks at the light - if(prob(effectchance)) - 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(50)) - C.visible_message("[C] pounces on the light!","LIGHT!") - C.Move(targloc) - C.resting = TRUE - C.update_canmove() - 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 = 1 - 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(prob(20 - recharge_locked*5)) - energy += 1 - if(energy >= max_energy) - energy = max_energy - recharging = 0 - recharge_locked = FALSE - ..() +/obj/item/laser_pointer + name = "laser pointer" + desc = "Don't shine it in your eyes!" + icon = 'icons/obj/device.dmi' + icon_state = "pointer" + item_state = "pen" + var/pointer_icon_state + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + materials = list(MAT_METAL=500, MAT_GLASS=500) + w_class = WEIGHT_CLASS_SMALL + var/turf/pointer_loc + var/energy = 5 + var/max_energy = 5 + var/effectchance = 33 + var/recharging = 0 + 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/New() + ..() + diode = new(src) + if(!pointer_icon_state) + pointer_icon_state = pick("red_laser","green_laser","blue_laser","purple_laser") + +/obj/item/laser_pointer/upgraded/New() + ..() + 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(istype(W, /obj/item/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/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) + log_combat(user, C, "shone in the eyes", src) + + 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." + else + outmsg = "You fail to blind [C] by shining [src] at [C.p_their()] eyes!" + + //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.Knockdown(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(!iscatperson(H) || H.incapacitated() || H.eye_blind ) + continue + if(!H.lying) + H.setDir(get_dir(H,targloc)) // kitty always looks at the light + if(prob(effectchance)) + 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(50)) + C.visible_message("[C] pounces on the light!","LIGHT!") + C.Move(targloc) + C.resting = TRUE + C.update_canmove() + 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 = 1 + 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(prob(20 - recharge_locked*5)) + energy += 1 + if(energy >= max_energy) + energy = max_energy + recharging = 0 + recharge_locked = FALSE + ..() diff --git a/code/game/objects/items/devices/lightreplacer.dm b/code/game/objects/items/devices/lightreplacer.dm index 2352a34e23..30eaca2ba2 100644 --- a/code/game/objects/items/devices/lightreplacer.dm +++ b/code/game/objects/items/devices/lightreplacer.dm @@ -1,272 +1,272 @@ - -// 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" - item_state = "electronic" - 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_BELT - force = 8 - - var/max_uses = 20 - var/uses = 0 - var/failmsg = "" - // 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/New() - uses = max_uses / 2 - failmsg = "The [name]'s refill light blinks red." - ..() - -/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, "[src.name] 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.name]. 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 the [L.name] into the [src.name]") - 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() - return TRUE - -/obj/item/lightreplacer/attack_self(mob/user) - to_chat(user, status_string()) - -/obj/item/lightreplacer/update_icon() - 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, 1) - 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] has fabricated a new bulb from the broken glass it has stored. It now has [uses] uses.") - playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) - 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, failmsg) - 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, 1) - 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, failmsg) - -/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" + item_state = "electronic" + 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_BELT + force = 8 + + var/max_uses = 20 + var/uses = 0 + var/failmsg = "" + // 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/New() + uses = max_uses / 2 + failmsg = "The [name]'s refill light blinks red." + ..() + +/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, "[src.name] 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.name]. 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 the [L.name] into the [src.name]") + 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() + return TRUE + +/obj/item/lightreplacer/attack_self(mob/user) + to_chat(user, status_string()) + +/obj/item/lightreplacer/update_icon() + 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, 1) + 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] has fabricated a new bulb from the broken glass it has stored. It now has [uses] uses.") + playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) + 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, failmsg) + 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, 1) + 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, failmsg) + +/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 f3b53ba352..6d0a78974a 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -1,266 +1,266 @@ -#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" - item_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 - materials = list(MAT_METAL=50, MAT_GLASS=20) - var/obj/machinery/buffer // simple machine buffer for device linkage - toolspeed = 1 - tool_behaviour = TOOL_MULTITOOL - usesound = 'sound/weapons/empty.ogg' - var/datum/integrated_io/selected_io = null //functional for integrated circuits. - var/mode = 0 - -/obj/item/multitool/chaplain - name = "\improper hypertool" - desc = "Used for pulsing wires to test which to cut. Also emits microwaves to fry some brains!" - damtype = BRAIN - force = 18 - armour_penetration = 35 - hitsound = 'sound/effects/sparks4.ogg' - var/chaplain_spawnable = TRUE - total_mass = TOTAL_MASS_MEDIEVAL_WEAPON - throw_speed = 3 - throw_range = 4 - throwforce = 10 - obj_flags = UNIQUE_RENAME - -/obj/item/multitool/chaplain/Initialize() - . = ..() - AddComponent(/datum/component/anti_magic, TRUE, TRUE, FALSE, null, null, FALSE) - -/obj/item/multitool/examine(mob/user) - . = ..() - if(selected_io) - . += "Activate [src] to detach the data wire." - if(buffer) - . += "Its buffer contains [buffer]." - -/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 wasnt recommended by doctors - -/obj/item/multitool/attack_self(mob/user) - if(selected_io) - selected_io = null - to_chat(user, "You clear the wired connection from the multitool.") - update_icon() - -/obj/item/multitool/update_icon() - if(selected_io) - icon_state = "multitool_red" - else - icon_state = "multitool" - -/obj/item/multitool/proc/wire(var/datum/integrated_io/io, mob/user) - if(!io.holder.assembly) - to_chat(user, "\The [io.holder] needs to be secured inside an assembly first.") - return - - if(selected_io) - if(io == selected_io) - to_chat(user, "Wiring \the [selected_io.holder]'s [selected_io.name] into itself is rather pointless.") - return - if(io.io_type != selected_io.io_type) - to_chat(user, "Those two types of channels are incompatible. The first is a [selected_io.io_type], \ - while the second is a [io.io_type].") - return - if(io.holder.assembly && io.holder.assembly != selected_io.holder.assembly) - to_chat(user, "Both \the [io.holder] and \the [selected_io.holder] need to be inside the same assembly.") - return - io.connect_pin(selected_io) - - to_chat(user, "You connect \the [selected_io.holder]'s [selected_io.name] to \the [io.holder]'s [io.name].") - selected_io.holder.interact(user) // This is to update the UI. - selected_io = null - - else - selected_io = io - to_chat(user, "You link \the multitool to \the [selected_io.holder]'s [selected_io.name] data channel.") - - update_icon() - - -/obj/item/multitool/proc/unwire(var/datum/integrated_io/io1, var/datum/integrated_io/io2, mob/user) - if(!io1.linked.len || !io2.linked.len) - to_chat(user, "There is nothing connected to the data channel.") - return - - if(!(io1 in io2.linked) || !(io2 in io1.linked) ) - to_chat(user, "These data pins aren't connected!") - return - else - io1.disconnect_pin(io2) - to_chat(user, "You clip the data connection between the [io1.holder.displayed_name]'s \ - [io1.name] and the [io2.holder.displayed_name]'s [io2.name].") - io1.holder.interact(user) // This is to update the UI. - update_icon() - - - -// 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/aiEye/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/aiEye/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/update_icon() - if(selected_io) - icon_state = "multitool_red" - else - icon_state = "[initial(icon_state)][detect_state]" - -/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/aiEye/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/aiEye/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/cyborg - name = "multitool" - desc = "Optimised and stripped-down version of a regular multitool." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "multitool_cyborg" - toolspeed = 0.5 - -/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/advanced - name = "advanced multitool" - desc = "The reproduction of an abductor's multitool, this multitool is a classy silver." - icon = 'icons/obj/advancedtools.dmi' - icon_state = "multitool" - toolspeed = 0.2 +#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" + item_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 + materials = list(MAT_METAL=50, MAT_GLASS=20) + var/obj/machinery/buffer // simple machine buffer for device linkage + toolspeed = 1 + tool_behaviour = TOOL_MULTITOOL + usesound = 'sound/weapons/empty.ogg' + var/datum/integrated_io/selected_io = null //functional for integrated circuits. + var/mode = 0 + +/obj/item/multitool/chaplain + name = "\improper hypertool" + desc = "Used for pulsing wires to test which to cut. Also emits microwaves to fry some brains!" + damtype = BRAIN + force = 18 + armour_penetration = 35 + hitsound = 'sound/effects/sparks4.ogg' + var/chaplain_spawnable = TRUE + total_mass = TOTAL_MASS_MEDIEVAL_WEAPON + throw_speed = 3 + throw_range = 4 + throwforce = 10 + obj_flags = UNIQUE_RENAME + +/obj/item/multitool/chaplain/Initialize() + . = ..() + AddComponent(/datum/component/anti_magic, TRUE, TRUE, FALSE, null, null, FALSE) + +/obj/item/multitool/examine(mob/user) + . = ..() + if(selected_io) + . += "Activate [src] to detach the data wire." + if(buffer) + . += "Its buffer contains [buffer]." + +/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 wasnt recommended by doctors + +/obj/item/multitool/attack_self(mob/user) + if(selected_io) + selected_io = null + to_chat(user, "You clear the wired connection from the multitool.") + update_icon() + +/obj/item/multitool/update_icon() + if(selected_io) + icon_state = "multitool_red" + else + icon_state = "multitool" + +/obj/item/multitool/proc/wire(var/datum/integrated_io/io, mob/user) + if(!io.holder.assembly) + to_chat(user, "\The [io.holder] needs to be secured inside an assembly first.") + return + + if(selected_io) + if(io == selected_io) + to_chat(user, "Wiring \the [selected_io.holder]'s [selected_io.name] into itself is rather pointless.") + return + if(io.io_type != selected_io.io_type) + to_chat(user, "Those two types of channels are incompatible. The first is a [selected_io.io_type], \ + while the second is a [io.io_type].") + return + if(io.holder.assembly && io.holder.assembly != selected_io.holder.assembly) + to_chat(user, "Both \the [io.holder] and \the [selected_io.holder] need to be inside the same assembly.") + return + io.connect_pin(selected_io) + + to_chat(user, "You connect \the [selected_io.holder]'s [selected_io.name] to \the [io.holder]'s [io.name].") + selected_io.holder.interact(user) // This is to update the UI. + selected_io = null + + else + selected_io = io + to_chat(user, "You link \the multitool to \the [selected_io.holder]'s [selected_io.name] data channel.") + + update_icon() + + +/obj/item/multitool/proc/unwire(var/datum/integrated_io/io1, var/datum/integrated_io/io2, mob/user) + if(!io1.linked.len || !io2.linked.len) + to_chat(user, "There is nothing connected to the data channel.") + return + + if(!(io1 in io2.linked) || !(io2 in io1.linked) ) + to_chat(user, "These data pins aren't connected!") + return + else + io1.disconnect_pin(io2) + to_chat(user, "You clip the data connection between the [io1.holder.displayed_name]'s \ + [io1.name] and the [io2.holder.displayed_name]'s [io2.name].") + io1.holder.interact(user) // This is to update the UI. + update_icon() + + + +// 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/aiEye/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/aiEye/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/update_icon() + if(selected_io) + icon_state = "multitool_red" + else + icon_state = "[initial(icon_state)][detect_state]" + +/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/aiEye/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/aiEye/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/cyborg + name = "multitool" + desc = "Optimised and stripped-down version of a regular multitool." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "multitool_cyborg" + toolspeed = 0.5 + +/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/advanced + name = "advanced multitool" + desc = "The reproduction of an abductor's multitool, this multitool is a classy silver." + icon = 'icons/obj/advancedtools.dmi' + icon_state = "multitool" + toolspeed = 0.2 diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index 3ee4a14a91..cef75942a0 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -1,169 +1,169 @@ -/obj/item/paicard - name = "personal AI device" - icon = 'icons/obj/aicards.dmi' - icon_state = "pai" - item_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 - max_integrity = 200 - -/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.radio.wires.is_cut(WIRE_TX)) ? "Disabled" : "Enabled"]
                " - dat += "Receive: [(pai.radio.wires.is_cut(WIRE_RX)) ? "Disabled" : "Enabled"]
                " - if(pai.radio_short) - dat += "Reset radio short: \[RESET\]
                " - 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 += "\[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.") - 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["wires"]) - var/wire = text2num(href_list["wires"]) - if(pai.radio) - pai.radio.wires.cut(wire) - if(href_list["reset_radio_short"]) - pai.unshort_radio() - if(href_list["setlaws"]) - var/newlaws = copytext(sanitize(input("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]) as message),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, 1, -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-exclamation") - if(12) - src.add_overlay("pai-question") - if(13) - src.add_overlay("pai-sunglasses") - -/obj/item/paicard/proc/alertUpdate() - visible_message("[src] flashes a message across its screen, \"Additional personalities available for download.\"", "[src] bleeps electronically.") - -/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" + item_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 + max_integrity = 200 + +/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.radio.wires.is_cut(WIRE_TX)) ? "Disabled" : "Enabled"]
                " + dat += "Receive: [(pai.radio.wires.is_cut(WIRE_RX)) ? "Disabled" : "Enabled"]
                " + if(pai.radio_short) + dat += "Reset radio short: \[RESET\]
                " + 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 += "\[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.") + 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["wires"]) + var/wire = text2num(href_list["wires"]) + if(pai.radio) + pai.radio.wires.cut(wire) + if(href_list["reset_radio_short"]) + pai.unshort_radio() + if(href_list["setlaws"]) + var/newlaws = copytext(sanitize(input("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]) as message),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, 1, -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-exclamation") + if(12) + src.add_overlay("pai-question") + if(13) + src.add_overlay("pai-sunglasses") + +/obj/item/paicard/proc/alertUpdate() + visible_message("[src] flashes a message across its screen, \"Additional personalities available for download.\"", "[src] bleeps electronically.") + +/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 5276655dda..58fc77e739 100644 --- a/code/game/objects/items/devices/powersink.dm +++ b/code/game/objects/items/devices/powersink.dm @@ -1,152 +1,152 @@ -// Powersink - used to drain station power - -/obj/item/powersink - desc = "A nulling power sink which drains energy from electrical systems." - name = "power sink" - icon = 'icons/obj/device.dmi' - icon_state = "powersink0" - item_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 - materials = list(MAT_METAL=750) - var/drain_rate = 1600000 // amount of power to drain per tick - var/power_drained = 0 // has drained this much power - var/max_power = 1e10 // 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/const/DISCONNECTED = 0 - var/const/CLAMPED_OFF = 1 - var/const/OPERATING = 2 - - var/obj/structure/cable/attached // the attached cable - -/obj/item/powersink/update_icon() - 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 - - if(CLAMPED_OFF) - if(!attached) - return - if(mode == OPERATING) - STOP_PROCESSING(SSobj, src) - anchored = TRUE - - if(OPERATING) - if(!attached) - return - START_PROCESSING(SSobj, src) - anchored = TRUE - - mode = value - update_icon() - set_light(0) - -/obj/item/powersink/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/screwdriver)) - if(mode == DISCONNECTED) - var/turf/T = loc - if(isturf(T) && !T.intact) - attached = locate() in T - if(!attached) - to_chat(user, "This device 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 attach \the [src] to the cable.", - "You hear some wires being connected to something.") - else - to_chat(user, "This device must be placed over an exposed, powered cable node!") - else - set_mode(DISCONNECTED) - user.visible_message( \ - "[user] detaches \the [src] from the cable.", \ - "You detach \the [src] from the cable.", - "You hear some wires being disconnected from something.") - 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, 1, 1) - - if(power_drained >= max_power) - STOP_PROCESSING(SSobj, src) - explosion(src.loc, 4,8,16,32) - qdel(src) +// Powersink - used to drain station power + +/obj/item/powersink + desc = "A nulling power sink which drains energy from electrical systems." + name = "power sink" + icon = 'icons/obj/device.dmi' + icon_state = "powersink0" + item_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 + materials = list(MAT_METAL=750) + var/drain_rate = 1600000 // amount of power to drain per tick + var/power_drained = 0 // has drained this much power + var/max_power = 1e10 // 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/const/DISCONNECTED = 0 + var/const/CLAMPED_OFF = 1 + var/const/OPERATING = 2 + + var/obj/structure/cable/attached // the attached cable + +/obj/item/powersink/update_icon() + 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 + + if(CLAMPED_OFF) + if(!attached) + return + if(mode == OPERATING) + STOP_PROCESSING(SSobj, src) + anchored = TRUE + + if(OPERATING) + if(!attached) + return + START_PROCESSING(SSobj, src) + anchored = TRUE + + mode = value + update_icon() + set_light(0) + +/obj/item/powersink/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/screwdriver)) + if(mode == DISCONNECTED) + var/turf/T = loc + if(isturf(T) && !T.intact) + attached = locate() in T + if(!attached) + to_chat(user, "This device 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 attach \the [src] to the cable.", + "You hear some wires being connected to something.") + else + to_chat(user, "This device must be placed over an exposed, powered cable node!") + else + set_mode(DISCONNECTED) + user.visible_message( \ + "[user] detaches \the [src] from the cable.", \ + "You detach \the [src] from the cable.", + "You hear some wires being disconnected from something.") + 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, 1, 1) + + if(power_drained >= max_power) + STOP_PROCESSING(SSobj, src) + explosion(src.loc, 4,8,16,32) + qdel(src) diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm index f468121048..6aee67f2ee 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -1,146 +1,146 @@ -/obj/item/electropack - name = "electropack" - desc = "Dance my monkeys! DANCE!!!" - icon = 'icons/obj/radio.dmi' - icon_state = "electropack0" - item_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 - materials = list(MAT_METAL=10000, MAT_GLASS=2500) - - var/code = 2 - var/frequency = FREQ_ELECTROPACK - var/on = TRUE - var/shock_cooldown = FALSE - -/obj/item/electropack/suicide_act(mob/living/carbon/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) - -/obj/item/electropack/Initialize() - . = ..() - set_frequency(frequency) - -/obj/item/electropack/Destroy() - SSradio.remove_object(src, frequency) - . = ..() - -//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/living/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/Topic(href, href_list) - var/mob/living/carbon/C = usr - if(usr.stat || usr.restrained() || C.back == src) - return - - if(!usr.canUseTopic(src, BE_CLOSE)) - usr << browse(null, "window=radio") - onclose(usr, "radio") - return - - if(href_list["set"]) - if(href_list["set"] == "freq") - var/new_freq = input(usr, "Input a new receiving frequency", "Electropack Frequency", format_frequency(frequency)) as num|null - if(!usr.canUseTopic(src, BE_CLOSE)) - return - new_freq = unformat_frequency(new_freq) - new_freq = sanitize_frequency(new_freq, TRUE) - set_frequency(new_freq) - - if(href_list["set"] == "code") - var/new_code = input(usr, "Input a new receiving code", "Electropack Code", code) as num|null - if(!usr.canUseTopic(src, BE_CLOSE)) - return - new_code = round(new_code) - new_code = CLAMP(new_code, 1, 100) - code = new_code - - if(href_list["set"] == "power") - if(!usr.canUseTopic(src, BE_CLOSE)) - return - on = !(on) - icon_state = "electropack[on]" - - if(usr) - attack_self(usr) - - return - -/obj/item/electropack/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - SSradio.add_object(src, frequency, RADIO_SIGNALER) - return - -/obj/item/electropack/receive_signal(datum/signal/signal) - if(!signal || signal.data["code"] != code) - return - - if(isliving(loc) && on) - if(shock_cooldown == TRUE) - 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.Knockdown(100) - - if(master) - master.receive_signal() - return - -/obj/item/electropack/ui_interact(mob/user) - if(!ishuman(user)) - return - - user.set_machine(src) - var/dat = {" - -Turned [on ? "On" : "Off"] - Toggle
                -Frequency/Code for electropack:
                -Frequency: -[format_frequency(src.frequency)] -Set
                - -Code: -[src.code] -Set
                -
                "} - user << browse(dat, "window=radio") - onclose(user, "radio") - return +/obj/item/electropack + name = "electropack" + desc = "Dance my monkeys! DANCE!!!" + icon = 'icons/obj/radio.dmi' + icon_state = "electropack0" + item_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 + materials = list(MAT_METAL=10000, MAT_GLASS=2500) + + var/code = 2 + var/frequency = FREQ_ELECTROPACK + var/on = TRUE + var/shock_cooldown = FALSE + +/obj/item/electropack/suicide_act(mob/living/carbon/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) + +/obj/item/electropack/Initialize() + . = ..() + set_frequency(frequency) + +/obj/item/electropack/Destroy() + SSradio.remove_object(src, frequency) + . = ..() + +//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/living/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/Topic(href, href_list) + var/mob/living/carbon/C = usr + if(usr.stat || usr.restrained() || C.back == src) + return + + if(!usr.canUseTopic(src, BE_CLOSE)) + usr << browse(null, "window=radio") + onclose(usr, "radio") + return + + if(href_list["set"]) + if(href_list["set"] == "freq") + var/new_freq = input(usr, "Input a new receiving frequency", "Electropack Frequency", format_frequency(frequency)) as num|null + if(!usr.canUseTopic(src, BE_CLOSE)) + return + new_freq = unformat_frequency(new_freq) + new_freq = sanitize_frequency(new_freq, TRUE) + set_frequency(new_freq) + + if(href_list["set"] == "code") + var/new_code = input(usr, "Input a new receiving code", "Electropack Code", code) as num|null + if(!usr.canUseTopic(src, BE_CLOSE)) + return + new_code = round(new_code) + new_code = CLAMP(new_code, 1, 100) + code = new_code + + if(href_list["set"] == "power") + if(!usr.canUseTopic(src, BE_CLOSE)) + return + on = !(on) + icon_state = "electropack[on]" + + if(usr) + attack_self(usr) + + return + +/obj/item/electropack/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + SSradio.add_object(src, frequency, RADIO_SIGNALER) + return + +/obj/item/electropack/receive_signal(datum/signal/signal) + if(!signal || signal.data["code"] != code) + return + + if(isliving(loc) && on) + if(shock_cooldown == TRUE) + 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.Knockdown(100) + + if(master) + master.receive_signal() + return + +/obj/item/electropack/ui_interact(mob/user) + if(!ishuman(user)) + return + + user.set_machine(src) + var/dat = {" + +Turned [on ? "On" : "Off"] - Toggle
                +Frequency/Code for electropack:
                +Frequency: +[format_frequency(src.frequency)] +Set
                + +Code: +[src.code] +Set
                +
                "} + user << browse(dat, "window=radio") + onclose(user, "radio") + return diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index 42d9ec0269..13cd30561a 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -1,337 +1,337 @@ -// 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" - item_state = "headset" - materials = list(MAT_METAL=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/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" - item_state = "syndie_headset" - -/obj/item/radio/headset/syndicate/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(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" - item_state = "sec_headset_alt" - -/obj/item/radio/headset/headset_sec/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(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_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_com - name = "command radio headset" - desc = "A headset with a commanding channel.\nTo access the command channel, use :c." - 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" - item_state = "com_headset_alt" - -/obj/item/radio/headset/heads/captain/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(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" - item_state = "com_headset_alt" - -/obj/item/radio/headset/heads/hos/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(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_clown - name = "clown's headset" - desc = "A headset for the clown. Finally. A megaphone you can't take away." - icon_state = "srv_headset" - keyslot = new /obj/item/encryptionkey/headset_service - command = TRUE - commandspan = SPAN_CLOWN - -/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" - item_state = "cent_headset_alt" - keyslot = null - -/obj/item/radio/headset/headset_cent/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) - -/obj/item/radio/headset/ai - name = "\proper Integrated Subspace Transceiver " - keyslot2 = new /obj/item/encryptionkey/ai - command = TRUE - -/obj/item/radio/headset/ai/can_receive(freq, level) - return ..(freq, level, TRUE) - -/obj/item/radio/headset/attackby(obj/item/W, mob/user, params) - user.set_machine(src) - - if(istype(W, /obj/item/screwdriver)) - if(keyslot || keyslot2) - for(var/ch_name in channels) - SSradio.remove_object(src, GLOB.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - var/turf/T = user.drop_location() - if(T) - if(keyslot) - keyslot.forceMove(T) - keyslot = null - if(keyslot2) - keyslot2.forceMove(T) - 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"].") - return TRUE +// 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" + item_state = "headset" + materials = list(MAT_METAL=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/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" + item_state = "syndie_headset" + +/obj/item/radio/headset/syndicate/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(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" + item_state = "sec_headset_alt" + +/obj/item/radio/headset/headset_sec/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(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_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_com + name = "command radio headset" + desc = "A headset with a commanding channel.\nTo access the command channel, use :c." + 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" + item_state = "com_headset_alt" + +/obj/item/radio/headset/heads/captain/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(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" + item_state = "com_headset_alt" + +/obj/item/radio/headset/heads/hos/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(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_clown + name = "clown's headset" + desc = "A headset for the clown. Finally. A megaphone you can't take away." + icon_state = "srv_headset" + keyslot = new /obj/item/encryptionkey/headset_service + command = TRUE + commandspan = SPAN_CLOWN + +/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" + item_state = "cent_headset_alt" + keyslot = null + +/obj/item/radio/headset/headset_cent/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) + +/obj/item/radio/headset/ai + name = "\proper Integrated Subspace Transceiver " + keyslot2 = new /obj/item/encryptionkey/ai + command = TRUE + +/obj/item/radio/headset/ai/can_receive(freq, level) + return ..(freq, level, TRUE) + +/obj/item/radio/headset/attackby(obj/item/W, mob/user, params) + user.set_machine(src) + + if(istype(W, /obj/item/screwdriver)) + if(keyslot || keyslot2) + for(var/ch_name in channels) + SSradio.remove_object(src, GLOB.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + var/turf/T = user.drop_location() + if(T) + if(keyslot) + keyslot.forceMove(T) + keyslot = null + if(keyslot2) + keyslot2.forceMove(T) + 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"].") + return TRUE diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm index efdf055743..41161b46c9 100644 --- a/code/game/objects/items/devices/radio/intercom.dm +++ b/code/game/objects/items/devices/radio/intercom.dm @@ -1,150 +1,150 @@ -/obj/item/radio/intercom - name = "station intercom" - desc = "Talk through this." - icon_state = "intercom" - anchored = TRUE - w_class = WEIGHT_CLASS_BULKY - canhear_range = 2 - var/number = 0 - var/anyai = 1 - var/mob/living/silicon/ai/ai = list() - var/last_tick //used to delay the powercheck - dog_fashion = null - var/unfastened = FALSE - -/obj/item/radio/intercom/unscrewed - unfastened = TRUE - -/obj/item/radio/intercom/ratvar - name = "hierophant intercom" - desc = "A modified intercom that uses the Hierophant network instead of subspace tech. Can listen to and broadcast on any frequency." - icon_state = "intercom_ratvar" - freerange = TRUE - -/obj/item/radio/intercom/ratvar/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/screwdriver)) - to_chat(user, "[src] is fastened to the wall with [is_servant_of_ratvar(user) ? "replicant alloy" : "some material you've never seen"], and can't be removed.") - return //no unfastening! - . = ..() - -/obj/item/radio/intercom/ratvar/process() - if(!istype(SSticker.mode, /datum/game_mode/clockwork_cult)) - invisibility = INVISIBILITY_OBSERVER - alpha = 125 - emped = TRUE - else - invisibility = initial(invisibility) - alpha = initial(alpha) - emped = FALSE - ..() - -/obj/item/radio/intercom/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - START_PROCESSING(SSobj, src) - -/obj/item/radio/intercom/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/radio/intercom/examine(mob/user) - . = ..() - if(!unfastened) - . += "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(istype(I, /obj/item/screwdriver)) - if(unfastened) - 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.") - unfastened = 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.") - unfastened = TRUE - return - else if(istype(I, /obj/item/wrench)) - if(!unfastened) - 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, 1) - 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(!src.listening) - return FALSE - if(freq == FREQ_SYNDICATE) - if(!(src.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, atom/movable/source) - . = ..() - if (message_mode == MODE_INTERCOM) - return // Avoid hearing the same thing twice - if(!anyai && !(speaker in ai)) - return - ..() - -/obj/item/radio/intercom/process() - if(((world.timeofday - last_tick) > 30) || ((world.timeofday - last_tick) < 0)) - last_tick = world.timeofday - - var/area/A = get_area(src) - if(!A || emped) - on = FALSE - else - on = A.powered(EQUIP) // set "on" to the power status - - if(!on) - icon_state = "intercom-p" - else - icon_state = initial(icon_state) - -/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 - materials = list(MAT_METAL = 75, MAT_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 + var/number = 0 + var/anyai = 1 + var/mob/living/silicon/ai/ai = list() + var/last_tick //used to delay the powercheck + dog_fashion = null + var/unfastened = FALSE + +/obj/item/radio/intercom/unscrewed + unfastened = TRUE + +/obj/item/radio/intercom/ratvar + name = "hierophant intercom" + desc = "A modified intercom that uses the Hierophant network instead of subspace tech. Can listen to and broadcast on any frequency." + icon_state = "intercom_ratvar" + freerange = TRUE + +/obj/item/radio/intercom/ratvar/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/screwdriver)) + to_chat(user, "[src] is fastened to the wall with [is_servant_of_ratvar(user) ? "replicant alloy" : "some material you've never seen"], and can't be removed.") + return //no unfastening! + . = ..() + +/obj/item/radio/intercom/ratvar/process() + if(!istype(SSticker.mode, /datum/game_mode/clockwork_cult)) + invisibility = INVISIBILITY_OBSERVER + alpha = 125 + emped = TRUE + else + invisibility = initial(invisibility) + alpha = initial(alpha) + emped = FALSE + ..() + +/obj/item/radio/intercom/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + START_PROCESSING(SSobj, src) + +/obj/item/radio/intercom/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/radio/intercom/examine(mob/user) + . = ..() + if(!unfastened) + . += "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(istype(I, /obj/item/screwdriver)) + if(unfastened) + 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.") + unfastened = 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.") + unfastened = TRUE + return + else if(istype(I, /obj/item/wrench)) + if(!unfastened) + 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, 1) + 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(!src.listening) + return FALSE + if(freq == FREQ_SYNDICATE) + if(!(src.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, atom/movable/source) + . = ..() + if (message_mode == MODE_INTERCOM) + return // Avoid hearing the same thing twice + if(!anyai && !(speaker in ai)) + return + ..() + +/obj/item/radio/intercom/process() + if(((world.timeofday - last_tick) > 30) || ((world.timeofday - last_tick) < 0)) + last_tick = world.timeofday + + var/area/A = get_area(src) + if(!A || emped) + on = FALSE + else + on = A.powered(EQUIP) // set "on" to the power status + + if(!on) + icon_state = "intercom-p" + else + icon_state = initial(icon_state) + +/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 + materials = list(MAT_METAL = 75, MAT_GLASS = 25) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 22012a114f..8ccd15e397 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -1,429 +1,429 @@ -/obj/item/radio - icon = 'icons/obj/radio.dmi' - name = "station bounced radio" - icon_state = "walkietalkie" - item_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 - materials = list(MAT_METAL=75, MAT_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. - var/commandspan = SPAN_COMMAND //allow us to set what the fuck we want for headsets - - // 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 - - var/const/FREQ_LISTENING = 1 - //FREQ_BROADCASTING = 2 - -/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() - channels = list() - translate_binary = FALSE - syndie = FALSE - independent = FALSE - - 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]) - -/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) - ui = new(user, src, ui_key, "radio", name, 370, 220 + channels.len * 22, 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"] = istype(src, /obj/item/radio/headset) - - 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(tune == "input") - var/min = format_frequency(freerange ? MIN_FREE_FREQ : MIN_FREQ) - var/max = format_frequency(freerange ? MAX_FREE_FREQ : MAX_FREQ) - tune = input("Tune frequency ([min]-[max]):", name, format_frequency(frequency)) as null|num - if(!isnull(tune) && !..()) - if (tune < MIN_FREE_FREQ && tune <= MAX_FREE_FREQ / 10) - // allow typing 144.7 to get 1447 - tune *= 10 - . = TRUE - else 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_default_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 |= commandspan - - /* - 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 severely gibberish the message - 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)) - message = Gibberish(message,100) - break - - // 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, atom/movable/source) - . = ..() - 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 (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(istype(W, /obj/item/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 - spawn(200) - if(emped == curremp) //Don't fix it if it's been EMP'd again - emped = 0 - if (!istype(src, /obj/item/radio/intercom)) // intercoms will turn back on on their own - on = 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_switchable = TRUE - dog_fashion = null - -/obj/item/radio/borg/Initialize(mapload) - . = ..() - -/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(istype(W, /obj/item/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 - -/obj/item/radio/internal - var/obj/item/implant/radio/implant - -/obj/item/radio/internal/Initialize(mapload, obj/item/implant/radio/_implant) - . = ..() - implant = _implant - -/obj/item/radio/internal/Destroy() - if(implant?.imp_in) - qdel(implant) - else - return ..() +/obj/item/radio + icon = 'icons/obj/radio.dmi' + name = "station bounced radio" + icon_state = "walkietalkie" + item_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 + materials = list(MAT_METAL=75, MAT_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. + var/commandspan = SPAN_COMMAND //allow us to set what the fuck we want for headsets + + // 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 + + var/const/FREQ_LISTENING = 1 + //FREQ_BROADCASTING = 2 + +/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() + channels = list() + translate_binary = FALSE + syndie = FALSE + independent = FALSE + + 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]) + +/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) + ui = new(user, src, ui_key, "radio", name, 370, 220 + channels.len * 22, 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"] = istype(src, /obj/item/radio/headset) + + 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(tune == "input") + var/min = format_frequency(freerange ? MIN_FREE_FREQ : MIN_FREQ) + var/max = format_frequency(freerange ? MAX_FREE_FREQ : MAX_FREQ) + tune = input("Tune frequency ([min]-[max]):", name, format_frequency(frequency)) as null|num + if(!isnull(tune) && !..()) + if (tune < MIN_FREE_FREQ && tune <= MAX_FREE_FREQ / 10) + // allow typing 144.7 to get 1447 + tune *= 10 + . = TRUE + else 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_default_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 |= commandspan + + /* + 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 severely gibberish the message + 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)) + message = Gibberish(message,100) + break + + // 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, atom/movable/source) + . = ..() + 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 (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(istype(W, /obj/item/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 + spawn(200) + if(emped == curremp) //Don't fix it if it's been EMP'd again + emped = 0 + if (!istype(src, /obj/item/radio/intercom)) // intercoms will turn back on on their own + on = 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_switchable = TRUE + dog_fashion = null + +/obj/item/radio/borg/Initialize(mapload) + . = ..() + +/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(istype(W, /obj/item/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 + +/obj/item/radio/internal + var/obj/item/implant/radio/implant + +/obj/item/radio/internal/Initialize(mapload, obj/item/implant/radio/_implant) + . = ..() + implant = _implant + +/obj/item/radio/internal/Destroy() + if(implant?.imp_in) + qdel(implant) + else + return ..() diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 069c921914..3b296e5773 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -1,779 +1,779 @@ - -/* - -CONTAINS: -T-RAY -HEALTH ANALYZER -GAS ANALYZER -SLIME SCANNER - -*/ -/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." - icon = 'icons/obj/device.dmi' - icon_state = "t-ray0" - var/on = FALSE - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - materials = list(MAT_METAL=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/attack_self(mob/user) - - on = !on - icon_state = copytext(icon_state, 1, length(icon_state))+"[on]" - - if(on) - START_PROCESSING(SSobj, src) - -/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(O.level != 1) - continue - - if(O.invisibility == INVISIBILITY_MAXIMUM) - 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" - item_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 able to distinguish 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 - materials = list(MAT_METAL=200) - var/mode = 1 - var/scanmode = 0 - var/advanced = FALSE - -/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) - if(!scanmode) - to_chat(user, "You switch the health analyzer to scan chemical contents.") - scanmode = 1 - else - to_chat(user, "You switch the health analyzer to check physical health.") - scanmode = 0 - -/obj/item/healthanalyzer/attack(mob/living/M, mob/living/carbon/human/user) - - // Clumsiness/brain damage check - if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) - to_chat(user, "You stupidly try to analyze the floor's vitals!") - user.visible_message("[user] has analyzed the floor's vitals!") - var/msg = "*---------*\nAnalyzing results for The floor:\n\tOverall status: Healthy\n" - msg += "Key: Suffocation/Toxin/Burn/Brute\n" - msg += "\tDamage specifics: 0-0-0-0\n" - msg += "Body temperature: ???\n" - msg += "*---------*" - to_chat(user, msg) - return - - user.visible_message("[user] has analyzed [M]'s vitals.") - - if(scanmode == 0) - healthscan(user, M, mode, advanced) - else if(scanmode == 1) - chemscan(user, M) - - add_fingerprint(user) - - -// Used by the PDA medical scanner too -/proc/healthscan(mob/user, mob/living/M, mode = 1, advanced = FALSE) - if(isliving(user) && (user.incapacitated() || user.eye_blind)) - return - //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 - - var/msg = "*---------*\nAnalyzing results for [M]:\n\tOverall status: [mob_status]" - - // Damage descriptions - if(brute_loss > 10) - msg += "\n\t[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected." - if(fire_loss > 10) - msg += "\n\t[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected." - if(oxy_loss > 10) - msg += "\n\t[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected." - if(tox_loss > 10) - msg += "\n\t[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected." - if(M.getStaminaLoss()) - msg += "\n\tSubject appears to be suffering from fatigue." - if(advanced) - msg += "\n\tFatigue Level: [M.getStaminaLoss()]%." - if (M.getCloneLoss()) - msg += "\n\tSubject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage." - if(advanced) - msg += "\n\tCellular Damage Level: [M.getCloneLoss()]." - - - to_chat(user, msg) - msg = "" - - // Body part damage report - var/list/dmgreport = list() - if(iscarbon(M) && mode == 1) - 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) - dmgreport += "
                [entry][functions]
                \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - " - - for(var/o in damaged) - var/obj/item/bodypart/org = o //head, left arm, right arm, etc. - dmgreport += "\ - \ - " - dmgreport += "
                Damage:BruteBurnToxinSuffocation
                Overall:[brute_loss][fire_loss][tox_loss][oxy_loss]
                [capitalize(org.name)]:[(org.brute_dam > 0) ? "[org.brute_dam]" : "0"][(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]
                " - to_chat(user, dmgreport.Join()) - - - //Organ damages report - var/heart_ded = FALSE - if(iscarbon(M)) - var/mob/living/carbon/C = M - var/mob/living/carbon/human/H = M - for(var/organ in C.internal_organs) - var/temp_message - var/damage_message - var/obj/item/organ/O = organ - - //EYES - if(istype(O, /obj/item/organ/eyes)) - var/obj/item/organ/eyes/eyes = O - if(advanced) - if(HAS_TRAIT(C, TRAIT_BLIND)) - temp_message += " Subject is blind." - if(HAS_TRAIT(C, TRAIT_NEARSIGHT)) - temp_message += " Subject is nearsighted." - if(eyes.damage > 30) - damage_message += " Subject has severe eye damage." - else if(eyes.damage > 20) - damage_message += " Subject has significant eye damage." - else if(eyes.damage) - damage_message += " Subject has minor eye damage." - - - //EARS - else if(istype(O, /obj/item/organ/ears)) - var/obj/item/organ/ears/ears = O - if(advanced) - if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION)) - temp_message += " Subject is genetically deaf." - else if(HAS_TRAIT(C, TRAIT_DEAF)) - temp_message += " Subject is deaf." - else - if(ears.damage) - damage_message += " Subject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage." - if(ears.deaf) - damage_message += " Subject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf." - - - //BRAIN - else if(istype(O, /obj/item/organ/brain)) - if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 200) - damage_message += " Subject's brain non-functional. Neurine injection recomended." - else if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 120) - damage_message += " Severe brain damage detected. Subject likely to have mental traumas." - else if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 45) - damage_message += " Brain damage detected." - if(advanced) - temp_message += " Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%." - - //TRAUMAS - 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_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE) - trauma_desc += "permanent " - trauma_desc += B.scan_desc - trauma_text += trauma_desc - temp_message += " Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)]." - if(C.roundstart_quirks.len) - temp_message += " Subject has the following physiological traits: [C.get_trait_string()]." - - if(ishuman(C) && advanced) - //MON PETIT CHAUFFEUR - if(H.hallucinating()) - temp_message += " Subject is hallucinating." - - //MKUltra - if(H.has_status_effect(/datum/status_effect/chem/enthrall)) - temp_message += " Subject has abnormal brain fuctions." - - //Astrogen shenanigans - if(H.reagents.has_reagent(/datum/reagent/fermi/astral)) - if(H.mind) - temp_message += " Warning: subject may be possesed." - else - temp_message += " Subject appears to be astrally projecting." - - - //LIVER - else if(istype(O, /obj/item/organ/liver)) - var/obj/item/organ/liver/L = O - if(H.undergoing_liver_failure() && H.stat != DEAD) //might be depreciated - temp_message += "Subject is suffering from liver failure: Apply Corazone and begin a liver transplant immediately!" - if(L.swelling > 20) - temp_message += " Subject is suffering from an enlarged liver." //i.e. shrink their liver or give them a transplant. - - //HEART - else if(ishuman(M) && (istype(O, /obj/item/organ/heart))) - var/obj/item/organ/heart/He = O - if(H.undergoing_cardiac_arrest() && H.stat != DEAD) - temp_message += " Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!" - if(He.organ_flags & ORGAN_FAILING) - heart_ded = TRUE - - //TONGUE - else if(istype(O, /obj/item/organ/tongue)) - var/obj/item/organ/tongue/T = O - if(T.name == "fluffy tongue") - temp_message += " Subject is suffering from a fluffified tongue. Suggested cure: Yamerol or a tongue transplant." - - //HECK - else if(istype(O, /obj/item/organ/genital/penis)) - var/obj/item/organ/genital/penis/P = O - if(P.length>20) - temp_message += " Subject has a sizeable gentleman's organ at [P.length] inches." - - else if(istype(O, /obj/item/organ/genital/breasts)) - var/obj/item/organ/genital/breasts/Br = O - if(Br.cached_size>5) - temp_message += " Subject has a sizeable bosom with a [Br.size] cup." - - - - //GENERAL HANDLER - if(!damage_message) - if(O.organ_flags & ORGAN_FAILING) - damage_message += " End Stage [O.name] failure detected." - else if(O.damage > O.high_threshold) - damage_message += " Chronic [O.name] failure detected." - else if(O.damage > O.low_threshold && advanced) - damage_message += " Acute [O.name] failure detected." - - if(temp_message || damage_message) - msg += "\t[uppertext(O.name)]: [damage_message] [temp_message]\n" - - - - //END; LOOK FOR MISSING ORGANS? - var/breathes = TRUE - var/blooded = TRUE - if(C.dna && C.dna.species) - if(HAS_TRAIT_FROM(C, TRAIT_NOBREATH, SPECIES_TRAIT)) - breathes = FALSE - if(NOBLOOD in C.dna.species.species_traits) - blooded = FALSE - var/has_liver = (!(NOLIVER in C.dna.species.species_traits)) - var/has_stomach = (!(NOSTOMACH in C.dna.species.species_traits)) - if(!M.getorganslot(ORGAN_SLOT_EYES)) - msg += "\tSubject does not have eyes.\n" - if(!M.getorganslot(ORGAN_SLOT_EARS)) - msg += "\tSubject does not have ears.\n" - if(!M.getorganslot(ORGAN_SLOT_BRAIN)) - msg += "\tSubject's brain function is non-existent!\n" - if(has_liver && !M.getorganslot(ORGAN_SLOT_LIVER)) - msg += "\tSubject's liver is missing!\n" - if(blooded && !M.getorganslot(ORGAN_SLOT_HEART)) - msg += "\tSubject's heart is missing!\n" - if(breathes && !M.getorganslot(ORGAN_SLOT_LUNGS)) - msg += "\tSubject's lungs have collapsed from trauma!\n" - if(has_stomach && !M.getorganslot(ORGAN_SLOT_STOMACH)) - msg += "\tSubject's stomach is missing!\n" - - - if(M.radiation) - msg += "\tSubject is irradiated.\n" - msg += "\tRadiation Level: [M.radiation] rad\n" - - - - // Species and body temperature - var/mob/living/carbon/human/H = M //Start to use human only stuff here - if(ishuman(M)) - var/datum/species/S = H.dna.species - var/mutant = FALSE - if (H.dna.check_mutation(HULK)) - mutant = TRUE - else if (S.mutantlungs != initial(S.mutantlungs)) - mutant = TRUE - else if (S.mutant_brain != initial(S.mutant_brain)) - mutant = TRUE - else if (S.mutant_heart != initial(S.mutant_heart)) - mutant = TRUE - else if (S.mutanteyes != initial(S.mutanteyes)) - mutant = TRUE - else if (S.mutantears != initial(S.mutantears)) - mutant = TRUE - else if (S.mutanthands != initial(S.mutanthands)) - mutant = TRUE - else if (S.mutanttongue != initial(S.mutanttongue)) - mutant = TRUE - else if (S.mutanttail != initial(S.mutanttail)) - mutant = TRUE - else if (S.mutantliver != initial(S.mutantliver)) - mutant = TRUE - else if (S.mutantstomach != initial(S.mutantstomach)) - mutant = TRUE - - msg += "\tReported Species: [H.dna.custom_species ? H.dna.custom_species : S.name]\n" - msg += "\tBase Species: [S.name]\n" - if(mutant) - msg += "\tSubject has mutations present.\n" - msg += "\tBody 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))) - msg += "Time of Death: [M.tod]\n" - var/tdelta = round(world.time - M.timeofdeath) - if(tdelta < (DEFIB_TIME_LIMIT * 10)) - if(heart_ded) - msg += "Subject died [DisplayTimeText(tdelta)] ago, heart requires surgical intervention for defibrillation." - else - msg += "Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!" - if(advanced) - if(H.get_ghost() || H.key || H.client)//Since it can last a while. - msg += " Intervention recommended.\n" - else - msg += "\n" - - for(var/thing in M.diseases) - var/datum/disease/D = thing - if(!(D.visibility_flags & HIDDEN_SCANNER)) - msg += "Warning: [D.form] detected\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]\n" - - // 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)) - if(H.bleed_rate) - msg += "Subject is bleeding!\n" - var/blood_percent = round((C.scan_blood_volume() / (BLOOD_VOLUME_NORMAL * C.blood_ratio))*100) - var/blood_type = C.dna.blood_type - if(blood_id != ("blood" || "jellyblood"))//special blood substance - var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] - if(R) - blood_type = R.name - else - blood_type = blood_id - if(C.scan_blood_volume() <= (BLOOD_VOLUME_SAFE*C.blood_ratio) && C.scan_blood_volume() > (BLOOD_VOLUME_OKAY*C.blood_ratio)) - msg += "LOW blood level [blood_percent] %, [C.scan_blood_volume()] cl, type: [blood_type]\n" - else if(C.scan_blood_volume() <= (BLOOD_VOLUME_OKAY*C.blood_ratio)) - msg += "CRITICAL blood level [blood_percent] %, [C.scan_blood_volume()] cl, type: [blood_type]\n" - else - msg += "Blood level [blood_percent] %, [C.scan_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 += "[C.name] is modified with a [CI.name].
                " - if(cyberimp_detect) - msg += "Detected cybernetic modifications:\n" - msg += "[cyberimp_detect]\n" - msg += "*---------*" - to_chat(user, msg) - SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE) - -/proc/chemscan(mob/living/user, mob/living/M) - if(istype(M)) - if(M.reagents) - var/msg = "*---------*\n" - if(M.reagents.reagent_list.len) - var/list/datum/reagent/reagents = list() - for(var/datum/reagent/R in M.reagents.reagent_list) - if(R.chemical_flags & REAGENT_INVISIBLE) - continue - reagents += R - - if(length(reagents)) - msg += "Subject contains the following reagents:\n" - for(var/datum/reagent/R in reagents) - msg += "[R.volume] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : "."]\n" - else - msg += "Subject contains no reagents.\n" - - else - msg += "Subject contains no reagents.\n" - if(M.reagents.addiction_list.len) - msg += "Subject is addicted to the following reagents:\n" - for(var/datum/reagent/R in M.reagents.addiction_list) - msg += "[R.name]\n" - else - msg += "Subject is not addicted to any reagents.\n" - - var/datum/reagent/impure/fermiTox/F = M.reagents.has_reagent(/datum/reagent/impure/fermiTox) - if(istype(F)) - switch(F.volume) - if(5 to 10) - msg += "Subject contains a low amount of toxic isomers.\n" - if(10 to 25) - msg += "Subject contains toxic isomers.\n" - if(25 to 50) - msg += "Subject contains a substantial amount of toxic isomers.\n" - if(50 to 95) - msg += "Subject contains a high amount of toxic isomers.\n" - if(95 to INFINITY) - msg += "Subject contains a extremely dangerous amount of toxic isomers.\n" - - msg += "*---------*" - to_chat(user, msg) - -/obj/item/healthanalyzer/verb/toggle_mode() - set name = "Switch Verbosity" - set category = "Object" - - if(usr.stat || !usr.canmove || usr.restrained()) - return - - mode = !mode - switch (mode) - if(1) - to_chat(usr, "The scanner now shows specific limb damage.") - if(0) - to_chat(usr, "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 - -/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" - icon = 'icons/obj/device.dmi' - icon_state = "analyzer" - item_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 - materials = list(MAT_METAL=30, MAT_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.eye_blind) - return - - var/turf/location = user.loc - if(!istype(location)) - return - - var/datum/gas_mixture/environment = location.return_air() - - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - to_chat(user, "Results:") - if(abs(pressure - ONE_ATMOSPHERE) < 10) - to_chat(user, "Pressure: [round(pressure, 0.01)] kPa") - else - to_chat(user, "Pressure: [round(pressure, 0.01)] kPa") - if(total_moles) - var/list/env_gases = environment.gases - - var/o2_concentration = env_gases[/datum/gas/oxygen]/total_moles - var/n2_concentration = env_gases[/datum/gas/nitrogen]/total_moles - var/co2_concentration = env_gases[/datum/gas/carbon_dioxide]/total_moles - var/plasma_concentration = env_gases[/datum/gas/plasma]/total_moles - - if(abs(n2_concentration - N2STANDARD) < 20) - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen], 0.01)] mol)") - else - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen], 0.01)] mol)") - - if(abs(o2_concentration - O2STANDARD) < 2) - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen], 0.01)] mol)") - else - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen], 0.01)] mol)") - - if(co2_concentration > 0.01) - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide], 0.01)] mol)") - else - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide], 0.01)] mol)") - - if(plasma_concentration > 0.005) - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma], 0.01)] mol)") - else - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma], 0.01)] mol)") - - GAS_GARBAGE_COLLECT(environment.gases) - - for(var/id in env_gases) - if(id in GLOB.hardcoded_gases) - continue - var/gas_concentration = env_gases[id]/total_moles - to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id], 0.01)] mol)") - to_chat(user, "Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)") - -/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens - . = ..() - - if(user.canUseTopic(src)) - . = TRUE - 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 ? next_hit - world.time : -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(mixture, mob/living/user, atom/target = src) - var/icon = target - user.visible_message("[user] has used the analyzer on [icon2html(icon, viewers(user))] [target].", "You use the analyzer on [icon2html(icon, user)] [target].") - to_chat(user, "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 - to_chat(user, "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) - to_chat(user, "Moles: [round(total_moles, 0.01)] mol") - to_chat(user, "Volume: [volume] L") - to_chat(user, "Pressure: [round(pressure,0.01)] kPa") - - var/list/cached_gases = air_contents.gases - for(var/id in cached_gases) - var/gas_concentration = cached_gases[id]/total_moles - to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id], 0.01)] mol)") - to_chat(user, "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)") - - else - if(airs.len > 1) - to_chat(user, "This node is empty!") - else - to_chat(user, "[target] is empty!") - - if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected - var/fusion_power = round(cached_scan_results["fusion"], 0.01) - var/tier = fusionpower2text(fusion_power) - to_chat(user, "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.") - to_chat(user, "Power of the last fusion reaction: [fusion_power]\n This power indicates it was a [tier]-tier fusion reaction.") - return - -//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" - item_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 - materials = list(MAT_METAL=30, MAT_GLASS=20) - -/obj/item/slime_scanner/attack(mob/living/M, mob/living/user) - if(user.stat || user.eye_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) - to_chat(user, "========================") - to_chat(user, "Slime scan results:") - to_chat(user, "[T.colour] [T.is_adult ? "adult" : "baby"] slime") - to_chat(user, "Nutrition: [T.nutrition]/[T.get_max_nutrition()]") - if (T.nutrition < T.get_starve_nutrition()) - to_chat(user, "Warning: slime is starving!") - else if (T.nutrition < T.get_hunger_nutrition()) - to_chat(user, "Warning: slime is hungry") - to_chat(user, "Electric change strength: [T.powerlevel]") - to_chat(user, "Health: [round(T.health/T.maxHealth,0.01)*100]%") - if (T.slime_mutation[4] == T.colour) - to_chat(user, "This 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_chat(user, "Possible mutation: [T.slime_mutation[3]]") - to_chat(user, "Genetic destability: [T.mutation_chance/2] % chance of mutation on splitting") - else - to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)") - to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting") - else - to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]") - to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting") - if (T.cores > 1) - to_chat(user, "Multiple cores detected") - to_chat(user, "Growth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]") - if(T.effectmod) - to_chat(user, "Core mutation in progress: [T.effectmod]") - to_chat(user, "Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]") - to_chat(user, "========================") - - -/obj/item/nanite_scanner - name = "nanite scanner" - icon = 'icons/obj/device.dmi' - icon_state = "nanite_scanner" - item_state = "nanite_remote" - 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 - materials = list(MAT_METAL=200) - -/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user) - user.visible_message("[user] has analyzed [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.") + +/* + +CONTAINS: +T-RAY +HEALTH ANALYZER +GAS ANALYZER +SLIME SCANNER + +*/ +/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." + icon = 'icons/obj/device.dmi' + icon_state = "t-ray0" + var/on = FALSE + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + materials = list(MAT_METAL=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/attack_self(mob/user) + + on = !on + icon_state = copytext(icon_state, 1, length(icon_state))+"[on]" + + if(on) + START_PROCESSING(SSobj, src) + +/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(O.level != 1) + continue + + if(O.invisibility == INVISIBILITY_MAXIMUM) + 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" + item_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 able to distinguish 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 + materials = list(MAT_METAL=200) + var/mode = 1 + var/scanmode = 0 + var/advanced = FALSE + +/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) + if(!scanmode) + to_chat(user, "You switch the health analyzer to scan chemical contents.") + scanmode = 1 + else + to_chat(user, "You switch the health analyzer to check physical health.") + scanmode = 0 + +/obj/item/healthanalyzer/attack(mob/living/M, mob/living/carbon/human/user) + + // Clumsiness/brain damage check + if ((HAS_TRAIT(user, TRAIT_CLUMSY) || HAS_TRAIT(user, TRAIT_DUMB)) && prob(50)) + to_chat(user, "You stupidly try to analyze the floor's vitals!") + user.visible_message("[user] has analyzed the floor's vitals!") + var/msg = "*---------*\nAnalyzing results for The floor:\n\tOverall status: Healthy\n" + msg += "Key: Suffocation/Toxin/Burn/Brute\n" + msg += "\tDamage specifics: 0-0-0-0\n" + msg += "Body temperature: ???\n" + msg += "*---------*" + to_chat(user, msg) + return + + user.visible_message("[user] has analyzed [M]'s vitals.") + + if(scanmode == 0) + healthscan(user, M, mode, advanced) + else if(scanmode == 1) + chemscan(user, M) + + add_fingerprint(user) + + +// Used by the PDA medical scanner too +/proc/healthscan(mob/user, mob/living/M, mode = 1, advanced = FALSE) + if(isliving(user) && (user.incapacitated() || user.eye_blind)) + return + //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 + + var/msg = "*---------*\nAnalyzing results for [M]:\n\tOverall status: [mob_status]" + + // Damage descriptions + if(brute_loss > 10) + msg += "\n\t[brute_loss > 50 ? "Severe" : "Minor"] tissue damage detected." + if(fire_loss > 10) + msg += "\n\t[fire_loss > 50 ? "Severe" : "Minor"] burn damage detected." + if(oxy_loss > 10) + msg += "\n\t[oxy_loss > 50 ? "Severe" : "Minor"] oxygen deprivation detected." + if(tox_loss > 10) + msg += "\n\t[tox_loss > 50 ? "Severe" : "Minor"] amount of toxin damage detected." + if(M.getStaminaLoss()) + msg += "\n\tSubject appears to be suffering from fatigue." + if(advanced) + msg += "\n\tFatigue Level: [M.getStaminaLoss()]%." + if (M.getCloneLoss()) + msg += "\n\tSubject appears to have [M.getCloneLoss() > 30 ? "Severe" : "Minor"] cellular damage." + if(advanced) + msg += "\n\tCellular Damage Level: [M.getCloneLoss()]." + + + to_chat(user, msg) + msg = "" + + // Body part damage report + var/list/dmgreport = list() + if(iscarbon(M) && mode == 1) + 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) + dmgreport += "\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + + for(var/o in damaged) + var/obj/item/bodypart/org = o //head, left arm, right arm, etc. + dmgreport += "\ + \ + " + dmgreport += "
                Damage:BruteBurnToxinSuffocation
                Overall:[brute_loss][fire_loss][tox_loss][oxy_loss]
                [capitalize(org.name)]:[(org.brute_dam > 0) ? "[org.brute_dam]" : "0"][(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]
                " + to_chat(user, dmgreport.Join()) + + + //Organ damages report + var/heart_ded = FALSE + if(iscarbon(M)) + var/mob/living/carbon/C = M + var/mob/living/carbon/human/H = M + for(var/organ in C.internal_organs) + var/temp_message + var/damage_message + var/obj/item/organ/O = organ + + //EYES + if(istype(O, /obj/item/organ/eyes)) + var/obj/item/organ/eyes/eyes = O + if(advanced) + if(HAS_TRAIT(C, TRAIT_BLIND)) + temp_message += " Subject is blind." + if(HAS_TRAIT(C, TRAIT_NEARSIGHT)) + temp_message += " Subject is nearsighted." + if(eyes.damage > 30) + damage_message += " Subject has severe eye damage." + else if(eyes.damage > 20) + damage_message += " Subject has significant eye damage." + else if(eyes.damage) + damage_message += " Subject has minor eye damage." + + + //EARS + else if(istype(O, /obj/item/organ/ears)) + var/obj/item/organ/ears/ears = O + if(advanced) + if(HAS_TRAIT_FROM(C, TRAIT_DEAF, GENETIC_MUTATION)) + temp_message += " Subject is genetically deaf." + else if(HAS_TRAIT(C, TRAIT_DEAF)) + temp_message += " Subject is deaf." + else + if(ears.damage) + damage_message += " Subject has [ears.damage > ears.maxHealth ? "permanent ": "temporary "]hearing damage." + if(ears.deaf) + damage_message += " Subject is [ears.damage > ears.maxHealth ? "permanently ": "temporarily "] deaf." + + + //BRAIN + else if(istype(O, /obj/item/organ/brain)) + if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 200) + damage_message += " Subject's brain non-functional. Neurine injection recomended." + else if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 120) + damage_message += " Severe brain damage detected. Subject likely to have mental traumas." + else if (C.getOrganLoss(ORGAN_SLOT_BRAIN) >= 45) + damage_message += " Brain damage detected." + if(advanced) + temp_message += " Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%." + + //TRAUMAS + 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_MAGIC, TRAUMA_RESILIENCE_ABSOLUTE) + trauma_desc += "permanent " + trauma_desc += B.scan_desc + trauma_text += trauma_desc + temp_message += " Cerebral traumas detected: subject appears to be suffering from [english_list(trauma_text)]." + if(C.roundstart_quirks.len) + temp_message += " Subject has the following physiological traits: [C.get_trait_string()]." + + if(ishuman(C) && advanced) + //MON PETIT CHAUFFEUR + if(H.hallucinating()) + temp_message += " Subject is hallucinating." + + //MKUltra + if(H.has_status_effect(/datum/status_effect/chem/enthrall)) + temp_message += " Subject has abnormal brain fuctions." + + //Astrogen shenanigans + if(H.reagents.has_reagent(/datum/reagent/fermi/astral)) + if(H.mind) + temp_message += " Warning: subject may be possesed." + else + temp_message += " Subject appears to be astrally projecting." + + + //LIVER + else if(istype(O, /obj/item/organ/liver)) + var/obj/item/organ/liver/L = O + if(H.undergoing_liver_failure() && H.stat != DEAD) //might be depreciated + temp_message += "Subject is suffering from liver failure: Apply Corazone and begin a liver transplant immediately!" + if(L.swelling > 20) + temp_message += " Subject is suffering from an enlarged liver." //i.e. shrink their liver or give them a transplant. + + //HEART + else if(ishuman(M) && (istype(O, /obj/item/organ/heart))) + var/obj/item/organ/heart/He = O + if(H.undergoing_cardiac_arrest() && H.stat != DEAD) + temp_message += " Subject suffering from heart attack: Apply defibrillation or other electric shock immediately!" + if(He.organ_flags & ORGAN_FAILING) + heart_ded = TRUE + + //TONGUE + else if(istype(O, /obj/item/organ/tongue)) + var/obj/item/organ/tongue/T = O + if(T.name == "fluffy tongue") + temp_message += " Subject is suffering from a fluffified tongue. Suggested cure: Yamerol or a tongue transplant." + + //HECK + else if(istype(O, /obj/item/organ/genital/penis)) + var/obj/item/organ/genital/penis/P = O + if(P.length>20) + temp_message += " Subject has a sizeable gentleman's organ at [P.length] inches." + + else if(istype(O, /obj/item/organ/genital/breasts)) + var/obj/item/organ/genital/breasts/Br = O + if(Br.cached_size>5) + temp_message += " Subject has a sizeable bosom with a [Br.size] cup." + + + + //GENERAL HANDLER + if(!damage_message) + if(O.organ_flags & ORGAN_FAILING) + damage_message += " End Stage [O.name] failure detected." + else if(O.damage > O.high_threshold) + damage_message += " Chronic [O.name] failure detected." + else if(O.damage > O.low_threshold && advanced) + damage_message += " Acute [O.name] failure detected.
                " + + if(temp_message || damage_message) + msg += "\t[uppertext(O.name)]:
                [damage_message] [temp_message]\n" + + + + //END; LOOK FOR MISSING ORGANS? + var/breathes = TRUE + var/blooded = TRUE + if(C.dna && C.dna.species) + if(HAS_TRAIT_FROM(C, TRAIT_NOBREATH, SPECIES_TRAIT)) + breathes = FALSE + if(NOBLOOD in C.dna.species.species_traits) + blooded = FALSE + var/has_liver = (!(NOLIVER in C.dna.species.species_traits)) + var/has_stomach = (!(NOSTOMACH in C.dna.species.species_traits)) + if(!M.getorganslot(ORGAN_SLOT_EYES)) + msg += "\tSubject does not have eyes.\n" + if(!M.getorganslot(ORGAN_SLOT_EARS)) + msg += "\tSubject does not have ears.\n" + if(!M.getorganslot(ORGAN_SLOT_BRAIN)) + msg += "\tSubject's brain function is non-existent!\n" + if(has_liver && !M.getorganslot(ORGAN_SLOT_LIVER)) + msg += "\tSubject's liver is missing!\n" + if(blooded && !M.getorganslot(ORGAN_SLOT_HEART)) + msg += "\tSubject's heart is missing!\n" + if(breathes && !M.getorganslot(ORGAN_SLOT_LUNGS)) + msg += "\tSubject's lungs have collapsed from trauma!\n" + if(has_stomach && !M.getorganslot(ORGAN_SLOT_STOMACH)) + msg += "\tSubject's stomach is missing!\n" + + + if(M.radiation) + msg += "\tSubject is irradiated.\n" + msg += "\tRadiation Level: [M.radiation] rad\n" + + + + // Species and body temperature + var/mob/living/carbon/human/H = M //Start to use human only stuff here + if(ishuman(M)) + var/datum/species/S = H.dna.species + var/mutant = FALSE + if (H.dna.check_mutation(HULK)) + mutant = TRUE + else if (S.mutantlungs != initial(S.mutantlungs)) + mutant = TRUE + else if (S.mutant_brain != initial(S.mutant_brain)) + mutant = TRUE + else if (S.mutant_heart != initial(S.mutant_heart)) + mutant = TRUE + else if (S.mutanteyes != initial(S.mutanteyes)) + mutant = TRUE + else if (S.mutantears != initial(S.mutantears)) + mutant = TRUE + else if (S.mutanthands != initial(S.mutanthands)) + mutant = TRUE + else if (S.mutanttongue != initial(S.mutanttongue)) + mutant = TRUE + else if (S.mutanttail != initial(S.mutanttail)) + mutant = TRUE + else if (S.mutantliver != initial(S.mutantliver)) + mutant = TRUE + else if (S.mutantstomach != initial(S.mutantstomach)) + mutant = TRUE + + msg += "\tReported Species: [H.dna.custom_species ? H.dna.custom_species : S.name]\n" + msg += "\tBase Species: [S.name]\n" + if(mutant) + msg += "\tSubject has mutations present.\n" + msg += "\tBody 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))) + msg += "Time of Death: [M.tod]\n" + var/tdelta = round(world.time - M.timeofdeath) + if(tdelta < (DEFIB_TIME_LIMIT * 10)) + if(heart_ded) + msg += "Subject died [DisplayTimeText(tdelta)] ago, heart requires surgical intervention for defibrillation." + else + msg += "Subject died [DisplayTimeText(tdelta)] ago, defibrillation may be possible!" + if(advanced) + if(H.get_ghost() || H.key || H.client)//Since it can last a while. + msg += " Intervention recommended.\n" + else + msg += "\n" + + for(var/thing in M.diseases) + var/datum/disease/D = thing + if(!(D.visibility_flags & HIDDEN_SCANNER)) + msg += "Warning: [D.form] detected\nName: [D.name].\nType: [D.spread_text].\nStage: [D.stage]/[D.max_stages].\nPossible Cure: [D.cure_text]\n" + + // 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)) + if(H.bleed_rate) + msg += "Subject is bleeding!\n" + var/blood_percent = round((C.scan_blood_volume() / (BLOOD_VOLUME_NORMAL * C.blood_ratio))*100) + var/blood_type = C.dna.blood_type + if(blood_id != ("blood" || "jellyblood"))//special blood substance + var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id] + if(R) + blood_type = R.name + else + blood_type = blood_id + if(C.scan_blood_volume() <= (BLOOD_VOLUME_SAFE*C.blood_ratio) && C.scan_blood_volume() > (BLOOD_VOLUME_OKAY*C.blood_ratio)) + msg += "LOW blood level [blood_percent] %, [C.scan_blood_volume()] cl, type: [blood_type]\n" + else if(C.scan_blood_volume() <= (BLOOD_VOLUME_OKAY*C.blood_ratio)) + msg += "CRITICAL blood level [blood_percent] %, [C.scan_blood_volume()] cl, type: [blood_type]\n" + else + msg += "Blood level [blood_percent] %, [C.scan_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 += "[C.name] is modified with a [CI.name].
                " + if(cyberimp_detect) + msg += "Detected cybernetic modifications:\n" + msg += "[cyberimp_detect]\n" + msg += "*---------*" + to_chat(user, msg) + SEND_SIGNAL(M, COMSIG_NANITE_SCAN, user, FALSE) + +/proc/chemscan(mob/living/user, mob/living/M) + if(istype(M)) + if(M.reagents) + var/msg = "*---------*\n" + if(M.reagents.reagent_list.len) + var/list/datum/reagent/reagents = list() + for(var/datum/reagent/R in M.reagents.reagent_list) + if(R.chemical_flags & REAGENT_INVISIBLE) + continue + reagents += R + + if(length(reagents)) + msg += "Subject contains the following reagents:\n" + for(var/datum/reagent/R in reagents) + msg += "[R.volume] units of [R.name][R.overdosed == 1 ? " - OVERDOSING" : "."]\n" + else + msg += "Subject contains no reagents.\n" + + else + msg += "Subject contains no reagents.\n" + if(M.reagents.addiction_list.len) + msg += "Subject is addicted to the following reagents:\n" + for(var/datum/reagent/R in M.reagents.addiction_list) + msg += "[R.name]\n" + else + msg += "Subject is not addicted to any reagents.\n" + + var/datum/reagent/impure/fermiTox/F = M.reagents.has_reagent(/datum/reagent/impure/fermiTox) + if(istype(F)) + switch(F.volume) + if(5 to 10) + msg += "Subject contains a low amount of toxic isomers.\n" + if(10 to 25) + msg += "Subject contains toxic isomers.\n" + if(25 to 50) + msg += "Subject contains a substantial amount of toxic isomers.\n" + if(50 to 95) + msg += "Subject contains a high amount of toxic isomers.\n" + if(95 to INFINITY) + msg += "Subject contains a extremely dangerous amount of toxic isomers.\n" + + msg += "*---------*" + to_chat(user, msg) + +/obj/item/healthanalyzer/verb/toggle_mode() + set name = "Switch Verbosity" + set category = "Object" + + if(usr.stat || !usr.canmove || usr.restrained()) + return + + mode = !mode + switch (mode) + if(1) + to_chat(usr, "The scanner now shows specific limb damage.") + if(0) + to_chat(usr, "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 + +/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" + icon = 'icons/obj/device.dmi' + icon_state = "analyzer" + item_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 + materials = list(MAT_METAL=30, MAT_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.eye_blind) + return + + var/turf/location = user.loc + if(!istype(location)) + return + + var/datum/gas_mixture/environment = location.return_air() + + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + to_chat(user, "Results:") + if(abs(pressure - ONE_ATMOSPHERE) < 10) + to_chat(user, "Pressure: [round(pressure, 0.01)] kPa") + else + to_chat(user, "Pressure: [round(pressure, 0.01)] kPa") + if(total_moles) + var/list/env_gases = environment.gases + + var/o2_concentration = env_gases[/datum/gas/oxygen]/total_moles + var/n2_concentration = env_gases[/datum/gas/nitrogen]/total_moles + var/co2_concentration = env_gases[/datum/gas/carbon_dioxide]/total_moles + var/plasma_concentration = env_gases[/datum/gas/plasma]/total_moles + + if(abs(n2_concentration - N2STANDARD) < 20) + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen], 0.01)] mol)") + else + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen], 0.01)] mol)") + + if(abs(o2_concentration - O2STANDARD) < 2) + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen], 0.01)] mol)") + else + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen], 0.01)] mol)") + + if(co2_concentration > 0.01) + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide], 0.01)] mol)") + else + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide], 0.01)] mol)") + + if(plasma_concentration > 0.005) + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma], 0.01)] mol)") + else + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma], 0.01)] mol)") + + GAS_GARBAGE_COLLECT(environment.gases) + + for(var/id in env_gases) + if(id in GLOB.hardcoded_gases) + continue + var/gas_concentration = env_gases[id]/total_moles + to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id], 0.01)] mol)") + to_chat(user, "Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)") + +/obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens + . = ..() + + if(user.canUseTopic(src)) + . = TRUE + 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 ? next_hit - world.time : -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(mixture, mob/living/user, atom/target = src) + var/icon = target + user.visible_message("[user] has used the analyzer on [icon2html(icon, viewers(user))] [target].", "You use the analyzer on [icon2html(icon, user)] [target].") + to_chat(user, "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 + to_chat(user, "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) + to_chat(user, "Moles: [round(total_moles, 0.01)] mol") + to_chat(user, "Volume: [volume] L") + to_chat(user, "Pressure: [round(pressure,0.01)] kPa") + + var/list/cached_gases = air_contents.gases + for(var/id in cached_gases) + var/gas_concentration = cached_gases[id]/total_moles + to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id], 0.01)] mol)") + to_chat(user, "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)") + + else + if(airs.len > 1) + to_chat(user, "This node is empty!") + else + to_chat(user, "[target] is empty!") + + if(cached_scan_results && cached_scan_results["fusion"]) //notify the user if a fusion reaction was detected + var/fusion_power = round(cached_scan_results["fusion"], 0.01) + var/tier = fusionpower2text(fusion_power) + to_chat(user, "Large amounts of free neutrons detected in the air indicate that a fusion reaction took place.") + to_chat(user, "Power of the last fusion reaction: [fusion_power]\n This power indicates it was a [tier]-tier fusion reaction.") + return + +//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" + item_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 + materials = list(MAT_METAL=30, MAT_GLASS=20) + +/obj/item/slime_scanner/attack(mob/living/M, mob/living/user) + if(user.stat || user.eye_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) + to_chat(user, "========================") + to_chat(user, "Slime scan results:") + to_chat(user, "[T.colour] [T.is_adult ? "adult" : "baby"] slime") + to_chat(user, "Nutrition: [T.nutrition]/[T.get_max_nutrition()]") + if (T.nutrition < T.get_starve_nutrition()) + to_chat(user, "Warning: slime is starving!") + else if (T.nutrition < T.get_hunger_nutrition()) + to_chat(user, "Warning: slime is hungry") + to_chat(user, "Electric change strength: [T.powerlevel]") + to_chat(user, "Health: [round(T.health/T.maxHealth,0.01)*100]%") + if (T.slime_mutation[4] == T.colour) + to_chat(user, "This 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_chat(user, "Possible mutation: [T.slime_mutation[3]]") + to_chat(user, "Genetic destability: [T.mutation_chance/2] % chance of mutation on splitting") + else + to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]] (x2)") + to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting") + else + to_chat(user, "Possible mutations: [T.slime_mutation[1]], [T.slime_mutation[2]], [T.slime_mutation[3]], [T.slime_mutation[4]]") + to_chat(user, "Genetic destability: [T.mutation_chance] % chance of mutation on splitting") + if (T.cores > 1) + to_chat(user, "Multiple cores detected") + to_chat(user, "Growth progress: [T.amount_grown]/[SLIME_EVOLUTION_THRESHOLD]") + if(T.effectmod) + to_chat(user, "Core mutation in progress: [T.effectmod]") + to_chat(user, "Progress in core mutation: [T.applied] / [SLIME_EXTRACT_CROSSING_REQUIRED]") + to_chat(user, "========================") + + +/obj/item/nanite_scanner + name = "nanite scanner" + icon = 'icons/obj/device.dmi' + icon_state = "nanite_scanner" + item_state = "nanite_remote" + 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 + materials = list(MAT_METAL=200) + +/obj/item/nanite_scanner/attack(mob/living/M, mob/living/carbon/human/user) + user.visible_message("[user] has analyzed [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.") diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index a884524e11..ebc7c520ef 100644 --- a/code/game/objects/items/devices/taperecorder.dm +++ b/code/game/objects/items/devices/taperecorder.dm @@ -1,291 +1,291 @@ -/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" - item_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 - materials = list(MAT_METAL=60, MAT_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 - - -/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/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) - if(mytape) - if(!user.is_holding(src)) - return ..() - eject(user) - else - return ..() - -/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() - 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, atom/movable/source) - . = ..() - 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 || mytape.ruined) - return - if(recording) - stop() - else - record() - - -/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 = 0 - sleep(300) - canprint = 1 - - -//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' - item_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 - materials = list(MAT_METAL=20, MAT_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 && istype(I, /obj/item/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/New() - 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" + item_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 + materials = list(MAT_METAL=60, MAT_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 + + +/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/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) + if(mytape) + if(!user.is_holding(src)) + return ..() + eject(user) + else + return ..() + +/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() + 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, atom/movable/source) + . = ..() + 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 || mytape.ruined) + return + if(recording) + stop() + else + record() + + +/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 = 0 + sleep(300) + canprint = 1 + + +//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' + item_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 + materials = list(MAT_METAL=20, MAT_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 && istype(I, /obj/item/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/New() + 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 a8d57fab69..2c101b0ec5 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -1,237 +1,237 @@ -/obj/item/transfer_valve - icon = 'icons/obj/assemblies.dmi' - name = "tank transfer valve" - icon_state = "valve_1" - item_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/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 = 1 - -/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). - - GLOB.bombers += "[key_name(user)] attached a [item] to a transfer valve." - message_admins("[ADMIN_LOOKUPFLW(user)] attached a [item] to a transfer valve.") - log_game("[key_name(user)] attached a [item] to a transfer valve.") - attacher = user - return - -//Attached device memes -/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) - -/obj/item/transfer_valve/attack_hand()//Triggers mousetraps - . = ..() - if(.) - return - if(attached_device) - attached_device.attack_hand() - -//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/attack_self(mob/user) - user.set_machine(src) - var/dat = {" Valve properties: -
                Attachment one: [tank_one] [tank_one ? "Remove" : ""] -
                Attachment two: [tank_two] [tank_two ? "Remove" : ""] -
                Valve attachment: [attached_device ? "[attached_device]" : "None"] [attached_device ? "Remove" : ""] -
                Valve status: [ valve_open ? "Closed Open" : "Closed Open"]"} - - var/datum/browser/popup = new(user, "trans_valve", name) - popup.set_content(dat) - popup.open() - return - -/obj/item/transfer_valve/Topic(href, href_list) - ..() - if(!usr.canUseTopic(src)) - return - if(tank_one && href_list["tankone"]) - split_gases() - valve_open = FALSE - tank_one.forceMove(drop_location()) - tank_one = null - update_icon() - else if(tank_two && href_list["tanktwo"]) - split_gases() - valve_open = FALSE - tank_two.forceMove(drop_location()) - tank_two = null - update_icon() - else if(href_list["open"]) - toggle_valve() - else if(attached_device) - if(href_list["rem_device"]) - attached_device.on_detach() - attached_device = null - update_icon() - if(href_list["device"]) - attached_device.attack_self(usr) - - attack_self(usr) - add_fingerprint(usr) - -/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() - underlays = null - - 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/icon/J = new(icon, icon_state = "[tank_two.icon_state]") - J.Shift(WEST, 13) - underlays += J - 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, 0, 1) - 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 + icon = 'icons/obj/assemblies.dmi' + name = "tank transfer valve" + icon_state = "valve_1" + item_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/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 = 1 + +/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). + + GLOB.bombers += "[key_name(user)] attached a [item] to a transfer valve." + message_admins("[ADMIN_LOOKUPFLW(user)] attached a [item] to a transfer valve.") + log_game("[key_name(user)] attached a [item] to a transfer valve.") + attacher = user + return + +//Attached device memes +/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) + +/obj/item/transfer_valve/attack_hand()//Triggers mousetraps + . = ..() + if(.) + return + if(attached_device) + attached_device.attack_hand() + +//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/attack_self(mob/user) + user.set_machine(src) + var/dat = {" Valve properties: +
                Attachment one: [tank_one] [tank_one ? "Remove" : ""] +
                Attachment two: [tank_two] [tank_two ? "Remove" : ""] +
                Valve attachment: [attached_device ? "[attached_device]" : "None"] [attached_device ? "Remove" : ""] +
                Valve status: [ valve_open ? "Closed Open" : "Closed Open"]"} + + var/datum/browser/popup = new(user, "trans_valve", name) + popup.set_content(dat) + popup.open() + return + +/obj/item/transfer_valve/Topic(href, href_list) + ..() + if(!usr.canUseTopic(src)) + return + if(tank_one && href_list["tankone"]) + split_gases() + valve_open = FALSE + tank_one.forceMove(drop_location()) + tank_one = null + update_icon() + else if(tank_two && href_list["tanktwo"]) + split_gases() + valve_open = FALSE + tank_two.forceMove(drop_location()) + tank_two = null + update_icon() + else if(href_list["open"]) + toggle_valve() + else if(attached_device) + if(href_list["rem_device"]) + attached_device.on_detach() + attached_device = null + update_icon() + if(href_list["device"]) + attached_device.attack_self(usr) + + attack_self(usr) + add_fingerprint(usr) + +/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() + underlays = null + + 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/icon/J = new(icon, icon_state = "[tank_two.icon_state]") + J.Shift(WEST, 13) + underlays += J + 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, 0, 1) + 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 diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm index cb0d4ec4ac..f27884d3ae 100644 --- a/code/game/objects/items/dice.dm +++ b/code/game/objects/items/dice.dm @@ -1,197 +1,197 @@ -/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" - -/obj/item/storage/pill_bottle/dice/Initialize() - . = ..() - var/special_die = pick("1","2","fudge","space","00","8bd20","4dd6","100") - if(special_die == "1") - new /obj/item/dice/d1(src) - if(special_die == "2") - new /obj/item/dice/d2(src) - new /obj/item/dice/d4(src) - new /obj/item/dice/d6(src) - if(special_die == "fudge") - new /obj/item/dice/fudge(src) - if(special_die == "space") - new /obj/item/dice/d6/space(src) - new /obj/item/dice/d8(src) - new /obj/item/dice/d10(src) - if(special_die == "00") - new /obj/item/dice/d00(src) - new /obj/item/dice/d12(src) - new /obj/item/dice/d20(src) - if(special_die == "8bd20") - new /obj/item/dice/eightbd20(src) - if(special_die == "4dd6") - new /obj/item/dice/fourdd6(src) - if(special_die == "100") - new /obj/item/dice/d100(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/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/can_be_rigged = TRUE - var/rigged = FALSE - -/obj/item/dice/Initialize() - . = ..() - 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, 4) - -/obj/item/dice/d6 - name = "d6" - -/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/update_icon() - return - -/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/update_icon() - return - -/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/update_icon() - return - -/obj/item/dice/attack_self(mob/user) - diceroll(user) - -/obj/item/dice/throw_impact(atom/target) - diceroll(thrownby) - . = ..() - -/obj/item/dice/proc/diceroll(mob/user) - result = roll(sides) - if(rigged && result != rigged) - if(prob(CLAMP(1/(sides - 1) * 100, 25, 80))) - result = rigged - 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] has thrown [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_icon() - cut_overlays() - add_overlay("[src.icon_state]-[src.result]") - -/obj/item/dice/microwave_act(obj/machinery/microwave/M) - if(can_be_rigged) - rigged = result - ..(M) +/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" + +/obj/item/storage/pill_bottle/dice/Initialize() + . = ..() + var/special_die = pick("1","2","fudge","space","00","8bd20","4dd6","100") + if(special_die == "1") + new /obj/item/dice/d1(src) + if(special_die == "2") + new /obj/item/dice/d2(src) + new /obj/item/dice/d4(src) + new /obj/item/dice/d6(src) + if(special_die == "fudge") + new /obj/item/dice/fudge(src) + if(special_die == "space") + new /obj/item/dice/d6/space(src) + new /obj/item/dice/d8(src) + new /obj/item/dice/d10(src) + if(special_die == "00") + new /obj/item/dice/d00(src) + new /obj/item/dice/d12(src) + new /obj/item/dice/d20(src) + if(special_die == "8bd20") + new /obj/item/dice/eightbd20(src) + if(special_die == "4dd6") + new /obj/item/dice/fourdd6(src) + if(special_die == "100") + new /obj/item/dice/d100(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/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/can_be_rigged = TRUE + var/rigged = FALSE + +/obj/item/dice/Initialize() + . = ..() + 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, 4) + +/obj/item/dice/d6 + name = "d6" + +/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/update_icon() + return + +/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/update_icon() + return + +/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/update_icon() + return + +/obj/item/dice/attack_self(mob/user) + diceroll(user) + +/obj/item/dice/throw_impact(atom/target) + diceroll(thrownby) + . = ..() + +/obj/item/dice/proc/diceroll(mob/user) + result = roll(sides) + if(rigged && result != rigged) + if(prob(CLAMP(1/(sides - 1) * 100, 25, 80))) + result = rigged + 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] has thrown [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_icon() + cut_overlays() + add_overlay("[src.icon_state]-[src.result]") + +/obj/item/dice/microwave_act(obj/machinery/microwave/M) + if(can_be_rigged) + rigged = result + ..(M) diff --git a/code/game/objects/items/dna_injector.dm b/code/game/objects/items/dna_injector.dm index 10c81e9dbb..a03447c0f0 100644 --- a/code/game/objects/items/dna_injector.dm +++ b/code/game/objects/items/dna_injector.dm @@ -1,369 +1,369 @@ -/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/list/add_mutations_static = list() - var/list/remove_mutations_static = list() - - var/used = 0 - -/obj/item/dnainjector/attack_paw(mob/user) - return attack_hand(user) - -/obj/item/dnainjector/proc/prepare() - for(var/mut_key in add_mutations_static) - add_mutations.Add(GLOB.mutations_list[mut_key]) - for(var/mut_key in remove_mutations_static) - remove_mutations.Add(GLOB.mutations_list[mut_key]) - -/obj/item/dnainjector/proc/inject(mob/living/carbon/M, mob/user) - prepare() - - if(M.has_dna() && !HAS_TRAIT(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_NOCLONE)) - 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/datum/mutation/human/HM in remove_mutations) - HM.force_lose(M) - for(var/datum/mutation/human/HM in add_mutations) - if(HM.name == RACEMUT) - message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") - log_msg += " (MONKEY)" - HM.force_give(M) - 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 [target] with [src]!") - if(!do_mob(user, target) || used) - return - target.visible_message("[user] injects [target] with the syringe with [src]!", \ - "[user] injects [target] 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_static = 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_static = list(HULK) - -/obj/item/dnainjector/xraymut - name = "\improper DNA injector (X-ray)" - desc = "Finally you can see what the Captain does." - add_mutations_static = list(XRAY) - -/obj/item/dnainjector/antixray - name = "\improper DNA injector (Anti-X-ray)" - desc = "It will make you see harder." - remove_mutations_static = list(XRAY) - -///////////////////////////////////// -/obj/item/dnainjector/antiglasses - name = "\improper DNA injector (Anti-Glasses)" - desc = "Toss away those glasses!" - remove_mutations_static = list(BADSIGHT) - -/obj/item/dnainjector/glassesmut - name = "\improper DNA injector (Glasses)" - desc = "Will make you need dorkish glasses." - add_mutations_static = list(BADSIGHT) - -/obj/item/dnainjector/epimut - name = "\improper DNA injector (Epi.)" - desc = "Shake shake shake the room!" - add_mutations_static = list(EPILEPSY) - -/obj/item/dnainjector/antiepi - name = "\improper DNA injector (Anti-Epi.)" - desc = "Will fix you up from shaking the room." - remove_mutations_static = list(EPILEPSY) -//////////////////////////////////// -/obj/item/dnainjector/anticough - name = "\improper DNA injector (Anti-Cough)" - desc = "Will stop that awful noise." - remove_mutations_static = list(COUGH) - -/obj/item/dnainjector/coughmut - name = "\improper DNA injector (Cough)" - desc = "Will bring forth a sound of horror from your throat." - add_mutations_static = list(COUGH) - -/obj/item/dnainjector/antidwarf - name = "\improper DNA injector (Anti-Dwarfism)" - desc = "Helps you grow big and strong." - remove_mutations_static = list(DWARFISM) - -/obj/item/dnainjector/dwarf - name = "\improper DNA injector (Dwarfism)" - desc = "It's a small world after all." - add_mutations_static = list(DWARFISM) - -/obj/item/dnainjector/clumsymut - name = "\improper DNA injector (Clumsy)" - desc = "Makes clown minions." - add_mutations_static = list(CLOWNMUT) - -/obj/item/dnainjector/anticlumsy - name = "\improper DNA injector (Anti-Clumsy)" - desc = "Apply this for Security Clown." - remove_mutations_static = list(CLOWNMUT) - -/obj/item/dnainjector/antitour - name = "\improper DNA injector (Anti-Tour.)" - desc = "Will cure Tourette's." - remove_mutations_static = list(TOURETTES) - -/obj/item/dnainjector/tourmut - name = "\improper DNA injector (Tour.)" - desc = "Gives you a nasty case of Tourette's." - add_mutations_static = list(TOURETTES) - -/obj/item/dnainjector/stuttmut - name = "\improper DNA injector (Stutt.)" - desc = "Makes you s-s-stuttterrr." - add_mutations_static = list(NERVOUS) - -/obj/item/dnainjector/antistutt - name = "\improper DNA injector (Anti-Stutt.)" - desc = "Fixes that speaking impairment." - remove_mutations_static = list(NERVOUS) - -/obj/item/dnainjector/antifire - name = "\improper DNA injector (Anti-Fire)" - desc = "Cures fire." - remove_mutations_static = list(COLDRES) - -/obj/item/dnainjector/firemut - name = "\improper DNA injector (Fire)" - desc = "Gives you fire." - add_mutations_static = list(COLDRES) - -/obj/item/dnainjector/blindmut - name = "\improper DNA injector (Blind)" - desc = "Makes you not see anything." - add_mutations_static = list(BLINDMUT) - -/obj/item/dnainjector/antiblind - name = "\improper DNA injector (Anti-Blind)" - desc = "IT'S A MIRACLE!!!" - remove_mutations_static = list(BLINDMUT) - -/obj/item/dnainjector/antitele - name = "\improper DNA injector (Anti-Tele.)" - desc = "Will make you not able to control your mind." - remove_mutations_static = list(TK) - -/obj/item/dnainjector/telemut - name = "\improper DNA injector (Tele.)" - desc = "Super brain man!" - add_mutations_static = 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_static = list(DEAFMUT) - -/obj/item/dnainjector/antideaf - name = "\improper DNA injector (Anti-Deaf)" - desc = "Will make you hear once more." - remove_mutations_static = list(DEAFMUT) - -/obj/item/dnainjector/h2m - name = "\improper DNA injector (Human > Monkey)" - desc = "Will make you a flea bag." - add_mutations_static = list(RACEMUT) - -/obj/item/dnainjector/m2h - name = "\improper DNA injector (Monkey > Human)" - desc = "Will make you...less hairy." - remove_mutations_static = list(RACEMUT) - -/obj/item/dnainjector/antichameleon - name = "\improper DNA injector (Anti-Chameleon)" - remove_mutations_static = list(CHAMELEON) - -/obj/item/dnainjector/chameleonmut - name = "\improper DNA injector (Chameleon)" - add_mutations_static = list(CHAMELEON) - -/obj/item/dnainjector/antiwacky - name = "\improper DNA injector (Anti-Wacky)" - remove_mutations_static = list(WACKY) - -/obj/item/dnainjector/wackymut - name = "\improper DNA injector (Wacky)" - add_mutations_static = list(WACKY) - -/obj/item/dnainjector/antimute - name = "\improper DNA injector (Anti-Mute)" - remove_mutations_static = list(MUT_MUTE) - -/obj/item/dnainjector/mutemut - name = "\improper DNA injector (Mute)" - add_mutations_static = list(MUT_MUTE) - -/obj/item/dnainjector/antismile - name = "\improper DNA injector (Anti-Smile)" - remove_mutations_static = list(SMILE) - -/obj/item/dnainjector/smilemut - name = "\improper DNA injector (Smile)" - add_mutations_static = list(SMILE) - -/obj/item/dnainjector/unintelligiblemut - name = "\improper DNA injector (Unintelligible)" - add_mutations_static = list(UNINTELLIGIBLE) - -/obj/item/dnainjector/antiunintelligible - name = "\improper DNA injector (Anti-Unintelligible)" - remove_mutations_static = list(UNINTELLIGIBLE) - -/obj/item/dnainjector/swedishmut - name = "\improper DNA injector (Swedish)" - add_mutations_static = list(SWEDISH) - -/obj/item/dnainjector/antiswedish - name = "\improper DNA injector (Anti-Swedish)" - remove_mutations_static = list(SWEDISH) - -/obj/item/dnainjector/chavmut - name = "\improper DNA injector (Chav)" - add_mutations_static = list(CHAV) - -/obj/item/dnainjector/antichav - name = "\improper DNA injector (Anti-Chav)" - remove_mutations_static = list(CHAV) - -/obj/item/dnainjector/elvismut - name = "\improper DNA injector (Elvis)" - add_mutations_static = list(ELVIS) - -/obj/item/dnainjector/antielvis - name = "\improper DNA injector (Anti-Elvis)" - remove_mutations_static = list(ELVIS) - -/obj/item/dnainjector/lasereyesmut - name = "\improper DNA injector (Laser Eyes)" - add_mutations_static = list(LASEREYES) - -/obj/item/dnainjector/antilasereyes - name = "\improper DNA injector (Anti-Laser Eyes)" - remove_mutations_static = list(LASEREYES) - -/obj/item/dnainjector/timed - var/duration = 600 - -/obj/item/dnainjector/timed/inject(mob/living/carbon/M, mob/user) - prepare() - 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_NOCLONE))) - 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/datum/mutation/human/HM in remove_mutations) - if(HM.name == RACEMUT) - if(ishuman(M)) - continue - M = HM.force_lose(M) - else - HM.force_lose(M) - for(var/datum/mutation/human/HM in add_mutations) - if((HM in M.dna.mutations) && !(M.dna.temporary_mutations[HM.name])) - continue //Skip permanent mutations we already have. - if(HM.name == RACEMUT && ishuman(M)) - message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") - log_msg += " (MONKEY)" - M = HM.force_give(M) - else - HM.force_give(M) - M.dna.temporary_mutations[HM.name] = 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_static = list(HULK) - -/obj/item/dnainjector/timed/h2m - name = "\improper DNA injector (Human > Monkey)" - desc = "Will make you a flea bag." - add_mutations_static = list(RACEMUT) +/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/list/add_mutations_static = list() + var/list/remove_mutations_static = list() + + var/used = 0 + +/obj/item/dnainjector/attack_paw(mob/user) + return attack_hand(user) + +/obj/item/dnainjector/proc/prepare() + for(var/mut_key in add_mutations_static) + add_mutations.Add(GLOB.mutations_list[mut_key]) + for(var/mut_key in remove_mutations_static) + remove_mutations.Add(GLOB.mutations_list[mut_key]) + +/obj/item/dnainjector/proc/inject(mob/living/carbon/M, mob/user) + prepare() + + if(M.has_dna() && !HAS_TRAIT(M, TRAIT_RADIMMUNE) && !HAS_TRAIT(M, TRAIT_NOCLONE)) + 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/datum/mutation/human/HM in remove_mutations) + HM.force_lose(M) + for(var/datum/mutation/human/HM in add_mutations) + if(HM.name == RACEMUT) + message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") + log_msg += " (MONKEY)" + HM.force_give(M) + 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 [target] with [src]!") + if(!do_mob(user, target) || used) + return + target.visible_message("[user] injects [target] with the syringe with [src]!", \ + "[user] injects [target] 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_static = 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_static = list(HULK) + +/obj/item/dnainjector/xraymut + name = "\improper DNA injector (X-ray)" + desc = "Finally you can see what the Captain does." + add_mutations_static = list(XRAY) + +/obj/item/dnainjector/antixray + name = "\improper DNA injector (Anti-X-ray)" + desc = "It will make you see harder." + remove_mutations_static = list(XRAY) + +///////////////////////////////////// +/obj/item/dnainjector/antiglasses + name = "\improper DNA injector (Anti-Glasses)" + desc = "Toss away those glasses!" + remove_mutations_static = list(BADSIGHT) + +/obj/item/dnainjector/glassesmut + name = "\improper DNA injector (Glasses)" + desc = "Will make you need dorkish glasses." + add_mutations_static = list(BADSIGHT) + +/obj/item/dnainjector/epimut + name = "\improper DNA injector (Epi.)" + desc = "Shake shake shake the room!" + add_mutations_static = list(EPILEPSY) + +/obj/item/dnainjector/antiepi + name = "\improper DNA injector (Anti-Epi.)" + desc = "Will fix you up from shaking the room." + remove_mutations_static = list(EPILEPSY) +//////////////////////////////////// +/obj/item/dnainjector/anticough + name = "\improper DNA injector (Anti-Cough)" + desc = "Will stop that awful noise." + remove_mutations_static = list(COUGH) + +/obj/item/dnainjector/coughmut + name = "\improper DNA injector (Cough)" + desc = "Will bring forth a sound of horror from your throat." + add_mutations_static = list(COUGH) + +/obj/item/dnainjector/antidwarf + name = "\improper DNA injector (Anti-Dwarfism)" + desc = "Helps you grow big and strong." + remove_mutations_static = list(DWARFISM) + +/obj/item/dnainjector/dwarf + name = "\improper DNA injector (Dwarfism)" + desc = "It's a small world after all." + add_mutations_static = list(DWARFISM) + +/obj/item/dnainjector/clumsymut + name = "\improper DNA injector (Clumsy)" + desc = "Makes clown minions." + add_mutations_static = list(CLOWNMUT) + +/obj/item/dnainjector/anticlumsy + name = "\improper DNA injector (Anti-Clumsy)" + desc = "Apply this for Security Clown." + remove_mutations_static = list(CLOWNMUT) + +/obj/item/dnainjector/antitour + name = "\improper DNA injector (Anti-Tour.)" + desc = "Will cure Tourette's." + remove_mutations_static = list(TOURETTES) + +/obj/item/dnainjector/tourmut + name = "\improper DNA injector (Tour.)" + desc = "Gives you a nasty case of Tourette's." + add_mutations_static = list(TOURETTES) + +/obj/item/dnainjector/stuttmut + name = "\improper DNA injector (Stutt.)" + desc = "Makes you s-s-stuttterrr." + add_mutations_static = list(NERVOUS) + +/obj/item/dnainjector/antistutt + name = "\improper DNA injector (Anti-Stutt.)" + desc = "Fixes that speaking impairment." + remove_mutations_static = list(NERVOUS) + +/obj/item/dnainjector/antifire + name = "\improper DNA injector (Anti-Fire)" + desc = "Cures fire." + remove_mutations_static = list(COLDRES) + +/obj/item/dnainjector/firemut + name = "\improper DNA injector (Fire)" + desc = "Gives you fire." + add_mutations_static = list(COLDRES) + +/obj/item/dnainjector/blindmut + name = "\improper DNA injector (Blind)" + desc = "Makes you not see anything." + add_mutations_static = list(BLINDMUT) + +/obj/item/dnainjector/antiblind + name = "\improper DNA injector (Anti-Blind)" + desc = "IT'S A MIRACLE!!!" + remove_mutations_static = list(BLINDMUT) + +/obj/item/dnainjector/antitele + name = "\improper DNA injector (Anti-Tele.)" + desc = "Will make you not able to control your mind." + remove_mutations_static = list(TK) + +/obj/item/dnainjector/telemut + name = "\improper DNA injector (Tele.)" + desc = "Super brain man!" + add_mutations_static = 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_static = list(DEAFMUT) + +/obj/item/dnainjector/antideaf + name = "\improper DNA injector (Anti-Deaf)" + desc = "Will make you hear once more." + remove_mutations_static = list(DEAFMUT) + +/obj/item/dnainjector/h2m + name = "\improper DNA injector (Human > Monkey)" + desc = "Will make you a flea bag." + add_mutations_static = list(RACEMUT) + +/obj/item/dnainjector/m2h + name = "\improper DNA injector (Monkey > Human)" + desc = "Will make you...less hairy." + remove_mutations_static = list(RACEMUT) + +/obj/item/dnainjector/antichameleon + name = "\improper DNA injector (Anti-Chameleon)" + remove_mutations_static = list(CHAMELEON) + +/obj/item/dnainjector/chameleonmut + name = "\improper DNA injector (Chameleon)" + add_mutations_static = list(CHAMELEON) + +/obj/item/dnainjector/antiwacky + name = "\improper DNA injector (Anti-Wacky)" + remove_mutations_static = list(WACKY) + +/obj/item/dnainjector/wackymut + name = "\improper DNA injector (Wacky)" + add_mutations_static = list(WACKY) + +/obj/item/dnainjector/antimute + name = "\improper DNA injector (Anti-Mute)" + remove_mutations_static = list(MUT_MUTE) + +/obj/item/dnainjector/mutemut + name = "\improper DNA injector (Mute)" + add_mutations_static = list(MUT_MUTE) + +/obj/item/dnainjector/antismile + name = "\improper DNA injector (Anti-Smile)" + remove_mutations_static = list(SMILE) + +/obj/item/dnainjector/smilemut + name = "\improper DNA injector (Smile)" + add_mutations_static = list(SMILE) + +/obj/item/dnainjector/unintelligiblemut + name = "\improper DNA injector (Unintelligible)" + add_mutations_static = list(UNINTELLIGIBLE) + +/obj/item/dnainjector/antiunintelligible + name = "\improper DNA injector (Anti-Unintelligible)" + remove_mutations_static = list(UNINTELLIGIBLE) + +/obj/item/dnainjector/swedishmut + name = "\improper DNA injector (Swedish)" + add_mutations_static = list(SWEDISH) + +/obj/item/dnainjector/antiswedish + name = "\improper DNA injector (Anti-Swedish)" + remove_mutations_static = list(SWEDISH) + +/obj/item/dnainjector/chavmut + name = "\improper DNA injector (Chav)" + add_mutations_static = list(CHAV) + +/obj/item/dnainjector/antichav + name = "\improper DNA injector (Anti-Chav)" + remove_mutations_static = list(CHAV) + +/obj/item/dnainjector/elvismut + name = "\improper DNA injector (Elvis)" + add_mutations_static = list(ELVIS) + +/obj/item/dnainjector/antielvis + name = "\improper DNA injector (Anti-Elvis)" + remove_mutations_static = list(ELVIS) + +/obj/item/dnainjector/lasereyesmut + name = "\improper DNA injector (Laser Eyes)" + add_mutations_static = list(LASEREYES) + +/obj/item/dnainjector/antilasereyes + name = "\improper DNA injector (Anti-Laser Eyes)" + remove_mutations_static = list(LASEREYES) + +/obj/item/dnainjector/timed + var/duration = 600 + +/obj/item/dnainjector/timed/inject(mob/living/carbon/M, mob/user) + prepare() + 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_NOCLONE))) + 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/datum/mutation/human/HM in remove_mutations) + if(HM.name == RACEMUT) + if(ishuman(M)) + continue + M = HM.force_lose(M) + else + HM.force_lose(M) + for(var/datum/mutation/human/HM in add_mutations) + if((HM in M.dna.mutations) && !(M.dna.temporary_mutations[HM.name])) + continue //Skip permanent mutations we already have. + if(HM.name == RACEMUT && ishuman(M)) + message_admins("[ADMIN_LOOKUPFLW(user)] injected [key_name_admin(M)] with the [name] (MONKEY)") + log_msg += " (MONKEY)" + M = HM.force_give(M) + else + HM.force_give(M) + M.dna.temporary_mutations[HM.name] = 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_static = list(HULK) + +/obj/item/dnainjector/timed/h2m + name = "\improper DNA injector (Human > Monkey)" + desc = "Will make you a flea bag." + add_mutations_static = list(RACEMUT) diff --git a/code/game/objects/items/extinguisher.dm b/code/game/objects/items/extinguisher.dm index 81317b6eab..5cd1f4831c 100644 --- a/code/game/objects/items/extinguisher.dm +++ b/code/game/objects/items/extinguisher.dm @@ -1,257 +1,257 @@ -/obj/item/extinguisher - name = "fire extinguisher" - desc = "A traditional red fire extinguisher." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "fire_extinguisher0" - item_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 - materials = list(MAT_METAL = 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" - item_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 - materials = list(MAT_METAL = 50, MAT_GLASS = 40) - max_water = 30 - sprite_name = "miniFE" - dog_fashion = null - -/obj/item/extinguisher/mini/family - name = "pocket fire extinguisher" - desc = "A old fashen pocket fire extinguisher that has been modified with a larger water tank, and a small high-power sprayer. It feels cool to the touch and has a small humming to it..." - icon_state = "miniFE0" - item_state = "miniFE" - throwforce = 1 - w_class = WEIGHT_CLASS_SMALL - force = 2 - max_water = 40 - power = 7 - cooling_power = 3 - -/obj/item/extinguisher/Initialize() - . = ..() - create_reagents(max_water, AMOUNT_VISIBLE) - reagents.add_reagent(chem, max_water) - - -/obj/item/extinguisher/advanced - name = "advanced fire extinguisher" - desc = "Used to stop thermonuclear fires from spreading inside your engine." - icon_state = "foam_extinguisher0" - //item_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/proc/refill() - create_reagents(max_water, AMOUNT_VISIBLE) - reagents.add_reagent(chem, max_water) - -/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) - . += "You can loose its screws 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) - if(transferred > 0) - to_chat(user, "\The [src] has been refilled by [transferred] units.") - playsound(src.loc, 'sound/effects/refill.ogg', 50, 1, -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, 1, -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) - - //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(var/list/particles, var/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.reaction(get_turf(W)) - for(var/A in get_turf(W)) - W.reagents.reaction(A) - if(W.loc == my_target) - break - 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(var/obj/B, var/movementdirection, var/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/screwdriver_act(mob/user, obj/item/tool) - if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - 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] by loosing the release valve's screws.") - -//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" + item_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 + materials = list(MAT_METAL = 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" + item_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 + materials = list(MAT_METAL = 50, MAT_GLASS = 40) + max_water = 30 + sprite_name = "miniFE" + dog_fashion = null + +/obj/item/extinguisher/mini/family + name = "pocket fire extinguisher" + desc = "A old fashen pocket fire extinguisher that has been modified with a larger water tank, and a small high-power sprayer. It feels cool to the touch and has a small humming to it..." + icon_state = "miniFE0" + item_state = "miniFE" + throwforce = 1 + w_class = WEIGHT_CLASS_SMALL + force = 2 + max_water = 40 + power = 7 + cooling_power = 3 + +/obj/item/extinguisher/Initialize() + . = ..() + create_reagents(max_water, AMOUNT_VISIBLE) + reagents.add_reagent(chem, max_water) + + +/obj/item/extinguisher/advanced + name = "advanced fire extinguisher" + desc = "Used to stop thermonuclear fires from spreading inside your engine." + icon_state = "foam_extinguisher0" + //item_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/proc/refill() + create_reagents(max_water, AMOUNT_VISIBLE) + reagents.add_reagent(chem, max_water) + +/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) + . += "You can loose its screws 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) + if(transferred > 0) + to_chat(user, "\The [src] has been refilled by [transferred] units.") + playsound(src.loc, 'sound/effects/refill.ogg', 50, 1, -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, 1, -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) + + //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(var/list/particles, var/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.reaction(get_turf(W)) + for(var/A in get_turf(W)) + W.reagents.reaction(A) + if(W.loc == my_target) + break + 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(var/obj/B, var/movementdirection, var/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/screwdriver_act(mob/user, obj/item/tool) + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + 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] by loosing the release valve's screws.") + +//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 b5fee4e8d7..cb16b558d3 100644 --- a/code/game/objects/items/grenades/chem_grenade.dm +++ b/code/game/objects/items/grenades/chem_grenade.dm @@ -1,602 +1,602 @@ -#define EMPTY 1 -#define WIRED 2 -#define READY 3 - -/obj/item/grenade/chem_grenade - name = "chemical grenade" - desc = "A custom made grenade." - icon_state = "chemg" - item_state = "flashbang" - w_class = WEIGHT_CLASS_SMALL - force = 2 - var/stage = EMPTY - var/list/beakers = list() - var/list/allowed_containers = list(/obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle) - var/affected_area = 3 - var/obj/item/assembly_holder/nadeassembly = null - var/assemblyattacher - 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. - -/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. - -/obj/item/grenade/chem_grenade/examine(mob/user) - display_timer = (stage == READY && !nadeassembly) //show/hide the timer based on assembly state - . = ..() - if(user.can_see_reagents()) - var/count = 0 - if(beakers.len) - . += "You scan the grenade and detect the following reagents:" - for(var/obj/item/reagent_containers/glass/G in beakers) - var/textcount = thtotext(++count) - for(var/datum/reagent/R in G.reagents.reagent_list) - . += "[R.volume] units of [R.name] in the [textcount] beaker." - if(beakers.len == 1) - . += "You detect no second beaker in the grenade." - else - . += "You scan the grenade, but detect nothing." - - -/obj/item/grenade/chem_grenade/attack_self(mob/user) - if(stage == READY && !active) - if(nadeassembly) - nadeassembly.attack_self(user) - else - ..() - - -/obj/item/grenade/chem_grenade/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/screwdriver)) - if(stage == WIRED) - if(beakers.len) - stage_change(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 == READY && !nadeassembly) - det_time = det_time == 50 ? 30 : 50 //toggle between 30 and 50 - to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") - else if(stage == EMPTY) - to_chat(user, "You need to add an activation mechanism!") - - else if(stage == WIRED && is_type_in_list(I, allowed_containers)) - . = 1 //no afterattack - 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 == EMPTY && istype(I, /obj/item/assembly_holder)) - . = 1 // no afterattack - var/obj/item/assembly_holder/A = I - if(isigniter(A.a_left) == isigniter(A.a_right)) //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it - return - if(!user.transferItemToLoc(I, src)) - return - - nadeassembly = A - A.master = src - assemblyattacher = user.ckey - - stage_change(WIRED) - to_chat(user, "You add [A] to the [initial(name)] assembly.") - - else if(stage == 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(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 == READY && istype(I, /obj/item/wirecutters) && !active) - stage_change(WIRED) - to_chat(user, "You unlock the [initial(name)] assembly.") - - else if(stage == WIRED && istype(I, /obj/item/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]") - beakers = list() - to_chat(user, "You open the [initial(name)] assembly and remove the payload.") - return // First use of the wrench remove beakers, then use the wrench to remove the activation mechanism. - if(nadeassembly) - nadeassembly.forceMove(drop_location()) - nadeassembly.master = null - nadeassembly = null - else // If "nadeassembly = null && stage == WIRED", then it most have been cable_coil that was used. - new /obj/item/stack/cable_coil(get_turf(src),1) - stage_change(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 == EMPTY) - name = "[initial(name)] casing" - desc = "A do it yourself [initial(name)]! [initial(casedesc)]" - icon_state = initial(icon_state) - else if(stage == WIRED) - name = "unsecured [initial(name)]" - desc = "An unsecured [initial(name)] assembly." - icon_state = "[initial(icon_state)]_ass" - else if(stage == READY) - name = initial(name) - desc = initial(desc) - icon_state = "[initial(icon_state)]_locked" - - -//assembly stuff -/obj/item/grenade/chem_grenade/receive_signal() - prime() - - -/obj/item/grenade/chem_grenade/Crossed(atom/movable/AM) - if(nadeassembly) - nadeassembly.Crossed(AM) - -/obj/item/grenade/chem_grenade/on_found(mob/finder) - if(nadeassembly) - nadeassembly.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] [beaker_number++] : " + pretty_string_from_reagent_list(exploded_beaker.reagents.reagent_list) + ";" - var/message = "[src] primed by [user] at [ADMIN_VERBOSEJMP(T)] contained [reagent_string]." - GLOB.bombers += message - message_admins(message) - user.log_message("primed [src] ([reagent_string])",LOG_GAME) - -/obj/item/grenade/chem_grenade/prime() - if(stage != 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, 1) - if(beakers.len) - for(var/obj/O in beakers) - O.forceMove(drop_location()) - beakers = list() - stage_change(EMPTY) - return - - if(nadeassembly) - var/mob/M = get_mob_by_ckey(assemblyattacher) - var/mob/last = get_mob_by_ckey(nadeassembly.fingerprintslast) - message_admins("grenade primed by an assembly, attached by [ADMIN_LOOKUPFLW(M)] and last touched by [ADMIN_LOOKUPFLW(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [ADMIN_VERBOSEJMP(detonation_turf)].") - log_game("grenade primed by an assembly, attached by [key_name(M)] and last touched by [key_name(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [AREACOORD(detonation_turf)]") - - 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 containers." - casedesc = "This casing affects a larger area than the basic model and can fit exotic containers, including slime cores. 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) - 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() - if(stage != 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 == 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(istype(I, /obj/item/multitool)) - switch(unit_spread) - if(0 to 24) - unit_spread += 5 - if(25 to 99) - unit_spread += 25 - else - unit_spread = 5 - to_chat(user, " You set the time release to [unit_spread] units per detonation.") - return - ..() - -/obj/item/grenade/chem_grenade/adv_release/prime() - if(stage != 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) - qdel(nadeassembly) - 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) - if(nadeassembly) - var/mob/M = get_mob_by_ckey(assemblyattacher) - var/mob/last = get_mob_by_ckey(nadeassembly.fingerprintslast) - message_admins("grenade primed by an assembly at [AREACOORD(DT)], attached by [ADMIN_LOOKUPFLW(M)] and last touched by [ADMIN_LOOKUPFLW(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]).") - log_game("grenade primed by an assembly at [AREACOORD(DT)], attached by [key_name(M)] and last touched by [key_name(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name])") - else - 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 - -#undef READY -#undef WIRED -#undef EMPTY +#define EMPTY 1 +#define WIRED 2 +#define READY 3 + +/obj/item/grenade/chem_grenade + name = "chemical grenade" + desc = "A custom made grenade." + icon_state = "chemg" + item_state = "flashbang" + w_class = WEIGHT_CLASS_SMALL + force = 2 + var/stage = EMPTY + var/list/beakers = list() + var/list/allowed_containers = list(/obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle) + var/affected_area = 3 + var/obj/item/assembly_holder/nadeassembly = null + var/assemblyattacher + 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. + +/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. + +/obj/item/grenade/chem_grenade/examine(mob/user) + display_timer = (stage == READY && !nadeassembly) //show/hide the timer based on assembly state + . = ..() + if(user.can_see_reagents()) + var/count = 0 + if(beakers.len) + . += "You scan the grenade and detect the following reagents:" + for(var/obj/item/reagent_containers/glass/G in beakers) + var/textcount = thtotext(++count) + for(var/datum/reagent/R in G.reagents.reagent_list) + . += "[R.volume] units of [R.name] in the [textcount] beaker." + if(beakers.len == 1) + . += "You detect no second beaker in the grenade." + else + . += "You scan the grenade, but detect nothing." + + +/obj/item/grenade/chem_grenade/attack_self(mob/user) + if(stage == READY && !active) + if(nadeassembly) + nadeassembly.attack_self(user) + else + ..() + + +/obj/item/grenade/chem_grenade/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/screwdriver)) + if(stage == WIRED) + if(beakers.len) + stage_change(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 == READY && !nadeassembly) + det_time = det_time == 50 ? 30 : 50 //toggle between 30 and 50 + to_chat(user, "You modify the time delay. It's set for [DisplayTimeText(det_time)].") + else if(stage == EMPTY) + to_chat(user, "You need to add an activation mechanism!") + + else if(stage == WIRED && is_type_in_list(I, allowed_containers)) + . = 1 //no afterattack + 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 == EMPTY && istype(I, /obj/item/assembly_holder)) + . = 1 // no afterattack + var/obj/item/assembly_holder/A = I + if(isigniter(A.a_left) == isigniter(A.a_right)) //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it + return + if(!user.transferItemToLoc(I, src)) + return + + nadeassembly = A + A.master = src + assemblyattacher = user.ckey + + stage_change(WIRED) + to_chat(user, "You add [A] to the [initial(name)] assembly.") + + else if(stage == 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(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 == READY && istype(I, /obj/item/wirecutters) && !active) + stage_change(WIRED) + to_chat(user, "You unlock the [initial(name)] assembly.") + + else if(stage == WIRED && istype(I, /obj/item/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]") + beakers = list() + to_chat(user, "You open the [initial(name)] assembly and remove the payload.") + return // First use of the wrench remove beakers, then use the wrench to remove the activation mechanism. + if(nadeassembly) + nadeassembly.forceMove(drop_location()) + nadeassembly.master = null + nadeassembly = null + else // If "nadeassembly = null && stage == WIRED", then it most have been cable_coil that was used. + new /obj/item/stack/cable_coil(get_turf(src),1) + stage_change(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 == EMPTY) + name = "[initial(name)] casing" + desc = "A do it yourself [initial(name)]! [initial(casedesc)]" + icon_state = initial(icon_state) + else if(stage == WIRED) + name = "unsecured [initial(name)]" + desc = "An unsecured [initial(name)] assembly." + icon_state = "[initial(icon_state)]_ass" + else if(stage == READY) + name = initial(name) + desc = initial(desc) + icon_state = "[initial(icon_state)]_locked" + + +//assembly stuff +/obj/item/grenade/chem_grenade/receive_signal() + prime() + + +/obj/item/grenade/chem_grenade/Crossed(atom/movable/AM) + if(nadeassembly) + nadeassembly.Crossed(AM) + +/obj/item/grenade/chem_grenade/on_found(mob/finder) + if(nadeassembly) + nadeassembly.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] [beaker_number++] : " + pretty_string_from_reagent_list(exploded_beaker.reagents.reagent_list) + ";" + var/message = "[src] primed by [user] at [ADMIN_VERBOSEJMP(T)] contained [reagent_string]." + GLOB.bombers += message + message_admins(message) + user.log_message("primed [src] ([reagent_string])",LOG_GAME) + +/obj/item/grenade/chem_grenade/prime() + if(stage != 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, 1) + if(beakers.len) + for(var/obj/O in beakers) + O.forceMove(drop_location()) + beakers = list() + stage_change(EMPTY) + return + + if(nadeassembly) + var/mob/M = get_mob_by_ckey(assemblyattacher) + var/mob/last = get_mob_by_ckey(nadeassembly.fingerprintslast) + message_admins("grenade primed by an assembly, attached by [ADMIN_LOOKUPFLW(M)] and last touched by [ADMIN_LOOKUPFLW(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [ADMIN_VERBOSEJMP(detonation_turf)].") + log_game("grenade primed by an assembly, attached by [key_name(M)] and last touched by [key_name(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [AREACOORD(detonation_turf)]") + + 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 containers." + casedesc = "This casing affects a larger area than the basic model and can fit exotic containers, including slime cores. 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) + 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() + if(stage != 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 == 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(istype(I, /obj/item/multitool)) + switch(unit_spread) + if(0 to 24) + unit_spread += 5 + if(25 to 99) + unit_spread += 25 + else + unit_spread = 5 + to_chat(user, " You set the time release to [unit_spread] units per detonation.") + return + ..() + +/obj/item/grenade/chem_grenade/adv_release/prime() + if(stage != 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) + qdel(nadeassembly) + 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) + if(nadeassembly) + var/mob/M = get_mob_by_ckey(assemblyattacher) + var/mob/last = get_mob_by_ckey(nadeassembly.fingerprintslast) + message_admins("grenade primed by an assembly at [AREACOORD(DT)], attached by [ADMIN_LOOKUPFLW(M)] and last touched by [ADMIN_LOOKUPFLW(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]).") + log_game("grenade primed by an assembly at [AREACOORD(DT)], attached by [key_name(M)] and last touched by [key_name(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name])") + else + 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 + +#undef READY +#undef WIRED +#undef EMPTY diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm index 724cfc8f9d..67036bd604 100644 --- a/code/game/objects/items/grenades/flashbang.dm +++ b/code/game/objects/items/grenades/flashbang.dm @@ -1,44 +1,44 @@ -/obj/item/grenade/flashbang - name = "flashbang" - icon_state = "flashbang" - item_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() - 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) - flashbang_mobs(flashbang_turf, flashbang_range) - qdel(src) - -/obj/item/grenade/flashbang/proc/flashbang_mobs(turf/source, range) - var/list/banged = get_hearers_in_view(range, source) - var/list/flashed = viewers(range, source) - for(var/mob/living/l in banged) - bang(l, source) - for(var/mob/living/l in flashed) - flash(l, source) - -/obj/item/grenade/flashbang/proc/bang(mob/living/M, turf/source) - if(M.stat == DEAD) //They're dead! - return - M.show_message("BANG", MSG_AUDIBLE) - var/distance = get_dist(get_turf(M), source) - if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. - M.Knockdown(200) - M.soundbang_act(1, 200, 10, 15) - else - M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5)) - -/obj/item/grenade/flashbang/proc/flash(mob/living/M, turf/source) - if(M.stat == DEAD) //They're dead! - return - var/distance = get_dist(get_turf(M), source) - if(M.flash_act(affect_silicon = 1)) - M.Knockdown(max(200/max(1,distance), 60)) +/obj/item/grenade/flashbang + name = "flashbang" + icon_state = "flashbang" + item_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() + 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) + flashbang_mobs(flashbang_turf, flashbang_range) + qdel(src) + +/obj/item/grenade/flashbang/proc/flashbang_mobs(turf/source, range) + var/list/banged = get_hearers_in_view(range, source) + var/list/flashed = viewers(range, source) + for(var/mob/living/l in banged) + bang(l, source) + for(var/mob/living/l in flashed) + flash(l, source) + +/obj/item/grenade/flashbang/proc/bang(mob/living/M, turf/source) + if(M.stat == DEAD) //They're dead! + return + M.show_message("BANG", MSG_AUDIBLE) + var/distance = get_dist(get_turf(M), source) + if(!distance || loc == M || loc == M.loc) //Stop allahu akbarring rooms with this. + M.Knockdown(200) + M.soundbang_act(1, 200, 10, 15) + else + M.soundbang_act(1, max(200/max(1,distance), 60), rand(0, 5)) + +/obj/item/grenade/flashbang/proc/flash(mob/living/M, turf/source) + if(M.stat == DEAD) //They're dead! + return + var/distance = get_dist(get_turf(M), source) + if(M.flash_act(affect_silicon = 1)) + M.Knockdown(max(200/max(1,distance), 60)) diff --git a/code/game/objects/items/grenades/ghettobomb.dm b/code/game/objects/items/grenades/ghettobomb.dm index 2d3d5e69b6..1725d84dcc 100644 --- a/code/game/objects/items/grenades/ghettobomb.dm +++ b/code/game/objects/items/grenades/ghettobomb.dm @@ -1,60 +1,60 @@ -//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" - item_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/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) - -/obj/item/grenade/iedcasing/CheckParts(list/parts_list) - ..() - var/obj/item/reagent_containers/food/drinks/soda_cans/can = locate() in contents - if(can) - 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(clown_check(user)) - to_chat(user, "You light the [name]!") - cut_overlay("improvised_grenade_filled") - preprime(user, null, FALSE) - -/obj/item/grenade/iedcasing/prime() //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/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" + item_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/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) + +/obj/item/grenade/iedcasing/CheckParts(list/parts_list) + ..() + var/obj/item/reagent_containers/food/drinks/soda_cans/can = locate() in contents + if(can) + 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(clown_check(user)) + to_chat(user, "You light the [name]!") + cut_overlay("improvised_grenade_filled") + preprime(user, null, FALSE) + +/obj/item/grenade/iedcasing/prime() //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/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 98a1aed4c1..4f3dab803d 100644 --- a/code/game/objects/items/grenades/grenade.dm +++ b/code/game/objects/items/grenades/grenade.dm @@ -1,124 +1,124 @@ -/obj/item/grenade - name = "grenade" - desc = "It has an adjustable timer." - w_class = WEIGHT_CLASS_SMALL - icon = 'icons/obj/grenade.dmi' - icon_state = "grenade" - item_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 - -/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, 1) - 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/clown_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 FALSE - 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 FALSE - return TRUE - - -/obj/item/grenade/examine(mob/user) - . = ..() - if(display_timer) - if(det_time > 1) - . += "The timer is set to [DisplayTimeText(det_time)]." - else - . += "\The [src] is set for instant detonation." - - -/obj/item/grenade/attack_self(mob/user) - if(!active) - if(clown_check(user)) - preprime(user) - -/obj/item/grenade/proc/log_grenade(mob/user, turf/T) - var/message = "[ADMIN_LOOKUPFLW(user)]) has primed \a [src] for detonation at [ADMIN_VERBOSEJMP(T)]" - GLOB.bombers += message - message_admins(message) - log_game("[key_name(user)] has primed \a [src] for detonation at [AREACOORD(T)].") - -/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(iscarbon(user)) - var/mob/living/carbon/C = user - C.throw_mode_on() - if(msg) - to_chat(user, "You prime [src]! [DisplayTimeText(det_time)]!") - playsound(src, 'sound/weapons/armbomb.ogg', volume, 1) - active = TRUE - icon_state = initial(icon_state) + "_active" - addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) - -/obj/item/grenade/proc/prime() - var/turf/T = get_turf(src) - log_game("Grenade detonation at [AREACOORD(T)], location [loc]") - -/obj/item/grenade/proc/update_mob() - if(ismob(loc)) - var/mob/M = loc - M.dropItemToGround(src) - else if(isitem(loc)) - var/obj/item/I = loc - I.grenade_prime_react(src) - - -/obj/item/grenade/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/screwdriver)) - switch(det_time) - if ("1") - det_time = 10 - to_chat(user, "You set the [name] for 1 second detonation time.") - if ("10") - det_time = 30 - to_chat(user, "You set the [name] for 3 second detonation time.") - if ("30") - det_time = 50 - to_chat(user, "You set the [name] for 5 second detonation time.") - if ("50") - det_time = 1 - to_chat(user, "You set the [name] for instant detonation.") - add_fingerprint(user) - else - return ..() - -/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/item/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!") - prime() - return TRUE //It hit the grenade, not them +/obj/item/grenade + name = "grenade" + desc = "It has an adjustable timer." + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/grenade.dmi' + icon_state = "grenade" + item_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 + +/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, 1) + 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/clown_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 FALSE + 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 FALSE + return TRUE + + +/obj/item/grenade/examine(mob/user) + . = ..() + if(display_timer) + if(det_time > 1) + . += "The timer is set to [DisplayTimeText(det_time)]." + else + . += "\The [src] is set for instant detonation." + + +/obj/item/grenade/attack_self(mob/user) + if(!active) + if(clown_check(user)) + preprime(user) + +/obj/item/grenade/proc/log_grenade(mob/user, turf/T) + var/message = "[ADMIN_LOOKUPFLW(user)]) has primed \a [src] for detonation at [ADMIN_VERBOSEJMP(T)]" + GLOB.bombers += message + message_admins(message) + log_game("[key_name(user)] has primed \a [src] for detonation at [AREACOORD(T)].") + +/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(iscarbon(user)) + var/mob/living/carbon/C = user + C.throw_mode_on() + if(msg) + to_chat(user, "You prime [src]! [DisplayTimeText(det_time)]!") + playsound(src, 'sound/weapons/armbomb.ogg', volume, 1) + active = TRUE + icon_state = initial(icon_state) + "_active" + addtimer(CALLBACK(src, .proc/prime), isnull(delayoverride)? det_time : delayoverride) + +/obj/item/grenade/proc/prime() + var/turf/T = get_turf(src) + log_game("Grenade detonation at [AREACOORD(T)], location [loc]") + +/obj/item/grenade/proc/update_mob() + if(ismob(loc)) + var/mob/M = loc + M.dropItemToGround(src) + else if(isitem(loc)) + var/obj/item/I = loc + I.grenade_prime_react(src) + + +/obj/item/grenade/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/screwdriver)) + switch(det_time) + if ("1") + det_time = 10 + to_chat(user, "You set the [name] for 1 second detonation time.") + if ("10") + det_time = 30 + to_chat(user, "You set the [name] for 3 second detonation time.") + if ("30") + det_time = 50 + to_chat(user, "You set the [name] for 5 second detonation time.") + if ("50") + det_time = 1 + to_chat(user, "You set the [name] for instant detonation.") + add_fingerprint(user) + else + return ..() + +/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/item/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!") + prime() + return TRUE //It hit the grenade, not them diff --git a/code/game/objects/items/grenades/smokebomb.dm b/code/game/objects/items/grenades/smokebomb.dm index d822ede78c..f26390faf6 100644 --- a/code/game/objects/items/grenades/smokebomb.dm +++ b/code/game/objects/items/grenades/smokebomb.dm @@ -1,31 +1,31 @@ -/obj/item/grenade/smokebomb - name = "smoke grenade" - desc = "The word 'Dank' is scribbled on it in crayon." - icon = 'icons/obj/grenade.dmi' - icon_state = "smokewhite" - det_time = 20 - item_state = "flashbang" - slot_flags = ITEM_SLOT_BELT - var/datum/effect_system/smoke_spread/bad/smoke - -/obj/item/grenade/smokebomb/New() - ..() - src.smoke = new /datum/effect_system/smoke_spread/bad - src.smoke.attach(src) - -/obj/item/grenade/smokebomb/Destroy() - qdel(smoke) - return ..() - -/obj/item/grenade/smokebomb/prime() - update_mob() - playsound(src.loc, 'sound/effects/smoke.ogg', 50, 1, -3) - smoke.set_up(4, src) - smoke.start() - - - 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) - sleep(80) - qdel(src) +/obj/item/grenade/smokebomb + name = "smoke grenade" + desc = "The word 'Dank' is scribbled on it in crayon." + icon = 'icons/obj/grenade.dmi' + icon_state = "smokewhite" + det_time = 20 + item_state = "flashbang" + slot_flags = ITEM_SLOT_BELT + var/datum/effect_system/smoke_spread/bad/smoke + +/obj/item/grenade/smokebomb/New() + ..() + src.smoke = new /datum/effect_system/smoke_spread/bad + src.smoke.attach(src) + +/obj/item/grenade/smokebomb/Destroy() + qdel(smoke) + return ..() + +/obj/item/grenade/smokebomb/prime() + update_mob() + playsound(src.loc, 'sound/effects/smoke.ogg', 50, 1, -3) + smoke.set_up(4, src) + smoke.start() + + + 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) + sleep(80) + qdel(src) diff --git a/code/game/objects/items/grenades/syndieminibomb.dm b/code/game/objects/items/grenades/syndieminibomb.dm index d457224e95..35e9abd982 100644 --- a/code/game/objects/items/grenades/syndieminibomb.dm +++ b/code/game/objects/items/grenades/syndieminibomb.dm @@ -1,50 +1,50 @@ -/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" - item_state = "flashbang" - - -/obj/item/grenade/syndieminibomb/prime() - update_mob() - explosion(src.loc,1,2,4,flame_range = 2) - 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" - -/obj/item/grenade/syndieminibomb/concussion/prime() - update_mob() - explosion(src.loc,0,2,3,flame_range = 3) - qdel(src) - -/obj/item/grenade/syndieminibomb/concussion/frag - name = "frag grenade" - desc = "Fire in the hole." - icon_state = "frag" - -/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" - item_state = "flashbang" - var/freeze_range = 4 - var/rad_damage = 350 - var/stamina_damage = 30 - -/obj/item/grenade/gluon/prime() - update_mob() - playsound(loc, 'sound/effects/empulse.ogg', 50, 1) - 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" + item_state = "flashbang" + + +/obj/item/grenade/syndieminibomb/prime() + update_mob() + explosion(src.loc,1,2,4,flame_range = 2) + 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" + +/obj/item/grenade/syndieminibomb/concussion/prime() + update_mob() + explosion(src.loc,0,2,3,flame_range = 3) + qdel(src) + +/obj/item/grenade/syndieminibomb/concussion/frag + name = "frag grenade" + desc = "Fire in the hole." + icon_state = "frag" + +/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" + item_state = "flashbang" + var/freeze_range = 4 + var/rad_damage = 350 + var/stamina_damage = 30 + +/obj/item/grenade/gluon/prime() + update_mob() + playsound(loc, 'sound/effects/empulse.ogg', 50, 1) + 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 246dd77684..849e14b476 100644 --- a/code/game/objects/items/handcuffs.dm +++ b/code/game/objects/items/handcuffs.dm @@ -1,376 +1,376 @@ -/obj/item/restraints - breakouttime = 600 - var/demoralize_criminals = TRUE // checked on carbon/carbon.dm to decide wheter to apply the handcuffed negative moodlet or not. - -/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" - item_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 - materials = list(MAT_METAL=500) - breakouttime = 600 //Deciseconds = 60s = 1 minute - 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 [C]!") - - playsound(loc, cuffsound, 30, 1, -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) - to_chat(user, "You handcuff [C].") - 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, var/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) - if(iscyborg(user)) - playsound(user, "law", 50, 0) - return - -/obj/item/restraints/handcuffs/sinew - name = "sinew restraints" - desc = "A pair of restraints fashioned from long strands of flesh." - icon = 'icons/obj/mining.dmi' - icon_state = "sinewcuff" - breakouttime = 300 //Deciseconds = 30s - cuffsound = 'sound/weapons/cablecuff.ogg' - -/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" - item_state = "coil" - item_color = "red" - color = "#ff0000" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - materials = list(MAT_METAL=150, MAT_GLASS=75) - breakouttime = 300 //Deciseconds = 30s - cuffsound = 'sound/weapons/cablecuff.ogg' - -/obj/item/restraints/handcuffs/cable/Initialize(mapload, param_color) - . = ..() - - var/list/cable_colors = GLOB.cable_colors - item_color = param_color || item_color || pick(cable_colors) - if(cable_colors[item_color]) - item_color = cable_colors[item_color] - update_icon() - -/obj/item/restraints/handcuffs/cable/update_icon() - color = null - add_atom_colour(item_color, FIXED_COLOUR_PRIORITY) - -/obj/item/restraints/handcuffs/cable/red - item_color = "red" - color = "#ff0000" - -/obj/item/restraints/handcuffs/cable/yellow - item_color = "yellow" - color = "#ffff00" - -/obj/item/restraints/handcuffs/cable/blue - item_color = "blue" - color = "#1919c8" - -/obj/item/restraints/handcuffs/cable/green - item_color = "green" - color = "#00aa00" - -/obj/item/restraints/handcuffs/cable/pink - item_color = "pink" - color = "#ff3ccd" - -/obj/item/restraints/handcuffs/cable/orange - item_color = "orange" - color = "#ff8000" - -/obj/item/restraints/handcuffs/cable/cyan - item_color = "cyan" - color = "#00ffff" - -/obj/item/restraints/handcuffs/cable/white - item_color = "white" - -/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 the cable restraint around the top of the rod.") - 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." - item_state = "zipties" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - materials = list() - breakouttime = 450 //Deciseconds = 45s - trashtype = /obj/item/restraints/handcuffs/cable/zipties/used - item_color = "white" - -/obj/item/restraints/handcuffs/cable/zipties/used - desc = "A pair of broken zipties." - icon_state = "cuff_used" - -/obj/item/restraints/handcuffs/cable/zipties/used/attack() - return - -/obj/item/restraints/handcuffs/alien - icon_state = "handcuffAlien" - -/obj/item/restraints/handcuffs/fake - name = "fake handcuffs" - desc = "Fake handcuffs meant for gag purposes." - breakouttime = 10 //Deciseconds = 1s - demoralize_criminals = FALSE - -/obj/item/restraints/handcuffs/fake/kinky - name = "kinky handcuffs" - desc = "Fake handcuffs meant for erotic roleplay." - icon = 'modular_citadel/icons/obj/items_and_weapons.dmi' - icon_state = "handcuffgag" - item_state = "kinkycuff" - -//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" - item_state = "legcuff" - 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 = 300 //Deciseconds = 30s = 0.5 minute - -/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() - . = ..() - 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, 1, -1) - return (BRUTELOSS) - -/obj/item/restraints/legcuffs/beartrap/attack_self(mob/user) - ..() - if(ishuman(user) && !user.stat && !user.restrained()) - armed = !armed - icon_state = "[initial(icon_state)][armed]" - to_chat(user, "[src] is now [armed ? "armed" : "disarmed"]") - -/obj/item/restraints/legcuffs/beartrap/Crossed(AM as mob|obj) - if(armed && isturf(src.loc)) - if(isliving(AM)) - var/mob/living/L = AM - var/snap = 0 - var/def_zone = BODY_ZONE_CHEST - if(iscarbon(L)) - var/mob/living/carbon/C = L - snap = 1 - if(!C.lying) - 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_inv_legcuffed() - SSblackbox.record_feedback("tally", "handcuffs", 1, type) - else if(isanimal(L)) - var/mob/living/simple_animal/SA = L - if(SA.mob_size > MOB_SIZE_TINY) - snap = 1 - if(L.movement_type & FLYING) - snap = 0 - if(snap) - armed = 0 - icon_state = "[initial(icon_state)][armed]" - playsound(src.loc, 'sound/effects/snap.ogg', 50, 1) - 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 - item_flags = DROPDEL - flags_1 = NONE - -/obj/item/restraints/legcuffs/beartrap/energy/New() - ..() - 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 - . = ..() - -/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" - 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) - if(!..()) - return - playsound(src.loc,'sound/weapons/bolathrow.ogg', 75, 1) - -/obj/item/restraints/legcuffs/bola/throw_impact(atom/hit_atom) - if(..() || !iscarbon(hit_atom))//if it gets caught or the target can't be cuffed, - return//abort - var/mob/living/carbon/C = hit_atom - if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) - visible_message("\The [src] ensnares [C]!") - C.legcuffed = src - forceMove(C) - C.update_inv_legcuffed() - SSblackbox.record_feedback("tally", "handcuffs", 1, type) - to_chat(C, "\The [src] ensnares you!") - C.Knockdown(knockdown) - -/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" - breakouttime = 70 - knockdown = 20 - -/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" - hitsound = 'sound/weapons/taserhit.ogg' - w_class = WEIGHT_CLASS_SMALL - breakouttime = 60 - -/obj/item/restraints/legcuffs/bola/energy/throw_impact(atom/hit_atom) - 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 + breakouttime = 600 + var/demoralize_criminals = TRUE // checked on carbon/carbon.dm to decide wheter to apply the handcuffed negative moodlet or not. + +/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" + item_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 + materials = list(MAT_METAL=500) + breakouttime = 600 //Deciseconds = 60s = 1 minute + 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 [C]!") + + playsound(loc, cuffsound, 30, 1, -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) + to_chat(user, "You handcuff [C].") + 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, var/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) + if(iscyborg(user)) + playsound(user, "law", 50, 0) + return + +/obj/item/restraints/handcuffs/sinew + name = "sinew restraints" + desc = "A pair of restraints fashioned from long strands of flesh." + icon = 'icons/obj/mining.dmi' + icon_state = "sinewcuff" + breakouttime = 300 //Deciseconds = 30s + cuffsound = 'sound/weapons/cablecuff.ogg' + +/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" + item_state = "coil" + item_color = "red" + color = "#ff0000" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + materials = list(MAT_METAL=150, MAT_GLASS=75) + breakouttime = 300 //Deciseconds = 30s + cuffsound = 'sound/weapons/cablecuff.ogg' + +/obj/item/restraints/handcuffs/cable/Initialize(mapload, param_color) + . = ..() + + var/list/cable_colors = GLOB.cable_colors + item_color = param_color || item_color || pick(cable_colors) + if(cable_colors[item_color]) + item_color = cable_colors[item_color] + update_icon() + +/obj/item/restraints/handcuffs/cable/update_icon() + color = null + add_atom_colour(item_color, FIXED_COLOUR_PRIORITY) + +/obj/item/restraints/handcuffs/cable/red + item_color = "red" + color = "#ff0000" + +/obj/item/restraints/handcuffs/cable/yellow + item_color = "yellow" + color = "#ffff00" + +/obj/item/restraints/handcuffs/cable/blue + item_color = "blue" + color = "#1919c8" + +/obj/item/restraints/handcuffs/cable/green + item_color = "green" + color = "#00aa00" + +/obj/item/restraints/handcuffs/cable/pink + item_color = "pink" + color = "#ff3ccd" + +/obj/item/restraints/handcuffs/cable/orange + item_color = "orange" + color = "#ff8000" + +/obj/item/restraints/handcuffs/cable/cyan + item_color = "cyan" + color = "#00ffff" + +/obj/item/restraints/handcuffs/cable/white + item_color = "white" + +/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 the cable restraint around the top of the rod.") + 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." + item_state = "zipties" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + materials = list() + breakouttime = 450 //Deciseconds = 45s + trashtype = /obj/item/restraints/handcuffs/cable/zipties/used + item_color = "white" + +/obj/item/restraints/handcuffs/cable/zipties/used + desc = "A pair of broken zipties." + icon_state = "cuff_used" + +/obj/item/restraints/handcuffs/cable/zipties/used/attack() + return + +/obj/item/restraints/handcuffs/alien + icon_state = "handcuffAlien" + +/obj/item/restraints/handcuffs/fake + name = "fake handcuffs" + desc = "Fake handcuffs meant for gag purposes." + breakouttime = 10 //Deciseconds = 1s + demoralize_criminals = FALSE + +/obj/item/restraints/handcuffs/fake/kinky + name = "kinky handcuffs" + desc = "Fake handcuffs meant for erotic roleplay." + icon = 'modular_citadel/icons/obj/items_and_weapons.dmi' + icon_state = "handcuffgag" + item_state = "kinkycuff" + +//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" + item_state = "legcuff" + 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 = 300 //Deciseconds = 30s = 0.5 minute + +/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() + . = ..() + 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, 1, -1) + return (BRUTELOSS) + +/obj/item/restraints/legcuffs/beartrap/attack_self(mob/user) + ..() + if(ishuman(user) && !user.stat && !user.restrained()) + armed = !armed + icon_state = "[initial(icon_state)][armed]" + to_chat(user, "[src] is now [armed ? "armed" : "disarmed"]") + +/obj/item/restraints/legcuffs/beartrap/Crossed(AM as mob|obj) + if(armed && isturf(src.loc)) + if(isliving(AM)) + var/mob/living/L = AM + var/snap = 0 + var/def_zone = BODY_ZONE_CHEST + if(iscarbon(L)) + var/mob/living/carbon/C = L + snap = 1 + if(!C.lying) + 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_inv_legcuffed() + SSblackbox.record_feedback("tally", "handcuffs", 1, type) + else if(isanimal(L)) + var/mob/living/simple_animal/SA = L + if(SA.mob_size > MOB_SIZE_TINY) + snap = 1 + if(L.movement_type & FLYING) + snap = 0 + if(snap) + armed = 0 + icon_state = "[initial(icon_state)][armed]" + playsound(src.loc, 'sound/effects/snap.ogg', 50, 1) + 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 + item_flags = DROPDEL + flags_1 = NONE + +/obj/item/restraints/legcuffs/beartrap/energy/New() + ..() + 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 + . = ..() + +/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" + 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) + if(!..()) + return + playsound(src.loc,'sound/weapons/bolathrow.ogg', 75, 1) + +/obj/item/restraints/legcuffs/bola/throw_impact(atom/hit_atom) + if(..() || !iscarbon(hit_atom))//if it gets caught or the target can't be cuffed, + return//abort + var/mob/living/carbon/C = hit_atom + if(!C.legcuffed && C.get_num_legs(FALSE) >= 2) + visible_message("\The [src] ensnares [C]!") + C.legcuffed = src + forceMove(C) + C.update_inv_legcuffed() + SSblackbox.record_feedback("tally", "handcuffs", 1, type) + to_chat(C, "\The [src] ensnares you!") + C.Knockdown(knockdown) + +/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" + breakouttime = 70 + knockdown = 20 + +/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" + hitsound = 'sound/weapons/taserhit.ogg' + w_class = WEIGHT_CLASS_SMALL + breakouttime = 60 + +/obj/item/restraints/legcuffs/bola/energy/throw_impact(atom/hit_atom) + 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) + ..() diff --git a/code/game/objects/items/hot_potato.dm b/code/game/objects/items/hot_potato.dm index 16ac2ad692..02ac894e64 100644 --- a/code/game/objects/items/hot_potato.dm +++ b/code/game/objects/items/hot_potato.dm @@ -1,171 +1,171 @@ -//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.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) - var/turf/T = get_turf(src) - message_admins("[user? "[ADMIN_LOOKUPFLW(user)] has primed [src]" : "A [src] has been primed"] (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range]) for detonation at [ADMIN_VERBOSEJMP(T)]") - log_game("[user ? "[key_name(user)] has primed [src]" : "A [src] has been primed"] ([detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range]) for detonation at [AREACOORD(T)]") - 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() - 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.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) + var/turf/T = get_turf(src) + message_admins("[user? "[ADMIN_LOOKUPFLW(user)] has primed [src]" : "A [src] has been primed"] (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range]) for detonation at [ADMIN_VERBOSEJMP(T)]") + log_game("[user ? "[key_name(user)] has primed [src]" : "A [src] has been primed"] ([detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range]) for detonation at [AREACOORD(T)]") + 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() + 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 b0859fd995..c1983b6b8c 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 - item_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) - if(SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTING, args) & COMPONENT_STOP_IMPLANTING) - return - LAZYINITLIST(target.implants) - if(!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 - - moveToNullspace() - 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) - SEND_SIGNAL(src, COMSIG_IMPLANT_REMOVING, args) - imp_in = null - source.implants -= src - for(var/X in actions) - var/datum/action/A = X - A.Remove(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 about this implant." - -/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 + item_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) + if(SEND_SIGNAL(src, COMSIG_IMPLANT_IMPLANTING, args) & COMPONENT_STOP_IMPLANTING) + return + LAZYINITLIST(target.implants) + if(!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 + + moveToNullspace() + 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) + SEND_SIGNAL(src, COMSIG_IMPLANT_REMOVING, args) + imp_in = null + source.implants -= src + for(var/X in actions) + var/datum/action/A = X + A.Remove(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 about this implant." + +/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 e26f38d792..4b8427386c 100644 --- a/code/game/objects/items/implants/implantcase.dm +++ b/code/game/objects/items/implants/implantcase.dm @@ -1,83 +1,83 @@ -/obj/item/implantcase - name = "implant case" - desc = "A glass case containing an implant." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "implantcase-0" - item_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 - materials = list(MAT_GLASS=500) - var/obj/item/implant/imp = null - var/imp_type - - -/obj/item/implantcase/update_icon() - if(imp) - icon_state = "implantcase-[imp.item_color]" - reagents = imp.reagents - else - icon_state = "implantcase-0" - reagents = null - - -/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() - I.update_icon() - else - if(imp) - if(I.imp) - return - imp.forceMove(I) - I.imp = imp - imp = null - update_icon() - I.update_icon() - - else - return ..() - -/obj/item/implantcase/Initialize(mapload) - . = ..() - if(imp_type) - imp = new imp_type(src) - update_icon() - - -/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" + item_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 + materials = list(MAT_GLASS=500) + var/obj/item/implant/imp = null + var/imp_type + + +/obj/item/implantcase/update_icon() + if(imp) + icon_state = "implantcase-[imp.item_color]" + reagents = imp.reagents + else + icon_state = "implantcase-0" + reagents = null + + +/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() + I.update_icon() + else + if(imp) + if(I.imp) + return + imp.forceMove(I) + I.imp = imp + imp = null + update_icon() + I.update_icon() + + else + return ..() + +/obj/item/implantcase/Initialize(mapload) + . = ..() + if(imp_type) + imp = new imp_type(src) + update_icon() + + +/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 781e1fa562..46a6cb2c35 100644 --- a/code/game/objects/items/implants/implantchair.dm +++ b/code/game/objects/items/implants/implantchair.dm @@ -1,193 +1,193 @@ -/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 - - 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, 375, 280, 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,"replenish"),replenish_cooldown) - if(injection_cooldown > 0) - ready = FALSE - addtimer(CALLBACK(src,"set_ready"),injection_cooldown) - else - playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 25, 1) - 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] has been implanted by [src].") - return TRUE - else if(istype(I, /obj/item/organ)) - var/obj/item/organ/P = I - P.Insert(M, drop_if_replaced = FALSE) - visible_message("[M] has been implanted by [src].") - return TRUE - -/obj/machinery/implantchair/update_icon() - icon_state = initial(icon_state) - if(state_open) - icon_state += "_open" - if(occupant) - icon_state += "_occupied" - if(ready) - add_overlay("ready") - else - cut_overlays() - -/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 || user.lying || !Adjacent(user) || !user.Adjacent(target) || !isliving(target) || !user.IsAdvancedToolUser()) - 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]'.") +/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 + + 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, 375, 280, 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,"replenish"),replenish_cooldown) + if(injection_cooldown > 0) + ready = FALSE + addtimer(CALLBACK(src,"set_ready"),injection_cooldown) + else + playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 25, 1) + 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] has been implanted by [src].") + return TRUE + else if(istype(I, /obj/item/organ)) + var/obj/item/organ/P = I + P.Insert(M, drop_if_replaced = FALSE) + visible_message("[M] has been implanted by [src].") + return TRUE + +/obj/machinery/implantchair/update_icon() + icon_state = initial(icon_state) + if(state_open) + icon_state += "_open" + if(occupant) + icon_state += "_occupied" + if(ready) + add_overlay("ready") + else + cut_overlays() + +/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 || user.lying || !Adjacent(user) || !user.Adjacent(target) || !isliving(target) || !user.IsAdvancedToolUser()) + 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 \ No newline at end of file diff --git a/code/game/objects/items/implants/implanter.dm b/code/game/objects/items/implants/implanter.dm index a1d360240d..ab902369cc 100644 --- a/code/game/objects/items/implants/implanter.dm +++ b/code/game/objects/items/implants/implanter.dm @@ -1,77 +1,77 @@ -/obj/item/implanter - name = "implanter" - desc = "A sterile automatic implant injector." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "implanter0" - item_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 - materials = list(MAT_METAL=600, MAT_GLASS=200) - var/obj/item/implant/imp = null - var/imp_type = null - - -/obj/item/implanter/update_icon() - 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] has implanted [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/adrenalin - name = "implanter (adrenalin)" - imp_type = /obj/item/implant/adrenalin - -/obj/item/implanter/emp - name = "implanter (EMP)" - imp_type = /obj/item/implant/emp - -/obj/item/implanter/stealth - name = "implanter (stealth)" +/obj/item/implanter + name = "implanter" + desc = "A sterile automatic implant injector." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "implanter0" + item_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 + materials = list(MAT_METAL=600, MAT_GLASS=200) + var/obj/item/implant/imp = null + var/imp_type = null + + +/obj/item/implanter/update_icon() + 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] has implanted [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/adrenalin + name = "implanter (adrenalin)" + imp_type = /obj/item/implant/adrenalin + +/obj/item/implanter/emp + name = "implanter (EMP)" + imp_type = /obj/item/implant/emp + +/obj/item/implanter/stealth + name = "implanter (stealth)" imp_type = /obj/item/implant/stealth \ No newline at end of file diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm index 6665d91c2b..e28909c84f 100644 --- a/code/game/objects/items/kitchen.dm +++ b/code/game/objects/items/kitchen.dm @@ -1,183 +1,183 @@ -/* Kitchen tools - * Contains: - * Fork - * Kitchen knives - * Ritual Knife - * Butcher's cleaver - * Combat Knife - * Rolling Pins - */ - -/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 - materials = list(MAT_METAL=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) - 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, 1) - 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 if(user.zone_selected == BODY_ZONE_PRECISE_EYES) - return eyestab(M,user) - else - return ..() - - -/obj/item/kitchen/knife - name = "kitchen knife" - 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 - materials = list(MAT_METAL=12000) - attack_verb = list("slashed", "stabbed", "sliced", "torn", "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) - var/bayonet = FALSE //Can this be attached to a gun? - -/obj/item/kitchen/knife/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 80 - force, 100, force - 10) //bonus chance increases depending on force - -/obj/item/kitchen/knife/attack(mob/living/carbon/M, mob/living/carbon/user) - if(user.zone_selected == BODY_ZONE_PRECISE_EYES) - return eyestab(M,user) - else - return ..() - -/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/ritual - name = "ritual knife" - desc = "The unearthly energies that once powered this blade are now dormant." - icon = 'icons/obj/wizard.dmi' - icon_state = "render" - item_state = "knife" - 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/butcher - name = "butcher's cleaver" - 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 - materials = list(MAT_METAL=18000) - attack_verb = list("cleaved", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - w_class = WEIGHT_CLASS_NORMAL - -/obj/item/kitchen/knife/combat - name = "combat knife" - icon_state = "buckknife" - item_state = "knife" - desc = "A military combat utility survival knife." - force = 20 - throwforce = 20 - attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "cut") - bayonet = TRUE - -/obj/item/kitchen/knife/combat/survival - name = "survival knife" - icon_state = "survivalknife" - item_state = "knife" - desc = "A hunting grade survival knife." - force = 15 - throwforce = 15 - bayonet = TRUE - -/obj/item/kitchen/knife/combat/bone - name = "bone dagger" - item_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." - force = 15 - throwforce = 15 - materials = list() - -/obj/item/kitchen/knife/combat/cyborg - name = "cyborg knife" - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "knife" - desc = "A cyborg-mounted plasteel knife. Extremely sharp and durable." - -/obj/item/kitchen/knife/carrotshiv - name = "carrot shiv" - icon_state = "carrotshiv" - item_state = "carrotshiv" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - desc = "Unlike other carrots, you should probably keep this far away from your eyes." - force = 8 - throwforce = 12//fuck git - materials = list() - 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) - -/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 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") - -/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/knife/scimitar - name = "Scimitar knife" - desc = "A knife used to cleanly butcher. Its razor-sharp edge has been honed for butchering, but has been poorly maintained over the years." - attack_verb = list("cleaved", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - -/obj/item/kitchen/knife/scimiar/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 90 - force, 100, force - 60) //bonus chance increases depending on force +/* Kitchen tools + * Contains: + * Fork + * Kitchen knives + * Ritual Knife + * Butcher's cleaver + * Combat Knife + * Rolling Pins + */ + +/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 + materials = list(MAT_METAL=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) + 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, 1) + 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 if(user.zone_selected == BODY_ZONE_PRECISE_EYES) + return eyestab(M,user) + else + return ..() + + +/obj/item/kitchen/knife + name = "kitchen knife" + 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 + materials = list(MAT_METAL=12000) + attack_verb = list("slashed", "stabbed", "sliced", "torn", "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) + var/bayonet = FALSE //Can this be attached to a gun? + +/obj/item/kitchen/knife/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 80 - force, 100, force - 10) //bonus chance increases depending on force + +/obj/item/kitchen/knife/attack(mob/living/carbon/M, mob/living/carbon/user) + if(user.zone_selected == BODY_ZONE_PRECISE_EYES) + return eyestab(M,user) + else + return ..() + +/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/ritual + name = "ritual knife" + desc = "The unearthly energies that once powered this blade are now dormant." + icon = 'icons/obj/wizard.dmi' + icon_state = "render" + item_state = "knife" + 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/butcher + name = "butcher's cleaver" + 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 + materials = list(MAT_METAL=18000) + attack_verb = list("cleaved", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + w_class = WEIGHT_CLASS_NORMAL + +/obj/item/kitchen/knife/combat + name = "combat knife" + icon_state = "buckknife" + item_state = "knife" + desc = "A military combat utility survival knife." + force = 20 + throwforce = 20 + attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "cut") + bayonet = TRUE + +/obj/item/kitchen/knife/combat/survival + name = "survival knife" + icon_state = "survivalknife" + item_state = "knife" + desc = "A hunting grade survival knife." + force = 15 + throwforce = 15 + bayonet = TRUE + +/obj/item/kitchen/knife/combat/bone + name = "bone dagger" + item_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." + force = 15 + throwforce = 15 + materials = list() + +/obj/item/kitchen/knife/combat/cyborg + name = "cyborg knife" + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "knife" + desc = "A cyborg-mounted plasteel knife. Extremely sharp and durable." + +/obj/item/kitchen/knife/carrotshiv + name = "carrot shiv" + icon_state = "carrotshiv" + item_state = "carrotshiv" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + desc = "Unlike other carrots, you should probably keep this far away from your eyes." + force = 8 + throwforce = 12//fuck git + materials = list() + 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) + +/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 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("bashed", "battered", "bludgeoned", "thrashed", "whacked") + +/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/knife/scimitar + name = "Scimitar knife" + desc = "A knife used to cleanly butcher. Its razor-sharp edge has been honed for butchering, but has been poorly maintained over the years." + attack_verb = list("cleaved", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + +/obj/item/kitchen/knife/scimiar/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 90 - force, 100, force - 60) //bonus chance increases depending on force diff --git a/code/game/objects/items/manuals.dm b/code/game/objects/items/manuals.dm index d038ea7b4a..9631488397 100644 --- a/code/game/objects/items/manuals.dm +++ b/code/game/objects/items/manuals.dm @@ -1,506 +1,506 @@ -/*********************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 easy, but prone to disaster. -

                -

                  -
                1. Find a dead person who is in need of cloning.
                2. -
                3. Take a blood sample with a syringe.
                4. -
                5. Inject a seed pack with the blood sample.
                6. -
                7. Plant the seeds.
                8. -
                9. Tend to the plants water and nutrition levels until it is time to harvest the cloned human.
                10. -
                -

                - 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: Powercell-powered electro-hydraulic system.
                • -
                • Powercell 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. -
                31. -
                32. Additional Information:
                33. -
                34. The firefighting variation is made in a similar fashion.
                35. -
                36. A firesuit must be connected to the Firefighter chassis for heat shielding.
                37. -
                38. Internal armor is plasteel for additional strength.
                39. -
                40. External armor must be installed in 2 parts, totaling 10 sheets.
                41. -
                42. Completed mech is more resiliant against fire, and is a bit more durable overall
                43. -
                44. Nanotrasen is determined to the safety of its investments employees.
                45. -
                - - - -

                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 faggot, 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() - ..() - -/obj/item/book/manual/wiki/proc/initialize_wikibook() - var/wikiurl = CONFIG_GET(string/wikiurltg) - if(wikiurl) - dat = {" - - - - - - -

                You start skimming through the manual...

                - - - - - - "} - -/obj/item/book/manual/wiki/cit - name = "Citadel infobook" - icon_state ="book8" - author = "Nanotrasen" - title = "Citadel infobook" - page_link = "" - window_size = "1500x800" //Too squashed otherwise - -/obj/item/book/manual/wiki/cit/initialize_wikibook() - var/wikiurl = CONFIG_GET(string/wikiurl) - if(wikiurl) - dat = {" - - - - - - -

                You start skimming through the manual...

                - - - - - - "} - -/obj/item/book/manual/wiki/cit/chemistry - name = "Chemistry Textbook" - icon_state ="chemistrybook" - author = "Nanotrasen" - title = "Chemistry Textbook" - page_link = "main/guides/guide_chemistry" - -/obj/item/book/manual/wiki/cit/chem_recipies - name = "Chemistry Recipies" - icon_state ="chemrecibook" - author = "Chemcat" - title = "Chemistry Recipies" - page_link = "main/guides/chem_recipies" - -/obj/item/book/manual/wiki/chemistry - name = "Outdated Chemistry Textbook" - icon_state ="chemistrybook_old" - author = "Nanotrasen" - title = "Outdated Chemistry Textbook" - page_link = "Guide_to_chemistry" - -/obj/item/book/manual/wiki/chemistry/Initialize() - ..() - new /obj/item/book/manual/wiki/cit/chemistry(loc) - new /obj/item/book/manual/wiki/cit/chem_recipies(loc) - -/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/medical_cloning - name = "Cloning techniques of the 26th century" - icon_state ="bookCloning" - author = "Medical Journal, volume 3" - title = "Cloning techniques of the 26th century" - page_link = "Guide_to_genetics#Cloning" - -/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/circuitry - name = "Circuitry for Dummies" - icon_state = "book1" - author = "Dr. Hans Asperger" - title = "Circuitry for Dummies" - page_link = "Guide_to_circuits" - -/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, 1, -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) - H.bleed_rate = 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) +/*********************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 easy, but prone to disaster. +

                +

                  +
                1. Find a dead person who is in need of cloning.
                2. +
                3. Take a blood sample with a syringe.
                4. +
                5. Inject a seed pack with the blood sample.
                6. +
                7. Plant the seeds.
                8. +
                9. Tend to the plants water and nutrition levels until it is time to harvest the cloned human.
                10. +
                +

                + 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: Powercell-powered electro-hydraulic system.
                • +
                • Powercell 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. +
                31. +
                32. Additional Information:
                33. +
                34. The firefighting variation is made in a similar fashion.
                35. +
                36. A firesuit must be connected to the Firefighter chassis for heat shielding.
                37. +
                38. Internal armor is plasteel for additional strength.
                39. +
                40. External armor must be installed in 2 parts, totaling 10 sheets.
                41. +
                42. Completed mech is more resiliant against fire, and is a bit more durable overall
                43. +
                44. Nanotrasen is determined to the safety of its investments employees.
                45. +
                + + + +

                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 faggot, 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() + ..() + +/obj/item/book/manual/wiki/proc/initialize_wikibook() + var/wikiurl = CONFIG_GET(string/wikiurltg) + if(wikiurl) + dat = {" + + + + + + +

                You start skimming through the manual...

                + + + + + + "} + +/obj/item/book/manual/wiki/cit + name = "Citadel infobook" + icon_state ="book8" + author = "Nanotrasen" + title = "Citadel infobook" + page_link = "" + window_size = "1500x800" //Too squashed otherwise + +/obj/item/book/manual/wiki/cit/initialize_wikibook() + var/wikiurl = CONFIG_GET(string/wikiurl) + if(wikiurl) + dat = {" + + + + + + +

                You start skimming through the manual...

                + + + + + + "} + +/obj/item/book/manual/wiki/cit/chemistry + name = "Chemistry Textbook" + icon_state ="chemistrybook" + author = "Nanotrasen" + title = "Chemistry Textbook" + page_link = "main/guides/guide_chemistry" + +/obj/item/book/manual/wiki/cit/chem_recipies + name = "Chemistry Recipies" + icon_state ="chemrecibook" + author = "Chemcat" + title = "Chemistry Recipies" + page_link = "main/guides/chem_recipies" + +/obj/item/book/manual/wiki/chemistry + name = "Outdated Chemistry Textbook" + icon_state ="chemistrybook_old" + author = "Nanotrasen" + title = "Outdated Chemistry Textbook" + page_link = "Guide_to_chemistry" + +/obj/item/book/manual/wiki/chemistry/Initialize() + ..() + new /obj/item/book/manual/wiki/cit/chemistry(loc) + new /obj/item/book/manual/wiki/cit/chem_recipies(loc) + +/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/medical_cloning + name = "Cloning techniques of the 26th century" + icon_state ="bookCloning" + author = "Medical Journal, volume 3" + title = "Cloning techniques of the 26th century" + page_link = "Guide_to_genetics#Cloning" + +/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/circuitry + name = "Circuitry for Dummies" + icon_state = "book1" + author = "Dr. Hans Asperger" + title = "Circuitry for Dummies" + page_link = "Guide_to_circuits" + +/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, 1, -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) + H.bleed_rate = 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) diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index 4d60716d0c..15a686bae6 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -1,386 +1,386 @@ -/obj/item/melee/transforming/energy - 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 - total_mass = 0.4 //Survival flashlights typically weigh around 5 ounces. - - -/obj/item/melee/transforming/energy/Initialize() - . = ..() - total_mass_on = (total_mass_on ? total_mass_on : (w_class_on * 0.75)) - 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(item_color) - icon_state = "sword[item_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(), 1, -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", "torn", "cut") - attack_verb_on = list() - light_color = "#40ceff" - total_mass = null - -/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, "embedded_impact_pain_multiplier" = 10) - armour_penetration = 35 - block_chance = 50 - -/obj/item/melee/transforming/energy/sword/transform_weapon(mob/living/user, supress_message_text) - . = ..() - if(. && active && item_color) - icon_state = "sword[item_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 - item_color = "red" - var/hitcost = 50 - -/obj/item/melee/transforming/energy/sword/cyborg/attack(mob/M, var/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" - item_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 - -/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) - item_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(istype(W, /obj/item/multitool)) - if(!hacked) - hacked = TRUE - item_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" - item_state = "lightblade" - -/*///////////////////////////////////////////////////////////////////////// -///////////// The TRUE Energy Sword /////////////////////////// -*////////////////////////////////////////////////////////////////////////// - -/obj/item/melee/transforming/energy/sword/cx - name = "non-eutactic blade" - desc = "The Non-Eutactic Blade utilizes a hardlight blade that is dynamically 'forged' on demand to create a deadly sharp edge that is unbreakable." - icon_state = "cxsword_hilt" - item_state = "cxsword" - force = 3 - force_on = 21 - throwforce = 5 - throwforce_on = 20 - hitsound = "swing_hit" //it starts deactivated - hitsound_on = 'sound/weapons/nebhit.ogg' - attack_verb_off = list("tapped", "poked") - throw_speed = 3 - throw_range = 5 - sharpness = IS_SHARP - embedding = list("embedded_pain_multiplier" = 6, "embed_chance" = 20, "embedded_fall_chance" = 60) - armour_penetration = 10 - block_chance = 35 - light_color = "#37FFF7" - actions_types = list() - -/obj/item/melee/transforming/energy/sword/cx/pre_altattackby(atom/A, mob/living/user, params) //checks if it can do right click memes - altafterattack(A, user, TRUE, params) - return TRUE - -/obj/item/melee/transforming/energy/sword/cx/altafterattack(atom/target, mob/living/carbon/user, proximity_flag, click_parameters) //does right click memes - if(istype(user)) - user.visible_message("[user] points the tip of [src] at [target].", "You point the tip of [src] at [target].") - return TRUE - -/obj/item/melee/transforming/energy/sword/cx/transform_weapon(mob/living/user, supress_message_text) - active = !active //I'd use a ..() here but it'd inherit from the regular esword's proc instead, so SPAGHETTI CODE - if(active) //also I'd need to rip out the iconstate changing bits - force = force_on - throwforce = throwforce_on - hitsound = hitsound_on - throw_speed = 4 - if(attack_verb_on.len) - attack_verb = attack_verb_on - w_class = w_class_on - START_PROCESSING(SSobj, src) - set_light(brightness_on) - update_icon() - else - force = initial(force) - throwforce = initial(throwforce) - hitsound = initial(hitsound) - throw_speed = initial(throw_speed) - if(attack_verb_off.len) - attack_verb = attack_verb_off - w_class = initial(w_class) - STOP_PROCESSING(SSobj, src) - set_light(0) - update_icon() - transform_messages(user, supress_message_text) - add_fingerprint(user) - return TRUE - -/obj/item/melee/transforming/energy/sword/cx/transform_messages(mob/living/user, supress_message_text) - playsound(user, active ? 'sound/weapons/nebon.ogg' : 'sound/weapons/neboff.ogg', 65, 1) - if(!supress_message_text) - to_chat(user, "[src] [active ? "is now active":"can now be concealed"].") - -/obj/item/melee/transforming/energy/sword/cx/update_icon() - var/mutable_appearance/blade_overlay = mutable_appearance(icon, "cxsword_blade") - var/mutable_appearance/gem_overlay = mutable_appearance(icon, "cxsword_gem") - - if(light_color) - blade_overlay.color = light_color - gem_overlay.color = light_color - - cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other - - add_overlay(gem_overlay) - - if(active) - add_overlay(blade_overlay) - if(ismob(loc)) - var/mob/M = loc - M.update_inv_hands() - -/obj/item/melee/transforming/energy/sword/cx/AltClick(mob/living/user) - . = ..() - if(!in_range(src, user)) //Basic checks to prevent abuse - return - if(user.incapacitated() || !istype(user)) - to_chat(user, "You can't do that right now!") - return TRUE - - if(alert("Are you sure you want to recolor your blade?", "Confirm Repaint", "Yes", "No") == "Yes") - var/energy_color_input = input(usr,"","Choose Energy Color",light_color) as color|null - if(energy_color_input) - light_color = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) - update_icon() - update_light() - return TRUE - -/obj/item/melee/transforming/energy/sword/cx/examine(mob/user) - . = ..() - . += "Alt-click to recolor it." - -/obj/item/melee/transforming/energy/sword/cx/worn_overlays(isinhands, icon_file, style_flags = NONE) - . = ..() - if(active) - if(isinhands) - var/mutable_appearance/blade_inhand = mutable_appearance(icon_file, "cxsword_blade") - blade_inhand.color = light_color - . += blade_inhand - -//Broken version. Not a toy, but not as strong. -/obj/item/melee/transforming/energy/sword/cx/broken - name = "misaligned non-eutactic blade" - desc = "The Non-Eutactic Blade utilizes a hardlight blade that is dynamically 'forged' on demand to create a deadly sharp edge that is unbreakable. This one seems to have a damaged handle and misaligned components, causing the blade to be unstable at best" - force_on = 15 //As strong a survival knife/bone dagger - -/obj/item/melee/transforming/energy/sword/cx/attackby(obj/item/W, mob/living/user, params) - if(istype(W, /obj/item/melee/transforming/energy/sword/cx)) - if(HAS_TRAIT(W, TRAIT_NODROP) || HAS_TRAIT(src, TRAIT_NODROP)) - to_chat(user, "\the [HAS_TRAIT(src, TRAIT_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_NODROP) ? W : src]!") - return - else - to_chat(user, "You combine the two light swords, making a single supermassive blade! You're cool.") - new /obj/item/twohanded/dualsaber/hypereutactic(user.drop_location()) - qdel(W) - qdel(src) - else - return ..() - -//////// Tatortot NEB /////////////// (same stats as regular esword) -/obj/item/melee/transforming/energy/sword/cx/traitor - name = "\improper Dragon's Tooth Sword" - desc = "The Dragon's Tooth sword is a blackmarket modification of a Non-Eutactic Blade, \ - which utilizes a hardlight blade that is dynamically 'forged' on demand to create a deadly sharp edge that is unbreakable. \ - It appears to have a wooden grip and a shaved down guard." - icon_state = "cxsword_hilt_traitor" - force_on = 30 - armour_penetration = 35 - embedding = list("embedded_pain_multiplier" = 10, "embed_chance" = 75, "embedded_fall_chance" = 0, "embedded_impact_pain_multiplier" = 10) - block_chance = 50 - hitsound_on = 'sound/weapons/blade1.ogg' - light_color = "#37F0FF" - -/obj/item/melee/transforming/energy/sword/cx/traitor/transform_messages(mob/living/user, supress_message_text) - playsound(user, active ? 'sound/weapons/saberon.ogg' : 'sound/weapons/saberoff.ogg', 35, 1) - if(!supress_message_text) - to_chat(user, "[src] [active ? "is now active":"can now be concealed"].") +/obj/item/melee/transforming/energy + 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 + total_mass = 0.4 //Survival flashlights typically weigh around 5 ounces. + + +/obj/item/melee/transforming/energy/Initialize() + . = ..() + total_mass_on = (total_mass_on ? total_mass_on : (w_class_on * 0.75)) + 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(item_color) + icon_state = "sword[item_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(), 1, -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", "torn", "cut") + attack_verb_on = list() + light_color = "#40ceff" + total_mass = null + +/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, "embedded_impact_pain_multiplier" = 10) + armour_penetration = 35 + block_chance = 50 + +/obj/item/melee/transforming/energy/sword/transform_weapon(mob/living/user, supress_message_text) + . = ..() + if(. && active && item_color) + icon_state = "sword[item_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 + item_color = "red" + var/hitcost = 50 + +/obj/item/melee/transforming/energy/sword/cyborg/attack(mob/M, var/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" + item_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 + +/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) + item_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(istype(W, /obj/item/multitool)) + if(!hacked) + hacked = TRUE + item_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" + item_state = "lightblade" + +/*///////////////////////////////////////////////////////////////////////// +///////////// The TRUE Energy Sword /////////////////////////// +*////////////////////////////////////////////////////////////////////////// + +/obj/item/melee/transforming/energy/sword/cx + name = "non-eutactic blade" + desc = "The Non-Eutactic Blade utilizes a hardlight blade that is dynamically 'forged' on demand to create a deadly sharp edge that is unbreakable." + icon_state = "cxsword_hilt" + item_state = "cxsword" + force = 3 + force_on = 21 + throwforce = 5 + throwforce_on = 20 + hitsound = "swing_hit" //it starts deactivated + hitsound_on = 'sound/weapons/nebhit.ogg' + attack_verb_off = list("tapped", "poked") + throw_speed = 3 + throw_range = 5 + sharpness = IS_SHARP + embedding = list("embedded_pain_multiplier" = 6, "embed_chance" = 20, "embedded_fall_chance" = 60) + armour_penetration = 10 + block_chance = 35 + light_color = "#37FFF7" + actions_types = list() + +/obj/item/melee/transforming/energy/sword/cx/pre_altattackby(atom/A, mob/living/user, params) //checks if it can do right click memes + altafterattack(A, user, TRUE, params) + return TRUE + +/obj/item/melee/transforming/energy/sword/cx/altafterattack(atom/target, mob/living/carbon/user, proximity_flag, click_parameters) //does right click memes + if(istype(user)) + user.visible_message("[user] points the tip of [src] at [target].", "You point the tip of [src] at [target].") + return TRUE + +/obj/item/melee/transforming/energy/sword/cx/transform_weapon(mob/living/user, supress_message_text) + active = !active //I'd use a ..() here but it'd inherit from the regular esword's proc instead, so SPAGHETTI CODE + if(active) //also I'd need to rip out the iconstate changing bits + force = force_on + throwforce = throwforce_on + hitsound = hitsound_on + throw_speed = 4 + if(attack_verb_on.len) + attack_verb = attack_verb_on + w_class = w_class_on + START_PROCESSING(SSobj, src) + set_light(brightness_on) + update_icon() + else + force = initial(force) + throwforce = initial(throwforce) + hitsound = initial(hitsound) + throw_speed = initial(throw_speed) + if(attack_verb_off.len) + attack_verb = attack_verb_off + w_class = initial(w_class) + STOP_PROCESSING(SSobj, src) + set_light(0) + update_icon() + transform_messages(user, supress_message_text) + add_fingerprint(user) + return TRUE + +/obj/item/melee/transforming/energy/sword/cx/transform_messages(mob/living/user, supress_message_text) + playsound(user, active ? 'sound/weapons/nebon.ogg' : 'sound/weapons/neboff.ogg', 65, 1) + if(!supress_message_text) + to_chat(user, "[src] [active ? "is now active":"can now be concealed"].") + +/obj/item/melee/transforming/energy/sword/cx/update_icon() + var/mutable_appearance/blade_overlay = mutable_appearance(icon, "cxsword_blade") + var/mutable_appearance/gem_overlay = mutable_appearance(icon, "cxsword_gem") + + if(light_color) + blade_overlay.color = light_color + gem_overlay.color = light_color + + cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other + + add_overlay(gem_overlay) + + if(active) + add_overlay(blade_overlay) + if(ismob(loc)) + var/mob/M = loc + M.update_inv_hands() + +/obj/item/melee/transforming/energy/sword/cx/AltClick(mob/living/user) + . = ..() + if(!in_range(src, user)) //Basic checks to prevent abuse + return + if(user.incapacitated() || !istype(user)) + to_chat(user, "You can't do that right now!") + return TRUE + + if(alert("Are you sure you want to recolor your blade?", "Confirm Repaint", "Yes", "No") == "Yes") + var/energy_color_input = input(usr,"","Choose Energy Color",light_color) as color|null + if(energy_color_input) + light_color = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) + update_icon() + update_light() + return TRUE + +/obj/item/melee/transforming/energy/sword/cx/examine(mob/user) + . = ..() + . += "Alt-click to recolor it." + +/obj/item/melee/transforming/energy/sword/cx/worn_overlays(isinhands, icon_file, style_flags = NONE) + . = ..() + if(active) + if(isinhands) + var/mutable_appearance/blade_inhand = mutable_appearance(icon_file, "cxsword_blade") + blade_inhand.color = light_color + . += blade_inhand + +//Broken version. Not a toy, but not as strong. +/obj/item/melee/transforming/energy/sword/cx/broken + name = "misaligned non-eutactic blade" + desc = "The Non-Eutactic Blade utilizes a hardlight blade that is dynamically 'forged' on demand to create a deadly sharp edge that is unbreakable. This one seems to have a damaged handle and misaligned components, causing the blade to be unstable at best" + force_on = 15 //As strong a survival knife/bone dagger + +/obj/item/melee/transforming/energy/sword/cx/attackby(obj/item/W, mob/living/user, params) + if(istype(W, /obj/item/melee/transforming/energy/sword/cx)) + if(HAS_TRAIT(W, TRAIT_NODROP) || HAS_TRAIT(src, TRAIT_NODROP)) + to_chat(user, "\the [HAS_TRAIT(src, TRAIT_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_NODROP) ? W : src]!") + return + else + to_chat(user, "You combine the two light swords, making a single supermassive blade! You're cool.") + new /obj/item/twohanded/dualsaber/hypereutactic(user.drop_location()) + qdel(W) + qdel(src) + else + return ..() + +//////// Tatortot NEB /////////////// (same stats as regular esword) +/obj/item/melee/transforming/energy/sword/cx/traitor + name = "\improper Dragon's Tooth Sword" + desc = "The Dragon's Tooth sword is a blackmarket modification of a Non-Eutactic Blade, \ + which utilizes a hardlight blade that is dynamically 'forged' on demand to create a deadly sharp edge that is unbreakable. \ + It appears to have a wooden grip and a shaved down guard." + icon_state = "cxsword_hilt_traitor" + force_on = 30 + armour_penetration = 35 + embedding = list("embedded_pain_multiplier" = 10, "embed_chance" = 75, "embedded_fall_chance" = 0, "embedded_impact_pain_multiplier" = 10) + block_chance = 50 + hitsound_on = 'sound/weapons/blade1.ogg' + light_color = "#37F0FF" + +/obj/item/melee/transforming/energy/sword/cx/traitor/transform_messages(mob/living/user, supress_message_text) + playsound(user, active ? 'sound/weapons/saberon.ogg' : 'sound/weapons/saberoff.ogg', 35, 1) + if(!supress_message_text) + to_chat(user, "[src] [active ? "is now active":"can now be concealed"].") diff --git a/code/game/objects/items/paiwire.dm b/code/game/objects/items/paiwire.dm index ef98ed62a1..331a51ad67 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 efe6bbadaa..056d9e4677 100644 --- a/code/game/objects/items/robot/robot_items.dm +++ b/code/game/objects/items/robot/robot_items.dm @@ -1,912 +1,912 @@ -/********************************************************************** - 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(M.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) - playsound(M, 'sound/weapons/genhit.ogg', 50, 1) - 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.Knockdown(100) - M.apply_effect(EFFECT_STUTTER, 5) - - M.visible_message("[user] has prodded [M] with [src]!", \ - "[user] has prodded you with [src]!") - - playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -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, 1, -1) - else if(ishuman(M)) - if(M.lying) - 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.recoveringstam) - M.resting = FALSE - M.update_canmove() - else - user.visible_message("[user] pets [M]!", \ - "You pet [M]!") - playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - if(1) - if(M.health >= 0) - if(ishuman(M)) - if(M.lying) - 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.recoveringstam) - M.resting = FALSE - M.update_canmove() - else - user.visible_message("[user] bops [M] on the head!", \ - "You bop [M] on the head!") - playsound(loc, 'sound/weapons/tap.ogg', 50, 1, -1) - if(2) - if(scooldown < world.time) - if(M.health >= 0) - if(ishuman(M)||ismonkey(M)) - M.electrocute_act(5, "[user]", safety = 1) - user.visible_message("[user] electrocutes [M] with [user.p_their()] touch!", \ - "You electrocute [M] with your touch!") - M.update_canmove() - 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, 1, -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, 1, -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/Initialize() - . = ..() - -/obj/item/borg/charger/update_icon() - ..() - 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.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.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]!") - return TRUE - -/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/effects/harmalarm.ogg', 70, 3) - cooldown = world.time + 200 - log_game("[key_name(user)] used a Cyborg Harm Alarm in [AREACOORD(user)]") - 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.Knockdown(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 - log_game("[key_name(user)] used an emagged Cyborg Harm Alarm in [AREACOORD(user)]") - -#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/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, 1) - 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, 1) - 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, 1) - 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/item/projectile/bullet/reusable/gumball - click_cooldown_override = 2 - - -/obj/item/projectile/bullet/reusable/gumball - name = "gumball" - desc = "Oh noes! A fast-moving gumball!" - icon_state = "gumball" - ammo_type = /obj/item/reagent_containers/food/snacks/gumball/cyborg - nodamage = TRUE - -/obj/item/projectile/bullet/reusable/gumball/handle_drop() - if(!dropped) - var/turf/T = get_turf(src) - var/obj/item/reagent_containers/food/snacks/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/item/projectile/bullet/reusable/lollipop - click_cooldown_override = 2 - -/obj/item/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/lollipop/cyborg - var/color2 = rgb(0, 0, 0) - nodamage = TRUE - -/obj/item/projectile/bullet/reusable/lollipop/New() - var/obj/item/reagent_containers/food/snacks/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/item/projectile/bullet/reusable/lollipop/handle_drop() - if(!dropped) - var/turf/T = get_turf(src) - var/obj/item/reagent_containers/food/snacks/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/item/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() - 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/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/item/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/item/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/item/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/xray/truesight_lens - name = "truesight lens" - icon = 'icons/obj/clockwork_objects.dmi' - icon_state = "truesight_lens" - -/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/New() - ..() - hud = new /obj/item/clothing/glasses/hud/health(src) - return - - -/obj/item/borg/sight/hud/sec - name = "security hud" - icon_state = "securityhud" - -/obj/item/borg/sight/hud/sec/New() - ..() - hud = new /obj/item/clothing/glasses/hud/security(src) - return - - -/********************************************************************** - Grippers oh god oh fuck -***********************************************************************/ - -/obj/item/weapon/gripper - name = "circuit gripper" - desc = "A simple grasping tool for inserting circuitboards into machinary." - icon = 'icons/obj/device.dmi' - icon_state = "gripper" - - item_flags = NOBLUDGEON - - //Has a list of items that it can hold. - var/list/can_hold = list( - /obj/item/circuitboard - ) - - var/obj/item/wrapped = null // Item currently being held. - -/obj/item/weapon/gripper/attack_self() - if(wrapped) - wrapped.forceMove(get_turf(wrapped)) - wrapped = null - return ..() - -/obj/item/weapon/gripper/afterattack(var/atom/target, var/mob/living/user, proximity, params) - - if(!proximity) - return - - if(!wrapped) - for(var/obj/item/thing in src.contents) - wrapped = thing - break - - if(wrapped) //Already have an item. - //Temporary put wrapped into user so target's attackby() checks pass. - wrapped.loc = user - - //Pass the attack on to the target. This might delete/relocate wrapped. - var/resolved = target.attackby(wrapped,user) - if(!resolved && wrapped && target) - wrapped.afterattack(target,user,1) - //If wrapped was neither deleted nor put into target, put it back into the gripper. - if(wrapped && user && (wrapped.loc == user)) - wrapped.loc = src - else - wrapped = null - return - - else if(istype(target,/obj/item)) - - var/obj/item/I = target - - var/grab = 0 - for(var/typepath in can_hold) - if(istype(I,typepath)) - grab = 1 - break - - //We can grab the item, finally. - if(grab) - to_chat(user, "You collect \the [I].") - I.loc = src - wrapped = I - return - else - to_chat(user, "Your gripper cannot hold \the [target].") - -/obj/item/weapon/gripper/mining - name = "shelter capsule deployer" - desc = "A simple grasping tool for carrying and deploying shelter capsules." - icon_state = "gripper_mining" - can_hold = list( - /obj/item/survivalcapsule - ) - -/obj/item/weapon/gripper/mining/attack_self() - if(wrapped) - wrapped.forceMove(get_turf(wrapped)) - wrapped.attack_self() - wrapped = null - return - -/obj/item/gun/energy/plasmacutter/cyborg - name = "cyborg plasma cutter" - desc = "A basic variation of the plasma cutter, compressed into a cyborg chassis. Less effective than normal plasma cutters." - force = 15 - ammo_type = list(/obj/item/ammo_casing/energy/plasma/weak) - can_charge = FALSE - selfcharge = EGUN_SELFCHARGE_BORG - cell_type = /obj/item/stock_parts/cell/secborg - charge_delay = 5 - -/obj/item/cyborg_clamp - name = "cyborg loading clamp" - desc = "Equipment for supply cyborgs. Lifts objects and loads them into cargo. Will not carry living beings." - icon = 'icons/mecha/mecha_equipment.dmi' - icon_state = "mecha_clamp" - tool_behaviour = TOOL_RETRACTOR - item_flags = NOBLUDGEON - flags_1 = NONE - var/cargo_capacity = 8 - var/cargo = list() - -/obj/item/cyborg_clamp/attack(mob/M, mob/user, def_zone) - return - -/obj/item/cyborg_clamp/afterattack(atom/movable/target, mob/user, proximity) - . = ..() - if(!proximity) - return FALSE - if(isobj(target)) - var/obj/O = target - if(!O.anchored) - if(contents.len < cargo_capacity) - user.visible_message("[user] lifts [target] and starts to load it into its cargo compartment.") - O.anchored = TRUE - if(do_mob(user, O, 20)) - for(var/mob/chump in target.GetAllContents()) - to_chat(user, "Error: Living entity detected in [target]. Cannot load.") - O.anchored = initial(O.anchored) - return - for(var/obj/item/disk/nuclear/diskie in target.GetAllContents()) - to_chat(user, "Error: Nuclear class authorization device detected in [target]. Cannot load.") - O.anchored = initial(O.anchored) - return - if(contents.len < cargo_capacity) //check both before and after - cargo += O - O.forceMove(src) - O.anchored = FALSE - to_chat(user, "[target] successfully loaded.") - playsound(loc, 'sound/effects/bin_close.ogg', 50, 0) - else - to_chat(user, "Not enough room in cargo compartment! Maximum of [cargo_capacity] objects!") - O.anchored = initial(O.anchored) - return - else - O.anchored = initial(O.anchored) - else - to_chat(user, "Not enough room in cargo compartment! Maximum of [cargo_capacity] objects!") - else - to_chat(user, "[target] is firmly secured!") - -/obj/item/cyborg_clamp/attack_self(mob/user) - var/obj/chosen_cargo = input(user, "Drop what?") as null|anything in cargo - if(!chosen_cargo) - return - chosen_cargo.forceMove(get_turf(chosen_cargo)) - cargo -= chosen_cargo - user.visible_message("[user] unloads [chosen_cargo] from its cargo.") - playsound(loc, 'sound/effects/bin_close.ogg', 50, 0) - -/obj/item/cyborg_clamp/Destroy() - for(var/atom/movable/target in cargo) - target.forceMove(get_turf(src)) - playsound(loc, 'sound/effects/bin_close.ogg', 50, 0) - return ..() - -/obj/item/card/id/miningborg - name = "mining point card" - desc = "A robotic ID strip used for claiming and transferring mining points. Must be held in an active slot to transfer points." - access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) +/********************************************************************** + 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(M.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) + playsound(M, 'sound/weapons/genhit.ogg', 50, 1) + 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.Knockdown(100) + M.apply_effect(EFFECT_STUTTER, 5) + + M.visible_message("[user] has prodded [M] with [src]!", \ + "[user] has prodded you with [src]!") + + playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -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, 1, -1) + else if(ishuman(M)) + if(M.lying) + 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.recoveringstam) + M.resting = FALSE + M.update_canmove() + else + user.visible_message("[user] pets [M]!", \ + "You pet [M]!") + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + if(1) + if(M.health >= 0) + if(ishuman(M)) + if(M.lying) + 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.recoveringstam) + M.resting = FALSE + M.update_canmove() + else + user.visible_message("[user] bops [M] on the head!", \ + "You bop [M] on the head!") + playsound(loc, 'sound/weapons/tap.ogg', 50, 1, -1) + if(2) + if(scooldown < world.time) + if(M.health >= 0) + if(ishuman(M)||ismonkey(M)) + M.electrocute_act(5, "[user]", safety = 1) + user.visible_message("[user] electrocutes [M] with [user.p_their()] touch!", \ + "You electrocute [M] with your touch!") + M.update_canmove() + 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, 1, -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, 1, -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/Initialize() + . = ..() + +/obj/item/borg/charger/update_icon() + ..() + 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.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.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]!") + return TRUE + +/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/effects/harmalarm.ogg', 70, 3) + cooldown = world.time + 200 + log_game("[key_name(user)] used a Cyborg Harm Alarm in [AREACOORD(user)]") + 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.Knockdown(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 + log_game("[key_name(user)] used an emagged Cyborg Harm Alarm in [AREACOORD(user)]") + +#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/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, 1) + 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, 1) + 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, 1) + 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/item/projectile/bullet/reusable/gumball + click_cooldown_override = 2 + + +/obj/item/projectile/bullet/reusable/gumball + name = "gumball" + desc = "Oh noes! A fast-moving gumball!" + icon_state = "gumball" + ammo_type = /obj/item/reagent_containers/food/snacks/gumball/cyborg + nodamage = TRUE + +/obj/item/projectile/bullet/reusable/gumball/handle_drop() + if(!dropped) + var/turf/T = get_turf(src) + var/obj/item/reagent_containers/food/snacks/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/item/projectile/bullet/reusable/lollipop + click_cooldown_override = 2 + +/obj/item/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/lollipop/cyborg + var/color2 = rgb(0, 0, 0) + nodamage = TRUE + +/obj/item/projectile/bullet/reusable/lollipop/New() + var/obj/item/reagent_containers/food/snacks/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/item/projectile/bullet/reusable/lollipop/handle_drop() + if(!dropped) + var/turf/T = get_turf(src) + var/obj/item/reagent_containers/food/snacks/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/item/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() + 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/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/item/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/item/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/item/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/xray/truesight_lens + name = "truesight lens" + icon = 'icons/obj/clockwork_objects.dmi' + icon_state = "truesight_lens" + +/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/New() + ..() + hud = new /obj/item/clothing/glasses/hud/health(src) + return + + +/obj/item/borg/sight/hud/sec + name = "security hud" + icon_state = "securityhud" + +/obj/item/borg/sight/hud/sec/New() + ..() + hud = new /obj/item/clothing/glasses/hud/security(src) + return + + +/********************************************************************** + Grippers oh god oh fuck +***********************************************************************/ + +/obj/item/weapon/gripper + name = "circuit gripper" + desc = "A simple grasping tool for inserting circuitboards into machinary." + icon = 'icons/obj/device.dmi' + icon_state = "gripper" + + item_flags = NOBLUDGEON + + //Has a list of items that it can hold. + var/list/can_hold = list( + /obj/item/circuitboard + ) + + var/obj/item/wrapped = null // Item currently being held. + +/obj/item/weapon/gripper/attack_self() + if(wrapped) + wrapped.forceMove(get_turf(wrapped)) + wrapped = null + return ..() + +/obj/item/weapon/gripper/afterattack(var/atom/target, var/mob/living/user, proximity, params) + + if(!proximity) + return + + if(!wrapped) + for(var/obj/item/thing in src.contents) + wrapped = thing + break + + if(wrapped) //Already have an item. + //Temporary put wrapped into user so target's attackby() checks pass. + wrapped.loc = user + + //Pass the attack on to the target. This might delete/relocate wrapped. + var/resolved = target.attackby(wrapped,user) + if(!resolved && wrapped && target) + wrapped.afterattack(target,user,1) + //If wrapped was neither deleted nor put into target, put it back into the gripper. + if(wrapped && user && (wrapped.loc == user)) + wrapped.loc = src + else + wrapped = null + return + + else if(istype(target,/obj/item)) + + var/obj/item/I = target + + var/grab = 0 + for(var/typepath in can_hold) + if(istype(I,typepath)) + grab = 1 + break + + //We can grab the item, finally. + if(grab) + to_chat(user, "You collect \the [I].") + I.loc = src + wrapped = I + return + else + to_chat(user, "Your gripper cannot hold \the [target].") + +/obj/item/weapon/gripper/mining + name = "shelter capsule deployer" + desc = "A simple grasping tool for carrying and deploying shelter capsules." + icon_state = "gripper_mining" + can_hold = list( + /obj/item/survivalcapsule + ) + +/obj/item/weapon/gripper/mining/attack_self() + if(wrapped) + wrapped.forceMove(get_turf(wrapped)) + wrapped.attack_self() + wrapped = null + return + +/obj/item/gun/energy/plasmacutter/cyborg + name = "cyborg plasma cutter" + desc = "A basic variation of the plasma cutter, compressed into a cyborg chassis. Less effective than normal plasma cutters." + force = 15 + ammo_type = list(/obj/item/ammo_casing/energy/plasma/weak) + can_charge = FALSE + selfcharge = EGUN_SELFCHARGE_BORG + cell_type = /obj/item/stock_parts/cell/secborg + charge_delay = 5 + +/obj/item/cyborg_clamp + name = "cyborg loading clamp" + desc = "Equipment for supply cyborgs. Lifts objects and loads them into cargo. Will not carry living beings." + icon = 'icons/mecha/mecha_equipment.dmi' + icon_state = "mecha_clamp" + tool_behaviour = TOOL_RETRACTOR + item_flags = NOBLUDGEON + flags_1 = NONE + var/cargo_capacity = 8 + var/cargo = list() + +/obj/item/cyborg_clamp/attack(mob/M, mob/user, def_zone) + return + +/obj/item/cyborg_clamp/afterattack(atom/movable/target, mob/user, proximity) + . = ..() + if(!proximity) + return FALSE + if(isobj(target)) + var/obj/O = target + if(!O.anchored) + if(contents.len < cargo_capacity) + user.visible_message("[user] lifts [target] and starts to load it into its cargo compartment.") + O.anchored = TRUE + if(do_mob(user, O, 20)) + for(var/mob/chump in target.GetAllContents()) + to_chat(user, "Error: Living entity detected in [target]. Cannot load.") + O.anchored = initial(O.anchored) + return + for(var/obj/item/disk/nuclear/diskie in target.GetAllContents()) + to_chat(user, "Error: Nuclear class authorization device detected in [target]. Cannot load.") + O.anchored = initial(O.anchored) + return + if(contents.len < cargo_capacity) //check both before and after + cargo += O + O.forceMove(src) + O.anchored = FALSE + to_chat(user, "[target] successfully loaded.") + playsound(loc, 'sound/effects/bin_close.ogg', 50, 0) + else + to_chat(user, "Not enough room in cargo compartment! Maximum of [cargo_capacity] objects!") + O.anchored = initial(O.anchored) + return + else + O.anchored = initial(O.anchored) + else + to_chat(user, "Not enough room in cargo compartment! Maximum of [cargo_capacity] objects!") + else + to_chat(user, "[target] is firmly secured!") + +/obj/item/cyborg_clamp/attack_self(mob/user) + var/obj/chosen_cargo = input(user, "Drop what?") as null|anything in cargo + if(!chosen_cargo) + return + chosen_cargo.forceMove(get_turf(chosen_cargo)) + cargo -= chosen_cargo + user.visible_message("[user] unloads [chosen_cargo] from its cargo.") + playsound(loc, 'sound/effects/bin_close.ogg', 50, 0) + +/obj/item/cyborg_clamp/Destroy() + for(var/atom/movable/target in cargo) + target.forceMove(get_turf(src)) + playsound(loc, 'sound/effects/bin_close.ogg', 50, 0) + return ..() + +/obj/item/card/id/miningborg + name = "mining point card" + desc = "A robotic ID strip used for claiming and transferring mining points. Must be held in an active slot to transfer points." + access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) icon_state = "data_1" \ No newline at end of file diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm index ab8030c9c2..2bf9ead4ea 100644 --- a/code/game/objects/items/robot/robot_parts.dm +++ b/code/game/objects/items/robot/robot_parts.dm @@ -1,413 +1,413 @@ - - -//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/New() - ..() - update_icon() - -/obj/item/robot_suit/prebuilt/New() - 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) - ..() - -/obj/item/robot_suit/update_icon() - cut_overlays() - if(l_arm) - add_overlay("[l_arm.icon_state]+o") - if(r_arm) - add_overlay("[r_arm.icon_state]+o") - if(chest) - add_overlay("[chest.icon_state]+o") - if(l_leg) - add_overlay("[l_leg.icon_state]+o") - if(r_leg) - add_overlay("[r_leg.icon_state]+o") - if(head) - add_overlay("[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(src.l_leg) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - src.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() - src.r_leg = W - update_icon() - - else if(istype(W, /obj/item/bodypart/l_arm/robot)) - if(src.l_arm) - return - if(!user.transferItemToLoc(W, src)) - return - W.icon_state = initial(W.icon_state) - W.cut_overlays() - src.l_arm = W - update_icon() - - else if(istype(W, /obj/item/bodypart/r_arm/robot)) - if(src.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() - src.r_arm = W - update_icon() - - else if(istype(W, /obj/item/bodypart/chest/robot)) - var/obj/item/bodypart/chest/robot/CH = W - if(src.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() - src.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(src.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() - src.head = HD - update_icon() - else - to_chat(user, "You need to attach a flash to it first!") - - else if (istype(W, /obj/item/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.brainmob) - to_chat(user, "Sticking an empty [M.name] into the frame would sort of defeat the purpose!") - return - - var/mob/living/brain/BM = M.brainmob - if(!BM.key || !BM.mind) - to_chat(user, "The MMI indicates that their mind is completely unresponsive; there's no point!") - return - - if(!BM.client) //braindead - to_chat(user, "The MMI indicates that their mind is currently inactive; it might change!") - return - - if(BM.stat == DEAD || (M.brain && M.brain.organ_flags & ORGAN_FAILING)) - to_chat(user, "Sticking a dead brain into the frame would sort of defeat the purpose!") - return - - if(jobban_isbanned(BM, "Cyborg") || QDELETED(src) || QDELETED(BM) || 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(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(BM.mind) - if(!istype(M.laws, /datum/ai_laws/ratvar)) - remove_servant_of_ratvar(BM, TRUE) - BM.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.") - - 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() - SSblackbox.record_feedback("amount", "cyborg_birth", 1) - forceMove(O) - O.robot_suit = src - - if(!locomotion) - O.lockcharge = 1 - O.update_canmove() - 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_canmove() - - 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(!istype(item_in_hand, /obj/item/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) - 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/New() + ..() + update_icon() + +/obj/item/robot_suit/prebuilt/New() + 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) + ..() + +/obj/item/robot_suit/update_icon() + cut_overlays() + if(l_arm) + add_overlay("[l_arm.icon_state]+o") + if(r_arm) + add_overlay("[r_arm.icon_state]+o") + if(chest) + add_overlay("[chest.icon_state]+o") + if(l_leg) + add_overlay("[l_leg.icon_state]+o") + if(r_leg) + add_overlay("[r_leg.icon_state]+o") + if(head) + add_overlay("[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(src.l_leg) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + src.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() + src.r_leg = W + update_icon() + + else if(istype(W, /obj/item/bodypart/l_arm/robot)) + if(src.l_arm) + return + if(!user.transferItemToLoc(W, src)) + return + W.icon_state = initial(W.icon_state) + W.cut_overlays() + src.l_arm = W + update_icon() + + else if(istype(W, /obj/item/bodypart/r_arm/robot)) + if(src.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() + src.r_arm = W + update_icon() + + else if(istype(W, /obj/item/bodypart/chest/robot)) + var/obj/item/bodypart/chest/robot/CH = W + if(src.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() + src.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(src.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() + src.head = HD + update_icon() + else + to_chat(user, "You need to attach a flash to it first!") + + else if (istype(W, /obj/item/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.brainmob) + to_chat(user, "Sticking an empty [M.name] into the frame would sort of defeat the purpose!") + return + + var/mob/living/brain/BM = M.brainmob + if(!BM.key || !BM.mind) + to_chat(user, "The MMI indicates that their mind is completely unresponsive; there's no point!") + return + + if(!BM.client) //braindead + to_chat(user, "The MMI indicates that their mind is currently inactive; it might change!") + return + + if(BM.stat == DEAD || (M.brain && M.brain.organ_flags & ORGAN_FAILING)) + to_chat(user, "Sticking a dead brain into the frame would sort of defeat the purpose!") + return + + if(jobban_isbanned(BM, "Cyborg") || QDELETED(src) || QDELETED(BM) || 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(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(BM.mind) + if(!istype(M.laws, /datum/ai_laws/ratvar)) + remove_servant_of_ratvar(BM, TRUE) + BM.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.") + + 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() + SSblackbox.record_feedback("amount", "cyborg_birth", 1) + forceMove(O) + O.robot_suit = src + + if(!locomotion) + O.lockcharge = 1 + O.update_canmove() + 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_canmove() + + 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(!istype(item_in_hand, /obj/item/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) + 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 8c743cc3de..62f36c60c0 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -1,726 +1,726 @@ -// 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" - var/locked = FALSE - var/installed = 0 - var/require_module = 0 - var/list/module_type - // 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 = stripped_input(user, "Enter new robot name", "Cyborg Reclassification", heldname, MAX_NAME_LEN) - -/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, 1) - - R.revive() - -/obj/item/borg/upgrade/vtec - name = "cyborg VTEC module" - desc = "Used to kick in a cyborg's VTEC systems, increasing their speed." - icon_state = "cyborg_upgrade2" - require_module = 1 - -/obj/item/borg/upgrade/vtec/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - if(!R.cansprint) - to_chat(R, "A VTEC unit is already installed!") - to_chat(user, "There's no room for another VTEC unit!") - return FALSE - - //R.speed = -2 // Gotta go fast. - //Citadel change - makes vtecs give an ability rather than reducing the borg's speed instantly - R.AddAbility(new/obj/effect/proc_holder/silicon/cyborg/vtecControl) - R.cansprint = 0 - -/obj/item/borg/upgrade/vtec/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - R.speed = initial(R.speed) - R.cansprint = 1 - -/obj/item/borg/upgrade/disablercooler - name = "cyborg rapid energy blaster cooling module" - desc = "Used to cool a mounted energy-based firearm, increasing the potential current in it and thus its recharge rate." - icon_state = "cyborg_upgrade3" - require_module = 1 - -/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/successflag - for(var/obj/item/gun/energy/T in R.module.modules) - if(T.charge_delay <= 2) - successflag = successflag || 2 - continue - T.charge_delay = max(2, T.charge_delay - 4) - successflag = 1 - if(!successflag) - to_chat(user, "There's no energy-based firearm in this unit!") - return FALSE - if(successflag == 2) - to_chat(R, "A cooling unit is already installed!") - to_chat(user, "There's no room for another cooling unit!") - return FALSE - -/obj/item/borg/upgrade/disablercooler/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/gun/energy/T in R.module.modules) - T.charge_delay = initial(T.charge_delay) - return . - return FALSE - -/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/premiumka - name = "mining cyborg premium KA" - desc = "A premium kinetic accelerator replacement for the mining module's standard kinetic accelerator." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/miner) - -/obj/item/borg/upgrade/premiumka/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/gun/energy/kinetic_accelerator/cyborg/KA in R.module) - for(var/obj/item/borg/upgrade/modkit/M in KA.modkits) - M.uninstall(src) - R.module.remove_module(KA, TRUE) - - var/obj/item/gun/energy/kinetic_accelerator/premiumka/cyborg/PKA = new /obj/item/gun/energy/kinetic_accelerator/premiumka/cyborg(R.module) - R.module.basic_modules += PKA - R.module.add_module(PKA, FALSE, TRUE) - -/obj/item/borg/upgrade/premiumka/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/gun/energy/kinetic_accelerator/premiumka/cyborg/PKA in R.module) - for(var/obj/item/borg/upgrade/modkit/M in PKA.modkits) - M.uninstall(src) - R.module.remove_module(PKA, TRUE) - - var/obj/item/gun/energy/kinetic_accelerator/cyborg/KA = new (R.module) - R.module.basic_modules += KA - R.module.add_module(KA, FALSE, TRUE) - - -/obj/item/borg/upgrade/advcutter - name = "mining cyborg advanced plasma cutter" - desc = "An upgrade for the mining cyborgs plasma cutter, bringing it to advanced operation." - icon_state = "cyborg_upgrade3" - require_module = 1 - module_type = list(/obj/item/robot_module/miner) - -/obj/item/borg/upgrade/advcutter/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - for(var/obj/item/gun/energy/plasmacutter/cyborg/C in R.module) - C.name = "advanced cyborg plasma cutter" - C.desc = "An improved version of the cyborg plasma cutter. Baring functionality identical to the standard hand held version." - C.icon_state = "adv_plasmacutter" - for(var/obj/item/ammo_casing/energy/plasma/weak/L in C.ammo_type) - L.projectile_type = /obj/item/projectile/plasma/adv - -/obj/item/borg/upgrade/advcutter/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - for(var/obj/item/gun/energy/plasmacutter/cyborg/C in R.module) - C.name = initial(name) - C.desc = initial(desc) - C.icon_state = initial(icon_state) - for(var/obj/item/ammo_casing/energy/plasma/weak/L in C.ammo_type) - L.projectile_type = initial(L.projectile_type) - -/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/butler) - -/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/butler) - -/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 tracks" - desc = "An upgrade kit to apply specialized coolant systems and insulation layers to mining cyborg tracks, 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/msg_cooldown = 0 - var/on = FALSE - var/powercost = 10 - var/mob/living/silicon/robot/cyborg - 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 - - cyborg = R - 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(cyborg) - QDEL_NULL(toggle_action) - cyborg = null - deactivate_sr() - -/obj/item/borg/upgrade/selfrepair/dropped() - . = ..() - addtimer(CALLBACK(src, .proc/check_dropped), 1) - -/obj/item/borg/upgrade/selfrepair/proc/check_dropped() - if(loc != cyborg) - toggle_action.Remove(cyborg) - QDEL_NULL(toggle_action) - cyborg = null - deactivate_sr() - -/obj/item/borg/upgrade/selfrepair/ui_action_click() - on = !on - if(on) - to_chat(cyborg, "You activate the self-repair module.") - START_PROCESSING(SSobj, src) - else - to_chat(cyborg, "You deactivate the self-repair module.") - STOP_PROCESSING(SSobj, src) - update_icon() - -/obj/item/borg/upgrade/selfrepair/update_icon() - if(cyborg) - icon_state = "selfrepair_[on ? "on" : "off"]" - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - else - icon_state = "cyborg_upgrade5" - -/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 - - if(cyborg && (cyborg.stat != DEAD) && on) - if(!cyborg.cell) - to_chat(cyborg, "Self-repair module deactivated. Please, insert the 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((world.time - 2000) > msg_cooldown ) - 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.") - msg_cooldown = world.time - 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, - /obj/item/robot_module/syndicate_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) - -/obj/item/borg/upgrade/hypospray/high_strength - name = "medical cyborg high-strength hypospray" - desc = "An upgrade to the Medical module's hypospray, containing \ - stronger versions of existing chemicals." - additional_reagents = list(/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/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/twohanded/shockpaddles/cyborg/S = locate() in R.module - R.module.remove_module(S, TRUE) - -/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/advhealth - name = "advanced cyborg health scanner" - desc = "An upgrade to the Medical modules, installing a built-in \ - advanced health scanner, for better readings on patients." - 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/advhealth/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - var/obj/item/healthanalyzer/advanced/AH = new(R.module) - R.module.basic_modules += AH - R.module.add_module(AH, FALSE, TRUE) - -/obj/item/borg/upgrade/processor/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/healthanalyzer/advanced/AH = locate() in R.module - R.module.remove_module(AH, 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(AI_SHELL) - -/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, 1, -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 (.) - R.resize = 0.5 - R.hasExpanded = FALSE - R.update_transform() - -/obj/item/borg/upgrade/rped - name = "engineering cyborg BSRPED" - desc = "A rapid part exchange device for the engineering cyborg." - icon = 'icons/obj/storage.dmi' - icon_state = "borg_BS_RPED" - 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/bluespace/cyborg/BSRPED = locate() in R - var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R - if(!RPED) - RPED = locate() in R.module - if(!BSRPED) - BSRPED = locate() in R.module //There's gotta be a smarter way to do this. - if(BSRPED) - to_chat(user, "This unit is already equipped with a BSRPED module.") - return FALSE - - BSRPED = new(R.module) - SEND_SIGNAL(RPED, COMSIG_TRY_STORAGE_QUICK_EMPTY) - qdel(RPED) - R.module.basic_modules += BSRPED - R.module.add_module(BSRPED, 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." - 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) - -/obj/item/borg/upgrade/pinpointer/action(mob/living/silicon/robot/R, user = usr) - . = ..() - if(.) - - var/obj/item/pinpointer/crew/PP = locate() in R - 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) - -/obj/item/borg/upgrade/pinpointer/deactivate(mob/living/silicon/robot/R, user = usr) - . = ..() - if (.) - var/obj/item/pinpointer/crew/PP = locate() in R.module - if (PP) - R.module.remove_module(PP, TRUE) - -/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 - -// Citadel's Vtech Controller -/obj/effect/proc_holder/silicon/cyborg/vtecControl - name = "vTec Control" - desc = "Allows finer-grained control of the vTec speed boost." - action_icon = 'icons/mob/actions.dmi' - action_icon_state = "Chevron_State_0" - - var/currentState = 0 - var/maxReduction = 1 - - -/obj/effect/proc_holder/silicon/cyborg/vtecControl/Click(mob/living/silicon/robot/user) - var/mob/living/silicon/robot/self = usr - - currentState = (currentState + 1) % 3 - - if(istype(self)) - switch(currentState) - if (0) - self.speed = initial(self.speed) - if (1) - self.speed = initial(self.speed) - maxReduction * 0.5 - if (2) - self.speed = initial(self.speed) - maxReduction * 1 - - action.button_icon_state = "Chevron_State_[currentState]" - action.UpdateButtonIcon() - - return +// 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" + var/locked = FALSE + var/installed = 0 + var/require_module = 0 + var/list/module_type + // 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 = stripped_input(user, "Enter new robot name", "Cyborg Reclassification", heldname, MAX_NAME_LEN) + +/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, 1) + + R.revive() + +/obj/item/borg/upgrade/vtec + name = "cyborg VTEC module" + desc = "Used to kick in a cyborg's VTEC systems, increasing their speed." + icon_state = "cyborg_upgrade2" + require_module = 1 + +/obj/item/borg/upgrade/vtec/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + if(!R.cansprint) + to_chat(R, "A VTEC unit is already installed!") + to_chat(user, "There's no room for another VTEC unit!") + return FALSE + + //R.speed = -2 // Gotta go fast. + //Citadel change - makes vtecs give an ability rather than reducing the borg's speed instantly + R.AddAbility(new/obj/effect/proc_holder/silicon/cyborg/vtecControl) + R.cansprint = 0 + +/obj/item/borg/upgrade/vtec/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + R.speed = initial(R.speed) + R.cansprint = 1 + +/obj/item/borg/upgrade/disablercooler + name = "cyborg rapid energy blaster cooling module" + desc = "Used to cool a mounted energy-based firearm, increasing the potential current in it and thus its recharge rate." + icon_state = "cyborg_upgrade3" + require_module = 1 + +/obj/item/borg/upgrade/disablercooler/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/successflag + for(var/obj/item/gun/energy/T in R.module.modules) + if(T.charge_delay <= 2) + successflag = successflag || 2 + continue + T.charge_delay = max(2, T.charge_delay - 4) + successflag = 1 + if(!successflag) + to_chat(user, "There's no energy-based firearm in this unit!") + return FALSE + if(successflag == 2) + to_chat(R, "A cooling unit is already installed!") + to_chat(user, "There's no room for another cooling unit!") + return FALSE + +/obj/item/borg/upgrade/disablercooler/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/gun/energy/T in R.module.modules) + T.charge_delay = initial(T.charge_delay) + return . + return FALSE + +/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/premiumka + name = "mining cyborg premium KA" + desc = "A premium kinetic accelerator replacement for the mining module's standard kinetic accelerator." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/miner) + +/obj/item/borg/upgrade/premiumka/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/gun/energy/kinetic_accelerator/cyborg/KA in R.module) + for(var/obj/item/borg/upgrade/modkit/M in KA.modkits) + M.uninstall(src) + R.module.remove_module(KA, TRUE) + + var/obj/item/gun/energy/kinetic_accelerator/premiumka/cyborg/PKA = new /obj/item/gun/energy/kinetic_accelerator/premiumka/cyborg(R.module) + R.module.basic_modules += PKA + R.module.add_module(PKA, FALSE, TRUE) + +/obj/item/borg/upgrade/premiumka/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/gun/energy/kinetic_accelerator/premiumka/cyborg/PKA in R.module) + for(var/obj/item/borg/upgrade/modkit/M in PKA.modkits) + M.uninstall(src) + R.module.remove_module(PKA, TRUE) + + var/obj/item/gun/energy/kinetic_accelerator/cyborg/KA = new (R.module) + R.module.basic_modules += KA + R.module.add_module(KA, FALSE, TRUE) + + +/obj/item/borg/upgrade/advcutter + name = "mining cyborg advanced plasma cutter" + desc = "An upgrade for the mining cyborgs plasma cutter, bringing it to advanced operation." + icon_state = "cyborg_upgrade3" + require_module = 1 + module_type = list(/obj/item/robot_module/miner) + +/obj/item/borg/upgrade/advcutter/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + for(var/obj/item/gun/energy/plasmacutter/cyborg/C in R.module) + C.name = "advanced cyborg plasma cutter" + C.desc = "An improved version of the cyborg plasma cutter. Baring functionality identical to the standard hand held version." + C.icon_state = "adv_plasmacutter" + for(var/obj/item/ammo_casing/energy/plasma/weak/L in C.ammo_type) + L.projectile_type = /obj/item/projectile/plasma/adv + +/obj/item/borg/upgrade/advcutter/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + for(var/obj/item/gun/energy/plasmacutter/cyborg/C in R.module) + C.name = initial(name) + C.desc = initial(desc) + C.icon_state = initial(icon_state) + for(var/obj/item/ammo_casing/energy/plasma/weak/L in C.ammo_type) + L.projectile_type = initial(L.projectile_type) + +/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/butler) + +/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/butler) + +/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 tracks" + desc = "An upgrade kit to apply specialized coolant systems and insulation layers to mining cyborg tracks, 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/msg_cooldown = 0 + var/on = FALSE + var/powercost = 10 + var/mob/living/silicon/robot/cyborg + 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 + + cyborg = R + 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(cyborg) + QDEL_NULL(toggle_action) + cyborg = null + deactivate_sr() + +/obj/item/borg/upgrade/selfrepair/dropped() + . = ..() + addtimer(CALLBACK(src, .proc/check_dropped), 1) + +/obj/item/borg/upgrade/selfrepair/proc/check_dropped() + if(loc != cyborg) + toggle_action.Remove(cyborg) + QDEL_NULL(toggle_action) + cyborg = null + deactivate_sr() + +/obj/item/borg/upgrade/selfrepair/ui_action_click() + on = !on + if(on) + to_chat(cyborg, "You activate the self-repair module.") + START_PROCESSING(SSobj, src) + else + to_chat(cyborg, "You deactivate the self-repair module.") + STOP_PROCESSING(SSobj, src) + update_icon() + +/obj/item/borg/upgrade/selfrepair/update_icon() + if(cyborg) + icon_state = "selfrepair_[on ? "on" : "off"]" + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + else + icon_state = "cyborg_upgrade5" + +/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 + + if(cyborg && (cyborg.stat != DEAD) && on) + if(!cyborg.cell) + to_chat(cyborg, "Self-repair module deactivated. Please, insert the 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((world.time - 2000) > msg_cooldown ) + 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.") + msg_cooldown = world.time + 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, + /obj/item/robot_module/syndicate_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) + +/obj/item/borg/upgrade/hypospray/high_strength + name = "medical cyborg high-strength hypospray" + desc = "An upgrade to the Medical module's hypospray, containing \ + stronger versions of existing chemicals." + additional_reagents = list(/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/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/twohanded/shockpaddles/cyborg/S = locate() in R.module + R.module.remove_module(S, TRUE) + +/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/advhealth + name = "advanced cyborg health scanner" + desc = "An upgrade to the Medical modules, installing a built-in \ + advanced health scanner, for better readings on patients." + 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/advhealth/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + var/obj/item/healthanalyzer/advanced/AH = new(R.module) + R.module.basic_modules += AH + R.module.add_module(AH, FALSE, TRUE) + +/obj/item/borg/upgrade/processor/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/healthanalyzer/advanced/AH = locate() in R.module + R.module.remove_module(AH, 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(AI_SHELL) + +/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, 1, -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 (.) + R.resize = 0.5 + R.hasExpanded = FALSE + R.update_transform() + +/obj/item/borg/upgrade/rped + name = "engineering cyborg BSRPED" + desc = "A rapid part exchange device for the engineering cyborg." + icon = 'icons/obj/storage.dmi' + icon_state = "borg_BS_RPED" + 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/bluespace/cyborg/BSRPED = locate() in R + var/obj/item/storage/part_replacer/cyborg/RPED = locate() in R + if(!RPED) + RPED = locate() in R.module + if(!BSRPED) + BSRPED = locate() in R.module //There's gotta be a smarter way to do this. + if(BSRPED) + to_chat(user, "This unit is already equipped with a BSRPED module.") + return FALSE + + BSRPED = new(R.module) + SEND_SIGNAL(RPED, COMSIG_TRY_STORAGE_QUICK_EMPTY) + qdel(RPED) + R.module.basic_modules += BSRPED + R.module.add_module(BSRPED, 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." + 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) + +/obj/item/borg/upgrade/pinpointer/action(mob/living/silicon/robot/R, user = usr) + . = ..() + if(.) + + var/obj/item/pinpointer/crew/PP = locate() in R + 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) + +/obj/item/borg/upgrade/pinpointer/deactivate(mob/living/silicon/robot/R, user = usr) + . = ..() + if (.) + var/obj/item/pinpointer/crew/PP = locate() in R.module + if (PP) + R.module.remove_module(PP, TRUE) + +/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 + +// Citadel's Vtech Controller +/obj/effect/proc_holder/silicon/cyborg/vtecControl + name = "vTec Control" + desc = "Allows finer-grained control of the vTec speed boost." + action_icon = 'icons/mob/actions.dmi' + action_icon_state = "Chevron_State_0" + + var/currentState = 0 + var/maxReduction = 1 + + +/obj/effect/proc_holder/silicon/cyborg/vtecControl/Click(mob/living/silicon/robot/user) + var/mob/living/silicon/robot/self = usr + + currentState = (currentState + 1) % 3 + + if(istype(self)) + switch(currentState) + if (0) + self.speed = initial(self.speed) + if (1) + self.speed = initial(self.speed) - maxReduction * 0.5 + if (2) + self.speed = initial(self.speed) - maxReduction * 1 + + action.button_icon_state = "Chevron_State_[currentState]" + action.UpdateButtonIcon() + + return diff --git a/code/game/objects/items/scrolls.dm b/code/game/objects/items/scrolls.dm index 28a4664a24..d58f670dc4 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 - item_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 + item_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 15127c2e0e..c23622ad65 100644 --- a/code/game/objects/items/shields.dm +++ b/code/game/objects/items/shields.dm @@ -1,183 +1,183 @@ -/obj/item/shield - name = "shield" - block_chance = 50 - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) - -/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 = 'icons/obj/items_and_weapons.dmi' - 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 - materials = list(MAT_GLASS=7500, MAT_METAL=1000) - attack_verb = list("shoved", "bashed") - var/cooldown = 0 //shield bash cooldown. based on world.time - -/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, 1) - cooldown = world.time - else - return ..() - -/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(attack_type == THROWN_PROJECTILE_ATTACK) - final_block_chance += 30 - if(attack_type == LEAP_ATTACK) - final_block_chance = 100 - return ..() - -/obj/item/shield/riot/roman - name = "\improper Roman shield" - desc = "Bears an inscription on the inside: \"Romanes venio domus\"." - icon_state = "roman_shield" - item_state = "roman_shield" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - -/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) - -/obj/item/shield/riot/buckler - name = "wooden buckler" - desc = "A medieval wooden buckler." - icon_state = "buckler" - item_state = "buckler" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - materials = list() - resistance_flags = FLAMMABLE - block_chance = 30 - -/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." - icon = 'icons/obj/items_and_weapons.dmi' - 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, 1) - 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, 1) - 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 = 'icons/obj/items_and_weapons.dmi' - icon_state = "teleriot0" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - 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, 1) - - 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/makeshift - name = "metal shield" - desc = "A large shield made of wired and welded sheets of metal. The handle is made of cloth and leather making it unwieldy." - armor = list("melee" = 25, "bullet" = 25, "laser" = 5, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 80) - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - icon = 'icons/obj/items_and_weapons.dmi' - item_state = "metal" - icon_state = "makeshift_shield" - materials = list(MAT_METAL = 18000) - slot_flags = null - block_chance = 35 - force = 10 - throwforce = 7 - -/obj/item/shield/riot/tower - name = "tower shield" - desc = "A massive shield that can block a lot of attacks, can take a lot of abuse before braking." - armor = list("melee" = 95, "bullet" = 95, "laser" = 75, "energy" = 60, "bomb" = 90, "bio" = 90, "rad" = 0, "fire" = 90, "acid" = 10) //Armor for the item, dosnt transfer to user - item_state = "metal" - icon_state = "metal" - icon = 'icons/obj/items_and_weapons.dmi' - block_chance = 75 //1/4 shots will hit* - force = 16 - slowdown = 2 - throwforce = 15 //Massive pice of metal - w_class = WEIGHT_CLASS_HUGE - item_flags = SLOWS_WHILE_IN_HAND +/obj/item/shield + name = "shield" + block_chance = 50 + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) + +/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 = 'icons/obj/items_and_weapons.dmi' + 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 + materials = list(MAT_GLASS=7500, MAT_METAL=1000) + attack_verb = list("shoved", "bashed") + var/cooldown = 0 //shield bash cooldown. based on world.time + +/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, 1) + cooldown = world.time + else + return ..() + +/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(attack_type == THROWN_PROJECTILE_ATTACK) + final_block_chance += 30 + if(attack_type == LEAP_ATTACK) + final_block_chance = 100 + return ..() + +/obj/item/shield/riot/roman + name = "\improper Roman shield" + desc = "Bears an inscription on the inside: \"Romanes venio domus\"." + icon_state = "roman_shield" + item_state = "roman_shield" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + +/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) + +/obj/item/shield/riot/buckler + name = "wooden buckler" + desc = "A medieval wooden buckler." + icon_state = "buckler" + item_state = "buckler" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + materials = list() + resistance_flags = FLAMMABLE + block_chance = 30 + +/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." + icon = 'icons/obj/items_and_weapons.dmi' + 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, 1) + 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, 1) + 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 = 'icons/obj/items_and_weapons.dmi' + icon_state = "teleriot0" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + 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, 1) + + 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/makeshift + name = "metal shield" + desc = "A large shield made of wired and welded sheets of metal. The handle is made of cloth and leather making it unwieldy." + armor = list("melee" = 25, "bullet" = 25, "laser" = 5, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 80) + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + icon = 'icons/obj/items_and_weapons.dmi' + item_state = "metal" + icon_state = "makeshift_shield" + materials = list(MAT_METAL = 18000) + slot_flags = null + block_chance = 35 + force = 10 + throwforce = 7 + +/obj/item/shield/riot/tower + name = "tower shield" + desc = "A massive shield that can block a lot of attacks, can take a lot of abuse before braking." + armor = list("melee" = 95, "bullet" = 95, "laser" = 75, "energy" = 60, "bomb" = 90, "bio" = 90, "rad" = 0, "fire" = 90, "acid" = 10) //Armor for the item, dosnt transfer to user + item_state = "metal" + icon_state = "metal" + icon = 'icons/obj/items_and_weapons.dmi' + block_chance = 75 //1/4 shots will hit* + force = 16 + slowdown = 2 + throwforce = 15 //Massive pice of metal + w_class = WEIGHT_CLASS_HUGE + item_flags = SLOWS_WHILE_IN_HAND diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index 9087d0acc5..256e84973d 100644 --- a/code/game/objects/items/shooting_range.dm +++ b/code/game/objects/items/shooting_range.dm @@ -1,96 +1,96 @@ -/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/item/projectile/P) - ..() - playsound(src.loc, 'sound/items/bikehorn.ogg', 50, 1) - -/obj/item/target/bullet_act(obj/item/projectile/P) - if(istype(P, /obj/item/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/item/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/item/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 - return -1 - -#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/item/projectile/P) + ..() + playsound(src.loc, 'sound/items/bikehorn.ogg', 50, 1) + +/obj/item/target/bullet_act(obj/item/projectile/P) + if(istype(P, /obj/item/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/item/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/item/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 + return -1 + +#undef DECALTYPE_SCORCH +#undef DECALTYPE_BULLET diff --git a/code/game/objects/items/singularityhammer.dm b/code/game/objects/items/singularityhammer.dm index b6559c9091..f55dae67db 100644 --- a/code/game/objects/items/singularityhammer.dm +++ b/code/game/objects/items/singularityhammer.dm @@ -1,115 +1,115 @@ -/obj/item/twohanded/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 = "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 - force_unwielded = 5 - force_wielded = 20 - throwforce = 15 - throw_range = 1 - w_class = WEIGHT_CLASS_HUGE - var/charged = 5 - 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" - total_mass = TOTAL_MASS_MEDIEVAL_WEAPON - -/obj/item/twohanded/singularityhammer/New() - ..() - START_PROCESSING(SSobj, src) - -/obj/item/twohanded/singularityhammer/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/twohanded/singularityhammer/process() - if(charged < 5) - charged++ - return - -/obj/item/twohanded/singularityhammer/update_icon() //Currently only here to fuck with the on-mob icons. - icon_state = "mjollnir[wielded]" - return - -/obj/item/twohanded/singularityhammer/proc/vortex(turf/pull, mob/wielder) - for(var/atom/X in orange(5,pull)) - if(ismovableatom(X)) - var/atom/movable/A = X - if(A == wielder) - continue - if(A && !A.anchored && !ishuman(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_KNOCKDOWN, 0) - step_towards(H,pull) - step_towards(H,pull) - step_towards(H,pull) - return - -/obj/item/twohanded/singularityhammer/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) - . = ..() - if(!proximity) - return - if(wielded) - if(charged == 5) - charged = 0 - if(istype(A, /mob/living/)) - var/mob/living/Z = A - Z.take_bodypart_damage(20,0) - playsound(user, 'sound/weapons/marauder.ogg', 50, 1) - var/turf/target = get_turf(A) - vortex(target,user) - -/obj/item/twohanded/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 - force_unwielded = 5 - force_wielded = 25 - throwforce = 30 - throw_range = 7 - w_class = WEIGHT_CLASS_HUGE - total_mass = TOTAL_MASS_MEDIEVAL_WEAPON - -/obj/item/twohanded/mjollnir/proc/shock(mob/living/target) - target.Stun(60) - 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] was 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/twohanded/mjollnir/attack(mob/living/M, mob/user) - ..() - if(wielded) - playsound(src.loc, "sparks", 50, 1) - shock(M) - -/obj/item/twohanded/mjollnir/throw_impact(atom/target) - . = ..() - if(isliving(target)) - shock(target) - -/obj/item/twohanded/mjollnir/update_icon() //Currently only here to fuck with the on-mob icons. - icon_state = "mjollnir[wielded]" - return +/obj/item/twohanded/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 = "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 + force_unwielded = 5 + force_wielded = 20 + throwforce = 15 + throw_range = 1 + w_class = WEIGHT_CLASS_HUGE + var/charged = 5 + 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" + total_mass = TOTAL_MASS_MEDIEVAL_WEAPON + +/obj/item/twohanded/singularityhammer/New() + ..() + START_PROCESSING(SSobj, src) + +/obj/item/twohanded/singularityhammer/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/twohanded/singularityhammer/process() + if(charged < 5) + charged++ + return + +/obj/item/twohanded/singularityhammer/update_icon() //Currently only here to fuck with the on-mob icons. + icon_state = "mjollnir[wielded]" + return + +/obj/item/twohanded/singularityhammer/proc/vortex(turf/pull, mob/wielder) + for(var/atom/X in orange(5,pull)) + if(ismovableatom(X)) + var/atom/movable/A = X + if(A == wielder) + continue + if(A && !A.anchored && !ishuman(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_KNOCKDOWN, 0) + step_towards(H,pull) + step_towards(H,pull) + step_towards(H,pull) + return + +/obj/item/twohanded/singularityhammer/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) + . = ..() + if(!proximity) + return + if(wielded) + if(charged == 5) + charged = 0 + if(istype(A, /mob/living/)) + var/mob/living/Z = A + Z.take_bodypart_damage(20,0) + playsound(user, 'sound/weapons/marauder.ogg', 50, 1) + var/turf/target = get_turf(A) + vortex(target,user) + +/obj/item/twohanded/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 + force_unwielded = 5 + force_wielded = 25 + throwforce = 30 + throw_range = 7 + w_class = WEIGHT_CLASS_HUGE + total_mass = TOTAL_MASS_MEDIEVAL_WEAPON + +/obj/item/twohanded/mjollnir/proc/shock(mob/living/target) + target.Stun(60) + 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] was 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/twohanded/mjollnir/attack(mob/living/M, mob/user) + ..() + if(wielded) + playsound(src.loc, "sparks", 50, 1) + shock(M) + +/obj/item/twohanded/mjollnir/throw_impact(atom/target) + . = ..() + if(isliving(target)) + shock(target) + +/obj/item/twohanded/mjollnir/update_icon() //Currently only here to fuck with the on-mob icons. + icon_state = "mjollnir[wielded]" + return diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm index a660e76da1..bb1a475f1f 100644 --- a/code/game/objects/items/stacks/bscrystal.dm +++ b/code/game/objects/items/stacks/bscrystal.dm @@ -1,89 +1,89 @@ -//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" - w_class = WEIGHT_CLASS_TINY - materials = list(MAT_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) - -/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, 1) - 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) - 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, 1) - 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." - materials = list(MAT_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" - item_state = "sheet-polycrystal" - singular_name = "bluespace polycrystal" - desc = "A stable polycrystal, made of fused-together bluespace crystals. You could probably break one off." - materials = list(MAT_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" + w_class = WEIGHT_CLASS_TINY + materials = list(MAT_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) + +/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, 1) + 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) + 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, 1) + 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." + materials = list(MAT_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" + item_state = "sheet-polycrystal" + singular_name = "bluespace polycrystal" + desc = "A stable polycrystal, made of fused-together bluespace crystals. You could probably break one off." + materials = list(MAT_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 14c20af6b3..baf099fdb5 100644 --- a/code/game/objects/items/stacks/medical.dm +++ b/code/game/objects/items/stacks/medical.dm @@ -1,155 +1,155 @@ -/obj/item/stack/medical - name = "medical pack" - singular_name = "medical pack" - icon = 'icons/obj/stack_objects.dmi' - amount = 12 - max_amount = 12 - 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 - -/obj/item/stack/medical/attack(mob/living/M, mob/user) - . = ..() - if(!M.can_inject(user, TRUE)) - return - if(M == user) - 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 - if(heal(M, user)) - log_combat(user, M, "healed", src.name) - use(1) - - -/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 - 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].") - return - to_chat(user, "\The [src] won't work on a robotic limb!") - -/obj/item/stack/medical/get_belt_overlay() - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "pouch") - -/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' - var/heal_brute = 20 - self_delay = 20 - grind_results = list(/datum/reagent/medicine/styptic_powder = 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, 0) - to_chat(user, "You can't heal [M] with the \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 that is extremely effective at stopping bleeding, heals minor wounds." - gender = PLURAL - singular_name = "medical gauze" - icon_state = "gauze" - var/stop_bleeding = 1800 - var/heal_brute = 5 - self_delay = 10 - -/obj/item/stack/medical/gauze/heal(mob/living/M, mob/user) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(!H.bleedsuppress && H.bleed_rate) //so you can't stack bleed suppression - H.suppress_bloodloss(stop_bleeding) - to_chat(user, "You stop the bleeding of [M]!") - return TRUE - to_chat(user, "You can not use \the [src] on [M]!") - -/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 can stop bleeding, but does not heal wounds." - stop_bleeding = 900 - heal_brute = 0 - -/obj/item/stack/medical/gauze/cyborg - materials = list() - is_cyborg = 1 - cost = 250 - -/obj/item/stack/medical/ointment - name = "ointment" - desc = "Used to treat those nasty burn wounds." - 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' - var/heal_burn = 20 - self_delay = 20 - grind_results = list(/datum/reagent/medicine/silver_sulfadiazine = 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, 0, heal_burn) - to_chat(user, "You can't heal [M] with the \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 + name = "medical pack" + singular_name = "medical pack" + icon = 'icons/obj/stack_objects.dmi' + amount = 12 + max_amount = 12 + 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 + +/obj/item/stack/medical/attack(mob/living/M, mob/user) + . = ..() + if(!M.can_inject(user, TRUE)) + return + if(M == user) + 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 + if(heal(M, user)) + log_combat(user, M, "healed", src.name) + use(1) + + +/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 + 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].") + return + to_chat(user, "\The [src] won't work on a robotic limb!") + +/obj/item/stack/medical/get_belt_overlay() + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "pouch") + +/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' + var/heal_brute = 20 + self_delay = 20 + grind_results = list(/datum/reagent/medicine/styptic_powder = 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, 0) + to_chat(user, "You can't heal [M] with the \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 that is extremely effective at stopping bleeding, heals minor wounds." + gender = PLURAL + singular_name = "medical gauze" + icon_state = "gauze" + var/stop_bleeding = 1800 + var/heal_brute = 5 + self_delay = 10 + +/obj/item/stack/medical/gauze/heal(mob/living/M, mob/user) + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(!H.bleedsuppress && H.bleed_rate) //so you can't stack bleed suppression + H.suppress_bloodloss(stop_bleeding) + to_chat(user, "You stop the bleeding of [M]!") + return TRUE + to_chat(user, "You can not use \the [src] on [M]!") + +/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 can stop bleeding, but does not heal wounds." + stop_bleeding = 900 + heal_brute = 0 + +/obj/item/stack/medical/gauze/cyborg + materials = list() + is_cyborg = 1 + cost = 250 + +/obj/item/stack/medical/ointment + name = "ointment" + desc = "Used to treat those nasty burn wounds." + 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' + var/heal_burn = 20 + self_delay = 20 + grind_results = list(/datum/reagent/medicine/silver_sulfadiazine = 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, 0, heal_burn) + to_chat(user, "You can't heal [M] with the \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 diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 7d2e0c0b43..537873624c 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -1,87 +1,87 @@ -GLOBAL_LIST_INIT(rod_recipes, list ( \ - new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = 1, on_floor = 1), \ - 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), \ - )) - -/obj/item/stack/rods - name = "metal rod" - desc = "Some rods. Can be used for building or something." - singular_name = "metal rod" - icon_state = "rods" - item_state = "rods" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_NORMAL - force = 9 - throwforce = 10 - throw_speed = 3 - throw_range = 7 - materials = list(MAT_METAL=1000) - max_amount = 50 - attack_verb = list("hit", "bludgeoned", "whacked") - hitsound = 'sound/weapons/grenadelaunch.ogg' - 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) - . = ..() - - recipes = GLOB.rod_recipes - update_icon() - -/obj/item/stack/rods/update_icon() - var/amount = get_amount() - if((amount <= 5) && (amount > 0)) - icon_state = "rods-[amount]" - else - icon_state = "rods" - -/obj/item/stack/rods/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/weldingtool)) - 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 - materials = list() - is_cyborg = 1 - cost = 250 - -/obj/item/stack/rods/cyborg/update_icon() - return - -/obj/item/stack/rods/ten - amount = 10 - -/obj/item/stack/rods/twentyfive - amount = 25 - -/obj/item/stack/rods/fifty - amount = 50 +GLOBAL_LIST_INIT(rod_recipes, list ( \ + new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 10, one_per_turf = 1, on_floor = 1), \ + 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), \ + )) + +/obj/item/stack/rods + name = "metal rod" + desc = "Some rods. Can be used for building or something." + singular_name = "metal rod" + icon_state = "rods" + item_state = "rods" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_NORMAL + force = 9 + throwforce = 10 + throw_speed = 3 + throw_range = 7 + materials = list(MAT_METAL=1000) + max_amount = 50 + attack_verb = list("hit", "bludgeoned", "whacked") + hitsound = 'sound/weapons/grenadelaunch.ogg' + 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) + . = ..() + + recipes = GLOB.rod_recipes + update_icon() + +/obj/item/stack/rods/update_icon() + var/amount = get_amount() + if((amount <= 5) && (amount > 0)) + icon_state = "rods-[amount]" + else + icon_state = "rods" + +/obj/item/stack/rods/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/weldingtool)) + 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 + materials = list() + is_cyborg = 1 + cost = 250 + +/obj/item/stack/rods/cyborg/update_icon() + return + +/obj/item/stack/rods/ten + amount = 10 + +/obj/item/stack/rods/twentyfive + amount = 25 + +/obj/item/stack/rods/fifty + amount = 50 diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm index 53209fac2f..a33ae9c4a9 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -1,345 +1,345 @@ -/* 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" - item_state = "sheet-glass" - materials = list(MAT_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) - 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 - materials = list() - is_cyborg = 1 - cost = 500 - -/obj/item/stack/sheet/glass/fifty - amount = 50 - -/obj/item/stack/sheet/glass/five - amount = 5 - -/obj/item/stack/sheet/glass/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.glass_recipes - return ..() - -/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" - item_state = "sheet-pglass" - materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_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) - tableVariant = /obj/structure/table/plasmaglass - -/obj/item/stack/sheet/plasmaglass/fifty - amount = 50 - -/obj/item/stack/sheet/plasmaglass/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.pglass_recipes - return ..() - -/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" - item_state = "sheet-rglass" - materials = list(MAT_METAL=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_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 - materials = list() - 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 - source.use_charge(used * metcost) - glasource.use_charge(used * glacost) - -/obj/item/stack/sheet/rglass/cyborg/add(amount) - source.add_charge(amount * metcost) - glasource.add_charge(amount * glacost) - -/obj/item/stack/sheet/rglass/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.reinforced_glass_recipes - return ..() - -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" - item_state = "sheet-prglass" - materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS=MINERAL_MATERIAL_AMOUNT, MAT_METAL=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 - 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/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.prglass_recipes - return ..() - -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" - item_state = "sheet-titaniumglass" - materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_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/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.titaniumglass_recipes - return ..() - -GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( - new/datum/stack_recipe("plastitanium window", /obj/structure/window/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" - item_state = "sheet-plastitaniumglass" - materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_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/plastitaniumglass - -/obj/item/stack/sheet/plastitaniumglass/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.plastitaniumglass_recipes - return ..() - -/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 - item_state = "shard-glass" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - materials = list(MAT_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 - - -/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)) - I.attackby(src, user) - 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/glass/NG = new (user.loc) - for(var/obj/item/stack/sheet/glass/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 glass to the stack. It now contains [NG.amount] sheet\s.") - qdel(src) - return TRUE - -/obj/item/shard/Crossed(mob/living/L) - if(istype(L) && has_gravity(loc)) - if(HAS_TRAIT(L, TRAIT_LIGHT_STEP)) - playsound(loc, 'sound/effects/glass_step.ogg', 30, 1) - else - playsound(loc, 'sound/effects/glass_step.ogg', 50, 1) - return ..() - -/obj/item/shard/plasma - name = "purple shard" - desc = "A nasty looking shard of plasma glass." - force = 6 - throwforce = 11 - icon_state = "plasmalarge" - materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS=MINERAL_MATERIAL_AMOUNT) +/* 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" + item_state = "sheet-glass" + materials = list(MAT_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) + 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 + materials = list() + is_cyborg = 1 + cost = 500 + +/obj/item/stack/sheet/glass/fifty + amount = 50 + +/obj/item/stack/sheet/glass/five + amount = 5 + +/obj/item/stack/sheet/glass/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.glass_recipes + return ..() + +/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" + item_state = "sheet-pglass" + materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_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) + tableVariant = /obj/structure/table/plasmaglass + +/obj/item/stack/sheet/plasmaglass/fifty + amount = 50 + +/obj/item/stack/sheet/plasmaglass/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.pglass_recipes + return ..() + +/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" + item_state = "sheet-rglass" + materials = list(MAT_METAL=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_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 + materials = list() + 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 + source.use_charge(used * metcost) + glasource.use_charge(used * glacost) + +/obj/item/stack/sheet/rglass/cyborg/add(amount) + source.add_charge(amount * metcost) + glasource.add_charge(amount * glacost) + +/obj/item/stack/sheet/rglass/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.reinforced_glass_recipes + return ..() + +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" + item_state = "sheet-prglass" + materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS=MINERAL_MATERIAL_AMOUNT, MAT_METAL=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 + 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/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.prglass_recipes + return ..() + +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" + item_state = "sheet-titaniumglass" + materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_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/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.titaniumglass_recipes + return ..() + +GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( + new/datum/stack_recipe("plastitanium window", /obj/structure/window/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" + item_state = "sheet-plastitaniumglass" + materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_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/plastitaniumglass + +/obj/item/stack/sheet/plastitaniumglass/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.plastitaniumglass_recipes + return ..() + +/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 + item_state = "shard-glass" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + materials = list(MAT_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 + + +/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)) + I.attackby(src, user) + 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/glass/NG = new (user.loc) + for(var/obj/item/stack/sheet/glass/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 glass to the stack. It now contains [NG.amount] sheet\s.") + qdel(src) + return TRUE + +/obj/item/shard/Crossed(mob/living/L) + if(istype(L) && has_gravity(loc)) + if(HAS_TRAIT(L, TRAIT_LIGHT_STEP)) + playsound(loc, 'sound/effects/glass_step.ogg', 30, 1) + else + playsound(loc, 'sound/effects/glass_step.ogg', 50, 1) + return ..() + +/obj/item/shard/plasma + name = "purple shard" + desc = "A nasty looking shard of plasma glass." + force = 6 + throwforce = 11 + icon_state = "plasmalarge" + materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT * 0.5, MAT_GLASS=MINERAL_MATERIAL_AMOUNT) icon_prefix = "plasma" \ No newline at end of file diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index 7297bb3aae..0b85bf351e 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -1,248 +1,248 @@ -/obj/item/stack/sheet/animalhide - name = "hide" - desc = "Something went wrong." - icon_state = "sheet-hide" - item_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/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.human_recipes - return ..() - -/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" - item_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/gondola, 2), \ - new/datum/stack_recipe("gondola bedsheet", /obj/item/bedsheet/gondola, 1), \ - )) - -/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" - item_state = "sheet-gondola" - -/obj/item/stack/sheet/animalhide/gondola/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.gondola_recipes - return ..() - -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/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.corgi_recipes - return ..() - -/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" - item_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" - 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/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.monkey_recipes - return ..() - -/obj/item/stack/sheet/animalhide/lizard - name = "lizard skin" - desc = "Sssssss..." - singular_name = "lizard skin piece" - icon_state = "sheet-lizard" - item_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" - item_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/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.xeno_recipes - return ..() - -//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" - item_state = "sheet-hairlesshide" - -/obj/item/stack/sheet/wetleather - name = "wet leather" - desc = "This leather has been cleaned but still needs to be dried." - singular_name = "wet leather piece" - icon_state = "sheet-wetleather" - item_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" - item_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), \ -)) - -/obj/item/stack/sheet/leather/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.leather_recipes - return ..() - -/* - * 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 - - -GLOBAL_LIST_INIT(sinew_recipes, list ( \ - new/datum/stack_recipe("sinew restraints", /obj/item/restraints/handcuffs/sinew, 1), \ -)) - -/obj/item/stack/sheet/sinew/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.sinew_recipes - return ..() - - /* - * 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/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, 1, -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/wetleather/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/wetleather/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" + item_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/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.human_recipes + return ..() + +/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" + item_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/gondola, 2), \ + new/datum/stack_recipe("gondola bedsheet", /obj/item/bedsheet/gondola, 1), \ + )) + +/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" + item_state = "sheet-gondola" + +/obj/item/stack/sheet/animalhide/gondola/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.gondola_recipes + return ..() + +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/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.corgi_recipes + return ..() + +/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" + item_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" + 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/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.monkey_recipes + return ..() + +/obj/item/stack/sheet/animalhide/lizard + name = "lizard skin" + desc = "Sssssss..." + singular_name = "lizard skin piece" + icon_state = "sheet-lizard" + item_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" + item_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/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.xeno_recipes + return ..() + +//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" + item_state = "sheet-hairlesshide" + +/obj/item/stack/sheet/wetleather + name = "wet leather" + desc = "This leather has been cleaned but still needs to be dried." + singular_name = "wet leather piece" + icon_state = "sheet-wetleather" + item_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" + item_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), \ +)) + +/obj/item/stack/sheet/leather/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.leather_recipes + return ..() + +/* + * 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 + + +GLOBAL_LIST_INIT(sinew_recipes, list ( \ + new/datum/stack_recipe("sinew restraints", /obj/item/restraints/handcuffs/sinew, 1), \ +)) + +/obj/item/stack/sheet/sinew/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.sinew_recipes + return ..() + + /* + * 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/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, 1, -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/wetleather/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/wetleather/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 c42cfbe571..d0a916da34 100644 --- a/code/game/objects/items/stacks/sheets/light.dm +++ b/code/game/objects/items/stacks/sheets/light.dm @@ -1,35 +1,35 @@ -/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 908fc88383..af0dca02d3 100644 --- a/code/game/objects/items/stacks/sheets/sheets.dm +++ b/code/game/objects/items/stacks/sheets/sheets.dm @@ -1,18 +1,18 @@ -/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/perunit = MINERAL_MATERIAL_AMOUNT - 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 - var/is_fabric = FALSE //is this a valid material for the loom? - var/loom_result //result from pulling on the loom +/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/perunit = MINERAL_MATERIAL_AMOUNT + 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 + var/is_fabric = FALSE //is this a valid material for the loom? + var/loom_result //result from pulling on the loom var/pull_effort = 0 //amount of delay when pulling on the loom \ No newline at end of file diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm index d200471b5b..63c8fd9faf 100644 --- a/code/game/objects/items/storage/backpack.dm +++ b/code/game/objects/items/storage/backpack.dm @@ -1,620 +1,620 @@ -/* 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" - item_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/storage/backpack/holding - name = "bag of holding" - desc = "A backpack that opens into a localized pocket of Blue Space." - icon_state = "holdingpack" - item_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 - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/obj/item/storage/backpack/holding/satchel - name = "satchel of holding" - desc = "A satchel that opens into a localized pocket of Blue Space." - icon_state = "holdingsat" - item_state = "holdingsat" - species_exception = list(/datum/species/angel) - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/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_BULKY - 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, 1, -5) - qdel(user) - return - -/obj/item/storage/backpack/holding/singularity_act(current_size) - var/dist = max((current_size - 2),1) - explosion(src.loc,(dist),(dist*2),(dist*4)) - return - -/obj/item/storage/backpack/santabag - name = "Santa's Gift Bag" - desc = "Space Santa uses this to deliver toys to all the nice children in space in Christmas! Wow, it's pretty big!" - icon_state = "giftbag0" - item_state = "giftbag" - w_class = WEIGHT_CLASS_BULKY - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/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/cultpack - name = "trophy rack" - desc = "It's useful for both carrying extra gear and proudly declaring your insanity." - icon_state = "cultpack" - item_state = "backpack" - -/obj/item/storage/backpack/clown - name = "Giggles von Honkerton" - desc = "It's a backpack made by Honk! Co." - icon_state = "clownpack" - item_state = "clownpack" - -/obj/item/storage/backpack/explorer - name = "explorer bag" - desc = "A robust backpack for stashing your loot." - icon_state = "explorerpack" - item_state = "explorerpack" - -/obj/item/storage/backpack/mime - name = "Parcel Parceaux" - desc = "A silent backpack made for those silent workers. Silence Co." - icon_state = "mimepack" - item_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" - item_state = "medicalpack" - -/obj/item/storage/backpack/security - name = "security backpack" - desc = "It's a very robust backpack." - icon_state = "securitypack" - item_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" - item_state = "captainpack" - resistance_flags = FIRE_PROOF - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/obj/item/storage/backpack/industrial - name = "industrial backpack" - desc = "It's a tough backpack for the daily grind of station life." - icon_state = "engiepack" - item_state = "engiepack" - resistance_flags = FIRE_PROOF - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/obj/item/storage/backpack/botany - name = "botany backpack" - desc = "It's a backpack made of all-natural fibers." - icon_state = "botpack" - item_state = "botpack" - -/obj/item/storage/backpack/chemistry - name = "chemistry backpack" - desc = "A backpack specially designed to repel stains and hazardous liquids." - icon_state = "chempack" - item_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" - item_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" - item_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" - item_state = "viropack" - -/* - * Satchel Types - */ - -/obj/item/storage/backpack/satchel - name = "satchel" - desc = "A trendy looking satchel." - icon_state = "satchel-norm" - species_exception = list(/datum/species/angel) //satchels can be equipped since they are on the side, not back - -/obj/item/storage/backpack/satchel/leather - name = "leather satchel" - desc = "It's a very fancy satchel made with fine leather." - icon_state = "satchel" - -/obj/item/storage/backpack/satchel/leather/withwallet/PopulateContents() - new /obj/item/storage/wallet/random(src) - -/obj/item/storage/backpack/satchel/eng - name = "industrial satchel" - desc = "A tough satchel with extra pockets." - icon_state = "satchel-eng" - item_state = "engiepack" - resistance_flags = FIRE_PROOF - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/obj/item/storage/backpack/satchel/med - name = "medical satchel" - desc = "A sterile satchel used in medical departments." - icon_state = "satchel-med" - item_state = "medicalpack" - -/obj/item/storage/backpack/satchel/vir - name = "virologist satchel" - desc = "A sterile satchel with virologist colours." - icon_state = "satchel-vir" - item_state = "satchel-vir" - -/obj/item/storage/backpack/satchel/chem - name = "chemist satchel" - desc = "A sterile satchel with chemist colours." - icon_state = "satchel-chem" - item_state = "satchel-chem" - -/obj/item/storage/backpack/satchel/gen - name = "geneticist satchel" - desc = "A sterile satchel with geneticist colours." - icon_state = "satchel-gen" - item_state = "satchel-gen" - -/obj/item/storage/backpack/satchel/tox - name = "scientist satchel" - desc = "Useful for holding research materials." - icon_state = "satchel-tox" - item_state = "satchel-tox" - -/obj/item/storage/backpack/satchel/hyd - name = "botanist satchel" - desc = "A satchel made of all natural fibers." - icon_state = "satchel-hyd" - item_state = "satchel-hyd" - -/obj/item/storage/backpack/satchel/sec - name = "security satchel" - desc = "A robust satchel for security related needs." - icon_state = "satchel-sec" - item_state = "securitypack" - -/obj/item/storage/backpack/satchel/explorer - name = "explorer satchel" - desc = "A robust satchel for stashing your loot." - icon_state = "satchel-explorer" - item_state = "securitypack" - -/obj/item/storage/backpack/satchel/bone - name = "bone satchel" - desc = "A grotesque satchel made of sinews and bones." - icon = 'icons/obj/mining.dmi' - icon_state = "goliath_saddle" - slot_flags = ITEM_SLOT_BACK - -/obj/item/storage/backpack/satchel/bone/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 20 - STR.max_items = 15 - -/obj/item/storage/backpack/satchel/cap - name = "captain's satchel" - desc = "An exclusive satchel for Nanotrasen officers." - icon_state = "satchel-cap" - item_state = "captainpack" - resistance_flags = FIRE_PROOF - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/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" - w_class = WEIGHT_CLASS_BULKY //Can fit in backpacks itself. - level = 1 - component_type = /datum/component/storage/concrete/secret_satchel - -/obj/item/storage/backpack/satchel/flat/Initialize() - . = ..() - SSpersistence.new_secret_satchels += src - -/obj/item/storage/backpack/satchel/flat/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 6 - STR.cant_hold = typecacheof(list(/obj/item/storage/backpack/satchel/flat)) //muh recursive backpacks - -/obj/item/storage/backpack/satchel/flat/hide(intact) - if(intact) - invisibility = INVISIBILITY_MAXIMUM - anchored = TRUE //otherwise you can start pulling, cover it, and drag around an invisible backpack. - icon_state = "[initial(icon_state)]2" - else - invisibility = initial(invisibility) - anchored = FALSE - icon_state = initial(icon_state) - -/obj/item/storage/backpack/satchel/flat/PopulateContents() - new /obj/item/stack/tile/plasteel(src) - new /obj/item/crowbar(src) - -/obj/item/storage/backpack/satchel/flat/Destroy() - SSpersistence.new_secret_satchels -= src - return ..() - -/obj/item/storage/backpack/satchel/flat/secret - var/list/reward_one_of_these = list() //Intended for map editing - var/list/reward_all_of_these = list() //use paths! - var/revealed = FALSE - -/obj/item/storage/backpack/satchel/flat/secret/Initialize() - . = ..() - - if(isfloorturf(loc) && !isplatingturf(loc)) - hide(1) - -/obj/item/storage/backpack/satchel/flat/secret/hide(intact) - ..() - if(!intact && !revealed) - if(reward_one_of_these.len > 0) - var/reward = pick(reward_one_of_these) - new reward(src) - for(var/R in reward_all_of_these) - new R(src) - revealed = TRUE - -/obj/item/storage/backpack/duffelbag - name = "duffel bag" - desc = "A large duffel bag for holding extra things." - icon_state = "duffel" - item_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" - item_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" - item_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/surgical_drapes(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/reagent_containers/medspray/sterilizine(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" - item_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/surgicaldrill(src) - new /obj/item/cautery(src) - new /obj/item/surgical_drapes(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/reagent_containers/medspray/sterilizine(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" - item_state = "duffel-eng" - resistance_flags = FIRE_PROOF - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/obj/item/storage/backpack/duffelbag/durathread - name = "durathread duffel bag" - desc = "A lightweight duffel bag made out of durathread." - icon_state = "duffel-durathread" - item_state = "duffel-durathread" - resistance_flags = FIRE_PROOF - slowdown = 0 - -/obj/item/storage/backpack/duffelbag/drone - name = "drone duffel bag" - desc = "A large duffel bag for holding tools and hats." - icon_state = "duffel-drone" - item_state = "duffel-drone" - resistance_flags = FIRE_PROOF - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/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/random(src) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - new /obj/item/pipe_dispenser(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" - item_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/duffelbag/syndie - name = "suspicious looking duffel bag" - desc = "A large duffel bag for holding extra tactical supplies." - icon_state = "duffel-syndie" - item_state = "duffel-syndieammo" - slowdown = 0 - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/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" - item_state = "duffel-syndieammo" - -/obj/item/storage/backpack/duffelbag/syndie/hitman/PopulateContents() - new /obj/item/clothing/under/lawyer/blacksuit(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" - item_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" - item_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/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) - new /obj/item/implantcase(src) - new /obj/item/implanter(src) - new /obj/item/reagent_containers/medspray/sterilizine(src) - -/obj/item/storage/backpack/duffelbag/syndie/surgery_adv - name = "advanced surgery duffel bag" - desc = "A large duffel bag for holding surgical tools. Bears the logo of an advanced med-tech firm." - -/obj/item/storage/backpack/duffelbag/syndie/surgery_adv/PopulateContents() - new /obj/item/scalpel/advanced(src) - new /obj/item/retractor/advanced(src) - new /obj/item/surgicaldrill/advanced(src) - new /obj/item/surgical_drapes(src) - new /obj/item/storage/firstaid/tactical(src) - new /obj/item/clothing/suit/straight_jacket(src) - new /obj/item/clothing/mask/muzzle(src) - new /obj/item/mmi/syndie(src) - new /obj/item/implantcase(src) - new /obj/item/implanter(src) - new /obj/item/reagent_containers/medspray/sterilizine(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" - item_state = "duffel-syndieammo" - -/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun - desc = "A large duffel bag, packed to the brim with Bulldog shotgun drum 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/stun(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/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/ammo_box/magazine/m12g(src) - new /obj/item/gun/ballistic/automatic/shotgun/bulldog(src) - new /obj/item/ammo_box/magazine/m12g/stun(src) - new /obj/item/clothing/glasses/thermal/syndi(src) - -/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle - desc = "A large duffel bag containing a tactical medkit, a Donksoft machine gun, 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/nukeop(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/plastic/c4(src) - -/obj/item/storage/backpack/duffelbag/syndie/x4/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/grenade/plastic/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/pistolm9mm(src) - new /obj/item/ammo_box/magazine/pistolm9mm(src) - new /obj/item/reagent_containers/food/drinks/bottle/vodka/badminka(src) - new /obj/item/reagent_containers/syringe/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/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/duffelbag/syndie/shredderbundle - desc = "A large duffel bag containing two CX Shredders, some magazines, an elite hardsuit, and a chest rig." - -/obj/item/storage/backpack/duffelbag/syndie/shredderbundle/PopulateContents() - new /obj/item/ammo_box/magazine/flechette/shredder(src) - new /obj/item/ammo_box/magazine/flechette/shredder(src) - new /obj/item/ammo_box/magazine/flechette/shredder(src) - new /obj/item/ammo_box/magazine/flechette/shredder(src) - new /obj/item/gun/ballistic/automatic/flechette/shredder(src) - new /obj/item/gun/ballistic/automatic/flechette/shredder(src) - new /obj/item/storage/belt/military(src) - new /obj/item/clothing/suit/space/hardsuit/syndi/elite(src) +/* 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" + item_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/storage/backpack/holding + name = "bag of holding" + desc = "A backpack that opens into a localized pocket of Blue Space." + icon_state = "holdingpack" + item_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 + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/obj/item/storage/backpack/holding/satchel + name = "satchel of holding" + desc = "A satchel that opens into a localized pocket of Blue Space." + icon_state = "holdingsat" + item_state = "holdingsat" + species_exception = list(/datum/species/angel) + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/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_BULKY + 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, 1, -5) + qdel(user) + return + +/obj/item/storage/backpack/holding/singularity_act(current_size) + var/dist = max((current_size - 2),1) + explosion(src.loc,(dist),(dist*2),(dist*4)) + return + +/obj/item/storage/backpack/santabag + name = "Santa's Gift Bag" + desc = "Space Santa uses this to deliver toys to all the nice children in space in Christmas! Wow, it's pretty big!" + icon_state = "giftbag0" + item_state = "giftbag" + w_class = WEIGHT_CLASS_BULKY + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/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/cultpack + name = "trophy rack" + desc = "It's useful for both carrying extra gear and proudly declaring your insanity." + icon_state = "cultpack" + item_state = "backpack" + +/obj/item/storage/backpack/clown + name = "Giggles von Honkerton" + desc = "It's a backpack made by Honk! Co." + icon_state = "clownpack" + item_state = "clownpack" + +/obj/item/storage/backpack/explorer + name = "explorer bag" + desc = "A robust backpack for stashing your loot." + icon_state = "explorerpack" + item_state = "explorerpack" + +/obj/item/storage/backpack/mime + name = "Parcel Parceaux" + desc = "A silent backpack made for those silent workers. Silence Co." + icon_state = "mimepack" + item_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" + item_state = "medicalpack" + +/obj/item/storage/backpack/security + name = "security backpack" + desc = "It's a very robust backpack." + icon_state = "securitypack" + item_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" + item_state = "captainpack" + resistance_flags = FIRE_PROOF + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/obj/item/storage/backpack/industrial + name = "industrial backpack" + desc = "It's a tough backpack for the daily grind of station life." + icon_state = "engiepack" + item_state = "engiepack" + resistance_flags = FIRE_PROOF + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/obj/item/storage/backpack/botany + name = "botany backpack" + desc = "It's a backpack made of all-natural fibers." + icon_state = "botpack" + item_state = "botpack" + +/obj/item/storage/backpack/chemistry + name = "chemistry backpack" + desc = "A backpack specially designed to repel stains and hazardous liquids." + icon_state = "chempack" + item_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" + item_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" + item_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" + item_state = "viropack" + +/* + * Satchel Types + */ + +/obj/item/storage/backpack/satchel + name = "satchel" + desc = "A trendy looking satchel." + icon_state = "satchel-norm" + species_exception = list(/datum/species/angel) //satchels can be equipped since they are on the side, not back + +/obj/item/storage/backpack/satchel/leather + name = "leather satchel" + desc = "It's a very fancy satchel made with fine leather." + icon_state = "satchel" + +/obj/item/storage/backpack/satchel/leather/withwallet/PopulateContents() + new /obj/item/storage/wallet/random(src) + +/obj/item/storage/backpack/satchel/eng + name = "industrial satchel" + desc = "A tough satchel with extra pockets." + icon_state = "satchel-eng" + item_state = "engiepack" + resistance_flags = FIRE_PROOF + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/obj/item/storage/backpack/satchel/med + name = "medical satchel" + desc = "A sterile satchel used in medical departments." + icon_state = "satchel-med" + item_state = "medicalpack" + +/obj/item/storage/backpack/satchel/vir + name = "virologist satchel" + desc = "A sterile satchel with virologist colours." + icon_state = "satchel-vir" + item_state = "satchel-vir" + +/obj/item/storage/backpack/satchel/chem + name = "chemist satchel" + desc = "A sterile satchel with chemist colours." + icon_state = "satchel-chem" + item_state = "satchel-chem" + +/obj/item/storage/backpack/satchel/gen + name = "geneticist satchel" + desc = "A sterile satchel with geneticist colours." + icon_state = "satchel-gen" + item_state = "satchel-gen" + +/obj/item/storage/backpack/satchel/tox + name = "scientist satchel" + desc = "Useful for holding research materials." + icon_state = "satchel-tox" + item_state = "satchel-tox" + +/obj/item/storage/backpack/satchel/hyd + name = "botanist satchel" + desc = "A satchel made of all natural fibers." + icon_state = "satchel-hyd" + item_state = "satchel-hyd" + +/obj/item/storage/backpack/satchel/sec + name = "security satchel" + desc = "A robust satchel for security related needs." + icon_state = "satchel-sec" + item_state = "securitypack" + +/obj/item/storage/backpack/satchel/explorer + name = "explorer satchel" + desc = "A robust satchel for stashing your loot." + icon_state = "satchel-explorer" + item_state = "securitypack" + +/obj/item/storage/backpack/satchel/bone + name = "bone satchel" + desc = "A grotesque satchel made of sinews and bones." + icon = 'icons/obj/mining.dmi' + icon_state = "goliath_saddle" + slot_flags = ITEM_SLOT_BACK + +/obj/item/storage/backpack/satchel/bone/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 20 + STR.max_items = 15 + +/obj/item/storage/backpack/satchel/cap + name = "captain's satchel" + desc = "An exclusive satchel for Nanotrasen officers." + icon_state = "satchel-cap" + item_state = "captainpack" + resistance_flags = FIRE_PROOF + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/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" + w_class = WEIGHT_CLASS_BULKY //Can fit in backpacks itself. + level = 1 + component_type = /datum/component/storage/concrete/secret_satchel + +/obj/item/storage/backpack/satchel/flat/Initialize() + . = ..() + SSpersistence.new_secret_satchels += src + +/obj/item/storage/backpack/satchel/flat/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 6 + STR.cant_hold = typecacheof(list(/obj/item/storage/backpack/satchel/flat)) //muh recursive backpacks + +/obj/item/storage/backpack/satchel/flat/hide(intact) + if(intact) + invisibility = INVISIBILITY_MAXIMUM + anchored = TRUE //otherwise you can start pulling, cover it, and drag around an invisible backpack. + icon_state = "[initial(icon_state)]2" + else + invisibility = initial(invisibility) + anchored = FALSE + icon_state = initial(icon_state) + +/obj/item/storage/backpack/satchel/flat/PopulateContents() + new /obj/item/stack/tile/plasteel(src) + new /obj/item/crowbar(src) + +/obj/item/storage/backpack/satchel/flat/Destroy() + SSpersistence.new_secret_satchels -= src + return ..() + +/obj/item/storage/backpack/satchel/flat/secret + var/list/reward_one_of_these = list() //Intended for map editing + var/list/reward_all_of_these = list() //use paths! + var/revealed = FALSE + +/obj/item/storage/backpack/satchel/flat/secret/Initialize() + . = ..() + + if(isfloorturf(loc) && !isplatingturf(loc)) + hide(1) + +/obj/item/storage/backpack/satchel/flat/secret/hide(intact) + ..() + if(!intact && !revealed) + if(reward_one_of_these.len > 0) + var/reward = pick(reward_one_of_these) + new reward(src) + for(var/R in reward_all_of_these) + new R(src) + revealed = TRUE + +/obj/item/storage/backpack/duffelbag + name = "duffel bag" + desc = "A large duffel bag for holding extra things." + icon_state = "duffel" + item_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" + item_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" + item_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/surgical_drapes(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/reagent_containers/medspray/sterilizine(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" + item_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/surgicaldrill(src) + new /obj/item/cautery(src) + new /obj/item/surgical_drapes(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/reagent_containers/medspray/sterilizine(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" + item_state = "duffel-eng" + resistance_flags = FIRE_PROOF + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/obj/item/storage/backpack/duffelbag/durathread + name = "durathread duffel bag" + desc = "A lightweight duffel bag made out of durathread." + icon_state = "duffel-durathread" + item_state = "duffel-durathread" + resistance_flags = FIRE_PROOF + slowdown = 0 + +/obj/item/storage/backpack/duffelbag/drone + name = "drone duffel bag" + desc = "A large duffel bag for holding tools and hats." + icon_state = "duffel-drone" + item_state = "duffel-drone" + resistance_flags = FIRE_PROOF + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/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/random(src) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + new /obj/item/pipe_dispenser(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" + item_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/duffelbag/syndie + name = "suspicious looking duffel bag" + desc = "A large duffel bag for holding extra tactical supplies." + icon_state = "duffel-syndie" + item_state = "duffel-syndieammo" + slowdown = 0 + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/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" + item_state = "duffel-syndieammo" + +/obj/item/storage/backpack/duffelbag/syndie/hitman/PopulateContents() + new /obj/item/clothing/under/lawyer/blacksuit(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" + item_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" + item_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/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) + new /obj/item/implantcase(src) + new /obj/item/implanter(src) + new /obj/item/reagent_containers/medspray/sterilizine(src) + +/obj/item/storage/backpack/duffelbag/syndie/surgery_adv + name = "advanced surgery duffel bag" + desc = "A large duffel bag for holding surgical tools. Bears the logo of an advanced med-tech firm." + +/obj/item/storage/backpack/duffelbag/syndie/surgery_adv/PopulateContents() + new /obj/item/scalpel/advanced(src) + new /obj/item/retractor/advanced(src) + new /obj/item/surgicaldrill/advanced(src) + new /obj/item/surgical_drapes(src) + new /obj/item/storage/firstaid/tactical(src) + new /obj/item/clothing/suit/straight_jacket(src) + new /obj/item/clothing/mask/muzzle(src) + new /obj/item/mmi/syndie(src) + new /obj/item/implantcase(src) + new /obj/item/implanter(src) + new /obj/item/reagent_containers/medspray/sterilizine(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" + item_state = "duffel-syndieammo" + +/obj/item/storage/backpack/duffelbag/syndie/ammo/shotgun + desc = "A large duffel bag, packed to the brim with Bulldog shotgun drum 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/stun(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/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/ammo_box/magazine/m12g(src) + new /obj/item/gun/ballistic/automatic/shotgun/bulldog(src) + new /obj/item/ammo_box/magazine/m12g/stun(src) + new /obj/item/clothing/glasses/thermal/syndi(src) + +/obj/item/storage/backpack/duffelbag/syndie/med/medicalbundle + desc = "A large duffel bag containing a tactical medkit, a Donksoft machine gun, 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/nukeop(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/plastic/c4(src) + +/obj/item/storage/backpack/duffelbag/syndie/x4/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/grenade/plastic/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/pistolm9mm(src) + new /obj/item/ammo_box/magazine/pistolm9mm(src) + new /obj/item/reagent_containers/food/drinks/bottle/vodka/badminka(src) + new /obj/item/reagent_containers/syringe/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/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/duffelbag/syndie/shredderbundle + desc = "A large duffel bag containing two CX Shredders, some magazines, an elite hardsuit, and a chest rig." + +/obj/item/storage/backpack/duffelbag/syndie/shredderbundle/PopulateContents() + new /obj/item/ammo_box/magazine/flechette/shredder(src) + new /obj/item/ammo_box/magazine/flechette/shredder(src) + new /obj/item/ammo_box/magazine/flechette/shredder(src) + new /obj/item/ammo_box/magazine/flechette/shredder(src) + new /obj/item/gun/ballistic/automatic/flechette/shredder(src) + new /obj/item/gun/ballistic/automatic/flechette/shredder(src) + new /obj/item/storage/belt/military(src) + new /obj/item/clothing/suit/space/hardsuit/syndi/elite(src) diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm index 7664da6ce4..028f36b1d1 100644 --- a/code/game/objects/items/storage/bags.dm +++ b/code/game/objects/items/storage/bags.dm @@ -1,418 +1,418 @@ -/* - * 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 - -/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" - item_state = "trashbag" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - - w_class = WEIGHT_CLASS_BULKY - 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.cant_hold = typecacheof(list(/obj/item/disk/nuclear)) - STR.limited_random_access = TRUE - STR.limited_random_access_stack_position = 3 - -/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, 1, -1) - return (TOXLOSS) - -/obj/item/storage/bag/trash/update_icon() - if(contents.len == 0) - icon_state = "[initial(icon_state)]" - else if(contents.len < 12) - icon_state = "[initial(icon_state)]1" - else if(contents.len < 21) - icon_state = "[initial(icon_state)]2" - else icon_state = "[initial(icon_state)]3" - -/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 - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/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 - STR.limited_random_access_stack_position = 5 - -/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" - slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_POCKET - 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 - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - var/range = null - -/obj/item/storage/bag/ore/ComponentInitialize() - . = ..() - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.allow_quick_empty = TRUE - STR.can_hold = typecacheof(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 - if(issilicon(user)) - var/mob/living/silicon/robot/borgo = user - for(var/obj/item/cyborg_clamp/C in borgo.module.modules) - for(var/obj/structure/ore_box/B in C) - box = B - - 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) - if(range) - for(var/obj/item/stack/ore/ore in range(range, user)) - user.transferItemToLoc(ore, 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" - range = 1 - -/obj/item/storage/bag/ore/cyborg/ComponentInitialize() - . = ..() - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.allow_quick_empty = TRUE - STR.can_hold = typecacheof(list(/obj/item/stack/ore)) - STR.max_w_class = WEIGHT_CLASS_HUGE - STR.max_combined_stack_amount = 150 - -/obj/item/storage/bag/ore/large - name = "large mining satchel" - desc = "This bag can hold three times the ore in many small pockets. Shockingly foldable and compact for its volume." - -/obj/item/storage/bag/ore/large/ComponentInitialize() - . = ..() - var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) - STR.allow_quick_empty = TRUE - STR.can_hold = typecacheof(list(/obj/item/stack/ore)) - STR.max_w_class = WEIGHT_CLASS_HUGE - STR.max_combined_stack_amount = 150 - -/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" - w_class = WEIGHT_CLASS_TINY - 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.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/grown, /obj/item/seeds, /obj/item/grown, /obj/item/reagent_containers/honeycomb)) - -//////// - -/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.stat || !usr.canmove || usr.restrained()) - 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" - - var/capacity = 300; //the number of sheets it can carry. - w_class = WEIGHT_CLASS_NORMAL - 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.can_hold = typecacheof(list(/obj/item/stack/sheet)) - STR.cant_hold = typecacheof(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" - w_class = WEIGHT_CLASS_BULKY //Bigger than a book because physics - 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.can_hold = typecacheof(list(/obj/item/book, /obj/item/storage/book, /obj/item/spellbook)) - -/* - * Trays - Agouri - */ -/obj/item/storage/bag/tray - name = "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 - w_class = WEIGHT_CLASS_BULKY - flags_1 = CONDUCT_1 - materials = list(MAT_METAL=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) - spawn() - for(var/i = 1, i <= rand(1,2), i++) - if(I) - step(I, pick(NORTH,SOUTH,EAST,WEST)) - sleep(rand(2,4)) - - if(prob(50)) - playsound(M, 'sound/items/trayhit1.ogg', 50, 1) - else - playsound(M, 'sound/items/trayhit2.ogg', 50, 1) - - if(ishuman(M) || ismonkey(M)) - if(prob(10)) - M.Knockdown(40) - update_icon() - -/obj/item/storage/bag/tray/update_icon() - cut_overlays() - for(var/obj/item/I in contents) - add_overlay(new /mutable_appearance(I)) - -/obj/item/storage/bag/tray/Entered() - . = ..() - update_icon() - -/obj/item/storage/bag/tray/Exited() - . = ..() - update_icon() - -/* - * Chemistry bag - */ - -/obj/item/storage/bag/chemistry - name = "chemistry bag" - icon = 'icons/obj/chemical.dmi' - icon_state = "bag" - desc = "A bag for storing pills, patches, and bottles." - w_class = WEIGHT_CLASS_TINY - 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.can_hold = typecacheof(list(/obj/item/reagent_containers/pill, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/syringe/dart)) - -/* - * Biowaste bag (mostly for xenobiologists) - */ - -/obj/item/storage/bag/bio - name = "bio bag" - icon = 'icons/obj/chemical.dmi' - icon_state = "biobag" - desc = "A bag for the safe transportation and disposal of biowaste and other biological materials." - w_class = WEIGHT_CLASS_TINY - 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.can_hold = typecacheof(list(/obj/item/slime_extract, /obj/item/reagent_containers/syringe, /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/storage/bag/bio/holding - name = "bio bag of holding" - icon = 'icons/obj/chemical.dmi' - icon_state = "bspace_biobag" - desc = "A bag for the safe transportation and disposal of biowaste and other biological materials." - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/obj/item/storage/bag/bio/holding/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = INFINITY - STR.max_items = 100 +/* + * 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 + +/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" + item_state = "trashbag" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + + w_class = WEIGHT_CLASS_BULKY + 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.cant_hold = typecacheof(list(/obj/item/disk/nuclear)) + STR.limited_random_access = TRUE + STR.limited_random_access_stack_position = 3 + +/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, 1, -1) + return (TOXLOSS) + +/obj/item/storage/bag/trash/update_icon() + if(contents.len == 0) + icon_state = "[initial(icon_state)]" + else if(contents.len < 12) + icon_state = "[initial(icon_state)]1" + else if(contents.len < 21) + icon_state = "[initial(icon_state)]2" + else icon_state = "[initial(icon_state)]3" + +/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 + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/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 + STR.limited_random_access_stack_position = 5 + +/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" + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_POCKET + 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 + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + var/range = null + +/obj/item/storage/bag/ore/ComponentInitialize() + . = ..() + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.allow_quick_empty = TRUE + STR.can_hold = typecacheof(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 + if(issilicon(user)) + var/mob/living/silicon/robot/borgo = user + for(var/obj/item/cyborg_clamp/C in borgo.module.modules) + for(var/obj/structure/ore_box/B in C) + box = B + + 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) + if(range) + for(var/obj/item/stack/ore/ore in range(range, user)) + user.transferItemToLoc(ore, 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" + range = 1 + +/obj/item/storage/bag/ore/cyborg/ComponentInitialize() + . = ..() + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.allow_quick_empty = TRUE + STR.can_hold = typecacheof(list(/obj/item/stack/ore)) + STR.max_w_class = WEIGHT_CLASS_HUGE + STR.max_combined_stack_amount = 150 + +/obj/item/storage/bag/ore/large + name = "large mining satchel" + desc = "This bag can hold three times the ore in many small pockets. Shockingly foldable and compact for its volume." + +/obj/item/storage/bag/ore/large/ComponentInitialize() + . = ..() + var/datum/component/storage/concrete/stack/STR = GetComponent(/datum/component/storage/concrete/stack) + STR.allow_quick_empty = TRUE + STR.can_hold = typecacheof(list(/obj/item/stack/ore)) + STR.max_w_class = WEIGHT_CLASS_HUGE + STR.max_combined_stack_amount = 150 + +/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" + w_class = WEIGHT_CLASS_TINY + 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.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/grown, /obj/item/seeds, /obj/item/grown, /obj/item/reagent_containers/honeycomb)) + +//////// + +/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.stat || !usr.canmove || usr.restrained()) + 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" + + var/capacity = 300; //the number of sheets it can carry. + w_class = WEIGHT_CLASS_NORMAL + 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.can_hold = typecacheof(list(/obj/item/stack/sheet)) + STR.cant_hold = typecacheof(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" + w_class = WEIGHT_CLASS_BULKY //Bigger than a book because physics + 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.can_hold = typecacheof(list(/obj/item/book, /obj/item/storage/book, /obj/item/spellbook)) + +/* + * Trays - Agouri + */ +/obj/item/storage/bag/tray + name = "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 + w_class = WEIGHT_CLASS_BULKY + flags_1 = CONDUCT_1 + materials = list(MAT_METAL=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) + spawn() + for(var/i = 1, i <= rand(1,2), i++) + if(I) + step(I, pick(NORTH,SOUTH,EAST,WEST)) + sleep(rand(2,4)) + + if(prob(50)) + playsound(M, 'sound/items/trayhit1.ogg', 50, 1) + else + playsound(M, 'sound/items/trayhit2.ogg', 50, 1) + + if(ishuman(M) || ismonkey(M)) + if(prob(10)) + M.Knockdown(40) + update_icon() + +/obj/item/storage/bag/tray/update_icon() + cut_overlays() + for(var/obj/item/I in contents) + add_overlay(new /mutable_appearance(I)) + +/obj/item/storage/bag/tray/Entered() + . = ..() + update_icon() + +/obj/item/storage/bag/tray/Exited() + . = ..() + update_icon() + +/* + * Chemistry bag + */ + +/obj/item/storage/bag/chemistry + name = "chemistry bag" + icon = 'icons/obj/chemical.dmi' + icon_state = "bag" + desc = "A bag for storing pills, patches, and bottles." + w_class = WEIGHT_CLASS_TINY + 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.can_hold = typecacheof(list(/obj/item/reagent_containers/pill, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/syringe/dart)) + +/* + * Biowaste bag (mostly for xenobiologists) + */ + +/obj/item/storage/bag/bio + name = "bio bag" + icon = 'icons/obj/chemical.dmi' + icon_state = "biobag" + desc = "A bag for the safe transportation and disposal of biowaste and other biological materials." + w_class = WEIGHT_CLASS_TINY + 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.can_hold = typecacheof(list(/obj/item/slime_extract, /obj/item/reagent_containers/syringe, /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/storage/bag/bio/holding + name = "bio bag of holding" + icon = 'icons/obj/chemical.dmi' + icon_state = "bspace_biobag" + desc = "A bag for the safe transportation and disposal of biowaste and other biological materials." + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/obj/item/storage/bag/bio/holding/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = INFINITY + STR.max_items = 100 diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 0961726e6e..082cbe245d 100755 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -1,793 +1,793 @@ -/obj/item/storage/belt - name = "belt" - desc = "Can hold various things." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "utilitybelt" - item_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 - var/content_overlays = FALSE //If this is true, the belt will gain overlays based on what it's holding - var/worn_overlays = FALSE //worn counterpart of the above. - -/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_icon() - cut_overlays() - if(content_overlays) - for(var/obj/item/I in contents) - var/mutable_appearance/M = I.get_belt_overlay() - add_overlay(M) - ..() - -/obj/item/storage/belt/worn_overlays(isinhands, icon_file, style_flags = NONE) - . = ..() - if(!isinhands && worn_overlays) - for(var/obj/item/I in contents) - . += I.get_worn_belt_overlay(icon_file) - -/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 = "utilitybelt" - item_state = "utility" - content_overlays = TRUE - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //because this is easier than trying to have showers wash all contents. - -/obj/item/storage/belt/utility/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - var/static/list/can_hold = typecacheof(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, - /obj/item/forcefield_projector, - /obj/item/assembly/signaler - )) - STR.can_hold = can_hold - -/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 = "utilitybelt_ce" - item_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,30,pick("red","yellow","orange")) - 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,30,pick("red","yellow","orange")) - -/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,30,pick("red","yellow","orange")) - - -/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/servant/PopulateContents() - new /obj/item/screwdriver/brass(src) - new /obj/item/wirecutters/brass(src) - new /obj/item/wrench/brass(src) - new /obj/item/crowbar/brass(src) - new /obj/item/weldingtool/experimental/brass(src) - new /obj/item/multitool(src) - new /obj/item/stack/cable_coil(src, 30, "yellow") - -/obj/item/storage/belt/medical - name = "medical belt" - desc = "Can hold various medical equipment." - icon_state = "medicalbelt" - item_state = "medical" - content_overlays = TRUE - -/obj/item/storage/belt/medical/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.can_hold = typecacheof(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/medspray, - /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/hypospray/mkii, - /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/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/storage/belt/medical/surgery_belt_adv - name = "surgical supply belt" - desc = "A specialized belt designed for holding surgical equipment. It seems to have specific pockets for each and every surgical tool you can think of." - content_overlays = FALSE - -/obj/item/storage/belt/medical/surgery_belt_adv/PopulateContents() - new /obj/item/scalpel/advanced(src) - new /obj/item/retractor/advanced(src) - new /obj/item/surgicaldrill/advanced(src) - new /obj/item/surgical_drapes(src) - -/obj/item/storage/belt/security - name = "security belt" - desc = "Can hold security gear like handcuffs and flashes." - icon_state = "securitybelt" - item_state = "security"//Could likely use a better one. - 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.can_hold = typecacheof(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/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/mining - name = "explorer's webbing" - desc = "A versatile chest rig, cherished by miners and hunters alike." - icon_state = "explorer1" - item_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_BULKY - STR.max_combined_w_class = 20 - STR.can_hold = typecacheof(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/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/storage/bag/plants, - /obj/item/stack/marker_beacon - )) - - -/obj/item/storage/belt/mining/vendor - contents = newlist(/obj/item/survivalcapsule) - -/obj/item/storage/belt/mining/alt - icon_state = "explorer2" - item_state = "explorer2" - -/obj/item/storage/belt/mining/primitive - name = "hunter's belt" - desc = "A versatile belt, woven from sinew." - icon_state = "ebelt" - item_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" - item_state = "soulstonebelt" - -/obj/item/storage/belt/soulstone/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.can_hold = typecacheof(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" - item_state = "champion" - materials = list(MAT_GOLD=400) - -/obj/item/storage/belt/champion/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 1 - STR.can_hold = 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" - item_state = "militarywebbing" - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/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.can_hold = typecacheof(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/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, - /obj/item/reagent_containers/food/drinks/drinkingglass/filled/syndicatebomb - )) - 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" - item_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,30,"white") - -/obj/item/storage/belt/military/army - name = "army belt" - desc = "A belt used by military forces." - icon_state = "grenadebeltold" - item_state = "security" - -/obj/item/storage/belt/military/assault - name = "assault belt" - desc = "A tactical assault belt." - icon_state = "assaultbelt" - item_state = "security" - -/obj/item/storage/belt/military/assault/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - -/obj/item/storage/belt/durathread - name = "durathread toolbelt" - desc = "A toolbelt made out of durathread, it seems resistant enough to hold even big tools like an RCD, it also has higher capacity." - icon_state = "webbing-durathread" - item_state = "webbing-durathread" - resistance_flags = FIRE_PROOF - -/obj/item/storage/belt/durathread/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 14 - STR.max_combined_w_class = 32 - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.can_hold = typecacheof(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, - /obj/item/forcefield_projector, - /obj/item/assembly/signaler, - /obj/item/lightreplacer, - /obj/item/rcd_ammo, - /obj/item/construction/rcd, - /obj/item/pipe_dispenser, - /obj/item/stack/rods, - /obj/item/stack/tile/plasteel, - /obj/item/grenade/chem_grenade/metalfoam, - /obj/item/grenade/chem_grenade/smart_metal_foam - )) - -/obj/item/storage/belt/grenade - name = "grenadier belt" - desc = "A belt for holding grenades." - icon_state = "grenadebeltnew" - item_state = "security" - -/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.can_hold = typecacheof(list( - /obj/item/grenade, - /obj/item/screwdriver, - /obj/item/lighter, - /obj/item/multitool, - /obj/item/reagent_containers/food/drinks/bottle/molotov, - /obj/item/grenade/plastic/c4, - )) - -/obj/item/storage/belt/grenade/full/PopulateContents() - new /obj/item/grenade/flashbang(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) - new /obj/item/grenade/syndieminibomb/concussion/frag(src) - new /obj/item/grenade/gluon(src) - new /obj/item/grenade/gluon(src) - new /obj/item/grenade/gluon(src) - new /obj/item/grenade/gluon(src) - new /obj/item/grenade/chem_grenade/incendiary(src) - new /obj/item/grenade/chem_grenade/incendiary(src) - new /obj/item/grenade/chem_grenade/facid(src) - new /obj/item/grenade/syndieminibomb(src) - new /obj/item/grenade/syndieminibomb(src) - new /obj/item/screwdriver(src) - new /obj/item/multitool(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" - item_state = "soulstonebelt" - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/obj/item/storage/belt/wands/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.can_hold = typecacheof(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" - item_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_BULKY // Set to this so the light replacer can fit. - STR.can_hold = typecacheof(list( - /obj/item/grenade/chem_grenade, - /obj/item/lightreplacer, - /obj/item/flashlight, - /obj/item/reagent_containers/glass/beaker, - /obj/item/reagent_containers/glass/bottle, - /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/paint/paint_remover, - /obj/item/assembly/mousetrap, - /obj/item/screwdriver, - /obj/item/stack/cable_coil - )) - -/obj/item/storage/belt/bandolier - name = "bandolier" - desc = "A bandolier for holding ammunition." - icon_state = "bandolier" - item_state = "bandolier" - -/obj/item/storage/belt/bandolier/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 18 - STR.display_numerical_stacking = TRUE - STR.can_hold = typecacheof(list( - /obj/item/ammo_casing - )) - -/obj/item/storage/belt/bandolier/durathread - name = "durathread bandolier" - desc = "An double stacked bandolier made out of durathread." - icon_state = "bandolier-durathread" - item_state = "bandolier-durathread" - resistance_flags = FIRE_PROOF - -/obj/item/storage/belt/bandolier/durathread/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 32 - STR.display_numerical_stacking = TRUE - STR.can_hold = typecacheof(list( - /obj/item/ammo_casing - )) - -/obj/item/storage/belt/medolier - name = "medolier" - desc = "A medical bandolier for holding smartdarts." - icon_state = "medolier" - item_state = "medolier" - -/obj/item/storage/belt/medolier/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 15 - STR.display_numerical_stacking = FALSE - STR.allow_quick_gather = TRUE - STR.allow_quick_empty = TRUE - STR.click_gather = TRUE - STR.can_hold = typecacheof(list( - /obj/item/reagent_containers/syringe/dart - )) - -/obj/item/storage/belt/medolier/full/PopulateContents() - for(var/i in 1 to 16) - new /obj/item/reagent_containers/syringe/dart/(src) - -/obj/item/storage/belt/medolier/afterattack(obj/target, mob/user , proximity) - if(!(istype(target, /obj/item/reagent_containers/glass/beaker))) - return - if(!proximity) - return - if(!target.reagents) - return - - for(var/obj/item/reagent_containers/syringe/dart/D in contents) - if(round(target.reagents.total_volume, 1) <= 0) - to_chat(user, "You soak as many of the darts as you can with the contents from [target].") - return - if(D.mode == SYRINGE_INJECT) - continue - - D.afterattack(target, user, proximity) - - ..() - -/obj/item/storage/belt/holster - name = "shoulder holster" - desc = "A holster to carry a handgun and ammo. WARNING: Badasses only." - icon_state = "holster" - item_state = "holster" - alternate_worn_layer = UNDER_SUIT_LAYER - -/obj/item/storage/belt/holster/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 3 - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.can_hold = typecacheof(list( - /obj/item/gun/ballistic/automatic/pistol, - /obj/item/gun/ballistic/revolver, - /obj/item/ammo_box, - /obj/item/toy/gun, - /obj/item/gun/energy/e_gun/mini - )) - -/obj/item/storage/belt/holster/full/PopulateContents() - new /obj/item/gun/ballistic/revolver/detective(src) - new /obj/item/ammo_box/c38(src) - new /obj/item/ammo_box/c38(src) - -/obj/item/storage/belt/fannypack - name = "fannypack" - desc = "A dorky fannypack for keeping small items in." - icon_state = "fannypack_leather" - item_state = "fannypack_leather" - -/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" - item_state = "fannypack_black" - -/obj/item/storage/belt/fannypack/red - name = "red fannypack" - icon_state = "fannypack_red" - item_state = "fannypack_red" - -/obj/item/storage/belt/fannypack/purple - name = "purple fannypack" - icon_state = "fannypack_purple" - item_state = "fannypack_purple" - -/obj/item/storage/belt/fannypack/blue - name = "blue fannypack" - icon_state = "fannypack_blue" - item_state = "fannypack_blue" - -/obj/item/storage/belt/fannypack/orange - name = "orange fannypack" - icon_state = "fannypack_orange" - item_state = "fannypack_orange" - -/obj/item/storage/belt/fannypack/white - name = "white fannypack" - icon_state = "fannypack_white" - item_state = "fannypack_white" - -/obj/item/storage/belt/fannypack/green - name = "green fannypack" - icon_state = "fannypack_green" - item_state = "fannypack_green" - -/obj/item/storage/belt/fannypack/pink - name = "pink fannypack" - icon_state = "fannypack_pink" - item_state = "fannypack_pink" - -/obj/item/storage/belt/fannypack/cyan - name = "cyan fannypack" - icon_state = "fannypack_cyan" - item_state = "fannypack_cyan" - -/obj/item/storage/belt/fannypack/yellow - name = "yellow fannypack" - icon_state = "fannypack_yellow" - item_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" - item_state = "sheath" - w_class = WEIGHT_CLASS_BULKY - content_overlays = TRUE - worn_overlays = TRUE - var/list/fitting_swords = list(/obj/item/melee/sabre, /obj/item/melee/baton/stunsword) - var/starting_sword = /obj/item/melee/sabre - -/obj/item/storage/belt/sabre/ComponentInitialize() - . = ..() - 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.can_hold = typecacheof(fitting_swords) - STR.quickdraw = TRUE - -/obj/item/storage/belt/sabre/examine(mob/user) - . = ..() - if(length(contents)) - . += "Alt-click it to quickly draw the blade." - -/obj/item/storage/belt/sabre/update_icon() - . = ..() - if(isliving(loc)) - var/mob/living/L = loc - L.regenerate_icons() - -/obj/item/storage/belt/sabre/PopulateContents() - new starting_sword(src) - -/obj/item/storage/belt/sabre/rapier - name = "rapier sheath" - desc = "A black, thin sheath that looks to house only a long thin blade. Feels like its made of metal." - icon_state = "rsheath" - item_state = "rsheath" - force = 5 - throwforce = 15 - block_chance = 30 - w_class = WEIGHT_CLASS_BULKY - attack_verb = list("bashed", "slashes", "prods", "pokes") - fitting_swords = list(/obj/item/melee/rapier) - starting_sword = /obj/item/melee/rapier - -/obj/item/storage/belt/sabre/rapier/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(attack_type == PROJECTILE_ATTACK) - final_block_chance = 0 //To thin to block bullets - return ..() +/obj/item/storage/belt + name = "belt" + desc = "Can hold various things." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "utilitybelt" + item_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 + var/content_overlays = FALSE //If this is true, the belt will gain overlays based on what it's holding + var/worn_overlays = FALSE //worn counterpart of the above. + +/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_icon() + cut_overlays() + if(content_overlays) + for(var/obj/item/I in contents) + var/mutable_appearance/M = I.get_belt_overlay() + add_overlay(M) + ..() + +/obj/item/storage/belt/worn_overlays(isinhands, icon_file, style_flags = NONE) + . = ..() + if(!isinhands && worn_overlays) + for(var/obj/item/I in contents) + . += I.get_worn_belt_overlay(icon_file) + +/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 = "utilitybelt" + item_state = "utility" + content_overlays = TRUE + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //because this is easier than trying to have showers wash all contents. + +/obj/item/storage/belt/utility/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + var/static/list/can_hold = typecacheof(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, + /obj/item/forcefield_projector, + /obj/item/assembly/signaler + )) + STR.can_hold = can_hold + +/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 = "utilitybelt_ce" + item_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,30,pick("red","yellow","orange")) + 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,30,pick("red","yellow","orange")) + +/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,30,pick("red","yellow","orange")) + + +/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/servant/PopulateContents() + new /obj/item/screwdriver/brass(src) + new /obj/item/wirecutters/brass(src) + new /obj/item/wrench/brass(src) + new /obj/item/crowbar/brass(src) + new /obj/item/weldingtool/experimental/brass(src) + new /obj/item/multitool(src) + new /obj/item/stack/cable_coil(src, 30, "yellow") + +/obj/item/storage/belt/medical + name = "medical belt" + desc = "Can hold various medical equipment." + icon_state = "medicalbelt" + item_state = "medical" + content_overlays = TRUE + +/obj/item/storage/belt/medical/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.can_hold = typecacheof(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/medspray, + /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/hypospray/mkii, + /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/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/storage/belt/medical/surgery_belt_adv + name = "surgical supply belt" + desc = "A specialized belt designed for holding surgical equipment. It seems to have specific pockets for each and every surgical tool you can think of." + content_overlays = FALSE + +/obj/item/storage/belt/medical/surgery_belt_adv/PopulateContents() + new /obj/item/scalpel/advanced(src) + new /obj/item/retractor/advanced(src) + new /obj/item/surgicaldrill/advanced(src) + new /obj/item/surgical_drapes(src) + +/obj/item/storage/belt/security + name = "security belt" + desc = "Can hold security gear like handcuffs and flashes." + icon_state = "securitybelt" + item_state = "security"//Could likely use a better one. + 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.can_hold = typecacheof(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/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/mining + name = "explorer's webbing" + desc = "A versatile chest rig, cherished by miners and hunters alike." + icon_state = "explorer1" + item_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_BULKY + STR.max_combined_w_class = 20 + STR.can_hold = typecacheof(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/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/storage/bag/plants, + /obj/item/stack/marker_beacon + )) + + +/obj/item/storage/belt/mining/vendor + contents = newlist(/obj/item/survivalcapsule) + +/obj/item/storage/belt/mining/alt + icon_state = "explorer2" + item_state = "explorer2" + +/obj/item/storage/belt/mining/primitive + name = "hunter's belt" + desc = "A versatile belt, woven from sinew." + icon_state = "ebelt" + item_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" + item_state = "soulstonebelt" + +/obj/item/storage/belt/soulstone/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.can_hold = typecacheof(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" + item_state = "champion" + materials = list(MAT_GOLD=400) + +/obj/item/storage/belt/champion/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 1 + STR.can_hold = 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" + item_state = "militarywebbing" + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/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.can_hold = typecacheof(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/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, + /obj/item/reagent_containers/food/drinks/drinkingglass/filled/syndicatebomb + )) + 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" + item_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,30,"white") + +/obj/item/storage/belt/military/army + name = "army belt" + desc = "A belt used by military forces." + icon_state = "grenadebeltold" + item_state = "security" + +/obj/item/storage/belt/military/assault + name = "assault belt" + desc = "A tactical assault belt." + icon_state = "assaultbelt" + item_state = "security" + +/obj/item/storage/belt/military/assault/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + +/obj/item/storage/belt/durathread + name = "durathread toolbelt" + desc = "A toolbelt made out of durathread, it seems resistant enough to hold even big tools like an RCD, it also has higher capacity." + icon_state = "webbing-durathread" + item_state = "webbing-durathread" + resistance_flags = FIRE_PROOF + +/obj/item/storage/belt/durathread/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 14 + STR.max_combined_w_class = 32 + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.can_hold = typecacheof(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, + /obj/item/forcefield_projector, + /obj/item/assembly/signaler, + /obj/item/lightreplacer, + /obj/item/rcd_ammo, + /obj/item/construction/rcd, + /obj/item/pipe_dispenser, + /obj/item/stack/rods, + /obj/item/stack/tile/plasteel, + /obj/item/grenade/chem_grenade/metalfoam, + /obj/item/grenade/chem_grenade/smart_metal_foam + )) + +/obj/item/storage/belt/grenade + name = "grenadier belt" + desc = "A belt for holding grenades." + icon_state = "grenadebeltnew" + item_state = "security" + +/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.can_hold = typecacheof(list( + /obj/item/grenade, + /obj/item/screwdriver, + /obj/item/lighter, + /obj/item/multitool, + /obj/item/reagent_containers/food/drinks/bottle/molotov, + /obj/item/grenade/plastic/c4, + )) + +/obj/item/storage/belt/grenade/full/PopulateContents() + new /obj/item/grenade/flashbang(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/syndieminibomb/concussion/frag(src) + new /obj/item/grenade/gluon(src) + new /obj/item/grenade/gluon(src) + new /obj/item/grenade/gluon(src) + new /obj/item/grenade/gluon(src) + new /obj/item/grenade/chem_grenade/incendiary(src) + new /obj/item/grenade/chem_grenade/incendiary(src) + new /obj/item/grenade/chem_grenade/facid(src) + new /obj/item/grenade/syndieminibomb(src) + new /obj/item/grenade/syndieminibomb(src) + new /obj/item/screwdriver(src) + new /obj/item/multitool(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" + item_state = "soulstonebelt" + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/obj/item/storage/belt/wands/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.can_hold = typecacheof(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" + item_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_BULKY // Set to this so the light replacer can fit. + STR.can_hold = typecacheof(list( + /obj/item/grenade/chem_grenade, + /obj/item/lightreplacer, + /obj/item/flashlight, + /obj/item/reagent_containers/glass/beaker, + /obj/item/reagent_containers/glass/bottle, + /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/paint/paint_remover, + /obj/item/assembly/mousetrap, + /obj/item/screwdriver, + /obj/item/stack/cable_coil + )) + +/obj/item/storage/belt/bandolier + name = "bandolier" + desc = "A bandolier for holding ammunition." + icon_state = "bandolier" + item_state = "bandolier" + +/obj/item/storage/belt/bandolier/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 18 + STR.display_numerical_stacking = TRUE + STR.can_hold = typecacheof(list( + /obj/item/ammo_casing + )) + +/obj/item/storage/belt/bandolier/durathread + name = "durathread bandolier" + desc = "An double stacked bandolier made out of durathread." + icon_state = "bandolier-durathread" + item_state = "bandolier-durathread" + resistance_flags = FIRE_PROOF + +/obj/item/storage/belt/bandolier/durathread/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 32 + STR.display_numerical_stacking = TRUE + STR.can_hold = typecacheof(list( + /obj/item/ammo_casing + )) + +/obj/item/storage/belt/medolier + name = "medolier" + desc = "A medical bandolier for holding smartdarts." + icon_state = "medolier" + item_state = "medolier" + +/obj/item/storage/belt/medolier/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 15 + STR.display_numerical_stacking = FALSE + STR.allow_quick_gather = TRUE + STR.allow_quick_empty = TRUE + STR.click_gather = TRUE + STR.can_hold = typecacheof(list( + /obj/item/reagent_containers/syringe/dart + )) + +/obj/item/storage/belt/medolier/full/PopulateContents() + for(var/i in 1 to 16) + new /obj/item/reagent_containers/syringe/dart/(src) + +/obj/item/storage/belt/medolier/afterattack(obj/target, mob/user , proximity) + if(!(istype(target, /obj/item/reagent_containers/glass/beaker))) + return + if(!proximity) + return + if(!target.reagents) + return + + for(var/obj/item/reagent_containers/syringe/dart/D in contents) + if(round(target.reagents.total_volume, 1) <= 0) + to_chat(user, "You soak as many of the darts as you can with the contents from [target].") + return + if(D.mode == SYRINGE_INJECT) + continue + + D.afterattack(target, user, proximity) + + ..() + +/obj/item/storage/belt/holster + name = "shoulder holster" + desc = "A holster to carry a handgun and ammo. WARNING: Badasses only." + icon_state = "holster" + item_state = "holster" + alternate_worn_layer = UNDER_SUIT_LAYER + +/obj/item/storage/belt/holster/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 3 + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.can_hold = typecacheof(list( + /obj/item/gun/ballistic/automatic/pistol, + /obj/item/gun/ballistic/revolver, + /obj/item/ammo_box, + /obj/item/toy/gun, + /obj/item/gun/energy/e_gun/mini + )) + +/obj/item/storage/belt/holster/full/PopulateContents() + new /obj/item/gun/ballistic/revolver/detective(src) + new /obj/item/ammo_box/c38(src) + new /obj/item/ammo_box/c38(src) + +/obj/item/storage/belt/fannypack + name = "fannypack" + desc = "A dorky fannypack for keeping small items in." + icon_state = "fannypack_leather" + item_state = "fannypack_leather" + +/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" + item_state = "fannypack_black" + +/obj/item/storage/belt/fannypack/red + name = "red fannypack" + icon_state = "fannypack_red" + item_state = "fannypack_red" + +/obj/item/storage/belt/fannypack/purple + name = "purple fannypack" + icon_state = "fannypack_purple" + item_state = "fannypack_purple" + +/obj/item/storage/belt/fannypack/blue + name = "blue fannypack" + icon_state = "fannypack_blue" + item_state = "fannypack_blue" + +/obj/item/storage/belt/fannypack/orange + name = "orange fannypack" + icon_state = "fannypack_orange" + item_state = "fannypack_orange" + +/obj/item/storage/belt/fannypack/white + name = "white fannypack" + icon_state = "fannypack_white" + item_state = "fannypack_white" + +/obj/item/storage/belt/fannypack/green + name = "green fannypack" + icon_state = "fannypack_green" + item_state = "fannypack_green" + +/obj/item/storage/belt/fannypack/pink + name = "pink fannypack" + icon_state = "fannypack_pink" + item_state = "fannypack_pink" + +/obj/item/storage/belt/fannypack/cyan + name = "cyan fannypack" + icon_state = "fannypack_cyan" + item_state = "fannypack_cyan" + +/obj/item/storage/belt/fannypack/yellow + name = "yellow fannypack" + icon_state = "fannypack_yellow" + item_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" + item_state = "sheath" + w_class = WEIGHT_CLASS_BULKY + content_overlays = TRUE + worn_overlays = TRUE + var/list/fitting_swords = list(/obj/item/melee/sabre, /obj/item/melee/baton/stunsword) + var/starting_sword = /obj/item/melee/sabre + +/obj/item/storage/belt/sabre/ComponentInitialize() + . = ..() + 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.can_hold = typecacheof(fitting_swords) + STR.quickdraw = TRUE + +/obj/item/storage/belt/sabre/examine(mob/user) + . = ..() + if(length(contents)) + . += "Alt-click it to quickly draw the blade." + +/obj/item/storage/belt/sabre/update_icon() + . = ..() + if(isliving(loc)) + var/mob/living/L = loc + L.regenerate_icons() + +/obj/item/storage/belt/sabre/PopulateContents() + new starting_sword(src) + +/obj/item/storage/belt/sabre/rapier + name = "rapier sheath" + desc = "A black, thin sheath that looks to house only a long thin blade. Feels like its made of metal." + icon_state = "rsheath" + item_state = "rsheath" + force = 5 + throwforce = 15 + block_chance = 30 + w_class = WEIGHT_CLASS_BULKY + attack_verb = list("bashed", "slashes", "prods", "pokes") + fitting_swords = list(/obj/item/melee/rapier) + starting_sword = /obj/item/melee/rapier + +/obj/item/storage/belt/sabre/rapier/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(attack_type == PROJECTILE_ATTACK) + final_block_chance = 0 //To thin to block bullets + return ..() diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index 4b2b3e2203..d5aa43148c 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -1,1270 +1,1270 @@ -/* - * 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, - * Ammo types, - * Action Figure Boxes, - * Various paper bags, - * Colored boxes - * - * For syndicate call-ins see uplink_kits.dm - */ - -/obj/item/storage/box - name = "box" - desc = "It's just an ordinary box." - icon_state = "box" - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - resistance_flags = FLAMMABLE - var/foldable = /obj/item/stack/sheet/cardboard - var/illustration = "writing" - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //exploits ahoy - -/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,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -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_icon() - . = ..() - if(illustration) - cut_overlays() - add_overlay(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/packageWrap)) - return 0 - return ..() - -//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_plantgene - name = "plant data disks box" - illustration = "disk_kit" - -/obj/item/storage/box/disks_plantgene/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/disk/plantgene(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/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/survival/radio/PopulateContents() - ..() // we want the survival stuff too. - new /obj/item/radio/off(src) - -/obj/item/storage/box/survival_mining/PopulateContents() - new /obj/item/clothing/mask/gas/explorer(src) - new /obj/item/crowbar/red(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) - -// Engineer survival box -/obj/item/storage/box/engineer/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/engi(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/engineer/radio/PopulateContents() - ..() // we want the regular items too. - new /obj/item/radio/off(src) - -// Syndie survival box -/obj/item/storage/box/syndie/PopulateContents() - new /obj/item/clothing/mask/gas/syndicate(src) - - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -// Security survival box -/obj/item/storage/box/security/PopulateContents() - new /obj/item/clothing/mask/gas/sechailer(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/security/radio/PopulateContents() - ..() // we want the regular stuff too - new /obj/item/radio/off(src) - -/obj/item/storage/box/seclooking - icon_state = "secbox" - illustration = null - -/obj/item/storage/box/cells - name = "box of powercells" - desc = "Contains powercells." - illustration = "power_cell" - -/obj/item/storage/box/ammoshells - name = "box of loose ammo" - desc = "Contains loose ammo." - illustration = "loose_ammo" - -/obj/item/storage/box/otwo - name = "box of o2 supplies" - desc = "Contains o2 supplies." - illustration = "02" - -/obj/item/storage/box/otwo/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - -/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/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/medsprays - name = "box of medical sprayers" - desc = "A box full of medical sprayers, with unscrewable caps and precision spray heads." - -/obj/item/storage/box/medsprays/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/medspray( src ) - -/obj/item/storage/box/injectors - name = "box of DNA injectors" - desc = "This box contains injectors, it seems." - -/obj/item/storage/box/injectors/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/dnainjector/h2m(src) - for(var/i in 1 to 3) - new /obj/item/dnainjector/m2h(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/flashes - name = "box of flashbulbs" - desc = "WARNING: Flashes can cause serious eye damage, protective eyewear is required." - icon_state = "secbox" - illustration = "flashbang" - -/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." - illustration = "flashbang" - -/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." - illustration = "flashbang" - -/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 = "flashbang" - -/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." - illustration = "implant" - -/obj/item/storage/box/trackimp/PopulateContents() - for(var/i in 1 to 4) - new /obj/item/implantcase/tracking(src) - new /obj/item/implanter(src) - new /obj/item/implantpad(src) - new /obj/item/locator(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() - for(var/i in 1 to 3) - new /obj/item/implantcase/tracking(src) - new /obj/item/implanter(src) - new /obj/item/implantpad(src) - new /obj/item/locator(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() - for(var/i in 1 to 5) - new /obj/item/implantcase/chem(src) - new /obj/item/implanter(src) - new /obj/item/implantpad(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() - for(var/i in 1 to 5) - new /obj/item/implantcase/exile(src) - new /obj/item/implanter(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." - -/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." - -/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." - -/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 - -/obj/item/storage/box/donkpockets/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket)) - -/obj/item/storage/box/donkpockets/PopulateContents() - for(var/i in 1 to 6) - new /obj/item/reagent_containers/food/snacks/donkpocket(src) - -/obj/item/storage/box/monkeycubes - name = "monkey cube box" - desc = "Drymate brand monkey cubes. Just add water!" - icon_state = "monkeycubebox" - illustration = null - -/obj/item/storage/box/monkeycubes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 7 - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/monkeycube)) - -/obj/item/storage/box/monkeycubes/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/reagent_containers/food/snacks/monkeycube(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() - new /obj/item/pda(src) - new /obj/item/pda(src) - new /obj/item/pda(src) - 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." - 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." - 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." - illustration = "firing_pins" - -/obj/item/storage/box/firingpins/PopulateContents() - for(var/i in 1 to 5) - new /obj/item/firing_pin(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 = "firing_pins" - -/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" - -/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 = "mousetraps" - -/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.can_hold = typecacheof(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" - item_state = "zippo" - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_BELT - -/obj/item/storage/box/matches/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 10 - STR.can_hold = typecacheof(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." - item_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.can_hold = typecacheof(list(/obj/item/light/tube, /obj/item/light/bulb)) - STR.max_combined_w_class = 21 - STR.click_gather = TRUE - -/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." - -/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 = "flashbang" - -/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 = "flashbang" - -/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, 1, -5) - user.visible_message("[user] hugs \the [src].","You hug \the [src].") - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT,"hugbox", /datum/mood_event/hugbox) - -/////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 an 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 buckshot (Lethal)" - 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/lethalslugs - name = "box of 12g shotgun slugs" - desc = "A box full of lethal 12g slug, designed for riot shotguns." - icon_state = "12g_box" - illustration = null - -/obj/item/storage/box/lethalslugs/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun(src) - -/obj/item/storage/box/stunslug - name = "box of stun slugs" - desc = "A box full of stun 12g slugs." - icon_state = "stunslug_box" - illustration = null - -/obj/item/storage/box/stunslug/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/stunslug(src) - -/obj/item/storage/box/techsslug - name = "box of tech shotgun shells" - desc = "A box full of tech shotgun shells." - icon_state = "techslug_box" - illustration = null - -/obj/item/storage/box/techsslug/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/techshell(src) - -/obj/item/storage/box/fireshot - name = "box of incendiary ammo" - desc = "A box full of incendiary ammo." - icon_state = "fireshot_box" - illustration = null - -/obj/item/storage/box/fireshot/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/ammo_casing/shotgun/incendiary(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/mechfigures - name = "box of mech figures" - desc = "The latest set of collectable mech figures." - icon_state = "box" - -/obj/item/storage/box/mechfigures/PopulateContents() - for(var/i in 1 to 4) - var/randomFigure = pick(subtypesof(/obj/item/toy/prize/)) - new randomFigure(src) - - - - -#define NODESIGN "None" -#define NANOTRASEN "NanotrasenStandard" -#define SYNDI "SyndiSnacks" -#define HEART "Heart" -#define SMILEY "SmileyFace" - -/obj/item/storage/box/papersack - name = "paper sack" - desc = "A sack neatly crafted out of paper." - icon_state = "paperbag_None" - item_state = "paperbag_None" - resistance_flags = FLAMMABLE - foldable = null - var/design = NODESIGN - -/obj/item/storage/box/papersack/update_icon() - if(contents.len == 0) - icon_state = "[item_state]" - else icon_state = "[item_state]_closed" - -/obj/item/storage/box/papersack/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pen)) - //if a pen is used on the sack, dialogue to change its design appears - if(contents.len) - to_chat(user, "You can't modify [src] with items still inside!") - return - var/list/designs = list(NODESIGN, NANOTRASEN, SYNDI, HEART, SMILEY, "Cancel") - var/switchDesign = input("Select a Design:", "Paper Sack Design", designs[1]) in designs - if(get_dist(usr, src) > 1) - to_chat(usr, "You have moved too far away!") - return - var/choice = designs.Find(switchDesign) - if(design == designs[choice] || designs[choice] == "Cancel") - return 0 - to_chat(usr, "You make some modifications to [src] using your pen.") - design = designs[choice] - icon_state = "paperbag_[design]" - item_state = "paperbag_[design]" - switch(designs[choice]) - if(NODESIGN) - desc = "A sack neatly crafted out of paper." - if(NANOTRASEN) - desc = "A standard Nanotrasen paper lunch sack for loyal employees on the go." - if(SYNDI) - 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(SMILEY) - desc = "A paper sack with a crude smile etched onto the side." - return 0 - else if(W.get_sharpness()) - if(!contents.len) - if(item_state == "paperbag_None") - user.show_message("You cut eyeholes into [src].", MSG_VISUAL) - new /obj/item/clothing/head/papersack(user.loc) - qdel(src) - return 0 - else if(item_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 0 - return ..() - -#undef NODESIGN -#undef NANOTRASEN -#undef SYNDI -#undef HEART -#undef SMILEY - -/obj/item/storage/box/ingredients //This box is for the randomly 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]'." - item_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/faggot(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/faggot(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/faggot(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" - -/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." - -/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." - -/obj/item/storage/box/silver_sulf/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/pill/patch/silver_sulf(src) - -/obj/item/storage/box/fountainpens - name = "box of fountain pens" - -/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 = "flashbang" - -/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() - new /obj/item/stock_parts/capacitor(src) - new /obj/item/stock_parts/capacitor(src) - new /obj/item/stock_parts/capacitor(src) - new /obj/item/stock_parts/scanning_module(src) - new /obj/item/stock_parts/scanning_module(src) - new /obj/item/stock_parts/scanning_module(src) - new /obj/item/stock_parts/manipulator(src) - new /obj/item/stock_parts/manipulator(src) - new /obj/item/stock_parts/manipulator(src) - new /obj/item/stock_parts/micro_laser(src) - new /obj/item/stock_parts/micro_laser(src) - new /obj/item/stock_parts/micro_laser(src) - new /obj/item/stock_parts/matter_bin(src) - new /obj/item/stock_parts/matter_bin(src) - new /obj/item/stock_parts/matter_bin(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() - new /obj/item/stock_parts/capacitor/quadratic(src) - new /obj/item/stock_parts/capacitor/quadratic(src) - new /obj/item/stock_parts/capacitor/quadratic(src) - new /obj/item/stock_parts/scanning_module/triphasic(src) - new /obj/item/stock_parts/scanning_module/triphasic(src) - new /obj/item/stock_parts/scanning_module/triphasic(src) - new /obj/item/stock_parts/manipulator/femto(src) - new /obj/item/stock_parts/manipulator/femto(src) - new /obj/item/stock_parts/manipulator/femto(src) - new /obj/item/stock_parts/micro_laser/quadultra(src) - new /obj/item/stock_parts/micro_laser/quadultra(src) - new /obj/item/stock_parts/micro_laser/quadultra(src) - new /obj/item/stock_parts/matter_bin/bluespace(src) - new /obj/item/stock_parts/matter_bin/bluespace(src) - new /obj/item/stock_parts/matter_bin/bluespace(src) - -//Colored boxes. -/obj/item/storage/box/green - icon_state = "box_green" - illustration = null - -/obj/item/storage/box/blue - icon_state = "box_blue" - illustration = null - -/obj/item/storage/box/purple - icon_state = "box_purple" - illustration = null - -/obj/item/storage/box/red - icon_state = "box_red" - illustration = null - -/obj/item/storage/box/yellow - icon_state = "box_yellow" - illustration = null - -/obj/item/storage/box/brown - icon_state = "box_brown" - illustration = null - -/obj/item/storage/box/pink - icon_state = "box_pink" - illustration = null - -/obj/item/storage/box/mre //base MRE type. - name = "Nanotrasen MRE Ration Kit Menu 0" - desc = "A package containing food suspended in an outdated bluespace pocket which lasts for centuries. If you're lucky you may even be able to enjoy the meal without getting food poisoning." - icon_state = "mre" - illustration = null - var/can_expire = TRUE - var/spawner_chance = 2 - var/expiration_date - var/expiration_date_min = 2300 - var/expiration_date_max = 2700 - -/obj/item/storage/box/mre/Initialize() - . = ..() - if(can_expire) - expiration_date = rand(expiration_date_min, expiration_date_max) - desc += "\nAn expiry date is listed on it. It reads: [expiration_date]
                " - var/spess_current_year = GLOB.year_integer - if(expiration_date < spess_current_year) - var/gross_risk = min(round(spess_current_year - expiration_date * 0.1), 1) - var/toxic_risk = min(round(spess_current_year - expiration_date * 0.01), 1) - for(var/obj/item/reagent_containers/food/snacks/S in contents) - if(prob(gross_risk)) - ENABLE_BITFIELD(S.foodtype, GROSS) - if(prob(toxic_risk)) - ENABLE_BITFIELD(S.foodtype, TOXIC) - -/obj/item/storage/box/mre/menu1 - name = "\improper Nanotrasen MRE Ration Kit Menu 1" - -/obj/item/storage/box/mre/menu1/safe - desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." - spawner_chance = 0 - can_expire = FALSE - -/obj/item/storage/box/mre/menu1/PopulateContents() - new /obj/item/reagent_containers/food/snacks/breadslice/plain(src) - new /obj/item/reagent_containers/food/snacks/breadslice/creamcheese(src) - new /obj/item/reagent_containers/food/condiment/pack/ketchup(src) - new /obj/item/reagent_containers/food/snacks/chocolatebar(src) - new /obj/item/tank/internals/emergency_oxygen(src) - -/obj/item/storage/box/mre/menu2 - name = "\improper Nanotrasen MRE Ration Kit Menu 2" - -/obj/item/storage/box/mre/menu2/safe - spawner_chance = 0 - desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." - can_expire = FALSE - -/obj/item/storage/box/mre/menu2/PopulateContents() - new /obj/item/reagent_containers/food/snacks/omelette(src) - new /obj/item/reagent_containers/food/snacks/meat/cutlet/plain(src) - new /obj/item/reagent_containers/food/snacks/fries(src) - new /obj/item/reagent_containers/food/snacks/chocolatebar(src) - new /obj/item/tank/internals/emergency_oxygen(src) - -/obj/item/storage/box/mre/menu3 - name = "\improper Nanotrasen MRE Ration Kit Menu 3" - desc = "The holy grail of MREs. This item contains the fabled MRE pizza, spicy nachos and a sample of coffee instant type 2. Any NT employee lucky enough to get their hands on one of these is truly blessed." - icon_state = "menu3" - can_expire = FALSE //always fresh, never expired. - spawner_chance = 1 - -/obj/item/storage/box/mre/menu3/PopulateContents() - new /obj/item/reagent_containers/food/snacks/pizzaslice/pepperoni(src) - new /obj/item/reagent_containers/food/snacks/breadslice/plain(src) - new /obj/item/reagent_containers/food/snacks/cubannachos(src) - new /obj/item/reagent_containers/food/snacks/grown/chili(src) - new /obj/item/reagent_containers/food/drinks/coffee/type2(src) - new /obj/item/tank/internals/emergency_oxygen(src) - -/obj/item/storage/box/mre/menu4 - name = "\improper Nanotrasen MRE Ration Kit Menu 4" - -/obj/item/storage/box/mre/menu4/safe - spawner_chance = 0 - desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." - can_expire = FALSE - -/obj/item/storage/box/mre/menu4/PopulateContents() - if(prob(66)) - new /obj/item/reagent_containers/food/snacks/salad/boiledrice(src) - else - new /obj/item/reagent_containers/food/snacks/salad/ricebowl(src) - new /obj/item/reagent_containers/food/snacks/burger/tofu(src) - new /obj/item/reagent_containers/food/snacks/salad/fruit(src) - new /obj/item/reagent_containers/food/snacks/cracker(src) - new /obj/item/tank/internals/emergency_oxygen(src) - -//Where do I put this? -/obj/item/secbat - name = "Secbat box" - desc = "Contained inside is a secbat for use with law enforcement." - icon = 'icons/obj/storage.dmi' - icon_state = "box" - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - -/obj/item/secbat/attack_self(mob/user) - new /mob/living/simple_animal/hostile/retaliate/bat/secbat(user.loc) - to_chat(user, "You open the box, releasing the secbat!") - var/obj/item/stack/sheet/cardboard/I = new(user.drop_location()) - qdel(src) - user.put_in_hands(I) - -/obj/item/storage/box/marshmallow - name = "box of marshmallows" - desc = "A box of marshmallows." - illustration = "marshmallow" - -/obj/item/storage/box/marshmallow/PopulateContents() - for (var/i in 1 to 5) +/* + * 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, + * Ammo types, + * Action Figure Boxes, + * Various paper bags, + * Colored boxes + * + * For syndicate call-ins see uplink_kits.dm + */ + +/obj/item/storage/box + name = "box" + desc = "It's just an ordinary box." + icon_state = "box" + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + resistance_flags = FLAMMABLE + var/foldable = /obj/item/stack/sheet/cardboard + var/illustration = "writing" + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //exploits ahoy + +/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,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -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_icon() + . = ..() + if(illustration) + cut_overlays() + add_overlay(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/packageWrap)) + return 0 + return ..() + +//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_plantgene + name = "plant data disks box" + illustration = "disk_kit" + +/obj/item/storage/box/disks_plantgene/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/disk/plantgene(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/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/survival/radio/PopulateContents() + ..() // we want the survival stuff too. + new /obj/item/radio/off(src) + +/obj/item/storage/box/survival_mining/PopulateContents() + new /obj/item/clothing/mask/gas/explorer(src) + new /obj/item/crowbar/red(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) + +// Engineer survival box +/obj/item/storage/box/engineer/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/engi(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +/obj/item/storage/box/engineer/radio/PopulateContents() + ..() // we want the regular items too. + new /obj/item/radio/off(src) + +// Syndie survival box +/obj/item/storage/box/syndie/PopulateContents() + new /obj/item/clothing/mask/gas/syndicate(src) + + if(!isplasmaman(loc)) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + else + new /obj/item/tank/internals/plasmaman/belt(src) + +// Security survival box +/obj/item/storage/box/security/PopulateContents() + new /obj/item/clothing/mask/gas/sechailer(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/security/radio/PopulateContents() + ..() // we want the regular stuff too + new /obj/item/radio/off(src) + +/obj/item/storage/box/seclooking + icon_state = "secbox" + illustration = null + +/obj/item/storage/box/cells + name = "box of powercells" + desc = "Contains powercells." + illustration = "power_cell" + +/obj/item/storage/box/ammoshells + name = "box of loose ammo" + desc = "Contains loose ammo." + illustration = "loose_ammo" + +/obj/item/storage/box/otwo + name = "box of o2 supplies" + desc = "Contains o2 supplies." + illustration = "02" + +/obj/item/storage/box/otwo/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/tank/internals/emergency_oxygen/engi(src) + +/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/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/medsprays + name = "box of medical sprayers" + desc = "A box full of medical sprayers, with unscrewable caps and precision spray heads." + +/obj/item/storage/box/medsprays/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/medspray( src ) + +/obj/item/storage/box/injectors + name = "box of DNA injectors" + desc = "This box contains injectors, it seems." + +/obj/item/storage/box/injectors/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/dnainjector/h2m(src) + for(var/i in 1 to 3) + new /obj/item/dnainjector/m2h(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/flashes + name = "box of flashbulbs" + desc = "WARNING: Flashes can cause serious eye damage, protective eyewear is required." + icon_state = "secbox" + illustration = "flashbang" + +/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." + illustration = "flashbang" + +/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." + illustration = "flashbang" + +/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 = "flashbang" + +/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." + illustration = "implant" + +/obj/item/storage/box/trackimp/PopulateContents() + for(var/i in 1 to 4) + new /obj/item/implantcase/tracking(src) + new /obj/item/implanter(src) + new /obj/item/implantpad(src) + new /obj/item/locator(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() + for(var/i in 1 to 3) + new /obj/item/implantcase/tracking(src) + new /obj/item/implanter(src) + new /obj/item/implantpad(src) + new /obj/item/locator(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() + for(var/i in 1 to 5) + new /obj/item/implantcase/chem(src) + new /obj/item/implanter(src) + new /obj/item/implantpad(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() + for(var/i in 1 to 5) + new /obj/item/implantcase/exile(src) + new /obj/item/implanter(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." + +/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." + +/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." + +/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 + +/obj/item/storage/box/donkpockets/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donkpocket)) + +/obj/item/storage/box/donkpockets/PopulateContents() + for(var/i in 1 to 6) + new /obj/item/reagent_containers/food/snacks/donkpocket(src) + +/obj/item/storage/box/monkeycubes + name = "monkey cube box" + desc = "Drymate brand monkey cubes. Just add water!" + icon_state = "monkeycubebox" + illustration = null + +/obj/item/storage/box/monkeycubes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 7 + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/monkeycube)) + +/obj/item/storage/box/monkeycubes/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/reagent_containers/food/snacks/monkeycube(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() + new /obj/item/pda(src) + new /obj/item/pda(src) + new /obj/item/pda(src) + 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." + 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." + 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." + illustration = "firing_pins" + +/obj/item/storage/box/firingpins/PopulateContents() + for(var/i in 1 to 5) + new /obj/item/firing_pin(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 = "firing_pins" + +/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" + +/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 = "mousetraps" + +/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.can_hold = typecacheof(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" + item_state = "zippo" + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_BELT + +/obj/item/storage/box/matches/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 10 + STR.can_hold = typecacheof(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." + item_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.can_hold = typecacheof(list(/obj/item/light/tube, /obj/item/light/bulb)) + STR.max_combined_w_class = 21 + STR.click_gather = TRUE + +/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." + +/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 = "flashbang" + +/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 = "flashbang" + +/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, 1, -5) + user.visible_message("[user] hugs \the [src].","You hug \the [src].") + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT,"hugbox", /datum/mood_event/hugbox) + +/////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 an 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 buckshot (Lethal)" + 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/lethalslugs + name = "box of 12g shotgun slugs" + desc = "A box full of lethal 12g slug, designed for riot shotguns." + icon_state = "12g_box" + illustration = null + +/obj/item/storage/box/lethalslugs/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun(src) + +/obj/item/storage/box/stunslug + name = "box of stun slugs" + desc = "A box full of stun 12g slugs." + icon_state = "stunslug_box" + illustration = null + +/obj/item/storage/box/stunslug/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/stunslug(src) + +/obj/item/storage/box/techsslug + name = "box of tech shotgun shells" + desc = "A box full of tech shotgun shells." + icon_state = "techslug_box" + illustration = null + +/obj/item/storage/box/techsslug/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/techshell(src) + +/obj/item/storage/box/fireshot + name = "box of incendiary ammo" + desc = "A box full of incendiary ammo." + icon_state = "fireshot_box" + illustration = null + +/obj/item/storage/box/fireshot/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/ammo_casing/shotgun/incendiary(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/mechfigures + name = "box of mech figures" + desc = "The latest set of collectable mech figures." + icon_state = "box" + +/obj/item/storage/box/mechfigures/PopulateContents() + for(var/i in 1 to 4) + var/randomFigure = pick(subtypesof(/obj/item/toy/prize/)) + new randomFigure(src) + + + + +#define NODESIGN "None" +#define NANOTRASEN "NanotrasenStandard" +#define SYNDI "SyndiSnacks" +#define HEART "Heart" +#define SMILEY "SmileyFace" + +/obj/item/storage/box/papersack + name = "paper sack" + desc = "A sack neatly crafted out of paper." + icon_state = "paperbag_None" + item_state = "paperbag_None" + resistance_flags = FLAMMABLE + foldable = null + var/design = NODESIGN + +/obj/item/storage/box/papersack/update_icon() + if(contents.len == 0) + icon_state = "[item_state]" + else icon_state = "[item_state]_closed" + +/obj/item/storage/box/papersack/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pen)) + //if a pen is used on the sack, dialogue to change its design appears + if(contents.len) + to_chat(user, "You can't modify [src] with items still inside!") + return + var/list/designs = list(NODESIGN, NANOTRASEN, SYNDI, HEART, SMILEY, "Cancel") + var/switchDesign = input("Select a Design:", "Paper Sack Design", designs[1]) in designs + if(get_dist(usr, src) > 1) + to_chat(usr, "You have moved too far away!") + return + var/choice = designs.Find(switchDesign) + if(design == designs[choice] || designs[choice] == "Cancel") + return 0 + to_chat(usr, "You make some modifications to [src] using your pen.") + design = designs[choice] + icon_state = "paperbag_[design]" + item_state = "paperbag_[design]" + switch(designs[choice]) + if(NODESIGN) + desc = "A sack neatly crafted out of paper." + if(NANOTRASEN) + desc = "A standard Nanotrasen paper lunch sack for loyal employees on the go." + if(SYNDI) + 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(SMILEY) + desc = "A paper sack with a crude smile etched onto the side." + return 0 + else if(W.get_sharpness()) + if(!contents.len) + if(item_state == "paperbag_None") + user.show_message("You cut eyeholes into [src].", MSG_VISUAL) + new /obj/item/clothing/head/papersack(user.loc) + qdel(src) + return 0 + else if(item_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 0 + return ..() + +#undef NODESIGN +#undef NANOTRASEN +#undef SYNDI +#undef HEART +#undef SMILEY + +/obj/item/storage/box/ingredients //This box is for the randomly 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]'." + item_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/faggot(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/faggot(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/faggot(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" + +/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." + +/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." + +/obj/item/storage/box/silver_sulf/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/pill/patch/silver_sulf(src) + +/obj/item/storage/box/fountainpens + name = "box of fountain pens" + +/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 = "flashbang" + +/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() + new /obj/item/stock_parts/capacitor(src) + new /obj/item/stock_parts/capacitor(src) + new /obj/item/stock_parts/capacitor(src) + new /obj/item/stock_parts/scanning_module(src) + new /obj/item/stock_parts/scanning_module(src) + new /obj/item/stock_parts/scanning_module(src) + new /obj/item/stock_parts/manipulator(src) + new /obj/item/stock_parts/manipulator(src) + new /obj/item/stock_parts/manipulator(src) + new /obj/item/stock_parts/micro_laser(src) + new /obj/item/stock_parts/micro_laser(src) + new /obj/item/stock_parts/micro_laser(src) + new /obj/item/stock_parts/matter_bin(src) + new /obj/item/stock_parts/matter_bin(src) + new /obj/item/stock_parts/matter_bin(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() + new /obj/item/stock_parts/capacitor/quadratic(src) + new /obj/item/stock_parts/capacitor/quadratic(src) + new /obj/item/stock_parts/capacitor/quadratic(src) + new /obj/item/stock_parts/scanning_module/triphasic(src) + new /obj/item/stock_parts/scanning_module/triphasic(src) + new /obj/item/stock_parts/scanning_module/triphasic(src) + new /obj/item/stock_parts/manipulator/femto(src) + new /obj/item/stock_parts/manipulator/femto(src) + new /obj/item/stock_parts/manipulator/femto(src) + new /obj/item/stock_parts/micro_laser/quadultra(src) + new /obj/item/stock_parts/micro_laser/quadultra(src) + new /obj/item/stock_parts/micro_laser/quadultra(src) + new /obj/item/stock_parts/matter_bin/bluespace(src) + new /obj/item/stock_parts/matter_bin/bluespace(src) + new /obj/item/stock_parts/matter_bin/bluespace(src) + +//Colored boxes. +/obj/item/storage/box/green + icon_state = "box_green" + illustration = null + +/obj/item/storage/box/blue + icon_state = "box_blue" + illustration = null + +/obj/item/storage/box/purple + icon_state = "box_purple" + illustration = null + +/obj/item/storage/box/red + icon_state = "box_red" + illustration = null + +/obj/item/storage/box/yellow + icon_state = "box_yellow" + illustration = null + +/obj/item/storage/box/brown + icon_state = "box_brown" + illustration = null + +/obj/item/storage/box/pink + icon_state = "box_pink" + illustration = null + +/obj/item/storage/box/mre //base MRE type. + name = "Nanotrasen MRE Ration Kit Menu 0" + desc = "A package containing food suspended in an outdated bluespace pocket which lasts for centuries. If you're lucky you may even be able to enjoy the meal without getting food poisoning." + icon_state = "mre" + illustration = null + var/can_expire = TRUE + var/spawner_chance = 2 + var/expiration_date + var/expiration_date_min = 2300 + var/expiration_date_max = 2700 + +/obj/item/storage/box/mre/Initialize() + . = ..() + if(can_expire) + expiration_date = rand(expiration_date_min, expiration_date_max) + desc += "\nAn expiry date is listed on it. It reads: [expiration_date]" + var/spess_current_year = GLOB.year_integer + if(expiration_date < spess_current_year) + var/gross_risk = min(round(spess_current_year - expiration_date * 0.1), 1) + var/toxic_risk = min(round(spess_current_year - expiration_date * 0.01), 1) + for(var/obj/item/reagent_containers/food/snacks/S in contents) + if(prob(gross_risk)) + ENABLE_BITFIELD(S.foodtype, GROSS) + if(prob(toxic_risk)) + ENABLE_BITFIELD(S.foodtype, TOXIC) + +/obj/item/storage/box/mre/menu1 + name = "\improper Nanotrasen MRE Ration Kit Menu 1" + +/obj/item/storage/box/mre/menu1/safe + desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." + spawner_chance = 0 + can_expire = FALSE + +/obj/item/storage/box/mre/menu1/PopulateContents() + new /obj/item/reagent_containers/food/snacks/breadslice/plain(src) + new /obj/item/reagent_containers/food/snacks/breadslice/creamcheese(src) + new /obj/item/reagent_containers/food/condiment/pack/ketchup(src) + new /obj/item/reagent_containers/food/snacks/chocolatebar(src) + new /obj/item/tank/internals/emergency_oxygen(src) + +/obj/item/storage/box/mre/menu2 + name = "\improper Nanotrasen MRE Ration Kit Menu 2" + +/obj/item/storage/box/mre/menu2/safe + spawner_chance = 0 + desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." + can_expire = FALSE + +/obj/item/storage/box/mre/menu2/PopulateContents() + new /obj/item/reagent_containers/food/snacks/omelette(src) + new /obj/item/reagent_containers/food/snacks/meat/cutlet/plain(src) + new /obj/item/reagent_containers/food/snacks/fries(src) + new /obj/item/reagent_containers/food/snacks/chocolatebar(src) + new /obj/item/tank/internals/emergency_oxygen(src) + +/obj/item/storage/box/mre/menu3 + name = "\improper Nanotrasen MRE Ration Kit Menu 3" + desc = "The holy grail of MREs. This item contains the fabled MRE pizza, spicy nachos and a sample of coffee instant type 2. Any NT employee lucky enough to get their hands on one of these is truly blessed." + icon_state = "menu3" + can_expire = FALSE //always fresh, never expired. + spawner_chance = 1 + +/obj/item/storage/box/mre/menu3/PopulateContents() + new /obj/item/reagent_containers/food/snacks/pizzaslice/pepperoni(src) + new /obj/item/reagent_containers/food/snacks/breadslice/plain(src) + new /obj/item/reagent_containers/food/snacks/cubannachos(src) + new /obj/item/reagent_containers/food/snacks/grown/chili(src) + new /obj/item/reagent_containers/food/drinks/coffee/type2(src) + new /obj/item/tank/internals/emergency_oxygen(src) + +/obj/item/storage/box/mre/menu4 + name = "\improper Nanotrasen MRE Ration Kit Menu 4" + +/obj/item/storage/box/mre/menu4/safe + spawner_chance = 0 + desc = "A package containing food suspended in a bluespace pocket capable of lasting till the end of time." + can_expire = FALSE + +/obj/item/storage/box/mre/menu4/PopulateContents() + if(prob(66)) + new /obj/item/reagent_containers/food/snacks/salad/boiledrice(src) + else + new /obj/item/reagent_containers/food/snacks/salad/ricebowl(src) + new /obj/item/reagent_containers/food/snacks/burger/tofu(src) + new /obj/item/reagent_containers/food/snacks/salad/fruit(src) + new /obj/item/reagent_containers/food/snacks/cracker(src) + new /obj/item/tank/internals/emergency_oxygen(src) + +//Where do I put this? +/obj/item/secbat + name = "Secbat box" + desc = "Contained inside is a secbat for use with law enforcement." + icon = 'icons/obj/storage.dmi' + icon_state = "box" + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + +/obj/item/secbat/attack_self(mob/user) + new /mob/living/simple_animal/hostile/retaliate/bat/secbat(user.loc) + to_chat(user, "You open the box, releasing the secbat!") + var/obj/item/stack/sheet/cardboard/I = new(user.drop_location()) + qdel(src) + user.put_in_hands(I) + +/obj/item/storage/box/marshmallow + name = "box of marshmallows" + desc = "A box of marshmallows." + illustration = "marshmallow" + +/obj/item/storage/box/marshmallow/PopulateContents() + for (var/i in 1 to 5) new /obj/item/reagent_containers/food/snacks/marshmallow(src) \ No newline at end of file diff --git a/code/game/objects/items/storage/briefcase.dm b/code/game/objects/items/storage/briefcase.dm index 782ffd3add..a0ec1c2306 100644 --- a/code/game/objects/items/storage/briefcase.dm +++ b/code/game/objects/items/storage/briefcase.dm @@ -1,115 +1,115 @@ -/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/crafted - desc = "Hand crafted suitcase made of leather and cloth." - force = 6 - max_integrity = 50 - -/obj/item/storage/briefcase/crafted/PopulateContents() - return //So we dont spawn items - -/obj/item/storage/briefcase/lawyer - folder_path = /obj/item/folder/blue - -/obj/item/storage/briefcase/lawyer/family - name = "battered briefcase" - icon_state = "gbriefcase" - lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' - desc = "An old briefcase with a golden trim. It's clear they don't make them as good as they used to. Comes with an added belt clip!" - slot_flags = ITEM_SLOT_BELT - -/obj/item/storage/briefcase/lawyer/family/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 14 - -/obj/item/storage/briefcase/lawyer/family/PopulateContents() - new /obj/item/stamp/law(src) - new /obj/item/pen/fountain(src) - new /obj/item/paper_bin(src) - new /obj/item/storage/box/evidence(src) - new /obj/item/storage/box/lawyer(src) - -/obj/item/storage/box/lawyer - name = "Box of lawyer tools" - desc = "A custom made box, full of items used by a Lawyer, all packed into one box!" - -/obj/item/storage/box/lawyer/PopulateContents() - new /obj/item/clothing/gloves/color/white(src) - new /obj/item/folder/white(src) - new /obj/item/clothing/glasses/regular(src) - new /obj/item/folder/red(src) - new /obj/item/gavelhammer(src) - new /obj/item/stack/sheet/cloth(src) - new /obj/item/reagent_containers/glass/beaker/waterbottle(src) - -/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/modularbundle - desc = "Its label reads \"genuine hardened Captain leather\", but suspiciously has no other tags or branding." - force = 10 - -/obj/item/storage/briefcase/modularbundle/PopulateContents() - new /obj/item/gun/ballistic/automatic/pistol/modular(src) - new /obj/item/suppressor(src) - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/ammo_box/magazine/m10mm/soporific(src) - new /obj/item/ammo_box/c10mm/soporific(src) - new /obj/item/clothing/under/lawyer/blacksuit(src) - new /obj/item/clothing/accessory/waistcoat(src) - new /obj/item/clothing/suit/toggle/lawyer/black/syndie(src) - -/obj/item/storage/briefcase/medical - name = "medical briefcase" - icon_state = "medbriefcase" - desc = "A white with a blue cross brieface, this is ment to hold medical gear that would not be able to normally fit in a bag." - -/obj/item/storage/briefcase/medical/PopulateContents() - new /obj/item/clothing/neck/stethoscope(src) - new /obj/item/healthanalyzer(src) - ..() //In case of paperwork - +/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/crafted + desc = "Hand crafted suitcase made of leather and cloth." + force = 6 + max_integrity = 50 + +/obj/item/storage/briefcase/crafted/PopulateContents() + return //So we dont spawn items + +/obj/item/storage/briefcase/lawyer + folder_path = /obj/item/folder/blue + +/obj/item/storage/briefcase/lawyer/family + name = "battered briefcase" + icon_state = "gbriefcase" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + desc = "An old briefcase with a golden trim. It's clear they don't make them as good as they used to. Comes with an added belt clip!" + slot_flags = ITEM_SLOT_BELT + +/obj/item/storage/briefcase/lawyer/family/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 14 + +/obj/item/storage/briefcase/lawyer/family/PopulateContents() + new /obj/item/stamp/law(src) + new /obj/item/pen/fountain(src) + new /obj/item/paper_bin(src) + new /obj/item/storage/box/evidence(src) + new /obj/item/storage/box/lawyer(src) + +/obj/item/storage/box/lawyer + name = "Box of lawyer tools" + desc = "A custom made box, full of items used by a Lawyer, all packed into one box!" + +/obj/item/storage/box/lawyer/PopulateContents() + new /obj/item/clothing/gloves/color/white(src) + new /obj/item/folder/white(src) + new /obj/item/clothing/glasses/regular(src) + new /obj/item/folder/red(src) + new /obj/item/gavelhammer(src) + new /obj/item/stack/sheet/cloth(src) + new /obj/item/reagent_containers/glass/beaker/waterbottle(src) + +/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/modularbundle + desc = "Its label reads \"genuine hardened Captain leather\", but suspiciously has no other tags or branding." + force = 10 + +/obj/item/storage/briefcase/modularbundle/PopulateContents() + new /obj/item/gun/ballistic/automatic/pistol/modular(src) + new /obj/item/suppressor(src) + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/ammo_box/magazine/m10mm/soporific(src) + new /obj/item/ammo_box/c10mm/soporific(src) + new /obj/item/clothing/under/lawyer/blacksuit(src) + new /obj/item/clothing/accessory/waistcoat(src) + new /obj/item/clothing/suit/toggle/lawyer/black/syndie(src) + +/obj/item/storage/briefcase/medical + name = "medical briefcase" + icon_state = "medbriefcase" + desc = "A white with a blue cross brieface, this is ment to hold medical gear that would not be able to normally fit in a bag." + +/obj/item/storage/briefcase/medical/PopulateContents() + new /obj/item/clothing/neck/stethoscope(src) + new /obj/item/healthanalyzer(src) + ..() //In case of paperwork + diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm index f05078de81..78f306df6e 100644 --- a/code/game/objects/items/storage/fancy.dm +++ b/code/game/objects/items/storage/fancy.dm @@ -1,354 +1,354 @@ -/* - * 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' - icon_state = "donutbox6" - name = "donut box" - desc = "Mmm. Donuts." - 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() - 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() - -/* - * Donut Box - */ - -/obj/item/storage/fancy/donut_box - icon = 'icons/obj/food/containers.dmi' - icon_state = "donutbox6" - icon_type = "donut" - name = "donut box" - spawn_type = /obj/item/reagent_containers/food/snacks/donut - fancy_open = TRUE - -/obj/item/storage/fancy/donut_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donut)) - -/* - * Egg Box - */ - -/obj/item/storage/fancy/egg_box - icon = 'icons/obj/food/containers.dmi' - item_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.can_hold = typecacheof(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" - item_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" - item_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 - -/obj/item/storage/fancy/cigarettes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.can_hold = typecacheof(list(/obj/item/clothing/mask/cigarette, /obj/item/lighter)) - -/obj/item/storage/fancy/cigarettes/examine(mob/user) - . = ..() - . += "Alt-click to extract contents." - -/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.") - return TRUE - -/obj/item/storage/fancy/cigarettes/update_icon() - if(fancy_open || !contents.len) - cut_overlays() - if(!contents.len) - icon_state = "[initial(icon_state)]_empty" - else - icon_state = initial(icon_state) - add_overlay("[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 - inserted_overlay.icon_state = "cigarette" - - inserted_overlay.icon_state = "[inserted_overlay.icon_state]_[cig_position]" - add_overlay(inserted_overlay) - cig_position++ - else - cut_overlays() - -/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, SLOT_WEAR_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_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" - icon_type = "rolling paper" - spawn_type = /obj/item/rollingpaper - -/obj/item/storage/fancy/rollingpapers/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 10 - STR.can_hold = typecacheof(list(/obj/item/rollingpaper)) - -/obj/item/storage/fancy/rollingpapers/update_icon() - cut_overlays() - if(!contents.len) - add_overlay("[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 - -/obj/item/storage/fancy/cigarettes/cigars/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - STR.can_hold = typecacheof(list(/obj/item/clothing/mask/cigarette/cigar)) - -/obj/item/storage/fancy/cigarettes/cigars/update_icon() - cut_overlays() - if(fancy_open) - icon_state = "[initial(icon_state)]_open" - - var/cigar_position = 0 //to keep track of the pixel_x offset of each new overlay. - for(var/obj/item/clothing/mask/cigarette/cigar/smokes in contents) - var/mutable_appearance/cigar_overlay = mutable_appearance(icon, "[smokes.icon_off]") - cigar_overlay.pixel_x = 3 * cigar_position - add_overlay(cigar_overlay) - cigar_position++ - - else - icon_state = "[initial(icon_state)]" - -/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' - item_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.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/tinychocolate)) +/* + * 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' + icon_state = "donutbox6" + name = "donut box" + desc = "Mmm. Donuts." + 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() + 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() + +/* + * Donut Box + */ + +/obj/item/storage/fancy/donut_box + icon = 'icons/obj/food/containers.dmi' + icon_state = "donutbox6" + icon_type = "donut" + name = "donut box" + spawn_type = /obj/item/reagent_containers/food/snacks/donut + fancy_open = TRUE + +/obj/item/storage/fancy/donut_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donut)) + +/* + * Egg Box + */ + +/obj/item/storage/fancy/egg_box + icon = 'icons/obj/food/containers.dmi' + item_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.can_hold = typecacheof(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" + item_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" + item_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 + +/obj/item/storage/fancy/cigarettes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.can_hold = typecacheof(list(/obj/item/clothing/mask/cigarette, /obj/item/lighter)) + +/obj/item/storage/fancy/cigarettes/examine(mob/user) + . = ..() + . += "Alt-click to extract contents." + +/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.") + return TRUE + +/obj/item/storage/fancy/cigarettes/update_icon() + if(fancy_open || !contents.len) + cut_overlays() + if(!contents.len) + icon_state = "[initial(icon_state)]_empty" + else + icon_state = initial(icon_state) + add_overlay("[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 + inserted_overlay.icon_state = "cigarette" + + inserted_overlay.icon_state = "[inserted_overlay.icon_state]_[cig_position]" + add_overlay(inserted_overlay) + cig_position++ + else + cut_overlays() + +/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, SLOT_WEAR_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_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" + icon_type = "rolling paper" + spawn_type = /obj/item/rollingpaper + +/obj/item/storage/fancy/rollingpapers/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 10 + STR.can_hold = typecacheof(list(/obj/item/rollingpaper)) + +/obj/item/storage/fancy/rollingpapers/update_icon() + cut_overlays() + if(!contents.len) + add_overlay("[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 + +/obj/item/storage/fancy/cigarettes/cigars/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + STR.can_hold = typecacheof(list(/obj/item/clothing/mask/cigarette/cigar)) + +/obj/item/storage/fancy/cigarettes/cigars/update_icon() + cut_overlays() + if(fancy_open) + icon_state = "[initial(icon_state)]_open" + + var/cigar_position = 0 //to keep track of the pixel_x offset of each new overlay. + for(var/obj/item/clothing/mask/cigarette/cigar/smokes in contents) + var/mutable_appearance/cigar_overlay = mutable_appearance(icon, "[smokes.icon_off]") + cigar_overlay.pixel_x = 3 * cigar_position + add_overlay(cigar_overlay) + cigar_position++ + + else + icon_state = "[initial(icon_state)]" + +/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' + item_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.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/tinychocolate)) diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm index 3d27370334..454942f00b 100644 --- a/code/game/objects/items/storage/lockbox.dm +++ b/code/game/objects/items/storage/lockbox.dm @@ -1,201 +1,201 @@ -/obj/item/storage/lockbox - name = "lockbox" - desc = "A locked box." - icon_state = "lockbox+l" - item_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) - return - 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] has been broken by [user] with an electromagnetic card!") - return TRUE - -/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" - item_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.can_hold = typecacheof(list(/obj/item/clothing/accessory/medal)) - -/obj/item/storage/lockbox/medal/examine(mob/user) - . = ..() - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(!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() - return TRUE - -/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() - cut_overlays() - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(locked) - icon_state = "medalbox+l" - open = FALSE - else - icon_state = "medalbox" - if(open) - icon_state += "open" - if(broken) - icon_state += "+b" - if(contents && open) - 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) - add_overlay(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/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/medal/engineering - name = "engineering medal box" - desc = "A locked box used to store medals to be given to the members of the engineering department." - req_access = list(ACCESS_CE) - -/obj/item/storage/lockbox/medal/engineering/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/engineer(src) - -/obj/item/storage/lockbox/medal/medical - name = "medical medal box" - desc = "A locked box used to store medals to be given to the members of the medical department." - req_access = list(ACCESS_CMO) - -/obj/item/storage/lockbox/medal/medical/PopulateContents() - for(var/i in 1 to 3) +/obj/item/storage/lockbox + name = "lockbox" + desc = "A locked box." + icon_state = "lockbox+l" + item_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) + return + 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] has been broken by [user] with an electromagnetic card!") + return TRUE + +/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" + item_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.can_hold = typecacheof(list(/obj/item/clothing/accessory/medal)) + +/obj/item/storage/lockbox/medal/examine(mob/user) + . = ..() + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(!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() + return TRUE + +/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() + cut_overlays() + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(locked) + icon_state = "medalbox+l" + open = FALSE + else + icon_state = "medalbox" + if(open) + icon_state += "open" + if(broken) + icon_state += "+b" + if(contents && open) + 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) + add_overlay(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/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/medal/engineering + name = "engineering medal box" + desc = "A locked box used to store medals to be given to the members of the engineering department." + req_access = list(ACCESS_CE) + +/obj/item/storage/lockbox/medal/engineering/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/engineer(src) + +/obj/item/storage/lockbox/medal/medical + name = "medical medal box" + desc = "A locked box used to store medals to be given to the members of the medical department." + req_access = list(ACCESS_CMO) + +/obj/item/storage/lockbox/medal/medical/PopulateContents() + for(var/i in 1 to 3) new /obj/item/clothing/accessory/medal/ribbon/medical_doctor(src) \ No newline at end of file diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm index a2bf5773d1..e177a9f9fd 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 (istype(W, /obj/item/screwdriver)) - if (W.use_tool(src, user, 20)) - open =! open - to_chat(user, "You [open ? "open" : "close"] the service panel.") - return - if (istype(W, /obj/item/wirecutters)) - to_chat(user, "[src] is protected from this sort of tampering, yet it appears the internal memory wires can still be pulsed.") - if ((istype(W, /obj/item/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" - item_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.cant_hold = typecacheof(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 (istype(W, /obj/item/screwdriver)) + if (W.use_tool(src, user, 20)) + open =! open + to_chat(user, "You [open ? "open" : "close"] the service panel.") + return + if (istype(W, /obj/item/wirecutters)) + to_chat(user, "[src] is protected from this sort of tampering, yet it appears the internal memory wires can still be pulsed.") + if ((istype(W, /obj/item/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" + item_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.cant_hold = typecacheof(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 b69567a2a5..8f4cb99048 100644 --- a/code/game/objects/items/storage/storage.dm +++ b/code/game/objects/items/storage/storage.dm @@ -1,28 +1,28 @@ - -/obj/item/storage - name = "storage" - icon = 'icons/obj/storage.dmi' - w_class = WEIGHT_CLASS_NORMAL - 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) - A.ex_act(severity, target) - CHECK_TICK - -//Cyberboss says: "USE THIS TO FILL IT, NOT INITIALIZE OR NEW" - -/obj/item/storage/proc/PopulateContents() + +/obj/item/storage + name = "storage" + icon = 'icons/obj/storage.dmi' + w_class = WEIGHT_CLASS_NORMAL + 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) + A.ex_act(severity, target) + CHECK_TICK + +//Cyberboss says: "USE THIS TO FILL IT, NOT INITIALIZE OR NEW" + +/obj/item/storage/proc/PopulateContents() diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm index 70c3a5dd4b..31efabf210 100644 --- a/code/game/objects/items/storage/toolbox.dm +++ b/code/game/objects/items/storage/toolbox.dm @@ -1,332 +1,332 @@ -GLOBAL_LIST_EMPTY(rubber_toolbox_icons) - -/obj/item/storage/toolbox - name = "toolbox" - desc = "Danger. Very robust." - icon_state = "red" - item_state = "toolbox_red" - 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 - materials = list(MAT_METAL = 500) - attack_verb = list("robusted") - hitsound = 'sound/weapons/smash.ogg' - var/latches = "single_latch" - var/has_latches = TRUE - var/can_rubberify = TRUE - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //very protecc too - -/obj/item/storage/toolbox/Initialize(mapload) - . = ..() - if(has_latches) - if(prob(10)) - latches = "double_latch" - if(prob(1)) - latches = "triple_latch" - if(mapload && can_rubberify && prob(5)) - rubberify() - update_icon() - -/obj/item/storage/toolbox/update_icon() - ..() - cut_overlays() - if(blood_DNA && blood_DNA.len) - add_blood_overlay() - if(has_latches) - var/icon/I = icon('icons/obj/storage.dmi', latches) - add_overlay(I) - - -/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" - -/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 - can_rubberify = FALSE - -/obj/item/storage/toolbox/mechanical - name = "mechanical toolbox" - icon_state = "blue" - item_state = "toolbox_blue" - -/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 - can_rubberify = FALSE - -/obj/item/storage/toolbox/mechanical/old/heirloom - name = "old, robust toolbox" //this will be named "X family toolbox" - desc = "It's seen better days." - //Citadel change buffed to base levels - total_mass = 2 - -/obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents() - return - -/obj/item/storage/toolbox/electrical - name = "electrical toolbox" - icon_state = "yellow" - item_state = "toolbox_yellow" - -/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,30,pickedcolor) - new /obj/item/stack/cable_coil(src,30,pickedcolor) - if(prob(5)) - new /obj/item/clothing/gloves/color/yellow(src) - else - new /obj/item/stack/cable_coil(src,30,pickedcolor) - -/obj/item/storage/toolbox/syndicate - name = "black and red toolbox" - icon_state = "syndicate" - item_state = "toolbox_syndi" - desc = "A toolbox painted black with a red stripe. It looks more heavier than normal toolboxes." - force = 15 - throwforce = 18 - -/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" - item_state = "toolbox_blue" - -/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,30,pickedcolor) - new /obj/item/wirecutters(src) - new /obj/item/multitool(src) - -/obj/item/storage/toolbox/brass - name = "brass box" - desc = "A huge brass box with several indentations in its surface." - icon_state = "brassbox" - item_state = null - has_latches = FALSE - resistance_flags = FIRE_PROOF | ACID_PROOF - w_class = WEIGHT_CLASS_HUGE - attack_verb = list("robusted", "crushed", "smashed") - can_rubberify = FALSE - var/fabricator_type = /obj/item/clockwork/replica_fabricator/scarab - -/obj/item/storage/toolbox/brass/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 28 - STR.max_items = 28 - -/obj/item/storage/toolbox/brass/prefilled/PopulateContents() - new fabricator_type(src) - new /obj/item/screwdriver/brass(src) - new /obj/item/wirecutters/brass(src) - new /obj/item/wrench/brass(src) - new /obj/item/crowbar/brass(src) - new /obj/item/weldingtool/experimental/brass(src) - -/obj/item/storage/toolbox/brass/prefilled/servant - slot_flags = ITEM_SLOT_BELT - fabricator_type = null - -/obj/item/storage/toolbox/brass/prefilled/ratvar - var/slab_type = /obj/item/clockwork/slab - -/obj/item/storage/toolbox/brass/prefilled/ratvar/PopulateContents() - ..() - new slab_type(src) - -/obj/item/storage/toolbox/brass/prefilled/ratvar/admin - slab_type = /obj/item/clockwork/slab/debug - fabricator_type = /obj/item/clockwork/replica_fabricator/scarab/debug - -/obj/item/storage/toolbox/plastitanium - name = "plastitanium toolbox" - desc = "A toolbox made out of plastitanium. Probably packs a massive punch." - total_mass = 5 - icon_state = "blue" - item_state = "toolbox_blue" - w_class = WEIGHT_CLASS_HUGE //heyo no bohing this! - force = 18 //spear damage - can_rubberify = FALSE - -/obj/item/storage/toolbox/plastitanium/afterattack(atom/A, mob/user, proximity) - . = ..() - if(proximity && isobj(A) && !isitem(A)) - var/obj/O = A - //50 total object damage but split up for stuff like damage deflection. - O.take_damage(22) - O.take_damage(10) - -/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" - item_state = "toolbox_green" - w_class = WEIGHT_CLASS_GIGANTIC //Holds more than a regular toolbox! - -/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/cable_coil/red(src) - new /obj/item/stack/cable_coil/yellow(src) - new /obj/item/stack/cable_coil/blue(src) - new /obj/item/stack/cable_coil/green(src) - new /obj/item/stack/cable_coil/pink(src) - new /obj/item/stack/cable_coil/orange(src) - new /obj/item/stack/cable_coil/cyan(src) - new /obj/item/stack/cable_coil/white(src) - -/obj/item/storage/toolbox/ammo - name = "ammo box" - desc = "It contains a few clips." - icon_state = "ammobox" - item_state = "ammobox" - -/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/gold_real - name = "golden toolbox" - desc = "A larger then normal toolbox made of gold plated plastitanium." - icon_state = "gold" - item_state = "toolbox_gold" - has_latches = FALSE - force = 16 // Less then a spear - throwforce = 14 - throw_speed = 5 - throw_range = 10 - -/obj/item/storage/toolbox/gold_real/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/ai_detect(src) - new /obj/item/clothing/gloves/combat(src) - -/obj/item/storage/toolbox/gold_real/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_combined_w_class = 40 - STR.max_items = 12 - -/obj/item/storage/toolbox/gold_fake // used in crafting - name = "golden toolbox" - desc = "A gold plated toolbox, fancy and harmless due to the gold plating being on cardboard!" - icon_state = "gold" - item_state = "toolbox_gold" - has_latches = FALSE - force = 0 - throwforce = 0 - can_rubberify = FALSE - -/obj/item/storage/toolbox/proc/rubberify() - name = "rubber [name]" - desc = replacetext(desc, "Danger", "Bouncy") - desc = replacetext(desc, "robust", "safe") - desc = replacetext(desc, "heavier", "bouncier") - DISABLE_BITFIELD(flags_1, CONDUCT_1) - materials = null - damtype = STAMINA - force += 3 //to compensate the higher stamina K.O. threshold compared to actual health. - throwforce += 3 - attack_verb += "bounced" - hitsound = 'sound/effects/clownstep1.ogg' - if(!GLOB.rubber_toolbox_icons[icon_state]) - generate_rubber_toolbox_icon() - icon = GLOB.rubber_toolbox_icons[icon_state] - AddComponent(/datum/component/bouncy) - -/obj/item/storage/toolbox/proc/generate_rubber_toolbox_icon() - var/icon/new_icon = icon(icon, icon_state) - var/icon/smooth = icon('icons/obj/storage.dmi', "rubber_toolbox_blend") - new_icon.Blend(smooth, ICON_MULTIPLY) - new_icon = fcopy_rsc(new_icon) - GLOB.rubber_toolbox_icons[icon_state] = new_icon - -/obj/item/storage/toolbox/rubber - name = "rubber toolbox" - desc = "Bouncy. Very safe." - flags_1 = null - materials = null - damtype = STAMINA - force = 17 - throwforce = 17 - attack_verb = list("robusted", "bounced") - can_rubberify = FALSE //we are already the future. - -/obj/item/storage/toolbox/rubber/Initialize() - icon_state = pick("blue", "red", "yellow", "green") - item_state = "toolbox_[icon_state]" - if(!GLOB.rubber_toolbox_icons[icon_state]) - generate_rubber_toolbox_icon() - icon = GLOB.rubber_toolbox_icons[icon_state] - . = ..() - AddComponent(/datum/component/bouncy) +GLOBAL_LIST_EMPTY(rubber_toolbox_icons) + +/obj/item/storage/toolbox + name = "toolbox" + desc = "Danger. Very robust." + icon_state = "red" + item_state = "toolbox_red" + 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 + materials = list(MAT_METAL = 500) + attack_verb = list("robusted") + hitsound = 'sound/weapons/smash.ogg' + var/latches = "single_latch" + var/has_latches = TRUE + var/can_rubberify = TRUE + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //very protecc too + +/obj/item/storage/toolbox/Initialize(mapload) + . = ..() + if(has_latches) + if(prob(10)) + latches = "double_latch" + if(prob(1)) + latches = "triple_latch" + if(mapload && can_rubberify && prob(5)) + rubberify() + update_icon() + +/obj/item/storage/toolbox/update_icon() + ..() + cut_overlays() + if(blood_DNA && blood_DNA.len) + add_blood_overlay() + if(has_latches) + var/icon/I = icon('icons/obj/storage.dmi', latches) + add_overlay(I) + + +/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" + +/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 + can_rubberify = FALSE + +/obj/item/storage/toolbox/mechanical + name = "mechanical toolbox" + icon_state = "blue" + item_state = "toolbox_blue" + +/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 + can_rubberify = FALSE + +/obj/item/storage/toolbox/mechanical/old/heirloom + name = "old, robust toolbox" //this will be named "X family toolbox" + desc = "It's seen better days." + //Citadel change buffed to base levels + total_mass = 2 + +/obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents() + return + +/obj/item/storage/toolbox/electrical + name = "electrical toolbox" + icon_state = "yellow" + item_state = "toolbox_yellow" + +/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,30,pickedcolor) + new /obj/item/stack/cable_coil(src,30,pickedcolor) + if(prob(5)) + new /obj/item/clothing/gloves/color/yellow(src) + else + new /obj/item/stack/cable_coil(src,30,pickedcolor) + +/obj/item/storage/toolbox/syndicate + name = "black and red toolbox" + icon_state = "syndicate" + item_state = "toolbox_syndi" + desc = "A toolbox painted black with a red stripe. It looks more heavier than normal toolboxes." + force = 15 + throwforce = 18 + +/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" + item_state = "toolbox_blue" + +/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,30,pickedcolor) + new /obj/item/wirecutters(src) + new /obj/item/multitool(src) + +/obj/item/storage/toolbox/brass + name = "brass box" + desc = "A huge brass box with several indentations in its surface." + icon_state = "brassbox" + item_state = null + has_latches = FALSE + resistance_flags = FIRE_PROOF | ACID_PROOF + w_class = WEIGHT_CLASS_HUGE + attack_verb = list("robusted", "crushed", "smashed") + can_rubberify = FALSE + var/fabricator_type = /obj/item/clockwork/replica_fabricator/scarab + +/obj/item/storage/toolbox/brass/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 28 + STR.max_items = 28 + +/obj/item/storage/toolbox/brass/prefilled/PopulateContents() + new fabricator_type(src) + new /obj/item/screwdriver/brass(src) + new /obj/item/wirecutters/brass(src) + new /obj/item/wrench/brass(src) + new /obj/item/crowbar/brass(src) + new /obj/item/weldingtool/experimental/brass(src) + +/obj/item/storage/toolbox/brass/prefilled/servant + slot_flags = ITEM_SLOT_BELT + fabricator_type = null + +/obj/item/storage/toolbox/brass/prefilled/ratvar + var/slab_type = /obj/item/clockwork/slab + +/obj/item/storage/toolbox/brass/prefilled/ratvar/PopulateContents() + ..() + new slab_type(src) + +/obj/item/storage/toolbox/brass/prefilled/ratvar/admin + slab_type = /obj/item/clockwork/slab/debug + fabricator_type = /obj/item/clockwork/replica_fabricator/scarab/debug + +/obj/item/storage/toolbox/plastitanium + name = "plastitanium toolbox" + desc = "A toolbox made out of plastitanium. Probably packs a massive punch." + total_mass = 5 + icon_state = "blue" + item_state = "toolbox_blue" + w_class = WEIGHT_CLASS_HUGE //heyo no bohing this! + force = 18 //spear damage + can_rubberify = FALSE + +/obj/item/storage/toolbox/plastitanium/afterattack(atom/A, mob/user, proximity) + . = ..() + if(proximity && isobj(A) && !isitem(A)) + var/obj/O = A + //50 total object damage but split up for stuff like damage deflection. + O.take_damage(22) + O.take_damage(10) + +/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" + item_state = "toolbox_green" + w_class = WEIGHT_CLASS_GIGANTIC //Holds more than a regular toolbox! + +/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/cable_coil/red(src) + new /obj/item/stack/cable_coil/yellow(src) + new /obj/item/stack/cable_coil/blue(src) + new /obj/item/stack/cable_coil/green(src) + new /obj/item/stack/cable_coil/pink(src) + new /obj/item/stack/cable_coil/orange(src) + new /obj/item/stack/cable_coil/cyan(src) + new /obj/item/stack/cable_coil/white(src) + +/obj/item/storage/toolbox/ammo + name = "ammo box" + desc = "It contains a few clips." + icon_state = "ammobox" + item_state = "ammobox" + +/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/gold_real + name = "golden toolbox" + desc = "A larger then normal toolbox made of gold plated plastitanium." + icon_state = "gold" + item_state = "toolbox_gold" + has_latches = FALSE + force = 16 // Less then a spear + throwforce = 14 + throw_speed = 5 + throw_range = 10 + +/obj/item/storage/toolbox/gold_real/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/ai_detect(src) + new /obj/item/clothing/gloves/combat(src) + +/obj/item/storage/toolbox/gold_real/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_combined_w_class = 40 + STR.max_items = 12 + +/obj/item/storage/toolbox/gold_fake // used in crafting + name = "golden toolbox" + desc = "A gold plated toolbox, fancy and harmless due to the gold plating being on cardboard!" + icon_state = "gold" + item_state = "toolbox_gold" + has_latches = FALSE + force = 0 + throwforce = 0 + can_rubberify = FALSE + +/obj/item/storage/toolbox/proc/rubberify() + name = "rubber [name]" + desc = replacetext(desc, "Danger", "Bouncy") + desc = replacetext(desc, "robust", "safe") + desc = replacetext(desc, "heavier", "bouncier") + DISABLE_BITFIELD(flags_1, CONDUCT_1) + materials = null + damtype = STAMINA + force += 3 //to compensate the higher stamina K.O. threshold compared to actual health. + throwforce += 3 + attack_verb += "bounced" + hitsound = 'sound/effects/clownstep1.ogg' + if(!GLOB.rubber_toolbox_icons[icon_state]) + generate_rubber_toolbox_icon() + icon = GLOB.rubber_toolbox_icons[icon_state] + AddComponent(/datum/component/bouncy) + +/obj/item/storage/toolbox/proc/generate_rubber_toolbox_icon() + var/icon/new_icon = icon(icon, icon_state) + var/icon/smooth = icon('icons/obj/storage.dmi', "rubber_toolbox_blend") + new_icon.Blend(smooth, ICON_MULTIPLY) + new_icon = fcopy_rsc(new_icon) + GLOB.rubber_toolbox_icons[icon_state] = new_icon + +/obj/item/storage/toolbox/rubber + name = "rubber toolbox" + desc = "Bouncy. Very safe." + flags_1 = null + materials = null + damtype = STAMINA + force = 17 + throwforce = 17 + attack_verb = list("robusted", "bounced") + can_rubberify = FALSE //we are already the future. + +/obj/item/storage/toolbox/rubber/Initialize() + icon_state = pick("blue", "red", "yellow", "green") + item_state = "toolbox_[icon_state]" + if(!GLOB.rubber_toolbox_icons[icon_state]) + generate_rubber_toolbox_icon() + icon = GLOB.rubber_toolbox_icons[icon_state] + . = ..() + AddComponent(/datum/component/bouncy) diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 817465a16c..35429c2f42 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -1,399 +1,399 @@ -/obj/item/storage/box/syndicate - -/obj/item/storage/box/syndicate/PopulateContents() - switch (pickweight(list("bloodyspai" = 3, "stealth" = 2, "bond" = 2, "screwed" = 2, "sabotage" = 3, "guns" = 2, "murder" = 2, "baseball" = 1, "implant" = 1, "hacker" = 3, "darklord" = 1, "sniper" = 1, "metaops" = 1, "ninja" = 1))) - if("bloodyspai") // 30 tc now this is more right - new /obj/item/clothing/under/chameleon(src) // 2 tc since it's not the full set - new /obj/item/clothing/mask/chameleon(src) // Goes with above - new /obj/item/card/id/syndicate(src) // 2 tc - new /obj/item/clothing/shoes/chameleon/noslip(src) // 2 tc - new /obj/item/camera_bug(src) // 1 tc - new /obj/item/multitool/ai_detect(src) // 1 tc - new /obj/item/encryptionkey/syndicate(src) // 2 tc - new /obj/item/reagent_containers/syringe/mulligan(src) // 4 tc - new /obj/item/switchblade(src) //I'll count this as 5 tc - new /obj/item/storage/fancy/cigarettes/cigpack_syndicate (src) // 2 tc this shit heals - new /obj/item/flashlight/emp(src) // 2 tc - new /obj/item/chameleon(src) // 7 tc - - if("stealth") // 31 tc - new /obj/item/gun/energy/kinetic_accelerator/crossbow(src) - new /obj/item/pen/sleepy(src) - new /obj/item/healthanalyzer/rad_laser(src) - new /obj/item/chameleon(src) - new /obj/item/soap/syndie(src) - new /obj/item/clothing/glasses/thermal/syndi(src) - - if("bond") // 29 tc - new /obj/item/gun/ballistic/automatic/pistol/suppressed(src) - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/ammo_box/magazine/m10mm(src) - new /obj/item/clothing/under/chameleon(src) - new /obj/item/card/id/syndicate(src) - new /obj/item/reagent_containers/syringe/stimulants(src) - new /obj/item/clothing/neck/tie/red(src) - - if("screwed") // 29 tc - new /obj/item/sbeacondrop/bomb(src) - new /obj/item/grenade/syndieminibomb(src) - new /obj/item/sbeacondrop/powersink(src) - new /obj/item/clothing/suit/space/syndicate/black/red(src) - new /obj/item/clothing/head/helmet/space/syndicate/black/red(src) - new /obj/item/encryptionkey/syndicate(src) - - if("guns") // 30 tc now - new /obj/item/gun/ballistic/revolver(src) - new /obj/item/ammo_box/a357(src) - new /obj/item/ammo_box/a357(src) - new /obj/item/card/emag(src) - new /obj/item/grenade/plastic/c4(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/clothing/mask/gas/clown_hat(src) - new /obj/item/clothing/under/suit_jacket/really_black(src) - new /obj/item/screwdriver/power(src) //2 tc item - - if("murder") // 35 tc - new /obj/item/melee/transforming/energy/sword/saber(src) - new /obj/item/clothing/glasses/thermal/syndi(src) - new /obj/item/card/emag(src) - new /obj/item/clothing/shoes/chameleon/noslip(src) - new /obj/item/encryptionkey/syndicate(src) - new /obj/item/grenade/syndieminibomb(src) - new /obj/item/clothing/glasses/phantomthief/syndicate(src) - new /obj/item/reagent_containers/syringe/stimulants(src) - - if("baseball") // 42~ tc - new /obj/item/melee/baseball_bat/ablative/syndi(src) //Lets say 12 tc, lesser sleeping carp - new /obj/item/clothing/glasses/sunglasses/garb(src) //Lets say 2 tc - new /obj/item/card/emag(src) //6 tc - new /obj/item/clothing/shoes/sneakers/noslip(src) //2tc - new /obj/item/encryptionkey/syndicate(src) //1tc - new /obj/item/autosurgeon/anti_drop(src) //Lets just say 7~ - new /obj/item/clothing/under/syndicate/baseball(src) //3tc - new /obj/item/clothing/head/soft/baseball(src) //Lets say 4 tc - new /obj/item/reagent_containers/hypospray/medipen/stimulants/baseball(src) //lets say 5tc - - if("implant") // 67+ tc holy shit what the fuck this is a lottery disguised as fun boxes isn't it? - new /obj/item/implanter/freedom(src) - new /obj/item/implanter/uplink/precharged(src) - new /obj/item/implanter/emp(src) - new /obj/item/implanter/adrenalin(src) - new /obj/item/implanter/explosive(src) - new /obj/item/implanter/storage(src) - new /obj/item/implanter/radio/syndicate(src) - new /obj/item/implanter/stealth(src) - - if("hacker") // 30 tc - new /obj/item/aiModule/syndicate(src) - new /obj/item/card/emag(src) - new /obj/item/encryptionkey/binary(src) - new /obj/item/aiModule/toyAI(src) - new /obj/item/multitool/ai_detect(src) - new /obj/item/flashlight/emp(src) - new /obj/item/emagrecharge(src) - - if("lordsingulo") // "36" tc aka 23 tc - new /obj/item/sbeacondrop(src) // 14 kinda useless - new /obj/item/clothing/suit/space/syndicate/black/red(src) //2 - new /obj/item/clothing/head/helmet/space/syndicate/black/red(src) //2 - new /obj/item/card/emag(src) //6 - new /obj/item/emagrecharge(src) //2 - new /obj/item/storage/toolbox/syndicate(src) //1 - new /obj/item/card/id/syndicate(src) //2 - new /obj/item/flashlight/emp(src) //2 - new /obj/item/jammer(src) //5 - - if("sabotage") // ~28 tc now - new /obj/item/grenade/plastic/c4 (src) - new /obj/item/grenade/plastic/c4 (src) - new /obj/item/grenade/plastic/x4 (src) - new /obj/item/grenade/plastic/x4 (src) - new /obj/item/doorCharge(src) - new /obj/item/doorCharge(src) - new /obj/item/camera_bug(src) - new /obj/item/sbeacondrop/powersink(src) - new /obj/item/cartridge/virus/syndicate(src) - new /obj/item/storage/toolbox/syndicate(src) //To actually get to those places - new /obj/item/pizzabox/bomb - - if("darklord") //20 tc + tk + summon item close enough for now - new /obj/item/twohanded/dualsaber(src) - new /obj/item/dnainjector/telemut/darkbundle(src) - new /obj/item/clothing/suit/hooded/chaplain_hoodie(src) - new /obj/item/card/id/syndicate(src) - new /obj/item/clothing/shoes/chameleon/noslip(src) //because slipping while being a dark lord sucks - new /obj/item/book/granter/spell/summonitem(src) - - if("sniper") //This shit is unique so can't really balance it around tc, also no silencer because getting killed without ANY indicator on what killed you sucks - new /obj/item/gun/ballistic/automatic/sniper_rifle(src) // 12 tc - new /obj/item/ammo_box/magazine/sniper_rounds/penetrator(src) - new /obj/item/clothing/glasses/thermal/syndi(src) - new /obj/item/clothing/gloves/color/latex/nitrile(src) - new /obj/item/clothing/mask/gas/clown_hat(src) - new /obj/item/clothing/under/suit_jacket/really_black(src) - - if("metaops") // 30 tc - new /obj/item/clothing/suit/space/hardsuit/syndi(src) // 8 tc - new /obj/item/gun/ballistic/automatic/shotgun/bulldog/unrestricted(src) // 8 tc - new /obj/item/implanter/explosive(src) // 2 tc - new /obj/item/ammo_box/magazine/m12g(src) // 2 tc - new /obj/item/ammo_box/magazine/m12g(src) // 2 tc - new /obj/item/grenade/plastic/c4 (src) // 1 tc - new /obj/item/grenade/plastic/c4 (src) // 1 tc - new /obj/item/card/emag(src) // 6 tc - - if("ninja") // 40~ tc worth - new /obj/item/katana(src) // Unique , basicly a better esword. 10 tc? - new /obj/item/implanter/adrenalin(src) // 8 tc - new /obj/item/throwing_star(src) // ~5 tc for all 6 - new /obj/item/throwing_star(src) - new /obj/item/throwing_star(src) - new /obj/item/implanter/emp(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/grenade/smokebomb(src) - new /obj/item/storage/belt/chameleon(src) // Unique but worth at least 2 tc - new /obj/item/card/id/syndicate(src) // 2 tc - new /obj/item/chameleon(src) // 7 tc - -/obj/item/storage/box/syndie_kit - name = "box" - desc = "A sleek, sturdy box." - icon_state = "syndiebox" - illustration = "writing_syndie" - -/obj/item/storage/box/syndie_kit/imp_freedom - name = "boxed freedom implant (with injector)" - -/obj/item/storage/box/syndie_kit/imp_freedom/PopulateContents() - var/obj/item/implanter/O = new(src) - O.imp = new /obj/item/implant/freedom(O) - O.update_icon() - -/obj/item/storage/box/syndie_kit/imp_microbomb - name = "Microbomb Implant (with injector)" - -/obj/item/storage/box/syndie_kit/imp_microbomb/PopulateContents() - var/obj/item/implanter/O = new(src) - O.imp = new /obj/item/implant/explosive(O) - O.update_icon() - -/obj/item/storage/box/syndie_kit/imp_macrobomb - name = "Macrobomb Implant (with injector)" - -/obj/item/storage/box/syndie_kit/imp_macrobomb/PopulateContents() - var/obj/item/implanter/O = new(src) - O.imp = new /obj/item/implant/explosive/macro(O) - O.update_icon() - -/obj/item/storage/box/syndie_kit/imp_uplink - name = "boxed uplink implant (with injector)" - -/obj/item/storage/box/syndie_kit/imp_uplink/PopulateContents() - ..() - var/obj/item/implanter/O = new(src) - O.imp = new /obj/item/implant/uplink(O) - O.update_icon() - -/obj/item/storage/box/syndie_kit/bioterror - name = "bioterror syringe box" - -/obj/item/storage/box/syndie_kit/bioterror/PopulateContents() - for(var/i in 1 to 7) - new /obj/item/reagent_containers/syringe/bioterror(src) - -/obj/item/storage/box/syndie_kit/imp_adrenal - name = "boxed adrenal implant (with injector)" - -/obj/item/storage/box/syndie_kit/imp_adrenal/PopulateContents() - var/obj/item/implanter/O = new(src) - O.imp = new /obj/item/implant/adrenalin(O) - O.update_icon() - -/obj/item/storage/box/syndie_kit/imp_storage - name = "boxed storage implant (with injector)" - -/obj/item/storage/box/syndie_kit/imp_storage/PopulateContents() - new /obj/item/implanter/storage(src) - -/obj/item/storage/box/syndie_kit/space - name = "boxed space suit and helmet" - -/obj/item/storage/box/syndie_kit/space/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.can_hold = typecacheof(list(/obj/item/clothing/suit/space/syndicate, /obj/item/clothing/head/helmet/space/syndicate)) - -/obj/item/storage/box/syndie_kit/space/PopulateContents() - new /obj/item/clothing/suit/space/syndicate/black/red(src) // Black and red is so in right now - new /obj/item/clothing/head/helmet/space/syndicate/black/red(src) - -/obj/item/storage/box/syndie_kit/emp - name = "boxed EMP kit" - -/obj/item/storage/box/syndie_kit/emp/PopulateContents() - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/empgrenade(src) - new /obj/item/grenade/empgrenade(src) - new /obj/item/implanter/emp(src) - -/obj/item/storage/box/syndie_kit/chemical - name = "boxed chemical kit" - -/obj/item/storage/box/syndie_kit/chemical/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 14 - -/obj/item/storage/box/syndie_kit/chemical/PopulateContents() - new /obj/item/reagent_containers/glass/bottle/polonium(src) - new /obj/item/reagent_containers/glass/bottle/venom(src) - new /obj/item/reagent_containers/glass/bottle/fentanyl(src) - new /obj/item/reagent_containers/glass/bottle/formaldehyde(src) - new /obj/item/reagent_containers/glass/bottle/spewium(src) - new /obj/item/reagent_containers/glass/bottle/cyanide(src) - new /obj/item/reagent_containers/glass/bottle/histamine(src) - new /obj/item/reagent_containers/glass/bottle/initropidril(src) - new /obj/item/reagent_containers/glass/bottle/pancuronium(src) - new /obj/item/reagent_containers/glass/bottle/sodium_thiopental(src) - new /obj/item/reagent_containers/glass/bottle/coniine(src) - new /obj/item/reagent_containers/glass/bottle/curare(src) - new /obj/item/reagent_containers/glass/bottle/amanitin(src) - new /obj/item/reagent_containers/syringe(src) - -/obj/item/storage/box/syndie_kit/nuke - name = "box" - -/obj/item/storage/box/syndie_kit/nuke/PopulateContents() - new /obj/item/screwdriver/nuke(src) - new /obj/item/nuke_core_container(src) - new /obj/item/paper/guides/antag/nuke_instructions(src) - -/obj/item/storage/box/syndie_kit/supermatter - name = "box" - -/obj/item/storage/box/syndie_kit/supermatter/PopulateContents() - new /obj/item/scalpel/supermatter(src) - new /obj/item/hemostat/supermatter(src) - new /obj/item/nuke_core_container/supermatter(src) - new /obj/item/paper/guides/antag/supermatter_sliver(src) - -/obj/item/storage/box/syndie_kit/tuberculosisgrenade - name = "boxed virus grenade kit" - -/obj/item/storage/box/syndie_kit/tuberculosisgrenade/PopulateContents() - new /obj/item/grenade/chem_grenade/tuberculosis(src) - for(var/i in 1 to 5) - new /obj/item/reagent_containers/hypospray/medipen/tuberculosiscure(src) - new /obj/item/reagent_containers/syringe(src) - new /obj/item/reagent_containers/glass/bottle/tuberculosiscure(src) - -/obj/item/storage/box/syndie_kit/chameleon - name = "chameleon kit" - -/obj/item/storage/box/syndie_kit/chameleon/PopulateContents() - new /obj/item/clothing/under/chameleon(src) - new /obj/item/clothing/suit/chameleon(src) - new /obj/item/clothing/gloves/chameleon(src) - new /obj/item/clothing/shoes/chameleon(src) - new /obj/item/clothing/glasses/chameleon(src) - new /obj/item/clothing/head/chameleon(src) - new /obj/item/clothing/mask/chameleon(src) - new /obj/item/storage/backpack/chameleon(src) - new /obj/item/radio/headset/chameleon(src) - new /obj/item/stamp/chameleon(src) - new /obj/item/pda/chameleon(src) - new /obj/item/clothing/neck/cloak/chameleon(src) - -//5*(2*4) = 5*8 = 45, 45 damage if you hit one person with all 5 stars. -//Not counting the damage it will do while embedded (2*4 = 8, at 15% chance) -/obj/item/storage/box/syndie_kit/throwing_weapons/PopulateContents() - new /obj/item/throwing_star(src) - new /obj/item/throwing_star(src) - new /obj/item/throwing_star(src) - new /obj/item/throwing_star(src) - new /obj/item/throwing_star(src) - new /obj/item/restraints/legcuffs/bola/tactical(src) - new /obj/item/restraints/legcuffs/bola/tactical(src) - -/obj/item/storage/box/syndie_kit/cutouts/PopulateContents() - for(var/i in 1 to 3) - new/obj/item/cardboard_cutout/adaptive(src) - new/obj/item/toy/crayon/rainbow(src) - -/obj/item/storage/box/syndie_kit/romerol/PopulateContents() - new /obj/item/reagent_containers/glass/bottle/romerol(src) - new /obj/item/reagent_containers/syringe(src) - new /obj/item/reagent_containers/dropper(src) - -/obj/item/storage/box/syndie_kit/ez_clean/PopulateContents() - for(var/i in 1 to 3) - new/obj/item/grenade/chem_grenade/ez_clean(src) - -/obj/item/storage/box/hug/reverse_revolver/PopulateContents() - new /obj/item/gun/ballistic/revolver/reverse(src) - -/obj/item/storage/box/syndie_kit/mimery/PopulateContents() - new /obj/item/book/granter/spell/mimery_blockade(src) - new /obj/item/book/granter/spell/mimery_guns(src) - -/obj/item/storage/box/syndie_kit/imp_radio/PopulateContents() - new /obj/item/implanter/radio/syndicate(src) - -/obj/item/storage/box/syndie_kit/centcom_costume/PopulateContents() - new /obj/item/clothing/under/rank/centcom_officer(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/gloves/color/black(src) - new /obj/item/radio/headset/headset_cent/empty(src) - new /obj/item/clothing/glasses/sunglasses(src) - new /obj/item/storage/backpack/satchel(src) - new /obj/item/pda/heads(src) - new /obj/item/clipboard(src) - -/obj/item/storage/box/syndie_kit/chameleon/broken/PopulateContents() - new /obj/item/clothing/under/chameleon/broken(src) - new /obj/item/clothing/suit/chameleon/broken(src) - new /obj/item/clothing/gloves/chameleon/broken(src) - new /obj/item/clothing/shoes/chameleon/noslip/broken(src) - new /obj/item/clothing/glasses/chameleon/broken(src) - new /obj/item/clothing/head/chameleon/broken(src) - new /obj/item/clothing/mask/chameleon/broken(src) - new /obj/item/storage/backpack/chameleon/broken(src) - new /obj/item/radio/headset/chameleon/broken(src) - new /obj/item/stamp/chameleon/broken(src) - new /obj/item/pda/chameleon/broken(src) - // No chameleon laser, they can't randomise for //REASONS// - -/obj/item/storage/box/syndie_kit/bee_grenades - name = "buzzkill grenade box" - desc = "A sleek, sturdy box with a buzzing noise coming from the inside. Uh oh." - -/obj/item/storage/box/syndie_kit/bee_grenades/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/grenade/spawnergrenade/buzzkill(src) - -/obj/item/storage/box/syndie_kit/kitchen_gun - name = "Kitchen Gun (TM) package" - -/obj/item/storage/box/syndie_kit/kitchen_gun/PopulateContents() - new /obj/item/gun/ballistic/automatic/pistol/m1911/kitchengun(src) - new /obj/item/ammo_box/magazine/m45/kitchengun(src) - new /obj/item/ammo_box/magazine/m45/kitchengun(src) - - -/obj/item/storage/box/strange_seeds_10pack - -/obj/item/storage/box/strange_seeds_10pack/PopulateContents() - for(var/i in 1 to 10) - new /obj/item/seeds/random(src) - - if(prob(50)) - new /obj/item/seeds/random(src) //oops, an additional packet might have slipped its way into the box - -/obj/item/storage/box/syndie_kit/revolver - -/obj/item/storage/box/syndie_kit/revolver/PopulateContents() - new /obj/item/gun/ballistic/revolver(src) - new /obj/item/ammo_box/a357(src) +/obj/item/storage/box/syndicate + +/obj/item/storage/box/syndicate/PopulateContents() + switch (pickweight(list("bloodyspai" = 3, "stealth" = 2, "bond" = 2, "screwed" = 2, "sabotage" = 3, "guns" = 2, "murder" = 2, "baseball" = 1, "implant" = 1, "hacker" = 3, "darklord" = 1, "sniper" = 1, "metaops" = 1, "ninja" = 1))) + if("bloodyspai") // 30 tc now this is more right + new /obj/item/clothing/under/chameleon(src) // 2 tc since it's not the full set + new /obj/item/clothing/mask/chameleon(src) // Goes with above + new /obj/item/card/id/syndicate(src) // 2 tc + new /obj/item/clothing/shoes/chameleon/noslip(src) // 2 tc + new /obj/item/camera_bug(src) // 1 tc + new /obj/item/multitool/ai_detect(src) // 1 tc + new /obj/item/encryptionkey/syndicate(src) // 2 tc + new /obj/item/reagent_containers/syringe/mulligan(src) // 4 tc + new /obj/item/switchblade(src) //I'll count this as 5 tc + new /obj/item/storage/fancy/cigarettes/cigpack_syndicate (src) // 2 tc this shit heals + new /obj/item/flashlight/emp(src) // 2 tc + new /obj/item/chameleon(src) // 7 tc + + if("stealth") // 31 tc + new /obj/item/gun/energy/kinetic_accelerator/crossbow(src) + new /obj/item/pen/sleepy(src) + new /obj/item/healthanalyzer/rad_laser(src) + new /obj/item/chameleon(src) + new /obj/item/soap/syndie(src) + new /obj/item/clothing/glasses/thermal/syndi(src) + + if("bond") // 29 tc + new /obj/item/gun/ballistic/automatic/pistol/suppressed(src) + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/ammo_box/magazine/m10mm(src) + new /obj/item/clothing/under/chameleon(src) + new /obj/item/card/id/syndicate(src) + new /obj/item/reagent_containers/syringe/stimulants(src) + new /obj/item/clothing/neck/tie/red(src) + + if("screwed") // 29 tc + new /obj/item/sbeacondrop/bomb(src) + new /obj/item/grenade/syndieminibomb(src) + new /obj/item/sbeacondrop/powersink(src) + new /obj/item/clothing/suit/space/syndicate/black/red(src) + new /obj/item/clothing/head/helmet/space/syndicate/black/red(src) + new /obj/item/encryptionkey/syndicate(src) + + if("guns") // 30 tc now + new /obj/item/gun/ballistic/revolver(src) + new /obj/item/ammo_box/a357(src) + new /obj/item/ammo_box/a357(src) + new /obj/item/card/emag(src) + new /obj/item/grenade/plastic/c4(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/clothing/mask/gas/clown_hat(src) + new /obj/item/clothing/under/suit_jacket/really_black(src) + new /obj/item/screwdriver/power(src) //2 tc item + + if("murder") // 35 tc + new /obj/item/melee/transforming/energy/sword/saber(src) + new /obj/item/clothing/glasses/thermal/syndi(src) + new /obj/item/card/emag(src) + new /obj/item/clothing/shoes/chameleon/noslip(src) + new /obj/item/encryptionkey/syndicate(src) + new /obj/item/grenade/syndieminibomb(src) + new /obj/item/clothing/glasses/phantomthief/syndicate(src) + new /obj/item/reagent_containers/syringe/stimulants(src) + + if("baseball") // 42~ tc + new /obj/item/melee/baseball_bat/ablative/syndi(src) //Lets say 12 tc, lesser sleeping carp + new /obj/item/clothing/glasses/sunglasses/garb(src) //Lets say 2 tc + new /obj/item/card/emag(src) //6 tc + new /obj/item/clothing/shoes/sneakers/noslip(src) //2tc + new /obj/item/encryptionkey/syndicate(src) //1tc + new /obj/item/autosurgeon/anti_drop(src) //Lets just say 7~ + new /obj/item/clothing/under/syndicate/baseball(src) //3tc + new /obj/item/clothing/head/soft/baseball(src) //Lets say 4 tc + new /obj/item/reagent_containers/hypospray/medipen/stimulants/baseball(src) //lets say 5tc + + if("implant") // 67+ tc holy shit what the fuck this is a lottery disguised as fun boxes isn't it? + new /obj/item/implanter/freedom(src) + new /obj/item/implanter/uplink/precharged(src) + new /obj/item/implanter/emp(src) + new /obj/item/implanter/adrenalin(src) + new /obj/item/implanter/explosive(src) + new /obj/item/implanter/storage(src) + new /obj/item/implanter/radio/syndicate(src) + new /obj/item/implanter/stealth(src) + + if("hacker") // 30 tc + new /obj/item/aiModule/syndicate(src) + new /obj/item/card/emag(src) + new /obj/item/encryptionkey/binary(src) + new /obj/item/aiModule/toyAI(src) + new /obj/item/multitool/ai_detect(src) + new /obj/item/flashlight/emp(src) + new /obj/item/emagrecharge(src) + + if("lordsingulo") // "36" tc aka 23 tc + new /obj/item/sbeacondrop(src) // 14 kinda useless + new /obj/item/clothing/suit/space/syndicate/black/red(src) //2 + new /obj/item/clothing/head/helmet/space/syndicate/black/red(src) //2 + new /obj/item/card/emag(src) //6 + new /obj/item/emagrecharge(src) //2 + new /obj/item/storage/toolbox/syndicate(src) //1 + new /obj/item/card/id/syndicate(src) //2 + new /obj/item/flashlight/emp(src) //2 + new /obj/item/jammer(src) //5 + + if("sabotage") // ~28 tc now + new /obj/item/grenade/plastic/c4 (src) + new /obj/item/grenade/plastic/c4 (src) + new /obj/item/grenade/plastic/x4 (src) + new /obj/item/grenade/plastic/x4 (src) + new /obj/item/doorCharge(src) + new /obj/item/doorCharge(src) + new /obj/item/camera_bug(src) + new /obj/item/sbeacondrop/powersink(src) + new /obj/item/cartridge/virus/syndicate(src) + new /obj/item/storage/toolbox/syndicate(src) //To actually get to those places + new /obj/item/pizzabox/bomb + + if("darklord") //20 tc + tk + summon item close enough for now + new /obj/item/twohanded/dualsaber(src) + new /obj/item/dnainjector/telemut/darkbundle(src) + new /obj/item/clothing/suit/hooded/chaplain_hoodie(src) + new /obj/item/card/id/syndicate(src) + new /obj/item/clothing/shoes/chameleon/noslip(src) //because slipping while being a dark lord sucks + new /obj/item/book/granter/spell/summonitem(src) + + if("sniper") //This shit is unique so can't really balance it around tc, also no silencer because getting killed without ANY indicator on what killed you sucks + new /obj/item/gun/ballistic/automatic/sniper_rifle(src) // 12 tc + new /obj/item/ammo_box/magazine/sniper_rounds/penetrator(src) + new /obj/item/clothing/glasses/thermal/syndi(src) + new /obj/item/clothing/gloves/color/latex/nitrile(src) + new /obj/item/clothing/mask/gas/clown_hat(src) + new /obj/item/clothing/under/suit_jacket/really_black(src) + + if("metaops") // 30 tc + new /obj/item/clothing/suit/space/hardsuit/syndi(src) // 8 tc + new /obj/item/gun/ballistic/automatic/shotgun/bulldog/unrestricted(src) // 8 tc + new /obj/item/implanter/explosive(src) // 2 tc + new /obj/item/ammo_box/magazine/m12g(src) // 2 tc + new /obj/item/ammo_box/magazine/m12g(src) // 2 tc + new /obj/item/grenade/plastic/c4 (src) // 1 tc + new /obj/item/grenade/plastic/c4 (src) // 1 tc + new /obj/item/card/emag(src) // 6 tc + + if("ninja") // 40~ tc worth + new /obj/item/katana(src) // Unique , basicly a better esword. 10 tc? + new /obj/item/implanter/adrenalin(src) // 8 tc + new /obj/item/throwing_star(src) // ~5 tc for all 6 + new /obj/item/throwing_star(src) + new /obj/item/throwing_star(src) + new /obj/item/implanter/emp(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/grenade/smokebomb(src) + new /obj/item/storage/belt/chameleon(src) // Unique but worth at least 2 tc + new /obj/item/card/id/syndicate(src) // 2 tc + new /obj/item/chameleon(src) // 7 tc + +/obj/item/storage/box/syndie_kit + name = "box" + desc = "A sleek, sturdy box." + icon_state = "syndiebox" + illustration = "writing_syndie" + +/obj/item/storage/box/syndie_kit/imp_freedom + name = "boxed freedom implant (with injector)" + +/obj/item/storage/box/syndie_kit/imp_freedom/PopulateContents() + var/obj/item/implanter/O = new(src) + O.imp = new /obj/item/implant/freedom(O) + O.update_icon() + +/obj/item/storage/box/syndie_kit/imp_microbomb + name = "Microbomb Implant (with injector)" + +/obj/item/storage/box/syndie_kit/imp_microbomb/PopulateContents() + var/obj/item/implanter/O = new(src) + O.imp = new /obj/item/implant/explosive(O) + O.update_icon() + +/obj/item/storage/box/syndie_kit/imp_macrobomb + name = "Macrobomb Implant (with injector)" + +/obj/item/storage/box/syndie_kit/imp_macrobomb/PopulateContents() + var/obj/item/implanter/O = new(src) + O.imp = new /obj/item/implant/explosive/macro(O) + O.update_icon() + +/obj/item/storage/box/syndie_kit/imp_uplink + name = "boxed uplink implant (with injector)" + +/obj/item/storage/box/syndie_kit/imp_uplink/PopulateContents() + ..() + var/obj/item/implanter/O = new(src) + O.imp = new /obj/item/implant/uplink(O) + O.update_icon() + +/obj/item/storage/box/syndie_kit/bioterror + name = "bioterror syringe box" + +/obj/item/storage/box/syndie_kit/bioterror/PopulateContents() + for(var/i in 1 to 7) + new /obj/item/reagent_containers/syringe/bioterror(src) + +/obj/item/storage/box/syndie_kit/imp_adrenal + name = "boxed adrenal implant (with injector)" + +/obj/item/storage/box/syndie_kit/imp_adrenal/PopulateContents() + var/obj/item/implanter/O = new(src) + O.imp = new /obj/item/implant/adrenalin(O) + O.update_icon() + +/obj/item/storage/box/syndie_kit/imp_storage + name = "boxed storage implant (with injector)" + +/obj/item/storage/box/syndie_kit/imp_storage/PopulateContents() + new /obj/item/implanter/storage(src) + +/obj/item/storage/box/syndie_kit/space + name = "boxed space suit and helmet" + +/obj/item/storage/box/syndie_kit/space/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.can_hold = typecacheof(list(/obj/item/clothing/suit/space/syndicate, /obj/item/clothing/head/helmet/space/syndicate)) + +/obj/item/storage/box/syndie_kit/space/PopulateContents() + new /obj/item/clothing/suit/space/syndicate/black/red(src) // Black and red is so in right now + new /obj/item/clothing/head/helmet/space/syndicate/black/red(src) + +/obj/item/storage/box/syndie_kit/emp + name = "boxed EMP kit" + +/obj/item/storage/box/syndie_kit/emp/PopulateContents() + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/empgrenade(src) + new /obj/item/grenade/empgrenade(src) + new /obj/item/implanter/emp(src) + +/obj/item/storage/box/syndie_kit/chemical + name = "boxed chemical kit" + +/obj/item/storage/box/syndie_kit/chemical/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 14 + +/obj/item/storage/box/syndie_kit/chemical/PopulateContents() + new /obj/item/reagent_containers/glass/bottle/polonium(src) + new /obj/item/reagent_containers/glass/bottle/venom(src) + new /obj/item/reagent_containers/glass/bottle/fentanyl(src) + new /obj/item/reagent_containers/glass/bottle/formaldehyde(src) + new /obj/item/reagent_containers/glass/bottle/spewium(src) + new /obj/item/reagent_containers/glass/bottle/cyanide(src) + new /obj/item/reagent_containers/glass/bottle/histamine(src) + new /obj/item/reagent_containers/glass/bottle/initropidril(src) + new /obj/item/reagent_containers/glass/bottle/pancuronium(src) + new /obj/item/reagent_containers/glass/bottle/sodium_thiopental(src) + new /obj/item/reagent_containers/glass/bottle/coniine(src) + new /obj/item/reagent_containers/glass/bottle/curare(src) + new /obj/item/reagent_containers/glass/bottle/amanitin(src) + new /obj/item/reagent_containers/syringe(src) + +/obj/item/storage/box/syndie_kit/nuke + name = "box" + +/obj/item/storage/box/syndie_kit/nuke/PopulateContents() + new /obj/item/screwdriver/nuke(src) + new /obj/item/nuke_core_container(src) + new /obj/item/paper/guides/antag/nuke_instructions(src) + +/obj/item/storage/box/syndie_kit/supermatter + name = "box" + +/obj/item/storage/box/syndie_kit/supermatter/PopulateContents() + new /obj/item/scalpel/supermatter(src) + new /obj/item/hemostat/supermatter(src) + new /obj/item/nuke_core_container/supermatter(src) + new /obj/item/paper/guides/antag/supermatter_sliver(src) + +/obj/item/storage/box/syndie_kit/tuberculosisgrenade + name = "boxed virus grenade kit" + +/obj/item/storage/box/syndie_kit/tuberculosisgrenade/PopulateContents() + new /obj/item/grenade/chem_grenade/tuberculosis(src) + for(var/i in 1 to 5) + new /obj/item/reagent_containers/hypospray/medipen/tuberculosiscure(src) + new /obj/item/reagent_containers/syringe(src) + new /obj/item/reagent_containers/glass/bottle/tuberculosiscure(src) + +/obj/item/storage/box/syndie_kit/chameleon + name = "chameleon kit" + +/obj/item/storage/box/syndie_kit/chameleon/PopulateContents() + new /obj/item/clothing/under/chameleon(src) + new /obj/item/clothing/suit/chameleon(src) + new /obj/item/clothing/gloves/chameleon(src) + new /obj/item/clothing/shoes/chameleon(src) + new /obj/item/clothing/glasses/chameleon(src) + new /obj/item/clothing/head/chameleon(src) + new /obj/item/clothing/mask/chameleon(src) + new /obj/item/storage/backpack/chameleon(src) + new /obj/item/radio/headset/chameleon(src) + new /obj/item/stamp/chameleon(src) + new /obj/item/pda/chameleon(src) + new /obj/item/clothing/neck/cloak/chameleon(src) + +//5*(2*4) = 5*8 = 45, 45 damage if you hit one person with all 5 stars. +//Not counting the damage it will do while embedded (2*4 = 8, at 15% chance) +/obj/item/storage/box/syndie_kit/throwing_weapons/PopulateContents() + new /obj/item/throwing_star(src) + new /obj/item/throwing_star(src) + new /obj/item/throwing_star(src) + new /obj/item/throwing_star(src) + new /obj/item/throwing_star(src) + new /obj/item/restraints/legcuffs/bola/tactical(src) + new /obj/item/restraints/legcuffs/bola/tactical(src) + +/obj/item/storage/box/syndie_kit/cutouts/PopulateContents() + for(var/i in 1 to 3) + new/obj/item/cardboard_cutout/adaptive(src) + new/obj/item/toy/crayon/rainbow(src) + +/obj/item/storage/box/syndie_kit/romerol/PopulateContents() + new /obj/item/reagent_containers/glass/bottle/romerol(src) + new /obj/item/reagent_containers/syringe(src) + new /obj/item/reagent_containers/dropper(src) + +/obj/item/storage/box/syndie_kit/ez_clean/PopulateContents() + for(var/i in 1 to 3) + new/obj/item/grenade/chem_grenade/ez_clean(src) + +/obj/item/storage/box/hug/reverse_revolver/PopulateContents() + new /obj/item/gun/ballistic/revolver/reverse(src) + +/obj/item/storage/box/syndie_kit/mimery/PopulateContents() + new /obj/item/book/granter/spell/mimery_blockade(src) + new /obj/item/book/granter/spell/mimery_guns(src) + +/obj/item/storage/box/syndie_kit/imp_radio/PopulateContents() + new /obj/item/implanter/radio/syndicate(src) + +/obj/item/storage/box/syndie_kit/centcom_costume/PopulateContents() + new /obj/item/clothing/under/rank/centcom_officer(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/gloves/color/black(src) + new /obj/item/radio/headset/headset_cent/empty(src) + new /obj/item/clothing/glasses/sunglasses(src) + new /obj/item/storage/backpack/satchel(src) + new /obj/item/pda/heads(src) + new /obj/item/clipboard(src) + +/obj/item/storage/box/syndie_kit/chameleon/broken/PopulateContents() + new /obj/item/clothing/under/chameleon/broken(src) + new /obj/item/clothing/suit/chameleon/broken(src) + new /obj/item/clothing/gloves/chameleon/broken(src) + new /obj/item/clothing/shoes/chameleon/noslip/broken(src) + new /obj/item/clothing/glasses/chameleon/broken(src) + new /obj/item/clothing/head/chameleon/broken(src) + new /obj/item/clothing/mask/chameleon/broken(src) + new /obj/item/storage/backpack/chameleon/broken(src) + new /obj/item/radio/headset/chameleon/broken(src) + new /obj/item/stamp/chameleon/broken(src) + new /obj/item/pda/chameleon/broken(src) + // No chameleon laser, they can't randomise for //REASONS// + +/obj/item/storage/box/syndie_kit/bee_grenades + name = "buzzkill grenade box" + desc = "A sleek, sturdy box with a buzzing noise coming from the inside. Uh oh." + +/obj/item/storage/box/syndie_kit/bee_grenades/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/grenade/spawnergrenade/buzzkill(src) + +/obj/item/storage/box/syndie_kit/kitchen_gun + name = "Kitchen Gun (TM) package" + +/obj/item/storage/box/syndie_kit/kitchen_gun/PopulateContents() + new /obj/item/gun/ballistic/automatic/pistol/m1911/kitchengun(src) + new /obj/item/ammo_box/magazine/m45/kitchengun(src) + new /obj/item/ammo_box/magazine/m45/kitchengun(src) + + +/obj/item/storage/box/strange_seeds_10pack + +/obj/item/storage/box/strange_seeds_10pack/PopulateContents() + for(var/i in 1 to 10) + new /obj/item/seeds/random(src) + + if(prob(50)) + new /obj/item/seeds/random(src) //oops, an additional packet might have slipped its way into the box + +/obj/item/storage/box/syndie_kit/revolver + +/obj/item/storage/box/syndie_kit/revolver/PopulateContents() + new /obj/item/gun/ballistic/revolver(src) + new /obj/item/ammo_box/a357(src) diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm index 6972587263..3ae0a4bd17 100644 --- a/code/game/objects/items/stunbaton.dm +++ b/code/game/objects/items/stunbaton.dm @@ -1,253 +1,253 @@ -#define STUNBATON_CHARGE_LENIENCY 0.3 -#define STUNBATON_DEPLETION_RATE 0.006 - -/obj/item/melee/baton - name = "stunbaton" - desc = "A stun baton for incapacitating people with." - icon_state = "stunbaton" - item_state = "baton" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - slot_flags = ITEM_SLOT_BELT - force = 10 - throwforce = 7 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("beaten") - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) - - var/stunforce = 140 - var/status = FALSE - var/obj/item/stock_parts/cell/cell - var/hitcost = 1000 - var/throw_hit_chance = 35 - var/preload_cell_type //if not empty the baton starts with this type of cell - -/obj/item/melee/baton/get_cell() - . = cell - if(iscyborg(loc)) - var/mob/living/silicon/robot/R = loc - . = R.get_cell() - -/obj/item/melee/baton/suicide_act(mob/user) - user.visible_message("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!") - return (FIRELOSS) - -/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() - -/obj/item/melee/baton/throw_impact(atom/hit_atom) - ..() - //Only mob/living types have stun handling - if(status && prob(throw_hit_chance) && iscarbon(hit_atom)) - baton_stun(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, chargecheck = TRUE, explode = TRUE) - var/obj/item/stock_parts/cell/copper_top = get_cell() - if(!copper_top) - switch_status(FALSE, TRUE) - return FALSE - //Note this value returned is significant, as it will determine - //if a stun is applied or not - - copper_top.use(min(chrgdeductamt, copper_top.charge), explode) - if(QDELETED(src)) - return FALSE - if(status && (!copper_top || !copper_top.charge || (chargecheck && copper_top.charge < (hitcost * STUNBATON_CHARGE_LENIENCY)))) - //we're below minimum, turn off - switch_status(FALSE) - -/obj/item/melee/baton/proc/switch_status(new_status = FALSE, silent = FALSE) - if(status != new_status) - status = new_status - if(!silent) - playsound(loc, "sparks", 75, 1, -1) - if(status) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - update_icon() - -/obj/item/melee/baton/process() - deductcharge(round(hitcost * STUNBATON_DEPLETION_RATE), FALSE, FALSE) - -/obj/item/melee/baton/update_icon() - if(status) - icon_state = "[initial(name)]_active" - else if(!cell) - icon_state = "[initial(name)]_nocell" - else - icon_state = "[initial(name)]" - -/obj/item/melee/baton/examine(mob/user) - . = ..() - var/obj/item/stock_parts/cell/copper_top = get_cell() - if(copper_top) - . += "\The [src] is [round(copper_top.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 < (hitcost * STUNBATON_CHARGE_LENIENCY)) - 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(istype(W, /obj/item/screwdriver)) - if(cell) - cell.update_icon() - cell.forceMove(get_turf(src)) - cell = null - to_chat(user, "You remove the cell from [src].") - switch_status(FALSE, TRUE) - else - return ..() - -/obj/item/melee/baton/attack_self(mob/user) - var/obj/item/stock_parts/cell/copper_top = get_cell() - if(!copper_top || copper_top.charge < (hitcost * STUNBATON_CHARGE_LENIENCY)) - switch_status(FALSE, TRUE) - if(!copper_top) - to_chat(user, "[src] does not have a power source!") - else - to_chat(user, "[src] is out of charge.") - else - switch_status(!status) - to_chat(user, "[src] is now [status ? "on" : "off"].") - add_fingerprint(user) - -/obj/item/melee/baton/attack(mob/M, mob/living/carbon/human/user) - if(status && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) - clowning_around(user) - return - - if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)//CIT CHANGE - makes it impossible to baton in stamina softcrit - to_chat(user, "You're too exhausted for that.")//CIT CHANGE - ditto - return //CIT CHANGE - ditto - - 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(status) - if(baton_stun(M, user)) - user.do_attack_animation(M) - user.adjustStaminaLossBuffered(getweight())//CIT CHANGE - makes stunbatonning others cost stamina - return - else - M.visible_message("[user] has prodded [M] with [src]. Luckily it was off.", \ - "[user] has prodded you with [src]. Luckily it was off") - else - if(status) - baton_stun(M, user) - ..() - - -/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/user) - if(L.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that - playsound(L, 'sound/weapons/genhit.ogg', 50, 1) - return FALSE - var/stunpwr = stunforce - var/obj/item/stock_parts/cell/our_cell = get_cell() - if(!our_cell) - switch_status(FALSE) - return FALSE - var/stuncharge = our_cell.charge - deductcharge(hitcost, FALSE) - if(QDELETED(src) || QDELETED(our_cell)) //it was rigged - return FALSE - if(stuncharge < hitcost) - if(stuncharge < (hitcost * STUNBATON_CHARGE_LENIENCY)) - L.visible_message("[user] has prodded [L] with [src]. Luckily it was out of charge.", \ - "[user] has prodded you with [src]. Luckily it was out of charge.") - return FALSE - stunpwr *= round(stuncharge/hitcost, 0.1) - - - L.Knockdown(stunpwr) - L.adjustStaminaLoss(stunpwr*0.1)//CIT CHANGE - makes stunbatons deal extra staminaloss. Todo: make this also deal pain when pain gets implemented. - L.apply_effect(EFFECT_STUTTER, stunforce) - SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK) - if(user) - L.lastattacker = user.real_name - L.lastattackerckey = user.ckey - L.visible_message("[user] has stunned [L] with [src]!", \ - "[user] has stunned you with [src]!") - log_combat(user, L, "stunned") - - playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) - - if(ishuman(L)) - var/mob/living/carbon/human/H = L - H.forcesay(GLOB.hit_appends) - - - return TRUE - -/obj/item/melee/baton/proc/clowning_around(mob/living/user) - user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ - "You accidentally hit yourself with [src]!") - SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK) - user.Knockdown(stunforce*3) - playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) - deductcharge(hitcost) - -/obj/item/melee/baton/emp_act(severity) - . = ..() - if (!(. & EMP_PROTECT_SELF)) - switch_status(FALSE) - if(!iscyborg(loc)) - deductcharge(1000 / severity, TRUE, FALSE) - -//Makeshift stun baton. Replacement for stun gloves. -/obj/item/melee/baton/cattleprod - name = "stunprod" - desc = "An improvised stun baton." - icon_state = "stunprod_nocell" - item_state = "prod" - 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 - stunforce = 100 - hitcost = 2000 - throw_hit_chance = 10 - slot_flags = ITEM_SLOT_BACK - var/obj/item/assembly/igniter/sparkler - -/obj/item/melee/baton/cattleprod/Initialize() - . = ..() - sparkler = new (src) - sparkler.activate_cooldown = 5 - -/obj/item/melee/baton/cattleprod/baton_stun() - sparkler?.activate() - . = ..() - -#undef STUNBATON_CHARGE_LENIENCY -#undef STUNBATON_DEPLETION_RATE +#define STUNBATON_CHARGE_LENIENCY 0.3 +#define STUNBATON_DEPLETION_RATE 0.006 + +/obj/item/melee/baton + name = "stunbaton" + desc = "A stun baton for incapacitating people with." + icon_state = "stunbaton" + item_state = "baton" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + slot_flags = ITEM_SLOT_BELT + force = 10 + throwforce = 7 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("beaten") + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) + + var/stunforce = 140 + var/status = FALSE + var/obj/item/stock_parts/cell/cell + var/hitcost = 1000 + var/throw_hit_chance = 35 + var/preload_cell_type //if not empty the baton starts with this type of cell + +/obj/item/melee/baton/get_cell() + . = cell + if(iscyborg(loc)) + var/mob/living/silicon/robot/R = loc + . = R.get_cell() + +/obj/item/melee/baton/suicide_act(mob/user) + user.visible_message("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!") + return (FIRELOSS) + +/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() + +/obj/item/melee/baton/throw_impact(atom/hit_atom) + ..() + //Only mob/living types have stun handling + if(status && prob(throw_hit_chance) && iscarbon(hit_atom)) + baton_stun(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, chargecheck = TRUE, explode = TRUE) + var/obj/item/stock_parts/cell/copper_top = get_cell() + if(!copper_top) + switch_status(FALSE, TRUE) + return FALSE + //Note this value returned is significant, as it will determine + //if a stun is applied or not + + copper_top.use(min(chrgdeductamt, copper_top.charge), explode) + if(QDELETED(src)) + return FALSE + if(status && (!copper_top || !copper_top.charge || (chargecheck && copper_top.charge < (hitcost * STUNBATON_CHARGE_LENIENCY)))) + //we're below minimum, turn off + switch_status(FALSE) + +/obj/item/melee/baton/proc/switch_status(new_status = FALSE, silent = FALSE) + if(status != new_status) + status = new_status + if(!silent) + playsound(loc, "sparks", 75, 1, -1) + if(status) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + update_icon() + +/obj/item/melee/baton/process() + deductcharge(round(hitcost * STUNBATON_DEPLETION_RATE), FALSE, FALSE) + +/obj/item/melee/baton/update_icon() + if(status) + icon_state = "[initial(name)]_active" + else if(!cell) + icon_state = "[initial(name)]_nocell" + else + icon_state = "[initial(name)]" + +/obj/item/melee/baton/examine(mob/user) + . = ..() + var/obj/item/stock_parts/cell/copper_top = get_cell() + if(copper_top) + . += "\The [src] is [round(copper_top.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 < (hitcost * STUNBATON_CHARGE_LENIENCY)) + 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(istype(W, /obj/item/screwdriver)) + if(cell) + cell.update_icon() + cell.forceMove(get_turf(src)) + cell = null + to_chat(user, "You remove the cell from [src].") + switch_status(FALSE, TRUE) + else + return ..() + +/obj/item/melee/baton/attack_self(mob/user) + var/obj/item/stock_parts/cell/copper_top = get_cell() + if(!copper_top || copper_top.charge < (hitcost * STUNBATON_CHARGE_LENIENCY)) + switch_status(FALSE, TRUE) + if(!copper_top) + to_chat(user, "[src] does not have a power source!") + else + to_chat(user, "[src] is out of charge.") + else + switch_status(!status) + to_chat(user, "[src] is now [status ? "on" : "off"].") + add_fingerprint(user) + +/obj/item/melee/baton/attack(mob/M, mob/living/carbon/human/user) + if(status && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) + clowning_around(user) + return + + if(user.getStaminaLoss() >= STAMINA_SOFTCRIT)//CIT CHANGE - makes it impossible to baton in stamina softcrit + to_chat(user, "You're too exhausted for that.")//CIT CHANGE - ditto + return //CIT CHANGE - ditto + + 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(status) + if(baton_stun(M, user)) + user.do_attack_animation(M) + user.adjustStaminaLossBuffered(getweight())//CIT CHANGE - makes stunbatonning others cost stamina + return + else + M.visible_message("[user] has prodded [M] with [src]. Luckily it was off.", \ + "[user] has prodded you with [src]. Luckily it was off") + else + if(status) + baton_stun(M, user) + ..() + + +/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/user) + if(L.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that + playsound(L, 'sound/weapons/genhit.ogg', 50, 1) + return FALSE + var/stunpwr = stunforce + var/obj/item/stock_parts/cell/our_cell = get_cell() + if(!our_cell) + switch_status(FALSE) + return FALSE + var/stuncharge = our_cell.charge + deductcharge(hitcost, FALSE) + if(QDELETED(src) || QDELETED(our_cell)) //it was rigged + return FALSE + if(stuncharge < hitcost) + if(stuncharge < (hitcost * STUNBATON_CHARGE_LENIENCY)) + L.visible_message("[user] has prodded [L] with [src]. Luckily it was out of charge.", \ + "[user] has prodded you with [src]. Luckily it was out of charge.") + return FALSE + stunpwr *= round(stuncharge/hitcost, 0.1) + + + L.Knockdown(stunpwr) + L.adjustStaminaLoss(stunpwr*0.1)//CIT CHANGE - makes stunbatons deal extra staminaloss. Todo: make this also deal pain when pain gets implemented. + L.apply_effect(EFFECT_STUTTER, stunforce) + SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK) + if(user) + L.lastattacker = user.real_name + L.lastattackerckey = user.ckey + L.visible_message("[user] has stunned [L] with [src]!", \ + "[user] has stunned you with [src]!") + log_combat(user, L, "stunned") + + playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) + + if(ishuman(L)) + var/mob/living/carbon/human/H = L + H.forcesay(GLOB.hit_appends) + + + return TRUE + +/obj/item/melee/baton/proc/clowning_around(mob/living/user) + user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!", \ + "You accidentally hit yourself with [src]!") + SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK) + user.Knockdown(stunforce*3) + playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1) + deductcharge(hitcost) + +/obj/item/melee/baton/emp_act(severity) + . = ..() + if (!(. & EMP_PROTECT_SELF)) + switch_status(FALSE) + if(!iscyborg(loc)) + deductcharge(1000 / severity, TRUE, FALSE) + +//Makeshift stun baton. Replacement for stun gloves. +/obj/item/melee/baton/cattleprod + name = "stunprod" + desc = "An improvised stun baton." + icon_state = "stunprod_nocell" + item_state = "prod" + 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 + stunforce = 100 + hitcost = 2000 + throw_hit_chance = 10 + slot_flags = ITEM_SLOT_BACK + var/obj/item/assembly/igniter/sparkler + +/obj/item/melee/baton/cattleprod/Initialize() + . = ..() + sparkler = new (src) + sparkler.activate_cooldown = 5 + +/obj/item/melee/baton/cattleprod/baton_stun() + sparkler?.activate() + . = ..() + +#undef STUNBATON_CHARGE_LENIENCY +#undef STUNBATON_DEPLETION_RATE diff --git a/code/game/objects/items/tanks/tank_types.dm b/code/game/objects/items/tanks/tank_types.dm index c16762467e..0e6dfaa1b3 100644 --- a/code/game/objects/items/tanks/tank_types.dm +++ b/code/game/objects/items/tanks/tank_types.dm @@ -1,175 +1,175 @@ -/* Types of tanks! - * Contains: - * Oxygen - * Anesthetic - * Air - * Plasma - * Emergency Oxygen - */ - -/* - * Oxygen - */ -/obj/item/tank/internals/oxygen - name = "oxygen tank" - desc = "A tank of oxygen." - icon_state = "oxygen" - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - force = 10 - dog_fashion = /datum/dog_fashion/back - - -/obj/item/tank/internals/oxygen/New() - ..() - air_contents.gases[/datum/gas/oxygen] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - return - - -/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 - - -/* - * Anesthetic - */ -/obj/item/tank/internals/anesthetic - name = "anesthetic tank" - desc = "A tank with an N2O/O2 gas mix." - icon_state = "anesthetic" - item_state = "an_tank" - force = 10 - -/obj/item/tank/internals/anesthetic/New() - ..() - air_contents.gases[/datum/gas/oxygen] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD - air_contents.gases[/datum/gas/nitrous_oxide] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD - return - -/* - * Air - */ -/obj/item/tank/internals/air - name = "air tank" - desc = "Mixed anyone?" - icon_state = "air" - item_state = "air" - force = 10 - dog_fashion = /datum/dog_fashion/back - -/obj/item/tank/internals/air/New() - ..() - air_contents.gases[/datum/gas/oxygen] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD - air_contents.gases[/datum/gas/nitrogen] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD - return - - -/* - * 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/New() - ..() - air_contents.gases[/datum/gas/plasma] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - return - -/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/New() - ..() // Plasma asserted in parent - air_contents.gases[/datum/gas/plasma] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - 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" - item_state = "plasmaman_tank" - force = 10 - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - -/obj/item/tank/internals/plasmaman/New() - ..() - air_contents.gases[/datum/gas/plasma] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - return - -/obj/item/tank/internals/plasmaman/full/New() - ..() // Plasma asserted in parent - air_contents.gases[/datum/gas/plasma] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - return - - -/obj/item/tank/internals/plasmaman/belt - icon_state = "plasmaman_tank_belt" - item_state = "plasmaman_tank_belt" - 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/New() - ..() // Plasma asserted in parent - air_contents.gases[/datum/gas/plasma] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - 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" - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - force = 4 - distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE - volume = 3 //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/New() - ..() - air_contents.gases[/datum/gas/oxygen] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) - return - -/obj/item/tank/internals/emergency_oxygen/engi - name = "extended-capacity emergency oxygen tank" - icon_state = "emergency_engi" - volume = 6 - -/obj/item/tank/internals/emergency_oxygen/double - name = "double emergency oxygen tank" - icon_state = "emergency_double" - volume = 10 +/* Types of tanks! + * Contains: + * Oxygen + * Anesthetic + * Air + * Plasma + * Emergency Oxygen + */ + +/* + * Oxygen + */ +/obj/item/tank/internals/oxygen + name = "oxygen tank" + desc = "A tank of oxygen." + icon_state = "oxygen" + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + force = 10 + dog_fashion = /datum/dog_fashion/back + + +/obj/item/tank/internals/oxygen/New() + ..() + air_contents.gases[/datum/gas/oxygen] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + return + + +/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 + + +/* + * Anesthetic + */ +/obj/item/tank/internals/anesthetic + name = "anesthetic tank" + desc = "A tank with an N2O/O2 gas mix." + icon_state = "anesthetic" + item_state = "an_tank" + force = 10 + +/obj/item/tank/internals/anesthetic/New() + ..() + air_contents.gases[/datum/gas/oxygen] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD + air_contents.gases[/datum/gas/nitrous_oxide] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD + return + +/* + * Air + */ +/obj/item/tank/internals/air + name = "air tank" + desc = "Mixed anyone?" + icon_state = "air" + item_state = "air" + force = 10 + dog_fashion = /datum/dog_fashion/back + +/obj/item/tank/internals/air/New() + ..() + air_contents.gases[/datum/gas/oxygen] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD + air_contents.gases[/datum/gas/nitrogen] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD + return + + +/* + * 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/New() + ..() + air_contents.gases[/datum/gas/plasma] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + return + +/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/New() + ..() // Plasma asserted in parent + air_contents.gases[/datum/gas/plasma] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + 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" + item_state = "plasmaman_tank" + force = 10 + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + +/obj/item/tank/internals/plasmaman/New() + ..() + air_contents.gases[/datum/gas/plasma] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + return + +/obj/item/tank/internals/plasmaman/full/New() + ..() // Plasma asserted in parent + air_contents.gases[/datum/gas/plasma] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + return + + +/obj/item/tank/internals/plasmaman/belt + icon_state = "plasmaman_tank_belt" + item_state = "plasmaman_tank_belt" + 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/New() + ..() // Plasma asserted in parent + air_contents.gases[/datum/gas/plasma] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + 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" + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + force = 4 + distribute_pressure = TANK_DEFAULT_RELEASE_PRESSURE + volume = 3 //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/New() + ..() + air_contents.gases[/datum/gas/oxygen] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + return + +/obj/item/tank/internals/emergency_oxygen/engi + name = "extended-capacity emergency oxygen tank" + icon_state = "emergency_engi" + volume = 6 + +/obj/item/tank/internals/emergency_oxygen/double + name = "double emergency oxygen tank" + icon_state = "emergency_double" + volume = 10 diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm index 92b0036ee0..8c0f315ca3 100644 --- a/code/game/objects/items/teleportation.dm +++ b/code/game/objects/items/teleportation.dm @@ -1,240 +1,240 @@ - -#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 - item_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 - materials = list(MAT_METAL=400) - -/obj/item/locator/attack_self(mob/user) - user.set_machine(src) - var/dat - if (temp) - dat = "[temp]

                Clear" - else - dat = {" -Persistent Signal Locator
                -Refresh"} - user << browse(dat, "window=radio") - onclose(user, "radio") - return - -/obj/item/locator/Topic(href, href_list) - ..() - if (usr.stat || usr.restrained()) - return - var/turf/current_location = get_turf(usr)//What turf is the user on? - if(!current_location || is_centcom_level(current_location.z))//If turf was not found or they're on CentCom - to_chat(usr, "[src] is malfunctioning.") - return - if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc))) - usr.set_machine(src) - if (href_list["refresh"]) - temp = "Persistent Signal Locator
                " - var/turf/sr = get_turf(src) - - if (sr) - temp += "Beacon Signals:
                " - for(var/obj/item/beacon/W in GLOB.teleportbeacons) - if (!W.renamed) - continue - var/turf/tr = get_turf(W) - if (tr.z == sr.z && tr) - var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) - if (direct < 5) - direct = "very strong" - else - if (direct < 10) - direct = "strong" - else - if (direct < 20) - direct = "weak" - else - direct = "very weak" - temp += "[W.name]-[dir2text(get_dir(sr, tr))]-[direct]
                " - - temp += "Implant Signals:
                " - for (var/obj/item/implant/tracking/W in GLOB.tracked_implants) - if (!isliving(W.imp_in)) - continue - var/mob/living/M = W.imp_in - if (M.stat == DEAD) - if (M.timeofdeath + W.lifespan_postmortem < world.time) - continue - - var/turf/tr = get_turf(M) - if (tr.z == sr.z && tr) - var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) - if (direct < 20) - if (direct < 5) - direct = "very strong" - else - if (direct < 10) - direct = "strong" - else - direct = "weak" - temp += "[W.imp_in.name]-[dir2text(get_dir(sr, tr))]-[direct]
                " - - temp += "You are at \[[sr.x],[sr.y],[sr.z]\] in orbital coordinates.

                Refresh
                " - else - temp += "Processing Error: Unable to locate orbital position.
                " - else - if (href_list["temp"]) - temp = null - if (ismob(src.loc)) - attack_self(src.loc) - else - for(var/mob/M in viewers(1, src)) - if (M.client) - src.attack_self(M) - 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" - item_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 - materials = list(MAT_METAL=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 FALSE - 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/implantcheckmate = FALSE - if(isliving(T)) - var/mob/living/M = T - if(!locate(/obj/item/implant/tracking) in M.implants) //The user was too slow and let the target mob's tracking implant expire or get removed. - implantcheckmate = TRUE - var/area/A = get_area(T) - if(A.noteleport || implantcheckmate) - 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)), src, 300, 1, null, atmos_link_override) - if(!(LAZYLEN(created) == 2)) - return - try_move_adjacent(created[1]) - 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 + item_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 + materials = list(MAT_METAL=400) + +/obj/item/locator/attack_self(mob/user) + user.set_machine(src) + var/dat + if (temp) + dat = "[temp]

                Clear" + else + dat = {" +Persistent Signal Locator
                +Refresh"} + user << browse(dat, "window=radio") + onclose(user, "radio") + return + +/obj/item/locator/Topic(href, href_list) + ..() + if (usr.stat || usr.restrained()) + return + var/turf/current_location = get_turf(usr)//What turf is the user on? + if(!current_location || is_centcom_level(current_location.z))//If turf was not found or they're on CentCom + to_chat(usr, "[src] is malfunctioning.") + return + if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc))) + usr.set_machine(src) + if (href_list["refresh"]) + temp = "Persistent Signal Locator
                " + var/turf/sr = get_turf(src) + + if (sr) + temp += "Beacon Signals:
                " + for(var/obj/item/beacon/W in GLOB.teleportbeacons) + if (!W.renamed) + continue + var/turf/tr = get_turf(W) + if (tr.z == sr.z && tr) + var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) + if (direct < 5) + direct = "very strong" + else + if (direct < 10) + direct = "strong" + else + if (direct < 20) + direct = "weak" + else + direct = "very weak" + temp += "[W.name]-[dir2text(get_dir(sr, tr))]-[direct]
                " + + temp += "Implant Signals:
                " + for (var/obj/item/implant/tracking/W in GLOB.tracked_implants) + if (!isliving(W.imp_in)) + continue + var/mob/living/M = W.imp_in + if (M.stat == DEAD) + if (M.timeofdeath + W.lifespan_postmortem < world.time) + continue + + var/turf/tr = get_turf(M) + if (tr.z == sr.z && tr) + var/direct = max(abs(tr.x - sr.x), abs(tr.y - sr.y)) + if (direct < 20) + if (direct < 5) + direct = "very strong" + else + if (direct < 10) + direct = "strong" + else + direct = "weak" + temp += "[W.imp_in.name]-[dir2text(get_dir(sr, tr))]-[direct]
                " + + temp += "You are at \[[sr.x],[sr.y],[sr.z]\] in orbital coordinates.

                Refresh
                " + else + temp += "Processing Error: Unable to locate orbital position.
                " + else + if (href_list["temp"]) + temp = null + if (ismob(src.loc)) + attack_self(src.loc) + else + for(var/mob/M in viewers(1, src)) + if (M.client) + src.attack_self(M) + 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" + item_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 + materials = list(MAT_METAL=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 FALSE + 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/implantcheckmate = FALSE + if(isliving(T)) + var/mob/living/M = T + if(!locate(/obj/item/implant/tracking) in M.implants) //The user was too slow and let the target mob's tracking implant expire or get removed. + implantcheckmate = TRUE + var/area/A = get_area(T) + if(A.noteleport || implantcheckmate) + 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)), src, 300, 1, null, atmos_link_override) + if(!(LAZYLEN(created) == 2)) + return + try_move_adjacent(created[1]) + 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 bc5bc6811e..7a6318cc4b 100644 --- a/code/game/objects/items/tools/crowbar.dm +++ b/code/game/objects/items/tools/crowbar.dm @@ -1,103 +1,103 @@ -/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 - materials = list(MAT_METAL=50) - - 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) - -/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, 1, -1) - return (BRUTELOSS) - -/obj/item/crowbar/red - icon_state = "crowbar_red" - force = 8 - -/obj/item/crowbar/brass - name = "brass crowbar" - desc = "A brass crowbar. It feels faintly warm to the touch." - resistance_flags = FIRE_PROOF | ACID_PROOF - icon_state = "crowbar_clock" - toolspeed = 0.5 - -/obj/item/crowbar/bronze - name = "bronze plated crowbar" - desc = "A bronze plated crowbar." - icon_state = "crowbar_brass" - toolspeed = 0.95 - -/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 - materials = list(MAT_METAL=70) - icon_state = "crowbar_large" - item_state = "crowbar" - toolspeed = 0.5 - -/obj/item/crowbar/cyborg - name = "hydraulic crowbar" - desc = "A hydraulic prying tool, compact but powerful. Designed to replace crowbar in construction cyborgs." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "crowbar_cyborg" - usesound = 'sound/items/jaws_pry.ogg' - force = 10 - toolspeed = 0.5 - -/obj/item/crowbar/power - name = "jaws of life" - desc = "A set of jaws of life, compressed through the magic of science. It's fitted with a prying head." - icon_state = "jaws_pry" - item_state = "jawsoflife" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) - - usesound = 'sound/items/jaws_pry.ogg' - force = 15 - toolspeed = 0.25 - -/obj/item/crowbar/power/suicide_act(mob/user) - 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, 1, -1) - return (BRUTELOSS) - -/obj/item/crowbar/power/attack_self(mob/user) - playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1) - var/obj/item/wirecutters/power/cutjaws = new /obj/item/wirecutters/power(drop_location()) - to_chat(user, "You attach the cutting jaws to [src].") - qdel(src) - user.put_in_active_hand(cutjaws) - -/obj/item/crowbar/advanced - name = "advanced crowbar" - desc = "A scientist's almost successful reproduction of an abductor's crowbar, it uses the same technology combined with a handle that can't quite hold it." - icon = 'icons/obj/advancedtools.dmi' - usesound = 'sound/weapons/sonic_jackhammer.ogg' - icon_state = "crowbar" +/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 + materials = list(MAT_METAL=50) + + 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) + +/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, 1, -1) + return (BRUTELOSS) + +/obj/item/crowbar/red + icon_state = "crowbar_red" + force = 8 + +/obj/item/crowbar/brass + name = "brass crowbar" + desc = "A brass crowbar. It feels faintly warm to the touch." + resistance_flags = FIRE_PROOF | ACID_PROOF + icon_state = "crowbar_clock" + toolspeed = 0.5 + +/obj/item/crowbar/bronze + name = "bronze plated crowbar" + desc = "A bronze plated crowbar." + icon_state = "crowbar_brass" + toolspeed = 0.95 + +/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 + materials = list(MAT_METAL=70) + icon_state = "crowbar_large" + item_state = "crowbar" + toolspeed = 0.5 + +/obj/item/crowbar/cyborg + name = "hydraulic crowbar" + desc = "A hydraulic prying tool, compact but powerful. Designed to replace crowbar in construction cyborgs." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "crowbar_cyborg" + usesound = 'sound/items/jaws_pry.ogg' + force = 10 + toolspeed = 0.5 + +/obj/item/crowbar/power + name = "jaws of life" + desc = "A set of jaws of life, compressed through the magic of science. It's fitted with a prying head." + icon_state = "jaws_pry" + item_state = "jawsoflife" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) + + usesound = 'sound/items/jaws_pry.ogg' + force = 15 + toolspeed = 0.25 + +/obj/item/crowbar/power/suicide_act(mob/user) + 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, 1, -1) + return (BRUTELOSS) + +/obj/item/crowbar/power/attack_self(mob/user) + playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1) + var/obj/item/wirecutters/power/cutjaws = new /obj/item/wirecutters/power(drop_location()) + to_chat(user, "You attach the cutting jaws to [src].") + qdel(src) + user.put_in_active_hand(cutjaws) + +/obj/item/crowbar/advanced + name = "advanced crowbar" + desc = "A scientist's almost successful reproduction of an abductor's crowbar, it uses the same technology combined with a handle that can't quite hold it." + icon = 'icons/obj/advancedtools.dmi' + usesound = 'sound/weapons/sonic_jackhammer.ogg' + icon_state = "crowbar" toolspeed = 0.2 \ No newline at end of file diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm index 599577a85b..115598fead 100644 --- a/code/game/objects/items/tools/screwdriver.dm +++ b/code/game/objects/items/tools/screwdriver.dm @@ -1,158 +1,158 @@ -/obj/item/screwdriver - name = "screwdriver" - desc = "You can be totally screwy with this." - icon = 'icons/obj/tools.dmi' - icon_state = "screwdriver_map" - item_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 - materials = list(MAT_METAL=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) - 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_icon() - if(!random_color) //icon override - return - cut_overlays() - var/mutable_appearance/base_overlay = mutable_appearance(icon, "screwdriver_screwybits") - base_overlay.appearance_flags = RESET_COLOR - add_overlay(base_overlay) - -/obj/item/screwdriver/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) - . = 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/attack(mob/living/carbon/M, mob/living/carbon/user) - if(!istype(M)) - return ..() - if(user.zone_selected != BODY_ZONE_PRECISE_EYES && user.zone_selected != BODY_ZONE_HEAD) - return ..() - return eyestab(M,user) - -/obj/item/screwdriver/brass - name = "brass screwdriver" - desc = "A screwdriver made of brass. The handle feels freezing cold." - resistance_flags = FIRE_PROOF | ACID_PROOF - icon_state = "screwdriver_clock" - item_state = "screwdriver_brass" - toolspeed = 0.5 - random_color = FALSE - -/obj/item/screwdriver/bronze - name = "bronze screwdriver" - desc = "A screwdriver plated with bronze." - icon_state = "screwdriver_brass" - item_state = "screwdriver_brass" - toolspeed = 0.95 - random_color = FALSE - -/obj/item/screwdriver/abductor - name = "alien screwdriver" - desc = "An ultrasonic screwdriver." - icon = 'icons/obj/abductor.dmi' - icon_state = "screwdriver_a" - item_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. It's fitted with a screw bit." - icon_state = "drill_screw" - item_state = "drill" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_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.25 - random_color = FALSE - -/obj/item/screwdriver/power/suicide_act(mob/user) - user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!") - return(BRUTELOSS) - -/obj/item/screwdriver/power/attack_self(mob/user) - playsound(get_turf(user),'sound/items/change_drill.ogg',50,1) - var/obj/item/wrench/power/b_drill = new /obj/item/wrench/power(drop_location()) - to_chat(user, "You attach the bolt driver bit to [src].") - qdel(src) - user.put_in_active_hand(b_drill) - -/obj/item/screwdriver/cyborg - name = "automated screwdriver" - desc = "An electrical 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/advanced - name = "advanced screwdriver" - desc = "A classy silver screwdriver with an alien alloy tip, it works almost as well as the real thing." - icon = 'icons/obj/advancedtools.dmi' - icon_state = "screwdriver_a" - item_state = "screwdriver_nuke" - usesound = 'sound/items/pshoom.ogg' - toolspeed = 0.2 +/obj/item/screwdriver + name = "screwdriver" + desc = "You can be totally screwy with this." + icon = 'icons/obj/tools.dmi' + icon_state = "screwdriver_map" + item_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 + materials = list(MAT_METAL=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) + 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_icon() + if(!random_color) //icon override + return + cut_overlays() + var/mutable_appearance/base_overlay = mutable_appearance(icon, "screwdriver_screwybits") + base_overlay.appearance_flags = RESET_COLOR + add_overlay(base_overlay) + +/obj/item/screwdriver/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) + . = 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/attack(mob/living/carbon/M, mob/living/carbon/user) + if(!istype(M)) + return ..() + if(user.zone_selected != BODY_ZONE_PRECISE_EYES && user.zone_selected != BODY_ZONE_HEAD) + return ..() + return eyestab(M,user) + +/obj/item/screwdriver/brass + name = "brass screwdriver" + desc = "A screwdriver made of brass. The handle feels freezing cold." + resistance_flags = FIRE_PROOF | ACID_PROOF + icon_state = "screwdriver_clock" + item_state = "screwdriver_brass" + toolspeed = 0.5 + random_color = FALSE + +/obj/item/screwdriver/bronze + name = "bronze screwdriver" + desc = "A screwdriver plated with bronze." + icon_state = "screwdriver_brass" + item_state = "screwdriver_brass" + toolspeed = 0.95 + random_color = FALSE + +/obj/item/screwdriver/abductor + name = "alien screwdriver" + desc = "An ultrasonic screwdriver." + icon = 'icons/obj/abductor.dmi' + icon_state = "screwdriver_a" + item_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. It's fitted with a screw bit." + icon_state = "drill_screw" + item_state = "drill" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_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.25 + random_color = FALSE + +/obj/item/screwdriver/power/suicide_act(mob/user) + user.visible_message("[user] is putting [src] to [user.p_their()] temple. It looks like [user.p_theyre()] trying to commit suicide!") + return(BRUTELOSS) + +/obj/item/screwdriver/power/attack_self(mob/user) + playsound(get_turf(user),'sound/items/change_drill.ogg',50,1) + var/obj/item/wrench/power/b_drill = new /obj/item/wrench/power(drop_location()) + to_chat(user, "You attach the bolt driver bit to [src].") + qdel(src) + user.put_in_active_hand(b_drill) + +/obj/item/screwdriver/cyborg + name = "automated screwdriver" + desc = "An electrical 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/advanced + name = "advanced screwdriver" + desc = "A classy silver screwdriver with an alien alloy tip, it works almost as well as the real thing." + icon = 'icons/obj/advancedtools.dmi' + icon_state = "screwdriver_a" + item_state = "screwdriver_nuke" + usesound = 'sound/items/pshoom.ogg' + toolspeed = 0.2 random_color = FALSE \ No newline at end of file diff --git a/code/game/objects/items/tools/wirecutters.dm b/code/game/objects/items/tools/wirecutters.dm index e4b95a5073..33fe8052e9 100644 --- a/code/game/objects/items/tools/wirecutters.dm +++ b/code/game/objects/items/tools/wirecutters.dm @@ -1,147 +1,147 @@ -/obj/item/wirecutters - name = "wirecutters" - desc = "This cuts wires." - icon = 'icons/obj/tools.dmi' - icon_state = "cutters_map" - item_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 - materials = list(MAT_METAL=80) - attack_verb = list("pinched", "nipped") - hitsound = 'sound/items/wirecutter.ogg' - usesound = 'sound/items/wirecutter.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_icon() - if(!random_color) //icon override - return - cut_overlays() - var/mutable_appearance/base_overlay = mutable_appearance(icon, "cutters_cutty_thingy") - base_overlay.appearance_flags = RESET_COLOR - add_overlay(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 - ..() - -/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, 1, -1) - return (BRUTELOSS) - -/obj/item/wirecutters/brass - name = "brass wirecutters" - desc = "A pair of eloquent wirecutters made of brass. The handle feels freezing cold to the touch." - resistance_flags = FIRE_PROOF | ACID_PROOF - icon_state = "cutters_clock" - random_color = FALSE - toolspeed = 0.5 - -/obj/item/wirecutters/bronze - name = "bronze plated wirecutters" - desc = "A pair of wirecutters plated with bronze." - icon_state = "cutters_brass" - random_color = FALSE - toolspeed = 0.95 //Wire cutters have 0 time bars though - -/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 = "wirecutters" - desc = "This cuts wires." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "wirecutters_cyborg" - toolspeed = 0.5 - random_color = FALSE - -/obj/item/wirecutters/power - name = "jaws of life" - desc = "A set of jaws of life, compressed through the magic of science. It's fitted with a cutting head." - icon_state = "jaws_cutter" - item_state = "jawsoflife" - - materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) - usesound = 'sound/items/jaws_cut.ogg' - toolspeed = 0.25 - random_color = FALSE - -/obj/item/wirecutters/power/suicide_act(mob/user) - 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, 1, -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,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1) - return (BRUTELOSS) - -/obj/item/wirecutters/power/attack_self(mob/user) - playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1) - var/obj/item/crowbar/power/pryjaws = new /obj/item/crowbar/power(drop_location()) - to_chat(user, "You attach the pry jaws to [src].") - qdel(src) - user.put_in_active_hand(pryjaws) - -/obj/item/wirecutters/power/attack(mob/living/carbon/C, mob/user) - if(istype(C)) - if(C.handcuffed) - user.visible_message("[user] cuts [C]'s restraints with [src]!") - qdel(C.handcuffed) - return - else if(C.has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) - var/man = C == user ? "your" : "[C]'\s" - user.visible_message("[user] attempts to remove the durathread strand from around [man] neck.", \ - "You attempt to remove the durathread strand from around [man] neck.") - if(do_after(user, 15, null, C)) - user.visible_message("[user] succesfuly removes the durathread strand.", - "You succesfuly remove the durathread strand.") - C.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) - return - ..() - -/obj/item/wirecutters/advanced - name = "advanced wirecutters" - desc = "A set of reproduction alien wirecutters, they have a silver handle with an exceedingly sharp blade." - icon = 'icons/obj/advancedtools.dmi' - icon_state = "cutters" - toolspeed = 0.2 +/obj/item/wirecutters + name = "wirecutters" + desc = "This cuts wires." + icon = 'icons/obj/tools.dmi' + icon_state = "cutters_map" + item_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 + materials = list(MAT_METAL=80) + attack_verb = list("pinched", "nipped") + hitsound = 'sound/items/wirecutter.ogg' + usesound = 'sound/items/wirecutter.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_icon() + if(!random_color) //icon override + return + cut_overlays() + var/mutable_appearance/base_overlay = mutable_appearance(icon, "cutters_cutty_thingy") + base_overlay.appearance_flags = RESET_COLOR + add_overlay(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 + ..() + +/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, 1, -1) + return (BRUTELOSS) + +/obj/item/wirecutters/brass + name = "brass wirecutters" + desc = "A pair of eloquent wirecutters made of brass. The handle feels freezing cold to the touch." + resistance_flags = FIRE_PROOF | ACID_PROOF + icon_state = "cutters_clock" + random_color = FALSE + toolspeed = 0.5 + +/obj/item/wirecutters/bronze + name = "bronze plated wirecutters" + desc = "A pair of wirecutters plated with bronze." + icon_state = "cutters_brass" + random_color = FALSE + toolspeed = 0.95 //Wire cutters have 0 time bars though + +/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 = "wirecutters" + desc = "This cuts wires." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "wirecutters_cyborg" + toolspeed = 0.5 + random_color = FALSE + +/obj/item/wirecutters/power + name = "jaws of life" + desc = "A set of jaws of life, compressed through the magic of science. It's fitted with a cutting head." + icon_state = "jaws_cutter" + item_state = "jawsoflife" + + materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_TITANIUM=25) + usesound = 'sound/items/jaws_cut.ogg' + toolspeed = 0.25 + random_color = FALSE + +/obj/item/wirecutters/power/suicide_act(mob/user) + 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, 1, -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,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1) + return (BRUTELOSS) + +/obj/item/wirecutters/power/attack_self(mob/user) + playsound(get_turf(user), 'sound/items/change_jaws.ogg', 50, 1) + var/obj/item/crowbar/power/pryjaws = new /obj/item/crowbar/power(drop_location()) + to_chat(user, "You attach the pry jaws to [src].") + qdel(src) + user.put_in_active_hand(pryjaws) + +/obj/item/wirecutters/power/attack(mob/living/carbon/C, mob/user) + if(istype(C)) + if(C.handcuffed) + user.visible_message("[user] cuts [C]'s restraints with [src]!") + qdel(C.handcuffed) + return + else if(C.has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) + var/man = C == user ? "your" : "[C]'\s" + user.visible_message("[user] attempts to remove the durathread strand from around [man] neck.", \ + "You attempt to remove the durathread strand from around [man] neck.") + if(do_after(user, 15, null, C)) + user.visible_message("[user] succesfuly removes the durathread strand.", + "You succesfuly remove the durathread strand.") + C.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) + return + ..() + +/obj/item/wirecutters/advanced + name = "advanced wirecutters" + desc = "A set of reproduction alien wirecutters, they have a silver handle with an exceedingly sharp blade." + icon = 'icons/obj/advancedtools.dmi' + icon_state = "cutters" + toolspeed = 0.2 random_color = FALSE \ No newline at end of file diff --git a/code/game/objects/items/tools/wrench.dm b/code/game/objects/items/tools/wrench.dm index 89f135ed67..b20fab2760 100644 --- a/code/game/objects/items/tools/wrench.dm +++ b/code/game/objects/items/tools/wrench.dm @@ -1,125 +1,125 @@ -/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' - materials = list(MAT_METAL=150) - - 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, 1, -1) - return (BRUTELOSS) - -/obj/item/wrench/cyborg - name = "automatic wrench" - desc = "An advanced robotic wrench. Can be found in construction cyborgs." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "wrench_cyborg" - toolspeed = 0.5 - -/obj/item/wrench/brass - name = "brass wrench" - desc = "A brass wrench. It's faintly warm to the touch." - resistance_flags = FIRE_PROOF | ACID_PROOF - icon_state = "wrench_clock" - toolspeed = 0.5 - -/obj/item/wrench/bronze - name = "bronze plated wrench" - desc = "A bronze plated wrench." - icon_state = "wrench_brass" - toolspeed = 0.95 - -/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/power - name = "hand drill" - desc = "A simple powered hand drill. It's fitted with a bolt bit." - icon_state = "drill_bolt" - item_state = "drill" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - usesound = 'sound/items/drill_use.ogg' - materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_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 - attack_verb = list("drilled", "screwed", "jabbed") - toolspeed = 0.25 - -/obj/item/wrench/power/attack_self(mob/user) - playsound(get_turf(user),'sound/items/change_drill.ogg',50,1) - var/obj/item/wirecutters/power/s_drill = new /obj/item/screwdriver/power(drop_location()) - to_chat(user, "You attach the screw driver bit to [src].") - qdel(src) - user.put_in_active_hand(s_drill) - -/obj/item/wrench/power/suicide_act(mob/user) - user.visible_message("[user] is pressing [src] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - -/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("wrenched", "medicaled", "tapped", "jabbed", "whacked") - -/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!") - // TODO Make them glow with the power of the M E D I C A L W R E N C H - // during their ascension - - // Stun stops them from wandering off - user.Stun(100, ignore_canstun = TRUE) - playsound(loc, 'sound/effects/pray.ogg', 50, 1, -1) - - // Let the sound effect finish playing - sleep(20) - - if(!user) - return - - for(var/obj/item/W in user) - user.dropItemToGround(W) - - var/obj/item/wrench/medical/W = new /obj/item/wrench/medical(loc) - W.add_fingerprint(user) - W.desc += " For some reason, it reminds you of [user.name]." - - if(!user) - return - - user.dust() - - return OXYLOSS - -/obj/item/wrench/advanced - name = "advanced wrench" - desc = "A wrench that uses the same magnetic technology that abductor tools use, but slightly more ineffeciently." - icon = 'icons/obj/advancedtools.dmi' - icon_state = "wrench" - usesound = 'sound/effects/empulse.ogg' +/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' + materials = list(MAT_METAL=150) + + 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, 1, -1) + return (BRUTELOSS) + +/obj/item/wrench/cyborg + name = "automatic wrench" + desc = "An advanced robotic wrench. Can be found in construction cyborgs." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "wrench_cyborg" + toolspeed = 0.5 + +/obj/item/wrench/brass + name = "brass wrench" + desc = "A brass wrench. It's faintly warm to the touch." + resistance_flags = FIRE_PROOF | ACID_PROOF + icon_state = "wrench_clock" + toolspeed = 0.5 + +/obj/item/wrench/bronze + name = "bronze plated wrench" + desc = "A bronze plated wrench." + icon_state = "wrench_brass" + toolspeed = 0.95 + +/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/power + name = "hand drill" + desc = "A simple powered hand drill. It's fitted with a bolt bit." + icon_state = "drill_bolt" + item_state = "drill" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + usesound = 'sound/items/drill_use.ogg' + materials = list(MAT_METAL=150,MAT_SILVER=50,MAT_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 + attack_verb = list("drilled", "screwed", "jabbed") + toolspeed = 0.25 + +/obj/item/wrench/power/attack_self(mob/user) + playsound(get_turf(user),'sound/items/change_drill.ogg',50,1) + var/obj/item/wirecutters/power/s_drill = new /obj/item/screwdriver/power(drop_location()) + to_chat(user, "You attach the screw driver bit to [src].") + qdel(src) + user.put_in_active_hand(s_drill) + +/obj/item/wrench/power/suicide_act(mob/user) + user.visible_message("[user] is pressing [src] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + +/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("wrenched", "medicaled", "tapped", "jabbed", "whacked") + +/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!") + // TODO Make them glow with the power of the M E D I C A L W R E N C H + // during their ascension + + // Stun stops them from wandering off + user.Stun(100, ignore_canstun = TRUE) + playsound(loc, 'sound/effects/pray.ogg', 50, 1, -1) + + // Let the sound effect finish playing + sleep(20) + + if(!user) + return + + for(var/obj/item/W in user) + user.dropItemToGround(W) + + var/obj/item/wrench/medical/W = new /obj/item/wrench/medical(loc) + W.add_fingerprint(user) + W.desc += " For some reason, it reminds you of [user.name]." + + if(!user) + return + + user.dust() + + return OXYLOSS + +/obj/item/wrench/advanced + name = "advanced wrench" + desc = "A wrench that uses the same magnetic technology that abductor tools use, but slightly more ineffeciently." + icon = 'icons/obj/advancedtools.dmi' + icon_state = "wrench" + usesound = 'sound/effects/empulse.ogg' toolspeed = 0.2 \ No newline at end of file diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm index 97f53d3ca7..4c6bcc08b4 100644 --- a/code/game/objects/items/trash.dm +++ b/code/game/objects/items/trash.dm @@ -1,81 +1,81 @@ -//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 - -/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/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/waffles - name = "waffles tray" - icon_state = "waffles" - -/obj/item/trash/plate - name = "plate" - icon_state = "plate" - resistance_flags = NONE - -/obj/item/trash/plate/alt - desc = "Still some dip left. Sadly still just trash..." - icon_state = "plate1" - -/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/boritos - name = "boritos bag" - icon_state = "boritos" - grind_results = list(/datum/reagent/aluminium = 1) //from the mylar bag - -/obj/item/trash/attack(mob/M, mob/living/user) - return +//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 + +/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/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/waffles + name = "waffles tray" + icon_state = "waffles" + +/obj/item/trash/plate + name = "plate" + icon_state = "plate" + resistance_flags = NONE + +/obj/item/trash/plate/alt + desc = "Still some dip left. Sadly still just trash..." + icon_state = "plate1" + +/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/boritos + name = "boritos bag" + icon_state = "boritos" + grind_results = list(/datum/reagent/aluminium = 1) //from the mylar bag + +/obj/item/trash/attack(mob/M, mob/living/user) + return diff --git a/code/game/objects/items/vending_items.dm b/code/game/objects/items/vending_items.dm index af647550ea..2964d31259 100755 --- 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" - item_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" + item_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 fe1086eb6e..b7b25f0b5a 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -1,688 +1,688 @@ -/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 the head with a bangammer", "you hear a bangammer stroking a head"); - 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" - item_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", "torn", "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" - item_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", "torn", "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 - total_mass = TOTAL_MASS_MEDIEVAL_WEAPON - -/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 - 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 - H.adjustStaminaLoss(-50) //CIT CHANGE - AND MAY HE NEVER SUCCUMB TO EXHAUSTION - 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) - if(!QDELETED(src)) - qdel(src) //If this ever happens, it's because you lost an arm - -/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() //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.client && 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_valhalla" - item_state = "cultblade" - remove_atom_colour(ADMIN_COLOUR_PRIORITY) - - name = new_name - playsound(user, 'sound/items/screwdriver2.ogg', 50, 1) - -/obj/item/katana - name = "katana" - desc = "Woefully underpowered in D20." - icon_state = "katana" - item_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_BULKY - hitsound = 'sound/weapons/bladeslice.ogg' - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "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 - total_mass = TOTAL_MASS_MEDIEVAL_WEAPON - -/obj/item/katana/cursed - slot_flags = null - -/obj/item/katana/cursed/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT) - -/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!") - playsound(src, 'sound/weapons/bladeslice.ogg', 50, 1) - return(BRUTELOSS) - -/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" - item_state = "rods" - flags_1 = CONDUCT_1 - force = 9 - throwforce = 10 - w_class = WEIGHT_CLASS_NORMAL - materials = list(MAT_METAL=1150, MAT_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/twohanded/spear/S = new /obj/item/twohanded/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" - item_state = "eshield0" - lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' - force = 2 - throwforce = 20 //This is never used on mobs since this has a 100% embed chance. - throw_speed = 4 - embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 100, "embedded_fall_chance" = 0) - w_class = WEIGHT_CLASS_SMALL - sharpness = IS_SHARP - materials = list(MAT_METAL=500, MAT_GLASS=500) - resistance_flags = FIRE_PROOF - - -/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 - materials = list(MAT_METAL=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, 1) - if(extended) - force = 20 - w_class = WEIGHT_CLASS_NORMAL - throwforce = 23 - icon_state = "switchblade_ext" - attack_verb = list("slashed", "stabbed", "sliced", "torn", "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" - item_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 - materials = list(MAT_METAL=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" - item_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/mounted_chainsaw - name = "mounted chainsaw" - desc = "A chainsaw that has replaced your arm." - icon_state = "chainsaw_on" - item_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_BULKY - force = 24 - throwforce = 0 - throw_range = 0 - throw_speed = 0 - sharpness = IS_SHARP - attack_verb = list("sawed", "torn", "cut", "chopped", "diced") - hitsound = 'sound/weapons/chainsawhit.ogg' - total_mass = TOTAL_MASS_HAND_REPLACEMENT - 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/twohanded/required/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") - -/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" - 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" - -/obj/item/melee/skateboard - name = "skateboard" - desc = "A skateboard. It can be placed on its wheels and ridden, or used as a strong weapon." - icon_state = "skateboard" - item_state = "skateboard" - force = 12 - throwforce = 4 - w_class = WEIGHT_CLASS_NORMAL - attack_verb = list("smacked", "whacked", "slammed", "smashed") - -/obj/item/melee/skateboard/attack_self(mob/user) - new /obj/vehicle/ridden/scooter/skateboard(get_turf(user)) - qdel(src) - -/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" - item_state = "baseball_bat" - lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' - force = 10 - throwforce = 12 - attack_verb = list("beat", "smacked") - w_class = WEIGHT_CLASS_BULKY - var/homerun_ready = 0 - var/homerun_able = 0 - total_mass = 2.7 //a regular wooden major league baseball bat weighs somewhere between 2 to 3.4 pounds, according to google - -/obj/item/melee/baseball_bat/chaplain - name = "blessed baseball bat" - desc = "There ain't a cult in the league that can withstand a swatter." - force = 14 - throwforce = 14 - obj_flags = UNIQUE_RENAME - var/chaplain_spawnable = TRUE - total_mass = TOTAL_MASS_MEDIEVAL_WEAPON - -/obj/item/melee/baseball_bat/chaplain/Initialize() - . = ..() - AddComponent(/datum/component/anti_magic, TRUE, TRUE, FALSE, null, null, FALSE) - -/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, 1) - 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) - target.ex_act(EXPLODE_HEAVY) - playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, 1) - homerun_ready = 0 - return - else if(!target.anchored) - target.throw_at(throw_target, rand(1,2), 7, user) - -/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" - item_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, 1) - if(picksound == 2) - playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, 1) - return 1 - -/obj/item/melee/baseball_bat/ablative/syndi - name = "syndicate major league bat" - desc = "A metal bat made by the syndicate for the major league team." - force = 18 //Spear damage... - throwforce = 30 - -/obj/item/melee/flyswatter - name = "flyswatter" - desc = "Useful for killing insects of all sizes." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "flyswatter" - item_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/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 - attack_verb = list("bopped") - -/obj/item/slapper - name = "slapper" - desc = "This is how real men fight." - icon_state = "latexballon" - item_state = "nothing" - force = 0 - throwforce = 0 - item_flags = DROPDEL | ABSTRACT - 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) - if(user.a_intent != INTENT_HARM && ((user.zone_selected == BODY_ZONE_PRECISE_MOUTH) || (user.zone_selected == BODY_ZONE_PRECISE_EYES) || (user.zone_selected == BODY_ZONE_HEAD))) - user.do_attack_animation(M) - playsound(M, 'sound/weapons/slap.ogg', 50, 1, -1) - user.visible_message("[user] slaps [M]!", - "You slap [M]!",\ - "You hear a slap.") - return - else - ..() - -/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" - item_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 - -/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 < reach) - to_chat(user, "[M] is too close to use [src] on.") - return - M.attack_hand(user) +/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 the head with a bangammer", "you hear a bangammer stroking a head"); + 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" + item_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", "torn", "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" + item_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", "torn", "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 + total_mass = TOTAL_MASS_MEDIEVAL_WEAPON + +/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 + 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 + H.adjustStaminaLoss(-50) //CIT CHANGE - AND MAY HE NEVER SUCCUMB TO EXHAUSTION + 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) + if(!QDELETED(src)) + qdel(src) //If this ever happens, it's because you lost an arm + +/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() //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.client && 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_valhalla" + item_state = "cultblade" + remove_atom_colour(ADMIN_COLOUR_PRIORITY) + + name = new_name + playsound(user, 'sound/items/screwdriver2.ogg', 50, 1) + +/obj/item/katana + name = "katana" + desc = "Woefully underpowered in D20." + icon_state = "katana" + item_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_BULKY + hitsound = 'sound/weapons/bladeslice.ogg' + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "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 + total_mass = TOTAL_MASS_MEDIEVAL_WEAPON + +/obj/item/katana/cursed + slot_flags = null + +/obj/item/katana/cursed/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT) + +/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!") + playsound(src, 'sound/weapons/bladeslice.ogg', 50, 1) + return(BRUTELOSS) + +/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" + item_state = "rods" + flags_1 = CONDUCT_1 + force = 9 + throwforce = 10 + w_class = WEIGHT_CLASS_NORMAL + materials = list(MAT_METAL=1150, MAT_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/twohanded/spear/S = new /obj/item/twohanded/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" + item_state = "eshield0" + lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi' + force = 2 + throwforce = 20 //This is never used on mobs since this has a 100% embed chance. + throw_speed = 4 + embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 100, "embedded_fall_chance" = 0) + w_class = WEIGHT_CLASS_SMALL + sharpness = IS_SHARP + materials = list(MAT_METAL=500, MAT_GLASS=500) + resistance_flags = FIRE_PROOF + + +/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 + materials = list(MAT_METAL=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, 1) + if(extended) + force = 20 + w_class = WEIGHT_CLASS_NORMAL + throwforce = 23 + icon_state = "switchblade_ext" + attack_verb = list("slashed", "stabbed", "sliced", "torn", "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" + item_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 + materials = list(MAT_METAL=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" + item_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/mounted_chainsaw + name = "mounted chainsaw" + desc = "A chainsaw that has replaced your arm." + icon_state = "chainsaw_on" + item_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_BULKY + force = 24 + throwforce = 0 + throw_range = 0 + throw_speed = 0 + sharpness = IS_SHARP + attack_verb = list("sawed", "torn", "cut", "chopped", "diced") + hitsound = 'sound/weapons/chainsawhit.ogg' + total_mass = TOTAL_MASS_HAND_REPLACEMENT + 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/twohanded/required/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") + +/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" + 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" + +/obj/item/melee/skateboard + name = "skateboard" + desc = "A skateboard. It can be placed on its wheels and ridden, or used as a strong weapon." + icon_state = "skateboard" + item_state = "skateboard" + force = 12 + throwforce = 4 + w_class = WEIGHT_CLASS_NORMAL + attack_verb = list("smacked", "whacked", "slammed", "smashed") + +/obj/item/melee/skateboard/attack_self(mob/user) + new /obj/vehicle/ridden/scooter/skateboard(get_turf(user)) + qdel(src) + +/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" + item_state = "baseball_bat" + lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi' + force = 10 + throwforce = 12 + attack_verb = list("beat", "smacked") + w_class = WEIGHT_CLASS_BULKY + var/homerun_ready = 0 + var/homerun_able = 0 + total_mass = 2.7 //a regular wooden major league baseball bat weighs somewhere between 2 to 3.4 pounds, according to google + +/obj/item/melee/baseball_bat/chaplain + name = "blessed baseball bat" + desc = "There ain't a cult in the league that can withstand a swatter." + force = 14 + throwforce = 14 + obj_flags = UNIQUE_RENAME + var/chaplain_spawnable = TRUE + total_mass = TOTAL_MASS_MEDIEVAL_WEAPON + +/obj/item/melee/baseball_bat/chaplain/Initialize() + . = ..() + AddComponent(/datum/component/anti_magic, TRUE, TRUE, FALSE, null, null, FALSE) + +/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, 1) + 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) + target.ex_act(EXPLODE_HEAVY) + playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, 1) + homerun_ready = 0 + return + else if(!target.anchored) + target.throw_at(throw_target, rand(1,2), 7, user) + +/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" + item_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, 1) + if(picksound == 2) + playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, 1) + return 1 + +/obj/item/melee/baseball_bat/ablative/syndi + name = "syndicate major league bat" + desc = "A metal bat made by the syndicate for the major league team." + force = 18 //Spear damage... + throwforce = 30 + +/obj/item/melee/flyswatter + name = "flyswatter" + desc = "Useful for killing insects of all sizes." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "flyswatter" + item_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/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 + attack_verb = list("bopped") + +/obj/item/slapper + name = "slapper" + desc = "This is how real men fight." + icon_state = "latexballon" + item_state = "nothing" + force = 0 + throwforce = 0 + item_flags = DROPDEL | ABSTRACT + 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) + if(user.a_intent != INTENT_HARM && ((user.zone_selected == BODY_ZONE_PRECISE_MOUTH) || (user.zone_selected == BODY_ZONE_PRECISE_EYES) || (user.zone_selected == BODY_ZONE_HEAD))) + user.do_attack_animation(M) + playsound(M, 'sound/weapons/slap.ogg', 50, 1, -1) + user.visible_message("[user] slaps [M]!", + "You slap [M]!",\ + "You hear a slap.") + return + else + ..() + +/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" + item_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 + +/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 < reach) + to_chat(user, "[M] is too close to use [src] on.") + return + M.attack_hand(user) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 14ac2f037e..8899f2f79f 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -1,249 +1,249 @@ - -/obj - var/crit_fail = FALSE - animate_movement = 2 - 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 - - 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 - - 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 //the item reskin - var/list/unique_reskin //List of options to reskin. - var/always_reskinnable = FALSE - - // 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" - - var/renamedByPlayer = FALSE //set when a player uses a pen on a renamable object - -/obj/vv_edit_var(vname, vval) - switch(vname) - if("anchored") - setAnchored(vval) - return TRUE - if("obj_flags") - if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION)) - return FALSE - if("control_object") - var/obj/O = vval - if(istype(O) && (O.obj_flags & 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 - 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 (findtext(flag,"!",1,2)) - flag = copytext(flag,1-(length(flag))) // 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, messy_throw) - . = ..() - 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(isAI(usr) || iscyborg(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/proc/hide(h) - return - -/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/proc/intercept_user_move(dir, mob, newLoc, oldLoc) - return - -/obj/vv_get_dropdown() - . = ..() - .["Delete all of type"] = "?_src_=vars;[HrefToken()];delall=[REF(src)]" - .["Osay"] = "?_src_=vars;[HrefToken()];osay[REF(src)]" - .["Modify armor values"] = "?_src_=vars;[HrefToken()];modarmor=[REF(src)]" - -/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 || always_reskinnable)) - . += "Alt-click it to reskin it." - -/obj/AltClick(mob/user) - . = ..() - if(unique_reskin && (!current_skin || always_reskinnable) && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - reskin_obj(user) - return TRUE - -/obj/proc/reskin_obj(mob/M) - if(!LAZYLEN(unique_reskin)) - return - var/dat = "Reskin options for [name]:\n" - for(var/V in unique_reskin) - var/output = icon2html(src, M, unique_reskin[V]) - dat += "[V]: [output]\n" - to_chat(M, dat) - - var/choice = input(M, always_reskinnable ? "Choose the a reskin for [src]" : "Warning, you can only reskin [src] once!","Reskin Object") as null|anything in unique_reskin - if(QDELETED(src) || !choice || (current_skin && !always_reskinnable) || M.incapacitated() || !in_range(M,src) || !unique_reskin[choice] || unique_reskin[choice] == current_skin) - return - current_skin = choice - icon_state = unique_reskin[choice] - to_chat(M, "[src] is now skinned as '[choice]'.") + +/obj + var/crit_fail = FALSE + animate_movement = 2 + 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 + + 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 + + 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 //the item reskin + var/list/unique_reskin //List of options to reskin. + var/always_reskinnable = FALSE + + // 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" + + var/renamedByPlayer = FALSE //set when a player uses a pen on a renamable object + +/obj/vv_edit_var(vname, vval) + switch(vname) + if("anchored") + setAnchored(vval) + return TRUE + if("obj_flags") + if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION)) + return FALSE + if("control_object") + var/obj/O = vval + if(istype(O) && (O.obj_flags & 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 + 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 (findtext(flag,"!",1,2)) + flag = copytext(flag,1-(length(flag))) // 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, messy_throw) + . = ..() + 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(isAI(usr) || iscyborg(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/proc/hide(h) + return + +/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/proc/intercept_user_move(dir, mob, newLoc, oldLoc) + return + +/obj/vv_get_dropdown() + . = ..() + .["Delete all of type"] = "?_src_=vars;[HrefToken()];delall=[REF(src)]" + .["Osay"] = "?_src_=vars;[HrefToken()];osay[REF(src)]" + .["Modify armor values"] = "?_src_=vars;[HrefToken()];modarmor=[REF(src)]" + +/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 || always_reskinnable)) + . += "Alt-click it to reskin it." + +/obj/AltClick(mob/user) + . = ..() + if(unique_reskin && (!current_skin || always_reskinnable) && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + reskin_obj(user) + return TRUE + +/obj/proc/reskin_obj(mob/M) + if(!LAZYLEN(unique_reskin)) + return + var/dat = "Reskin options for [name]:\n" + for(var/V in unique_reskin) + var/output = icon2html(src, M, unique_reskin[V]) + dat += "[V]: [output]\n" + to_chat(M, dat) + + var/choice = input(M, always_reskinnable ? "Choose the a reskin for [src]" : "Warning, you can only reskin [src] once!","Reskin Object") as null|anything in unique_reskin + if(QDELETED(src) || !choice || (current_skin && !always_reskinnable) || M.incapacitated() || !in_range(M,src) || !unique_reskin[choice] || unique_reskin[choice] == current_skin) + return + current_skin = choice + icon_state = unique_reskin[choice] + to_chat(M, "[src] is now skinned as '[choice]'.") diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index cf050f62d1..9246a00399 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -1,111 +1,111 @@ -/obj/structure - icon = 'icons/obj/structures.dmi' - pressure_resistance = 8 - max_integrity = 300 - interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT - var/climb_time = 20 - var/climb_stun = 20 - var/climbable = FALSE - var/mob/living/structureclimber - var/broken = 0 //similar to machinery's stat BROKEN - layer = BELOW_OBJ_LAYER - -/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.Knockdown(40) - structureclimber.visible_message("[structureclimber] has been 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 && iscarbon(O)) - if(user.canmove) - 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) - 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/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 + icon = 'icons/obj/structures.dmi' + pressure_resistance = 8 + max_integrity = 300 + interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT + var/climb_time = 20 + var/climb_stun = 20 + var/climbable = FALSE + var/mob/living/structureclimber + var/broken = 0 //similar to machinery's stat BROKEN + layer = BELOW_OBJ_LAYER + +/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.Knockdown(40) + structureclimber.visible_message("[structureclimber] has been 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 && iscarbon(O)) + if(user.canmove) + 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) + 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/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!" diff --git a/code/game/objects/structures/ai_core.dm b/code/game/objects/structures/ai_core.dm index 8964d1ca41..5b9a764925 100644 --- a/code/game/objects/structures/ai_core.dm +++ b/code/game/objects/structures/ai_core.dm @@ -1,329 +1,329 @@ -/obj/structure/AIcore - 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 = 0 - var/datum/ai_laws/laws - var/obj/item/circuitboard/circuit = null - var/obj/item/mmi/brain = null - var/can_deconstruct = TRUE - -/obj/structure/AIcore/Initialize() - . = ..() - laws = new - laws.set_laws_config() - -/obj/structure/AIcore/Destroy() - if(circuit) - qdel(circuit) - circuit = null - if(brain) - qdel(brain) - brain = null - return ..() - -/obj/structure/AIcore/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/AIcore/latejoin_inactive/examine(mob/user) - . = ..() - . += "Its transmitter seems to be [active? "on" : "off"]." - -/obj/structure/AIcore/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/AIcore/latejoin_inactive/attackby(obj/item/P, mob/user, params) - if(istype(P, /obj/item/multitool)) - active = !active - to_chat(user, "You [active? "activate" : "deactivate"] [src]'s transmitters.") - return - return ..() - -/obj/structure/AIcore/latejoin_inactive/Initialize() - . = ..() - GLOB.latejoin_ai_cores += src - -/obj/structure/AIcore/latejoin_inactive/Destroy() - GLOB.latejoin_ai_cores -= src - return ..() - -/obj/structure/AIcore/attackby(obj/item/P, mob/user, params) - if(istype(P, /obj/item/wrench)) - return default_unfasten_wrench(user, P, 20) - if(!anchored) - if(istype(P, /obj/item/weldingtool) && 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, 1) - to_chat(user, "You place the circuit board inside the frame.") - update_icon() - state = CIRCUIT_CORE - circuit = P - return - if(CIRCUIT_CORE) - if(istype(P, /obj/item/screwdriver)) - P.play_tool_sound(src) - to_chat(user, "You screw the circuit board into place.") - state = SCREWED_CORE - update_icon() - return - if(istype(P, /obj/item/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(istype(P, /obj/item/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, 1) - 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(istype(P, /obj/item/wirecutters)) - 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, 1) - 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/aiModule)) - if(brain && brain.laws.id != DEFAULT_AI_LAWID) - to_chat(user, "The installed [brain.name] already has set laws!") - return - var/obj/item/aiModule/module = P - module.install(laws, user) - return - - if(istype(P, /obj/item/mmi) && !brain) - var/obj/item/mmi/M = P - if(!M.brainmob) - to_chat(user, "Sticking an empty [M.name] into the frame would sort of defeat the purpose!") - return - if(M.brainmob.stat == DEAD) - to_chat(user, "Sticking a dead [M.name] into the frame would sort of defeat the purpose!") - return - - if(!M.brainmob.client) - to_chat(user, "Sticking an inactive [M.name] into the frame would sort of defeat the purpose.") - return - - if(!CONFIG_GET(flag/allow_ai) || (jobban_isbanned(M.brainmob, "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(!M.brainmob.mind) - to_chat(user, "This [M.name] is mindless!") - return - - if(!user.transferItemToLoc(M,src)) - return - - brain = M - to_chat(user, "You add [M.name] to the frame.") - update_icon() - return - - if(istype(P, /obj/item/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(istype(P, /obj/item/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(istype(P, /obj/item/screwdriver)) - P.play_tool_sound(src) - to_chat(user, "You connect the monitor.") - if(brain) - SSticker.mode.remove_antag_for_borging(brain.brainmob.mind) - if(!istype(brain.laws, /datum/ai_laws/ratvar)) - remove_servant_of_ratvar(brain.brainmob, TRUE) - - var/mob/living/silicon/ai/A = null - - if (brain.overrides_aicore_laws) - A = new /mob/living/silicon/ai(loc, brain.laws, brain.brainmob) - else - A = new /mob/living/silicon/ai(loc, laws, brain.brainmob) - - if(brain.force_replace_ai_name) - A.fully_replace_character_name(A.name, brain.replacement_ai_name()) - SSblackbox.record_feedback("amount", "ais_created", 1) - 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(istype(P, /obj/item/screwdriver)) - P.play_tool_sound(src) - to_chat(user, "You disconnect the monitor.") - state = GLASS_CORE - update_icon() - return - return ..() - -/obj/structure/AIcore/update_icon() - 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/AIcore/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/AIcore/deactivated - name = "inactive AI" - icon_state = "ai-empty" - anchored = TRUE - state = AI_READY_CORE - -/obj/structure/AIcore/deactivated/New() - ..() - 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 0 - return 1 - -/obj/structure/AIcore/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 = 0 - AI.radio_enabled = 1 - 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 - 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 +/obj/structure/AIcore + 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 = 0 + var/datum/ai_laws/laws + var/obj/item/circuitboard/circuit = null + var/obj/item/mmi/brain = null + var/can_deconstruct = TRUE + +/obj/structure/AIcore/Initialize() + . = ..() + laws = new + laws.set_laws_config() + +/obj/structure/AIcore/Destroy() + if(circuit) + qdel(circuit) + circuit = null + if(brain) + qdel(brain) + brain = null + return ..() + +/obj/structure/AIcore/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/AIcore/latejoin_inactive/examine(mob/user) + . = ..() + . += "Its transmitter seems to be [active? "on" : "off"]." + +/obj/structure/AIcore/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/AIcore/latejoin_inactive/attackby(obj/item/P, mob/user, params) + if(istype(P, /obj/item/multitool)) + active = !active + to_chat(user, "You [active? "activate" : "deactivate"] [src]'s transmitters.") + return + return ..() + +/obj/structure/AIcore/latejoin_inactive/Initialize() + . = ..() + GLOB.latejoin_ai_cores += src + +/obj/structure/AIcore/latejoin_inactive/Destroy() + GLOB.latejoin_ai_cores -= src + return ..() + +/obj/structure/AIcore/attackby(obj/item/P, mob/user, params) + if(istype(P, /obj/item/wrench)) + return default_unfasten_wrench(user, P, 20) + if(!anchored) + if(istype(P, /obj/item/weldingtool) && 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, 1) + to_chat(user, "You place the circuit board inside the frame.") + update_icon() + state = CIRCUIT_CORE + circuit = P + return + if(CIRCUIT_CORE) + if(istype(P, /obj/item/screwdriver)) + P.play_tool_sound(src) + to_chat(user, "You screw the circuit board into place.") + state = SCREWED_CORE + update_icon() + return + if(istype(P, /obj/item/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(istype(P, /obj/item/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, 1) + 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(istype(P, /obj/item/wirecutters)) + 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, 1) + 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/aiModule)) + if(brain && brain.laws.id != DEFAULT_AI_LAWID) + to_chat(user, "The installed [brain.name] already has set laws!") + return + var/obj/item/aiModule/module = P + module.install(laws, user) + return + + if(istype(P, /obj/item/mmi) && !brain) + var/obj/item/mmi/M = P + if(!M.brainmob) + to_chat(user, "Sticking an empty [M.name] into the frame would sort of defeat the purpose!") + return + if(M.brainmob.stat == DEAD) + to_chat(user, "Sticking a dead [M.name] into the frame would sort of defeat the purpose!") + return + + if(!M.brainmob.client) + to_chat(user, "Sticking an inactive [M.name] into the frame would sort of defeat the purpose.") + return + + if(!CONFIG_GET(flag/allow_ai) || (jobban_isbanned(M.brainmob, "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(!M.brainmob.mind) + to_chat(user, "This [M.name] is mindless!") + return + + if(!user.transferItemToLoc(M,src)) + return + + brain = M + to_chat(user, "You add [M.name] to the frame.") + update_icon() + return + + if(istype(P, /obj/item/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(istype(P, /obj/item/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(istype(P, /obj/item/screwdriver)) + P.play_tool_sound(src) + to_chat(user, "You connect the monitor.") + if(brain) + SSticker.mode.remove_antag_for_borging(brain.brainmob.mind) + if(!istype(brain.laws, /datum/ai_laws/ratvar)) + remove_servant_of_ratvar(brain.brainmob, TRUE) + + var/mob/living/silicon/ai/A = null + + if (brain.overrides_aicore_laws) + A = new /mob/living/silicon/ai(loc, brain.laws, brain.brainmob) + else + A = new /mob/living/silicon/ai(loc, laws, brain.brainmob) + + if(brain.force_replace_ai_name) + A.fully_replace_character_name(A.name, brain.replacement_ai_name()) + SSblackbox.record_feedback("amount", "ais_created", 1) + 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(istype(P, /obj/item/screwdriver)) + P.play_tool_sound(src) + to_chat(user, "You disconnect the monitor.") + state = GLASS_CORE + update_icon() + return + return ..() + +/obj/structure/AIcore/update_icon() + 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/AIcore/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/AIcore/deactivated + name = "inactive AI" + icon_state = "ai-empty" + anchored = TRUE + state = AI_READY_CORE + +/obj/structure/AIcore/deactivated/New() + ..() + 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 0 + return 1 + +/obj/structure/AIcore/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 = 0 + AI.radio_enabled = 1 + 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 + 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 diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm index 9935fc8ec5..8dc7caa5ad 100644 --- a/code/game/objects/structures/bedsheet_bin.dm +++ b/code/game/objects/structures/bedsheet_bin.dm @@ -1,399 +1,399 @@ -/* -CONTAINS: -BEDSHEETS -LINEN BINS -*/ - -/obj/item/bedsheet - name = "bedsheet" - desc = "A surprisingly soft linen bedsheet." - icon = 'icons/obj/bedsheets.dmi' - icon_state = "sheetwhite" - item_state = "bedsheet" - slot_flags = ITEM_SLOT_NECK - layer = MOB_LAYER - throwforce = 0 - throw_speed = 1 - throw_range = 2 - w_class = WEIGHT_CLASS_TINY - item_color = "white" - resistance_flags = FLAMMABLE - - 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].") - 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(istype(I, /obj/item/wirecutters) || 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" - item_color = "blue" - dream_messages = list("blue") - -/obj/item/bedsheet/green - icon_state = "sheetgreen" - item_color = "green" - dream_messages = list("green") - -/obj/item/bedsheet/grey - icon_state = "sheetgrey" - item_color = "grey" - dream_messages = list("grey") - -/obj/item/bedsheet/orange - icon_state = "sheetorange" - item_color = "orange" - dream_messages = list("orange") - -/obj/item/bedsheet/purple - icon_state = "sheetpurple" - item_color = "purple" - 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" - item_color = "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" - item_color = "rainbow" - dream_messages = list("red", "orange", "yellow", "green", "blue", "purple", "a rainbow") - -/obj/item/bedsheet/red - icon_state = "sheetred" - item_color = "red" - dream_messages = list("red") - -/obj/item/bedsheet/yellow - icon_state = "sheetyellow" - item_color = "yellow" - 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" - item_color = "mime" - 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" - item_color = "clown" - 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" - item_color = "captain" - 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" - item_color = "director" - 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" - item_color = "medical" - 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" - item_color = "cmo" - 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" - item_color = "hosred" - 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" - item_color = "hop" - 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" - item_color = "chief" - 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" - item_color = "qm" - dream_messages = list("a grey ID", "a shuttle", "a crate", "a sloth", "the quartermaster") - -/obj/item/bedsheet/brown - icon_state = "sheetbrown" - item_color = "cargo" - dream_messages = list("brown") - -/obj/item/bedsheet/black - icon_state = "sheetblack" - item_color = "black" - 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" - item_color = "centcom" - 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" - item_color = "syndie" - 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" - item_color = "cult" - 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" - item_color = "wiz" - dream_messages = list("a book", "an explosion", "lightning", "a staff", "a skeleton", "a robe", "magic") - -/obj/item/bedsheet/nanotrasen - name = "nanotrasen bedsheet" - desc = "It has the Nanotrasen logo on it and has an aura of duty." - icon_state = "sheetNT" - item_color = "nanotrasen" - dream_messages = list("authority", "an ending") - -/obj/item/bedsheet/ian - icon_state = "sheetian" - item_color = "ian" - dream_messages = list("a dog", "a corgi", "woof", "bark", "arf") - -/obj/item/bedsheet/runtime - icon_state = "sheetruntime" - item_color = "runtime" - dream_messages = list("a kitty", "a cat", "meow", "purr", "nya~") - -/obj/item/bedsheet/pirate - name = "pirate's bedsheet" - desc = "It has a Jolly Roger emblem on it and has a faint scent of grog." - icon_state = "sheetpirate" - item_color = "black" - dream_messages = list("doing whatever oneself wants", "cause a pirate is free", "being a pirate", "stealing", "landlubbers", "gold", "a buried treasure", "yarr", "avast", "a swashbuckler", "sailing the Seven Seas", "a parrot", "a monkey", "an island", "a talking skull") - -/obj/item/bedsheet/gondola - name = "gondola bedsheet" - desc = "A precious bedsheet made from the hide of a rare and peculiar critter." - icon_state = "sheetgondola" - item_color = "cargo" - var/g_mouth - var/g_eyes - -/obj/item/bedsheet/gondola/Initialize() - . = ..() - g_mouth = "sheetgondola_mouth[rand(1, 4)]" - g_eyes = "sheetgondola_eyes[rand(1, 4)]" - add_overlay(g_mouth) - add_overlay(g_eyes) - -/obj/item/bedsheet/gondola/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) - . = ..() - if(!isinhands) - . += mutable_appearance(icon_file, g_mouth) - . += mutable_appearance(icon_file, g_eyes) - -/obj/item/bedsheet/cosmos - name = "cosmic space bedsheet" - desc = "Made from the dreams of those who wonder at the stars." - icon_state = "sheetcosmos" - item_color = "cosmos" - 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" - item_color = "rainbow" - 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/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/sheet_types = list(/obj/item/bedsheet) - var/static/allowed_sheets = list(/obj/item/bedsheet, /obj/item/reagent_containers/rag/towel) - var/list/sheets = list() - var/obj/item/hidden = null - -/obj/structure/bedsheetbin/examine(mob/user) - . = ..() - if(amount < 1) - . += "There are no sheets in the bin." - else if(amount == 1) - . += "There is one sheet in the bin." - else - . += "There are [amount] sheets in the bin." - - -/obj/structure/bedsheetbin/update_icon() - 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(is_type_in_list(I, allowed_sheets)) - if(!user.transferItemToLoc(I, src)) - to_chat(user, "\The [I] is stuck to your hand, you cannot place it into the bin!") - return - sheets.Add(I) - amount++ - to_chat(user, "You put [I] in [src].") - update_icon() - 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(user.incapacitated()) - return - if(amount >= 1) - amount-- - - var/obj/item/B - if(sheets.len > 0) - B = sheets[sheets.len] - sheets.Remove(B) - - else - var/chosen = pick(sheet_types) - B = new chosen - - 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/B - if(sheets.len > 0) - B = sheets[sheets.len] - sheets.Remove(B) - - else - var/chosen = pick(sheet_types) - B = new chosen - - B.forceMove(drop_location()) - to_chat(user, "You telekinetically remove [B] from [src].") - update_icon() - - if(hidden) - hidden.forceMove(drop_location()) - hidden = null - -/obj/structure/bedsheetbin/towel - desc = "It looks rather cosy. This one is designed to hold towels." - sheet_types = list(/obj/item/reagent_containers/rag/towel) - -/obj/structure/bedsheetbin/color - sheet_types = list(/obj/item/bedsheet, /obj/item/bedsheet/blue, /obj/item/bedsheet/green, /obj/item/bedsheet/orange, \ - /obj/item/bedsheet/purple, /obj/item/bedsheet/red, /obj/item/bedsheet/yellow, /obj/item/bedsheet/brown, \ +/* +CONTAINS: +BEDSHEETS +LINEN BINS +*/ + +/obj/item/bedsheet + name = "bedsheet" + desc = "A surprisingly soft linen bedsheet." + icon = 'icons/obj/bedsheets.dmi' + icon_state = "sheetwhite" + item_state = "bedsheet" + slot_flags = ITEM_SLOT_NECK + layer = MOB_LAYER + throwforce = 0 + throw_speed = 1 + throw_range = 2 + w_class = WEIGHT_CLASS_TINY + item_color = "white" + resistance_flags = FLAMMABLE + + 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].") + 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(istype(I, /obj/item/wirecutters) || 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" + item_color = "blue" + dream_messages = list("blue") + +/obj/item/bedsheet/green + icon_state = "sheetgreen" + item_color = "green" + dream_messages = list("green") + +/obj/item/bedsheet/grey + icon_state = "sheetgrey" + item_color = "grey" + dream_messages = list("grey") + +/obj/item/bedsheet/orange + icon_state = "sheetorange" + item_color = "orange" + dream_messages = list("orange") + +/obj/item/bedsheet/purple + icon_state = "sheetpurple" + item_color = "purple" + 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" + item_color = "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" + item_color = "rainbow" + dream_messages = list("red", "orange", "yellow", "green", "blue", "purple", "a rainbow") + +/obj/item/bedsheet/red + icon_state = "sheetred" + item_color = "red" + dream_messages = list("red") + +/obj/item/bedsheet/yellow + icon_state = "sheetyellow" + item_color = "yellow" + 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" + item_color = "mime" + 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" + item_color = "clown" + 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" + item_color = "captain" + 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" + item_color = "director" + 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" + item_color = "medical" + 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" + item_color = "cmo" + 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" + item_color = "hosred" + 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" + item_color = "hop" + 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" + item_color = "chief" + 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" + item_color = "qm" + dream_messages = list("a grey ID", "a shuttle", "a crate", "a sloth", "the quartermaster") + +/obj/item/bedsheet/brown + icon_state = "sheetbrown" + item_color = "cargo" + dream_messages = list("brown") + +/obj/item/bedsheet/black + icon_state = "sheetblack" + item_color = "black" + 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" + item_color = "centcom" + 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" + item_color = "syndie" + 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" + item_color = "cult" + 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" + item_color = "wiz" + dream_messages = list("a book", "an explosion", "lightning", "a staff", "a skeleton", "a robe", "magic") + +/obj/item/bedsheet/nanotrasen + name = "nanotrasen bedsheet" + desc = "It has the Nanotrasen logo on it and has an aura of duty." + icon_state = "sheetNT" + item_color = "nanotrasen" + dream_messages = list("authority", "an ending") + +/obj/item/bedsheet/ian + icon_state = "sheetian" + item_color = "ian" + dream_messages = list("a dog", "a corgi", "woof", "bark", "arf") + +/obj/item/bedsheet/runtime + icon_state = "sheetruntime" + item_color = "runtime" + dream_messages = list("a kitty", "a cat", "meow", "purr", "nya~") + +/obj/item/bedsheet/pirate + name = "pirate's bedsheet" + desc = "It has a Jolly Roger emblem on it and has a faint scent of grog." + icon_state = "sheetpirate" + item_color = "black" + dream_messages = list("doing whatever oneself wants", "cause a pirate is free", "being a pirate", "stealing", "landlubbers", "gold", "a buried treasure", "yarr", "avast", "a swashbuckler", "sailing the Seven Seas", "a parrot", "a monkey", "an island", "a talking skull") + +/obj/item/bedsheet/gondola + name = "gondola bedsheet" + desc = "A precious bedsheet made from the hide of a rare and peculiar critter." + icon_state = "sheetgondola" + item_color = "cargo" + var/g_mouth + var/g_eyes + +/obj/item/bedsheet/gondola/Initialize() + . = ..() + g_mouth = "sheetgondola_mouth[rand(1, 4)]" + g_eyes = "sheetgondola_eyes[rand(1, 4)]" + add_overlay(g_mouth) + add_overlay(g_eyes) + +/obj/item/bedsheet/gondola/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) + . = ..() + if(!isinhands) + . += mutable_appearance(icon_file, g_mouth) + . += mutable_appearance(icon_file, g_eyes) + +/obj/item/bedsheet/cosmos + name = "cosmic space bedsheet" + desc = "Made from the dreams of those who wonder at the stars." + icon_state = "sheetcosmos" + item_color = "cosmos" + 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" + item_color = "rainbow" + 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/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/sheet_types = list(/obj/item/bedsheet) + var/static/allowed_sheets = list(/obj/item/bedsheet, /obj/item/reagent_containers/rag/towel) + var/list/sheets = list() + var/obj/item/hidden = null + +/obj/structure/bedsheetbin/examine(mob/user) + . = ..() + if(amount < 1) + . += "There are no sheets in the bin." + else if(amount == 1) + . += "There is one sheet in the bin." + else + . += "There are [amount] sheets in the bin." + + +/obj/structure/bedsheetbin/update_icon() + 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(is_type_in_list(I, allowed_sheets)) + if(!user.transferItemToLoc(I, src)) + to_chat(user, "\The [I] is stuck to your hand, you cannot place it into the bin!") + return + sheets.Add(I) + amount++ + to_chat(user, "You put [I] in [src].") + update_icon() + 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(user.incapacitated()) + return + if(amount >= 1) + amount-- + + var/obj/item/B + if(sheets.len > 0) + B = sheets[sheets.len] + sheets.Remove(B) + + else + var/chosen = pick(sheet_types) + B = new chosen + + 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/B + if(sheets.len > 0) + B = sheets[sheets.len] + sheets.Remove(B) + + else + var/chosen = pick(sheet_types) + B = new chosen + + B.forceMove(drop_location()) + to_chat(user, "You telekinetically remove [B] from [src].") + update_icon() + + if(hidden) + hidden.forceMove(drop_location()) + hidden = null + +/obj/structure/bedsheetbin/towel + desc = "It looks rather cosy. This one is designed to hold towels." + sheet_types = list(/obj/item/reagent_containers/rag/towel) + +/obj/structure/bedsheetbin/color + sheet_types = list(/obj/item/bedsheet, /obj/item/bedsheet/blue, /obj/item/bedsheet/green, /obj/item/bedsheet/orange, \ + /obj/item/bedsheet/purple, /obj/item/bedsheet/red, /obj/item/bedsheet/yellow, /obj/item/bedsheet/brown, \ /obj/item/bedsheet/black) \ No newline at end of file diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 38944c0384..9a095bf69d 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -1,616 +1,616 @@ -/obj/structure/closet - name = "closet" - desc = "It's a basic storage unit." - icon = 'icons/obj/closet.dmi' - icon_state = "generic" - density = TRUE - 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) - max_integrity = 200 - integrity_failure = 50 - armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) - 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/click.ogg' - var/close_sound = 'sound/machines/click.ogg' - 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" - var/obj/item/electronics/airlock/lockerelectronics //Installed electronics - var/lock_in_use = FALSE //Someone is doing some stuff with the lock here, better not proceed further - var/eigen_teleport = FALSE //If the closet leads to Mr Tumnus. - var/obj/structure/closet/eigen_target //Where you go to. - - -/obj/structure/closet/Initialize(mapload) - . = ..() - update_icon() - PopulateContents() - if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents - addtimer(CALLBACK(src, .proc/take_contents), 0) - if(secure) - lockerelectronics = new(src) - lockerelectronics.accesses = req_access - -//USE THIS TO FILL IT, NOT INITIALIZE OR NEW -/obj/structure/closet/proc/PopulateContents() - return - -/obj/structure/closet/Destroy() - dump_contents(override = FALSE) - return ..() - -/obj/structure/closet/update_icon() - cut_overlays() - if(opened & icon_door_override) - add_overlay("[icon_door]_open") - layer = OBJ_LAYER - return - else if(opened) - add_overlay("[icon_state]_open") - return - if(icon_door) - add_overlay("[icon_door]_door") - else - layer = BELOW_OBJ_LAYER - add_overlay("[icon_state]_door") - if(welded) - add_overlay("welded") - if(!secure) - return - if(broken) - add_overlay("off") - add_overlay("sparking") - else if(locked) - add_overlay("locked") - else - add_overlay("unlocked") - -/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) - else if(broken) - . += "The lock is screwed in." - else if(secure) - . += "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/CanPass(atom/movable/mover, turf/target) - if(wall_mounted) - return TRUE - return !density - -/obj/structure/closet/proc/can_open(mob/living/user) - 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/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access - if(!secure) - return FALSE - if(broken) - to_chat(user, "[src] is broken!") - return FALSE - if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics - to_chat(user, "[src] is missing locker electronics!") - return FALSE - if(!check_access) - return TRUE - if(allowed(user)) - return TRUE - to_chat(user, "Access denied.") - -/obj/structure/closet/proc/togglelock(mob/living/user) - add_fingerprint(user) - if(eigen_target) - return - if(opened) - return - if(!can_lock(user)) - return - locked = !locked - user.visible_message("[user] [locked ? null : "un"]locks [src].", - "You [locked ? null : "un"]lock [src].") - update_icon() - -/obj/structure/closet/proc/dump_contents(var/override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example - var/atom/L = drop_location() - for(var/atom/movable/AM in src) - if(AM == lockerelectronics && override) - continue - 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) - if(opened || !can_open(user)) - return - playsound(loc, open_sound, 15, 1, -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 1 - -/obj/structure/closet/proc/insert(atom/movable/AM) - if(contents.len >= storage_capacity) - return -1 - if(insertion_allowed(AM)) - if(eigen_teleport) // For teleporting people with linked lockers. - do_teleport(AM, get_turf(eigen_target), 0) - if(eigen_target.opened == FALSE) - eigen_target.bust_open() - else - 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(istype(AM, /obj/effect)) - return FALSE - - else if(isobj(AM)) - if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) - return FALSE - 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, 15, 1, -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/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/proc/handle_lock_addition(mob/user, obj/item/electronics/airlock/E) - add_fingerprint(user) - if(lock_in_use) - to_chat(user, "Wait for work on [src] to be done first!") - return - if(secure) - to_chat(user, "This locker already has a lock!") - return - if(broken) - to_chat(user, "Unscrew the broken lock first!") - return - if(!istype(E)) - return - user.visible_message("[user] begins installing a lock on [src]...","You begin installing a lock on [src]...") - lock_in_use = TRUE - playsound(loc, 'sound/items/screwdriver.ogg', 50, 1) - if(!do_after(user, 60, target = src)) - lock_in_use = FALSE - return - lock_in_use = FALSE - to_chat(user, "You finish the lock on [src]!") - E.forceMove(src) - lockerelectronics = E - req_access = E.accesses - secure = TRUE - update_icon() - return TRUE - -/obj/structure/closet/proc/handle_lock_removal(mob/user, obj/item/screwdriver/S) - if(lock_in_use) - to_chat(user, "Wait for work on [src] to be done first!") - return - if(locked) - to_chat(user, "Unlock it first!") - return - if(!secure) - to_chat(user, "[src] doesn't have a lock that you can remove!") - return - if(!istype(S)) - return - var/brokenword = broken ? "broken " : null - user.visible_message("[user] begins removing the [brokenword]lock on [src]...","You begin removing the [brokenword]lock on [src]...") - playsound(loc, S.usesound, 50, 1) - lock_in_use = TRUE - if(!do_after(user, 100 * S.toolspeed, target = src)) - lock_in_use = FALSE - return - to_chat(user, "You remove the [brokenword]lock from [src]!") - if(!QDELETED(lockerelectronics)) - lockerelectronics.add_fingerprint(user) - lockerelectronics.forceMove(user.loc) - lockerelectronics = null - req_access = null - secure = FALSE - broken = FALSE - locked = FALSE - lock_in_use = FALSE - update_icon() - return TRUE - - -/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 shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise - . = TRUE - if(opened) - if(istype(W, cutting_tool)) - var/welder = FALSE - if(istype(W, /obj/item/weldingtool)) - if(!W.tool_start_check(user, amount=0)) - return - to_chat(user, "You begin [welder ? "slicing" : "deconstructing"] \the [src] apart...") - welder = TRUE - if(W.use_tool(src, user, 40, volume=50)) - if(eigen_teleport) - to_chat(user, "The unstable nature of \the [src] makes it impossible to [welder ? "slice" : "deconstruct"]!") - return - if(!opened) - return - user.visible_message("[user] [welder ? "slice" : "deconstruct"]s apart \the [src].", - "You [welder ? "slice" : "deconstruct"] \the [src] apart with \the [W].", - "You hear [welder ? "welding" : "rustling of screws and metal"].") - deconstruct(TRUE) - return - if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too - return TRUE - else if(istype(W, /obj/item/electronics/airlock)) - handle_lock_addition(user, W) - else if(istype(W, /obj/item/screwdriver)) - handle_lock_removal(user, W) - else if(istype(W, /obj/item/weldingtool) && 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(eigen_teleport) - to_chat(user, "The unstable nature of \the [src] makes it impossible to weld!") - return - if(opened) - return - welded = !welded - after_weld(welded) - user.visible_message("[user] [welded ? "welds shut" : "unwelds"] \the [src].", - "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", - "You hear welding.") - update_icon() - else if(istype(W, /obj/item/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 && !(W.item_flags & NOBLUDGEON)) - if(W.GetID() || !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.lying) - 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.Knockdown(40) - 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/user) - . = ..() - if(.) - return - if(user.lying && 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 oview(1) - set category = "Object" - set name = "Toggle Open" - - if(!usr.canmove || usr.stat || usr.restrained()) - return - - if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) - return attack_hand(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(ismovableatom(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/AltClick(mob/user) - . = ..() - if(!user.canUseTopic(src, be_close=TRUE) || !isturf(loc)) - to_chat(user, "You can't do that right now!") - return TRUE - togglelock(user) - return TRUE - -/obj/structure/closet/CtrlShiftClick(mob/living/user) - if(!HAS_TRAIT(user, TRAIT_SKITTISH)) - return ..() - if(!user.canUseTopic(src) || !isturf(user.loc)) - return - dive_into(user) - -/obj/structure/closet/emag_act(mob/user) - . = ..() - if(!secure || broken) - return - 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, 1) - broken = TRUE - locked = FALSE - if(!QDELETED(lockerelectronics)) - QDEL_NULL(lockerelectronics) - 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) - return ..() - 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()) - if(!QDELETED(lockerelectronics)) - lockerelectronics.accesses = req_access - -/obj/structure/closet/contents_explosion(severity, target) - for(var/atom/A in contents) - A.ex_act(severity, target) - CHECK_TICK - -/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/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool) - return ..() && opened +/obj/structure/closet + name = "closet" + desc = "It's a basic storage unit." + icon = 'icons/obj/closet.dmi' + icon_state = "generic" + density = TRUE + 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) + max_integrity = 200 + integrity_failure = 50 + armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) + 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/click.ogg' + var/close_sound = 'sound/machines/click.ogg' + 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" + var/obj/item/electronics/airlock/lockerelectronics //Installed electronics + var/lock_in_use = FALSE //Someone is doing some stuff with the lock here, better not proceed further + var/eigen_teleport = FALSE //If the closet leads to Mr Tumnus. + var/obj/structure/closet/eigen_target //Where you go to. + + +/obj/structure/closet/Initialize(mapload) + . = ..() + update_icon() + PopulateContents() + if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents + addtimer(CALLBACK(src, .proc/take_contents), 0) + if(secure) + lockerelectronics = new(src) + lockerelectronics.accesses = req_access + +//USE THIS TO FILL IT, NOT INITIALIZE OR NEW +/obj/structure/closet/proc/PopulateContents() + return + +/obj/structure/closet/Destroy() + dump_contents(override = FALSE) + return ..() + +/obj/structure/closet/update_icon() + cut_overlays() + if(opened & icon_door_override) + add_overlay("[icon_door]_open") + layer = OBJ_LAYER + return + else if(opened) + add_overlay("[icon_state]_open") + return + if(icon_door) + add_overlay("[icon_door]_door") + else + layer = BELOW_OBJ_LAYER + add_overlay("[icon_state]_door") + if(welded) + add_overlay("welded") + if(!secure) + return + if(broken) + add_overlay("off") + add_overlay("sparking") + else if(locked) + add_overlay("locked") + else + add_overlay("unlocked") + +/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) + else if(broken) + . += "The lock is screwed in." + else if(secure) + . += "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/CanPass(atom/movable/mover, turf/target) + if(wall_mounted) + return TRUE + return !density + +/obj/structure/closet/proc/can_open(mob/living/user) + 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/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access + if(!secure) + return FALSE + if(broken) + to_chat(user, "[src] is broken!") + return FALSE + if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics + to_chat(user, "[src] is missing locker electronics!") + return FALSE + if(!check_access) + return TRUE + if(allowed(user)) + return TRUE + to_chat(user, "Access denied.") + +/obj/structure/closet/proc/togglelock(mob/living/user) + add_fingerprint(user) + if(eigen_target) + return + if(opened) + return + if(!can_lock(user)) + return + locked = !locked + user.visible_message("[user] [locked ? null : "un"]locks [src].", + "You [locked ? null : "un"]lock [src].") + update_icon() + +/obj/structure/closet/proc/dump_contents(var/override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example + var/atom/L = drop_location() + for(var/atom/movable/AM in src) + if(AM == lockerelectronics && override) + continue + 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) + if(opened || !can_open(user)) + return + playsound(loc, open_sound, 15, 1, -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 1 + +/obj/structure/closet/proc/insert(atom/movable/AM) + if(contents.len >= storage_capacity) + return -1 + if(insertion_allowed(AM)) + if(eigen_teleport) // For teleporting people with linked lockers. + do_teleport(AM, get_turf(eigen_target), 0) + if(eigen_target.opened == FALSE) + eigen_target.bust_open() + else + 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(istype(AM, /obj/effect)) + return FALSE + + else if(isobj(AM)) + if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) + return FALSE + 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, 15, 1, -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/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/proc/handle_lock_addition(mob/user, obj/item/electronics/airlock/E) + add_fingerprint(user) + if(lock_in_use) + to_chat(user, "Wait for work on [src] to be done first!") + return + if(secure) + to_chat(user, "This locker already has a lock!") + return + if(broken) + to_chat(user, "Unscrew the broken lock first!") + return + if(!istype(E)) + return + user.visible_message("[user] begins installing a lock on [src]...","You begin installing a lock on [src]...") + lock_in_use = TRUE + playsound(loc, 'sound/items/screwdriver.ogg', 50, 1) + if(!do_after(user, 60, target = src)) + lock_in_use = FALSE + return + lock_in_use = FALSE + to_chat(user, "You finish the lock on [src]!") + E.forceMove(src) + lockerelectronics = E + req_access = E.accesses + secure = TRUE + update_icon() + return TRUE + +/obj/structure/closet/proc/handle_lock_removal(mob/user, obj/item/screwdriver/S) + if(lock_in_use) + to_chat(user, "Wait for work on [src] to be done first!") + return + if(locked) + to_chat(user, "Unlock it first!") + return + if(!secure) + to_chat(user, "[src] doesn't have a lock that you can remove!") + return + if(!istype(S)) + return + var/brokenword = broken ? "broken " : null + user.visible_message("[user] begins removing the [brokenword]lock on [src]...","You begin removing the [brokenword]lock on [src]...") + playsound(loc, S.usesound, 50, 1) + lock_in_use = TRUE + if(!do_after(user, 100 * S.toolspeed, target = src)) + lock_in_use = FALSE + return + to_chat(user, "You remove the [brokenword]lock from [src]!") + if(!QDELETED(lockerelectronics)) + lockerelectronics.add_fingerprint(user) + lockerelectronics.forceMove(user.loc) + lockerelectronics = null + req_access = null + secure = FALSE + broken = FALSE + locked = FALSE + lock_in_use = FALSE + update_icon() + return TRUE + + +/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 shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise + . = TRUE + if(opened) + if(istype(W, cutting_tool)) + var/welder = FALSE + if(istype(W, /obj/item/weldingtool)) + if(!W.tool_start_check(user, amount=0)) + return + to_chat(user, "You begin [welder ? "slicing" : "deconstructing"] \the [src] apart...") + welder = TRUE + if(W.use_tool(src, user, 40, volume=50)) + if(eigen_teleport) + to_chat(user, "The unstable nature of \the [src] makes it impossible to [welder ? "slice" : "deconstruct"]!") + return + if(!opened) + return + user.visible_message("[user] [welder ? "slice" : "deconstruct"]s apart \the [src].", + "You [welder ? "slice" : "deconstruct"] \the [src] apart with \the [W].", + "You hear [welder ? "welding" : "rustling of screws and metal"].") + deconstruct(TRUE) + return + if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too + return TRUE + else if(istype(W, /obj/item/electronics/airlock)) + handle_lock_addition(user, W) + else if(istype(W, /obj/item/screwdriver)) + handle_lock_removal(user, W) + else if(istype(W, /obj/item/weldingtool) && 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(eigen_teleport) + to_chat(user, "The unstable nature of \the [src] makes it impossible to weld!") + return + if(opened) + return + welded = !welded + after_weld(welded) + user.visible_message("[user] [welded ? "welds shut" : "unwelds"] \the [src].", + "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", + "You hear welding.") + update_icon() + else if(istype(W, /obj/item/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 && !(W.item_flags & NOBLUDGEON)) + if(W.GetID() || !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.lying) + 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.Knockdown(40) + 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/user) + . = ..() + if(.) + return + if(user.lying && 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 oview(1) + set category = "Object" + set name = "Toggle Open" + + if(!usr.canmove || usr.stat || usr.restrained()) + return + + if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) + return attack_hand(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(ismovableatom(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/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, be_close=TRUE) || !isturf(loc)) + to_chat(user, "You can't do that right now!") + return TRUE + togglelock(user) + return TRUE + +/obj/structure/closet/CtrlShiftClick(mob/living/user) + if(!HAS_TRAIT(user, TRAIT_SKITTISH)) + return ..() + if(!user.canUseTopic(src) || !isturf(user.loc)) + return + dive_into(user) + +/obj/structure/closet/emag_act(mob/user) + . = ..() + if(!secure || broken) + return + 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, 1) + broken = TRUE + locked = FALSE + if(!QDELETED(lockerelectronics)) + QDEL_NULL(lockerelectronics) + 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) + return ..() + 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()) + if(!QDELETED(lockerelectronics)) + lockerelectronics.accesses = req_access + +/obj/structure/closet/contents_explosion(severity, target) + for(var/atom/A in contents) + A.ex_act(severity, target) + CHECK_TICK + +/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/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool) + return ..() && opened diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm index 6548ec737c..6719800340 100644 --- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm +++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm @@ -1,109 +1,109 @@ -/obj/structure/closet/cabinet - name = "cabinet" - desc = "Old will forever be in fashion." - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - material_drop = /obj/item/stack/sheet/mineral/wood - cutting_tool = /obj/item/screwdriver - -/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/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/combat(src) - new /obj/item/clothing/gloves/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 + max_integrity = 70 + material_drop = /obj/item/stack/sheet/mineral/wood + cutting_tool = /obj/item/screwdriver + +/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/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/combat(src) + new /obj/item/clothing/gloves/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 9deca71269..a5857029f5 100644 --- a/code/game/objects/structures/crates_lockers/closets/job_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/job_closets.dm @@ -1,366 +1,366 @@ -// 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() - ..() - new /obj/item/clothing/head/that(src) - new /obj/item/radio/headset/headset_srv(src) - new /obj/item/radio/headset/headset_srv(src) - new /obj/item/clothing/head/that(src) - new /obj/item/clothing/under/sl_suit(src) - new /obj/item/clothing/under/sl_suit(src) - new /obj/item/clothing/under/rank/bartender(src) - new /obj/item/clothing/under/rank/bartender(src) - new /obj/item/clothing/accessory/waistcoat(src) - new /obj/item/clothing/accessory/waistcoat(src) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/reagent_containers/rag(src) - new /obj/item/reagent_containers/rag(src) - new /obj/item/storage/box/beanbag(src) - new /obj/item/clothing/suit/armor/vest/alt(src) - new /obj/item/circuitboard/machine/dish_drive(src) - new /obj/item/clothing/glasses/sunglasses/reagent(src) - new /obj/item/clothing/neck/petcollar(src) - new /obj/item/storage/belt/bandolier(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() - ..() - new /obj/item/clothing/under/waiter(src) - new /obj/item/clothing/under/waiter(src) - new /obj/item/radio/headset/headset_srv(src) - new /obj/item/radio/headset/headset_srv(src) - new /obj/item/clothing/accessory/waistcoat(src) - new /obj/item/clothing/accessory/waistcoat(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/apron/chef(src) - new /obj/item/clothing/head/soft/mime(src) - new /obj/item/clothing/head/soft/mime(src) - new /obj/item/storage/box/mousetraps(src) - new /obj/item/storage/box/mousetraps(src) - new /obj/item/circuitboard/machine/dish_drive(src) - new /obj/item/clothing/suit/toggle/chef(src) - new /obj/item/clothing/under/rank/chef(src) - new /obj/item/clothing/head/chefhat(src) - new /obj/item/reagent_containers/rag(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/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/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/lawyer/female(src) - new /obj/item/clothing/under/lawyer/black(src) - new /obj/item/clothing/under/lawyer/red(src) - new /obj/item/clothing/under/lawyer/bluesuit(src) - new /obj/item/clothing/suit/toggle/lawyer(src) - new /obj/item/clothing/under/lawyer/purpsuit(src) - new /obj/item/clothing/suit/toggle/lawyer/purple(src) - new /obj/item/clothing/under/lawyer/blacksuit(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/holybeacon(src) - new /obj/item/clothing/accessory/pocketprotector/cosmetology(src) - new /obj/item/clothing/under/rank/chaplain(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/suit/chaplain/nun(src) - new /obj/item/clothing/head/nun_hood(src) - new /obj/item/clothing/suit/chaplain/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() - new /obj/item/clothing/suit/hooded/wintercoat/security(src) - new /obj/item/storage/backpack/security(src) - new /obj/item/storage/backpack/satchel/sec(src) - new /obj/item/storage/backpack/duffelbag/sec(src) - new /obj/item/storage/backpack/duffelbag/sec(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/security(src) - for(var/i in 1 to 2) - new /obj/item/clothing/under/rank/security/skirt(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/jackboots(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/beret/sec(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft/sec(src) - new /obj/item/clothing/mask/bandana/red(src) - new /obj/item/clothing/mask/bandana/red(src) - return - - -/obj/structure/closet/wardrobe/cargotech - name = "cargo wardrobe" - icon_door = "orange" - -/obj/structure/closet/wardrobe/cargotech/PopulateContents() - new /obj/item/clothing/suit/hooded/wintercoat/cargo(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/cargotech(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/gloves/fingerless(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/soft(src) - new /obj/item/radio/headset/headset_cargo(src) - -/obj/structure/closet/wardrobe/atmospherics_yellow - name = "atmospherics wardrobe" - icon_door = "atmos_wardrobe" - -/obj/structure/closet/wardrobe/atmospherics_yellow/PopulateContents() - new /obj/item/clothing/accessory/pocketprotector(src) - new /obj/item/storage/backpack/duffelbag/engineering(src) - new /obj/item/storage/backpack/satchel/eng(src) - new /obj/item/storage/backpack/industrial(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/atmospheric_technician(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/black(src) - return - -/obj/structure/closet/wardrobe/engineering_yellow - name = "engineering wardrobe" - icon_door = "yellow" - -/obj/structure/closet/wardrobe/engineering_yellow/PopulateContents() - new /obj/item/clothing/accessory/pocketprotector(src) - new /obj/item/storage/backpack/duffelbag/engineering(src) - new /obj/item/storage/backpack/industrial(src) - new /obj/item/storage/backpack/satchel/eng(src) - new /obj/item/clothing/suit/hooded/wintercoat/engineering(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/engineer(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/hazardvest(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/workboots(src) - for(var/i in 1 to 3) - new /obj/item/clothing/head/hardhat(src) - return - -/obj/structure/closet/wardrobe/white/medical - name = "medical doctor's wardrobe" - -/obj/structure/closet/wardrobe/white/medical/PopulateContents() - new /obj/item/clothing/accessory/pocketprotector(src) - new /obj/item/storage/backpack/duffelbag/med(src) - new /obj/item/storage/backpack/medic(src) - new /obj/item/storage/backpack/satchel/med(src) - new /obj/item/clothing/suit/hooded/wintercoat/medical(src) - new /obj/item/clothing/under/rank/nursesuit(src) - new /obj/item/clothing/head/nursehat(src) - new /obj/item/clothing/under/rank/medical/blue(src) - new /obj/item/clothing/under/rank/medical/green(src) - new /obj/item/clothing/under/rank/medical/purple(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/medical(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/toggle/labcoat(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/toggle/labcoat/emt(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/emt(src) - return - -/obj/structure/closet/wardrobe/robotics_black - name = "robotics wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/robotics_black/PopulateContents() - new /obj/item/clothing/glasses/hud/diagnostic(src) - new /obj/item/clothing/glasses/hud/diagnostic(src) - new /obj/item/clothing/under/rank/roboticist(src) - new /obj/item/clothing/under/rank/roboticist(src) - new /obj/item/clothing/suit/toggle/labcoat(src) - new /obj/item/clothing/suit/toggle/labcoat(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/shoes/sneakers/black(src) - new /obj/item/clothing/gloves/fingerless(src) - new /obj/item/clothing/gloves/fingerless(src) - new /obj/item/clothing/head/soft/black(src) - new /obj/item/clothing/head/soft/black(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() - new /obj/item/clothing/under/rank/chemist(src) - new /obj/item/clothing/under/rank/chemist(src) - new /obj/item/clothing/shoes/sneakers/white(src) - new /obj/item/clothing/shoes/sneakers/white(src) - new /obj/item/clothing/suit/toggle/labcoat/chemist(src) - new /obj/item/clothing/suit/toggle/labcoat/chemist(src) - new /obj/item/storage/backpack/chemistry(src) - new /obj/item/storage/backpack/chemistry(src) - new /obj/item/storage/backpack/satchel/chem(src) - new /obj/item/storage/backpack/satchel/chem(src) - new /obj/item/storage/bag/chemistry(src) - new /obj/item/storage/bag/chemistry(src) - return - - -/obj/structure/closet/wardrobe/genetics_white - name = "genetics wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/genetics_white/PopulateContents() - new /obj/item/clothing/under/rank/geneticist(src) - new /obj/item/clothing/under/rank/geneticist(src) - new /obj/item/clothing/shoes/sneakers/white(src) - new /obj/item/clothing/shoes/sneakers/white(src) - new /obj/item/clothing/suit/toggle/labcoat/genetics(src) - new /obj/item/clothing/suit/toggle/labcoat/genetics(src) - new /obj/item/storage/backpack/genetics(src) - new /obj/item/storage/backpack/genetics(src) - new /obj/item/storage/backpack/satchel/gen(src) - new /obj/item/storage/backpack/satchel/gen(src) - return - - -/obj/structure/closet/wardrobe/virology_white - name = "virology wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/virology_white/PopulateContents() - new /obj/item/clothing/under/rank/virologist(src) - new /obj/item/clothing/under/rank/virologist(src) - new /obj/item/clothing/shoes/sneakers/white(src) - new /obj/item/clothing/shoes/sneakers/white(src) - new /obj/item/clothing/suit/toggle/labcoat/virologist(src) - new /obj/item/clothing/suit/toggle/labcoat/virologist(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/clothing/mask/surgical(src) - new /obj/item/storage/backpack/virology(src) - new /obj/item/storage/backpack/virology(src) - new /obj/item/storage/backpack/satchel/vir(src) - new /obj/item/storage/backpack/satchel/vir(src) - return - -/obj/structure/closet/wardrobe/science_white - name = "science wardrobe" - icon_door = "white" - -/obj/structure/closet/wardrobe/science_white/PopulateContents() - new /obj/item/clothing/accessory/pocketprotector(src) - new /obj/item/storage/backpack/science(src) - new /obj/item/storage/backpack/science(src) - new /obj/item/storage/backpack/satchel/tox(src) - new /obj/item/storage/backpack/satchel/tox(src) - new /obj/item/clothing/suit/hooded/wintercoat/science(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/scientist(src) - for(var/i in 1 to 3) - new /obj/item/clothing/suit/toggle/labcoat/science(src) - for(var/i in 1 to 3) - new /obj/item/clothing/shoes/sneakers/white(src) - new /obj/item/radio/headset/headset_sci(src) - new /obj/item/radio/headset/headset_sci(src) - for(var/i in 1 to 3) - new /obj/item/clothing/mask/gas(src) - return - -/obj/structure/closet/wardrobe/botanist - name = "botanist wardrobe" - icon_door = "green" - -/obj/structure/closet/wardrobe/botanist/PopulateContents() - new /obj/item/storage/backpack/botany(src) - new /obj/item/storage/backpack/botany(src) - new /obj/item/storage/backpack/satchel/hyd(src) - new /obj/item/storage/backpack/satchel/hyd(src) - new /obj/item/clothing/suit/hooded/wintercoat/hydro(src) - new /obj/item/clothing/suit/apron(src) - new /obj/item/clothing/suit/apron(src) - new /obj/item/clothing/suit/apron/overalls(src) - new /obj/item/clothing/suit/apron/overalls(src) - for(var/i in 1 to 3) - new /obj/item/clothing/under/rank/hydroponics(src) - for(var/i in 1 to 3) - new /obj/item/clothing/mask/bandana(src) - - -/obj/structure/closet/wardrobe/curator - name = "treasure hunting wardrobe" - icon_door = "black" - -/obj/structure/closet/wardrobe/curator/PopulateContents() - new /obj/item/clothing/accessory/pocketprotector/full(src) - new /obj/item/clothing/head/fedora/curator(src) - new /obj/item/clothing/suit/curator(src) - new /obj/item/clothing/under/rank/curator/treasure_hunter(src) - new /obj/item/clothing/shoes/workboots/mining(src) - new /obj/item/storage/backpack/satchel/explorer(src) - -/obj/structure/closet/coffin/handle_lock_addition() - return - -/obj/structure/closet/coffin/handle_lock_removal() - return +// 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() + ..() + new /obj/item/clothing/head/that(src) + new /obj/item/radio/headset/headset_srv(src) + new /obj/item/radio/headset/headset_srv(src) + new /obj/item/clothing/head/that(src) + new /obj/item/clothing/under/sl_suit(src) + new /obj/item/clothing/under/sl_suit(src) + new /obj/item/clothing/under/rank/bartender(src) + new /obj/item/clothing/under/rank/bartender(src) + new /obj/item/clothing/accessory/waistcoat(src) + new /obj/item/clothing/accessory/waistcoat(src) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/reagent_containers/rag(src) + new /obj/item/reagent_containers/rag(src) + new /obj/item/storage/box/beanbag(src) + new /obj/item/clothing/suit/armor/vest/alt(src) + new /obj/item/circuitboard/machine/dish_drive(src) + new /obj/item/clothing/glasses/sunglasses/reagent(src) + new /obj/item/clothing/neck/petcollar(src) + new /obj/item/storage/belt/bandolier(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() + ..() + new /obj/item/clothing/under/waiter(src) + new /obj/item/clothing/under/waiter(src) + new /obj/item/radio/headset/headset_srv(src) + new /obj/item/radio/headset/headset_srv(src) + new /obj/item/clothing/accessory/waistcoat(src) + new /obj/item/clothing/accessory/waistcoat(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/apron/chef(src) + new /obj/item/clothing/head/soft/mime(src) + new /obj/item/clothing/head/soft/mime(src) + new /obj/item/storage/box/mousetraps(src) + new /obj/item/storage/box/mousetraps(src) + new /obj/item/circuitboard/machine/dish_drive(src) + new /obj/item/clothing/suit/toggle/chef(src) + new /obj/item/clothing/under/rank/chef(src) + new /obj/item/clothing/head/chefhat(src) + new /obj/item/reagent_containers/rag(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/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/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/lawyer/female(src) + new /obj/item/clothing/under/lawyer/black(src) + new /obj/item/clothing/under/lawyer/red(src) + new /obj/item/clothing/under/lawyer/bluesuit(src) + new /obj/item/clothing/suit/toggle/lawyer(src) + new /obj/item/clothing/under/lawyer/purpsuit(src) + new /obj/item/clothing/suit/toggle/lawyer/purple(src) + new /obj/item/clothing/under/lawyer/blacksuit(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/holybeacon(src) + new /obj/item/clothing/accessory/pocketprotector/cosmetology(src) + new /obj/item/clothing/under/rank/chaplain(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/suit/chaplain/nun(src) + new /obj/item/clothing/head/nun_hood(src) + new /obj/item/clothing/suit/chaplain/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() + new /obj/item/clothing/suit/hooded/wintercoat/security(src) + new /obj/item/storage/backpack/security(src) + new /obj/item/storage/backpack/satchel/sec(src) + new /obj/item/storage/backpack/duffelbag/sec(src) + new /obj/item/storage/backpack/duffelbag/sec(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/security(src) + for(var/i in 1 to 2) + new /obj/item/clothing/under/rank/security/skirt(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/jackboots(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/beret/sec(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft/sec(src) + new /obj/item/clothing/mask/bandana/red(src) + new /obj/item/clothing/mask/bandana/red(src) + return + + +/obj/structure/closet/wardrobe/cargotech + name = "cargo wardrobe" + icon_door = "orange" + +/obj/structure/closet/wardrobe/cargotech/PopulateContents() + new /obj/item/clothing/suit/hooded/wintercoat/cargo(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/cargotech(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/gloves/fingerless(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/soft(src) + new /obj/item/radio/headset/headset_cargo(src) + +/obj/structure/closet/wardrobe/atmospherics_yellow + name = "atmospherics wardrobe" + icon_door = "atmos_wardrobe" + +/obj/structure/closet/wardrobe/atmospherics_yellow/PopulateContents() + new /obj/item/clothing/accessory/pocketprotector(src) + new /obj/item/storage/backpack/duffelbag/engineering(src) + new /obj/item/storage/backpack/satchel/eng(src) + new /obj/item/storage/backpack/industrial(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/atmospheric_technician(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/black(src) + return + +/obj/structure/closet/wardrobe/engineering_yellow + name = "engineering wardrobe" + icon_door = "yellow" + +/obj/structure/closet/wardrobe/engineering_yellow/PopulateContents() + new /obj/item/clothing/accessory/pocketprotector(src) + new /obj/item/storage/backpack/duffelbag/engineering(src) + new /obj/item/storage/backpack/industrial(src) + new /obj/item/storage/backpack/satchel/eng(src) + new /obj/item/clothing/suit/hooded/wintercoat/engineering(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/engineer(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/hazardvest(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/workboots(src) + for(var/i in 1 to 3) + new /obj/item/clothing/head/hardhat(src) + return + +/obj/structure/closet/wardrobe/white/medical + name = "medical doctor's wardrobe" + +/obj/structure/closet/wardrobe/white/medical/PopulateContents() + new /obj/item/clothing/accessory/pocketprotector(src) + new /obj/item/storage/backpack/duffelbag/med(src) + new /obj/item/storage/backpack/medic(src) + new /obj/item/storage/backpack/satchel/med(src) + new /obj/item/clothing/suit/hooded/wintercoat/medical(src) + new /obj/item/clothing/under/rank/nursesuit(src) + new /obj/item/clothing/head/nursehat(src) + new /obj/item/clothing/under/rank/medical/blue(src) + new /obj/item/clothing/under/rank/medical/green(src) + new /obj/item/clothing/under/rank/medical/purple(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/medical(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/toggle/labcoat(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/toggle/labcoat/emt(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/emt(src) + return + +/obj/structure/closet/wardrobe/robotics_black + name = "robotics wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/robotics_black/PopulateContents() + new /obj/item/clothing/glasses/hud/diagnostic(src) + new /obj/item/clothing/glasses/hud/diagnostic(src) + new /obj/item/clothing/under/rank/roboticist(src) + new /obj/item/clothing/under/rank/roboticist(src) + new /obj/item/clothing/suit/toggle/labcoat(src) + new /obj/item/clothing/suit/toggle/labcoat(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/shoes/sneakers/black(src) + new /obj/item/clothing/gloves/fingerless(src) + new /obj/item/clothing/gloves/fingerless(src) + new /obj/item/clothing/head/soft/black(src) + new /obj/item/clothing/head/soft/black(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() + new /obj/item/clothing/under/rank/chemist(src) + new /obj/item/clothing/under/rank/chemist(src) + new /obj/item/clothing/shoes/sneakers/white(src) + new /obj/item/clothing/shoes/sneakers/white(src) + new /obj/item/clothing/suit/toggle/labcoat/chemist(src) + new /obj/item/clothing/suit/toggle/labcoat/chemist(src) + new /obj/item/storage/backpack/chemistry(src) + new /obj/item/storage/backpack/chemistry(src) + new /obj/item/storage/backpack/satchel/chem(src) + new /obj/item/storage/backpack/satchel/chem(src) + new /obj/item/storage/bag/chemistry(src) + new /obj/item/storage/bag/chemistry(src) + return + + +/obj/structure/closet/wardrobe/genetics_white + name = "genetics wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/genetics_white/PopulateContents() + new /obj/item/clothing/under/rank/geneticist(src) + new /obj/item/clothing/under/rank/geneticist(src) + new /obj/item/clothing/shoes/sneakers/white(src) + new /obj/item/clothing/shoes/sneakers/white(src) + new /obj/item/clothing/suit/toggle/labcoat/genetics(src) + new /obj/item/clothing/suit/toggle/labcoat/genetics(src) + new /obj/item/storage/backpack/genetics(src) + new /obj/item/storage/backpack/genetics(src) + new /obj/item/storage/backpack/satchel/gen(src) + new /obj/item/storage/backpack/satchel/gen(src) + return + + +/obj/structure/closet/wardrobe/virology_white + name = "virology wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/virology_white/PopulateContents() + new /obj/item/clothing/under/rank/virologist(src) + new /obj/item/clothing/under/rank/virologist(src) + new /obj/item/clothing/shoes/sneakers/white(src) + new /obj/item/clothing/shoes/sneakers/white(src) + new /obj/item/clothing/suit/toggle/labcoat/virologist(src) + new /obj/item/clothing/suit/toggle/labcoat/virologist(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/clothing/mask/surgical(src) + new /obj/item/storage/backpack/virology(src) + new /obj/item/storage/backpack/virology(src) + new /obj/item/storage/backpack/satchel/vir(src) + new /obj/item/storage/backpack/satchel/vir(src) + return + +/obj/structure/closet/wardrobe/science_white + name = "science wardrobe" + icon_door = "white" + +/obj/structure/closet/wardrobe/science_white/PopulateContents() + new /obj/item/clothing/accessory/pocketprotector(src) + new /obj/item/storage/backpack/science(src) + new /obj/item/storage/backpack/science(src) + new /obj/item/storage/backpack/satchel/tox(src) + new /obj/item/storage/backpack/satchel/tox(src) + new /obj/item/clothing/suit/hooded/wintercoat/science(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/scientist(src) + for(var/i in 1 to 3) + new /obj/item/clothing/suit/toggle/labcoat/science(src) + for(var/i in 1 to 3) + new /obj/item/clothing/shoes/sneakers/white(src) + new /obj/item/radio/headset/headset_sci(src) + new /obj/item/radio/headset/headset_sci(src) + for(var/i in 1 to 3) + new /obj/item/clothing/mask/gas(src) + return + +/obj/structure/closet/wardrobe/botanist + name = "botanist wardrobe" + icon_door = "green" + +/obj/structure/closet/wardrobe/botanist/PopulateContents() + new /obj/item/storage/backpack/botany(src) + new /obj/item/storage/backpack/botany(src) + new /obj/item/storage/backpack/satchel/hyd(src) + new /obj/item/storage/backpack/satchel/hyd(src) + new /obj/item/clothing/suit/hooded/wintercoat/hydro(src) + new /obj/item/clothing/suit/apron(src) + new /obj/item/clothing/suit/apron(src) + new /obj/item/clothing/suit/apron/overalls(src) + new /obj/item/clothing/suit/apron/overalls(src) + for(var/i in 1 to 3) + new /obj/item/clothing/under/rank/hydroponics(src) + for(var/i in 1 to 3) + new /obj/item/clothing/mask/bandana(src) + + +/obj/structure/closet/wardrobe/curator + name = "treasure hunting wardrobe" + icon_door = "black" + +/obj/structure/closet/wardrobe/curator/PopulateContents() + new /obj/item/clothing/accessory/pocketprotector/full(src) + new /obj/item/clothing/head/fedora/curator(src) + new /obj/item/clothing/suit/curator(src) + new /obj/item/clothing/under/rank/curator/treasure_hunter(src) + new /obj/item/clothing/shoes/workboots/mining(src) + new /obj/item/storage/backpack/satchel/explorer(src) + +/obj/structure/closet/coffin/handle_lock_addition() + return + +/obj/structure/closet/coffin/handle_lock_removal() + return 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 bfcb00f285..484d152a3f 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/bar.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/bar.dm @@ -1,13 +1,13 @@ -/obj/structure/closet/secure_closet/bar - name = "booze storage" - req_access = list(ACCESS_BAR) - icon_state = "cabinet" - resistance_flags = FLAMMABLE - max_integrity = 70 - material_drop = /obj/item/stack/sheet/mineral/wood - cutting_tool = /obj/item/screwdriver - -/obj/structure/closet/secure_closet/bar/PopulateContents() - ..() - for(var/i in 1 to 10) - new /obj/item/reagent_containers/food/drinks/beer( src ) +/obj/structure/closet/secure_closet/bar + name = "booze storage" + req_access = list(ACCESS_BAR) + icon_state = "cabinet" + resistance_flags = FLAMMABLE + max_integrity = 70 + material_drop = /obj/item/stack/sheet/mineral/wood + cutting_tool = /obj/item/screwdriver + +/obj/structure/closet/secure_closet/bar/PopulateContents() + ..() + for(var/i in 1 to 10) + new /obj/item/reagent_containers/food/drinks/beer( 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 b06073812a..e1537063bd 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm @@ -1,26 +1,26 @@ -/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/clothing/head/beret/qm(src) - new /obj/item/storage/lockbox/medal/cargo(src) - new /obj/item/clothing/under/rank/cargo(src) - new /obj/item/clothing/under/rank/cargo/skirt(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/radio/headset/heads/qm(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/clothing/suit/hooded/wintercoat/qm(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/clothing/head/beret/qm(src) + new /obj/item/storage/lockbox/medal/cargo(src) + new /obj/item/clothing/under/rank/cargo(src) + new /obj/item/clothing/under/rank/cargo/skirt(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/radio/headset/heads/qm(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/clothing/suit/hooded/wintercoat/qm(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 c26dc78e52..bdbc91554d 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/engineering.dm @@ -1,129 +1,129 @@ -/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/head/beret/ce(src) - new /obj/item/clothing/under/rank/chief_engineer(src) - new /obj/item/clothing/under/rank/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/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/inducer(src) - new /obj/item/circuitboard/machine/techfab/department/engineering(src) - new /obj/item/extinguisher/advanced(src) - new /obj/item/storage/photo_album/CE(src) - new /obj/item/storage/lockbox/medal/engineering(src) - new /obj/item/construction/rcd/loaded/upgraded(src) - new /obj/item/clothing/suit/hooded/wintercoat/ce(src) - new /obj/item/clothing/head/beret/ce/white(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() - ..() - new /obj/item/clothing/gloves/color/yellow(src) - new /obj/item/clothing/gloves/color/yellow(src) - new /obj/item/inducer(src) - new /obj/item/inducer(src) - for(var/i in 1 to 3) - new /obj/item/storage/toolbox/electrical(src) - for(var/i in 1 to 3) - new /obj/item/electronics/apc(src) - for(var/i in 1 to 3) - new /obj/item/multitool(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/largetank(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) - -/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/analyzer(src) - new /obj/item/holosign_creator/atmos(src) - new /obj/item/holosign_creator/firelock(src) - new /obj/item/watertank/atmos(src) - new /obj/item/clothing/suit/fire/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) - -/* - * Empty lockers - * Some of the lockers are filled with junk, and sometimes its nice to just fill it with your own set-up for your own map gimmicks. - */ - -/obj/structure/closet/secure_closet/engineering_chief/empty - -/obj/structure/closet/secure_closet/engineering_chief/empty/PopulateContents() - return - -/obj/structure/closet/secure_closet/engineering_electrical/empty - -/obj/structure/closet/secure_closet/engineering_electrical/empty/PopulateContents() - return - -/obj/structure/closet/secure_closet/engineering_welding/empty - -/obj/structure/closet/secure_closet/engineering_welding/empty/PopulateContents() - return - -/obj/structure/closet/secure_closet/engineering_personal/empty - -/obj/structure/closet/secure_closet/engineering_personal/empty/PopulateContents() - return - -/obj/structure/closet/secure_closet/atmospherics/empty - -/obj/structure/closet/secure_closet/atmospherics/empty/PopulateContents() - return +/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/head/beret/ce(src) + new /obj/item/clothing/under/rank/chief_engineer(src) + new /obj/item/clothing/under/rank/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/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/inducer(src) + new /obj/item/circuitboard/machine/techfab/department/engineering(src) + new /obj/item/extinguisher/advanced(src) + new /obj/item/storage/photo_album/CE(src) + new /obj/item/storage/lockbox/medal/engineering(src) + new /obj/item/construction/rcd/loaded/upgraded(src) + new /obj/item/clothing/suit/hooded/wintercoat/ce(src) + new /obj/item/clothing/head/beret/ce/white(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() + ..() + new /obj/item/clothing/gloves/color/yellow(src) + new /obj/item/clothing/gloves/color/yellow(src) + new /obj/item/inducer(src) + new /obj/item/inducer(src) + for(var/i in 1 to 3) + new /obj/item/storage/toolbox/electrical(src) + for(var/i in 1 to 3) + new /obj/item/electronics/apc(src) + for(var/i in 1 to 3) + new /obj/item/multitool(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/largetank(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) + +/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/analyzer(src) + new /obj/item/holosign_creator/atmos(src) + new /obj/item/holosign_creator/firelock(src) + new /obj/item/watertank/atmos(src) + new /obj/item/clothing/suit/fire/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) + +/* + * Empty lockers + * Some of the lockers are filled with junk, and sometimes its nice to just fill it with your own set-up for your own map gimmicks. + */ + +/obj/structure/closet/secure_closet/engineering_chief/empty + +/obj/structure/closet/secure_closet/engineering_chief/empty/PopulateContents() + return + +/obj/structure/closet/secure_closet/engineering_electrical/empty + +/obj/structure/closet/secure_closet/engineering_electrical/empty/PopulateContents() + return + +/obj/structure/closet/secure_closet/engineering_welding/empty + +/obj/structure/closet/secure_closet/engineering_welding/empty/PopulateContents() + return + +/obj/structure/closet/secure_closet/engineering_personal/empty + +/obj/structure/closet/secure_closet/engineering_personal/empty/PopulateContents() + return + +/obj/structure/closet/secure_closet/atmospherics/empty + +/obj/structure/closet/secure_closet/atmospherics/empty/PopulateContents() + return 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 dd72eb6b5d..9ed148b4e3 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm @@ -1,106 +1,106 @@ -/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) - if(opened || !can_open(user)) //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" - -/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/fridge - name = "refrigerator" - -/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) + if(opened || !can_open(user)) //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" + +/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/fridge + name = "refrigerator" + +/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 ac246d5053..a9e6243060 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) +/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/storage/box/disks_plantgene(src) \ No newline at end of file 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 ead133e22b..3167d8ac18 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm @@ -1,107 +1,107 @@ -/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() - ..() - new /obj/item/reagent_containers/glass/beaker(src) - new /obj/item/reagent_containers/glass/beaker(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/reagent_containers/dropper(src) - new /obj/item/storage/belt/medical(src) - new /obj/item/storage/box/syringes(src) - new /obj/item/reagent_containers/glass/bottle/toxin(src) - new /obj/item/reagent_containers/glass/bottle/morphine(src) - new /obj/item/reagent_containers/glass/bottle/morphine(src) - for(var/i in 1 to 3) - new /obj/item/reagent_containers/glass/bottle/epinephrine(src) - for(var/i in 1 to 3) - new /obj/item/reagent_containers/glass/bottle/charcoal(src) - new /obj/item/storage/box/rxglasses(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/CMO - name = "\proper chief medical officer's locker" - req_access = list(ACCESS_CMO) - icon_state = "cmo" - -/obj/structure/closet/secure_closet/CMO/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/cmo(src) - new /obj/item/clothing/head/beret/cmo(src) - new /obj/item/storage/backpack/duffelbag/med(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/chief_medical_officer(src) - new /obj/item/clothing/under/rank/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/storage/belt/medical(src) - new /obj/item/healthanalyzer/advanced(src) - new /obj/item/assembly/flash/handheld(src) - new /obj/item/storage/hypospraykit/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/storage/belt/medical/surgery_belt_adv(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) - new /obj/item/storage/lockbox/medal/medical(src) - new /obj/item/clothing/suit/hooded/wintercoat/cmo(src) - new /obj/item/clothing/head/beret/cmo/blue(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." - 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/medsprays(src) - new /obj/item/storage/box/medsprays(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() + ..() + new /obj/item/reagent_containers/glass/beaker(src) + new /obj/item/reagent_containers/glass/beaker(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/reagent_containers/dropper(src) + new /obj/item/storage/belt/medical(src) + new /obj/item/storage/box/syringes(src) + new /obj/item/reagent_containers/glass/bottle/toxin(src) + new /obj/item/reagent_containers/glass/bottle/morphine(src) + new /obj/item/reagent_containers/glass/bottle/morphine(src) + for(var/i in 1 to 3) + new /obj/item/reagent_containers/glass/bottle/epinephrine(src) + for(var/i in 1 to 3) + new /obj/item/reagent_containers/glass/bottle/charcoal(src) + new /obj/item/storage/box/rxglasses(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/CMO + name = "\proper chief medical officer's locker" + req_access = list(ACCESS_CMO) + icon_state = "cmo" + +/obj/structure/closet/secure_closet/CMO/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/cmo(src) + new /obj/item/clothing/head/beret/cmo(src) + new /obj/item/storage/backpack/duffelbag/med(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/chief_medical_officer(src) + new /obj/item/clothing/under/rank/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/storage/belt/medical(src) + new /obj/item/healthanalyzer/advanced(src) + new /obj/item/assembly/flash/handheld(src) + new /obj/item/storage/hypospraykit/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/storage/belt/medical/surgery_belt_adv(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) + new /obj/item/storage/lockbox/medal/medical(src) + new /obj/item/clothing/suit/hooded/wintercoat/cmo(src) + new /obj/item/clothing/head/beret/cmo/blue(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." + 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/medsprays(src) + new /obj/item/storage/box/medsprays(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 8cf0852aaa..14d32cd4fe 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm @@ -1,75 +1,75 @@ -/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/examine(mob/user) - . = ..() - if(registered_name) - . += "The display reads, \"Owned by [registered_name]\"." - -/obj/structure/closet/secure_closet/personal/check_access(obj/item/I) - . = ..() - if(!I || !istype(I)) - return - if(istype(I,/obj/item/modular_computer/tablet)) - var/obj/item/modular_computer/tablet/ourTablet = I - var/obj/item/computer_hardware/card_slot/card_slot = ourTablet.all_components[MC_CARD] - if(card_slot) - return registered_name == card_slot.stored_card.registered_name || registered_name == card_slot.stored_card2.registered_name - var/obj/item/card/id/ID = I.GetID() - if(ID && registered_name == ID.registered_name) - return TRUE - return FALSE - -/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 - material_drop = /obj/item/stack/sheet/mineral/wood - cutting_tool = /obj/item/screwdriver - -/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 ) - new /obj/item/clothing/head/colour(src) - -/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params) - var/obj/item/card/id/I = W.GetID() - if(!I || !istype(I)) - return ..() - if(!can_lock(user, FALSE)) //Can't do anything if there isn't a lock! - return - if(I.registered_name && !registered_name) - to_chat(user, "You claim [src].") - registered_name = I.registered_name - else - ..() - -/obj/structure/closet/secure_closet/personal/handle_lock_addition() //If lock construction is successful we don't care what access the electronics had, so we override it - if(..()) - req_access = list(ACCESS_ALL_PERSONAL_LOCKERS) - lockerelectronics.accesses = req_access - -/obj/structure/closet/secure_closet/personal/handle_lock_removal() - if(..()) - registered_name = null +/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/examine(mob/user) + . = ..() + if(registered_name) + . += "The display reads, \"Owned by [registered_name]\"." + +/obj/structure/closet/secure_closet/personal/check_access(obj/item/I) + . = ..() + if(!I || !istype(I)) + return + if(istype(I,/obj/item/modular_computer/tablet)) + var/obj/item/modular_computer/tablet/ourTablet = I + var/obj/item/computer_hardware/card_slot/card_slot = ourTablet.all_components[MC_CARD] + if(card_slot) + return registered_name == card_slot.stored_card.registered_name || registered_name == card_slot.stored_card2.registered_name + var/obj/item/card/id/ID = I.GetID() + if(ID && registered_name == ID.registered_name) + return TRUE + return FALSE + +/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 + material_drop = /obj/item/stack/sheet/mineral/wood + cutting_tool = /obj/item/screwdriver + +/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 ) + new /obj/item/clothing/head/colour(src) + +/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params) + var/obj/item/card/id/I = W.GetID() + if(!I || !istype(I)) + return ..() + if(!can_lock(user, FALSE)) //Can't do anything if there isn't a lock! + return + if(I.registered_name && !registered_name) + to_chat(user, "You claim [src].") + registered_name = I.registered_name + else + ..() + +/obj/structure/closet/secure_closet/personal/handle_lock_addition() //If lock construction is successful we don't care what access the electronics had, so we override it + if(..()) + req_access = list(ACCESS_ALL_PERSONAL_LOCKERS) + lockerelectronics.accesses = req_access + +/obj/structure/closet/secure_closet/personal/handle_lock_removal() + if(..()) + registered_name = null 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 4e1ca9a77d..3e4497bb45 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm @@ -1,34 +1,34 @@ -/obj/structure/closet/secure_closet/RD - name = "\proper research director's locker" - req_access = list(ACCESS_RD) - icon_state = "rd" - -/obj/structure/closet/secure_closet/RD/PopulateContents() - ..() - new /obj/item/clothing/neck/cloak/rd(src) - new /obj/item/clothing/head/beret/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/research_director(src) - new /obj/item/clothing/under/rank/research_director/skirt(src) - new /obj/item/clothing/under/rank/research_director/alt(src) - new /obj/item/clothing/under/rank/research_director/alt/skirt(src) - new /obj/item/clothing/under/rank/research_director/turtleneck(src) - new /obj/item/clothing/under/rank/research_director/turtleneck/skirt(src) - new /obj/item/clothing/shoes/sneakers/brown(src) - new /obj/item/cartridge/rd(src) - new /obj/item/clothing/gloves/color/latex(src) - new /obj/item/radio/headset/heads/rd(src) - new /obj/item/tank/internals/air(src) - new /obj/item/clothing/mask/gas(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) - new /obj/item/clothing/suit/hooded/wintercoat/rd(src) - +/obj/structure/closet/secure_closet/RD + name = "\proper research director's locker" + req_access = list(ACCESS_RD) + icon_state = "rd" + +/obj/structure/closet/secure_closet/RD/PopulateContents() + ..() + new /obj/item/clothing/neck/cloak/rd(src) + new /obj/item/clothing/head/beret/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/research_director(src) + new /obj/item/clothing/under/rank/research_director/skirt(src) + new /obj/item/clothing/under/rank/research_director/alt(src) + new /obj/item/clothing/under/rank/research_director/alt/skirt(src) + new /obj/item/clothing/under/rank/research_director/turtleneck(src) + new /obj/item/clothing/under/rank/research_director/turtleneck/skirt(src) + new /obj/item/clothing/shoes/sneakers/brown(src) + new /obj/item/cartridge/rd(src) + new /obj/item/clothing/gloves/color/latex(src) + new /obj/item/radio/headset/heads/rd(src) + new /obj/item/tank/internals/air(src) + new /obj/item/clothing/mask/gas(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) + new /obj/item/clothing/suit/hooded/wintercoat/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 dd503d76f7..57520f6f40 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,13 +1,13 @@ -/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 - -/obj/structure/closet/secure_closet/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(damage_flag == "melee" && damage_amount < 20) - return 0 +/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 + +/obj/structure/closet/secure_closet/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(damage_flag == "melee" && damage_amount < 20) + return 0 . = ..() \ No newline at end of file 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 835be8d2fc..ad482b68af 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -1,293 +1,293 @@ -/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/captainparade(src) - new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src) - new /obj/item/clothing/head/caphat/parade(src) - new /obj/item/clothing/head/caphat/beret(src) - new /obj/item/clothing/suit/captunic(src) - new /obj/item/clothing/under/rank/captain/femformal(src) //citadel edit - 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/restraints/handcuffs/cable/zipties(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/storage/photo_album/Captain(src) - new /obj/item/clothing/head/caphat/beret/white(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/clothing/under/rank/head_of_personnel(src) - new /obj/item/clothing/under/rank/head_of_personnel/skirt(src) - new /obj/item/clothing/head/hopcap(src) - new /obj/item/clothing/head/hopcap/beret(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/restraints/handcuffs/cable/zipties(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/civillian(src) - new /obj/item/circuitboard/machine/techfab/department/service(src) - new /obj/item/storage/photo_album/HoP(src) - new /obj/item/clothing/suit/hooded/wintercoat/hop(src) - new /obj/item/clothing/head/hopcap/beret/white(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/hosparadefem(src) - new /obj/item/clothing/under/hosparademale(src) - new /obj/item/clothing/suit/armor/vest/leather(src) - new /obj/item/clothing/suit/armor/hos(src) - new /obj/item/clothing/under/rank/head_of_security/skirt(src) - new /obj/item/clothing/under/rank/head_of_security/alt(src) - new /obj/item/clothing/under/rank/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/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/flashlight/seclite(src) - new /obj/item/pinpointer/nuke(src) - new /obj/item/circuitboard/machine/techfab/department/security(src) - new /obj/item/storage/photo_album/HoS(src) - new /obj/item/clothing/suit/hooded/wintercoat/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/warden/navyblue(src) - new /obj/item/clothing/under/rank/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) - new /obj/item/gun/ballistic/shotgun/automatic/combat/compact(src) - new /obj/item/clothing/head/beret/sec/corporatewarden(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 - material_drop = /obj/item/stack/sheet/mineral/wood - cutting_tool = /obj/item/screwdriver - -/obj/structure/closet/secure_closet/detective/PopulateContents() - ..() - new /obj/item/clothing/under/rank/det(src) - new /obj/item/clothing/under/rank/det/skirt(src) - new /obj/item/clothing/suit/det_suit(src) - new /obj/item/clothing/head/fedora/det_hat(src) - new /obj/item/clothing/gloves/color/black(src) - new /obj/item/clothing/under/rank/det/grey(src) - new /obj/item/clothing/under/rank/det/grey/skirt(src) - new /obj/item/clothing/accessory/waistcoat(src) - new /obj/item/clothing/suit/det_suit/grey(src) - new /obj/item/clothing/head/fedora(src) - new /obj/item/clothing/shoes/laceup(src) - 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/full(src) - new /obj/item/pinpointer/crew(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/armor/laserproof(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/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/captainparade(src) + new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src) + new /obj/item/clothing/head/caphat/parade(src) + new /obj/item/clothing/head/caphat/beret(src) + new /obj/item/clothing/suit/captunic(src) + new /obj/item/clothing/under/rank/captain/femformal(src) //citadel edit + 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/restraints/handcuffs/cable/zipties(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/storage/photo_album/Captain(src) + new /obj/item/clothing/head/caphat/beret/white(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/clothing/under/rank/head_of_personnel(src) + new /obj/item/clothing/under/rank/head_of_personnel/skirt(src) + new /obj/item/clothing/head/hopcap(src) + new /obj/item/clothing/head/hopcap/beret(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/restraints/handcuffs/cable/zipties(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/civillian(src) + new /obj/item/circuitboard/machine/techfab/department/service(src) + new /obj/item/storage/photo_album/HoP(src) + new /obj/item/clothing/suit/hooded/wintercoat/hop(src) + new /obj/item/clothing/head/hopcap/beret/white(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/hosparadefem(src) + new /obj/item/clothing/under/hosparademale(src) + new /obj/item/clothing/suit/armor/vest/leather(src) + new /obj/item/clothing/suit/armor/hos(src) + new /obj/item/clothing/under/rank/head_of_security/skirt(src) + new /obj/item/clothing/under/rank/head_of_security/alt(src) + new /obj/item/clothing/under/rank/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/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/flashlight/seclite(src) + new /obj/item/pinpointer/nuke(src) + new /obj/item/circuitboard/machine/techfab/department/security(src) + new /obj/item/storage/photo_album/HoS(src) + new /obj/item/clothing/suit/hooded/wintercoat/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/warden/navyblue(src) + new /obj/item/clothing/under/rank/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) + new /obj/item/gun/ballistic/shotgun/automatic/combat/compact(src) + new /obj/item/clothing/head/beret/sec/corporatewarden(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 + material_drop = /obj/item/stack/sheet/mineral/wood + cutting_tool = /obj/item/screwdriver + +/obj/structure/closet/secure_closet/detective/PopulateContents() + ..() + new /obj/item/clothing/under/rank/det(src) + new /obj/item/clothing/under/rank/det/skirt(src) + new /obj/item/clothing/suit/det_suit(src) + new /obj/item/clothing/head/fedora/det_hat(src) + new /obj/item/clothing/gloves/color/black(src) + new /obj/item/clothing/under/rank/det/grey(src) + new /obj/item/clothing/under/rank/det/grey/skirt(src) + new /obj/item/clothing/accessory/waistcoat(src) + new /obj/item/clothing/suit/det_suit/grey(src) + new /obj/item/clothing/head/fedora(src) + new /obj/item/clothing/shoes/laceup(src) + 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/full(src) + new /obj/item/pinpointer/crew(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/armor/laserproof(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) diff --git a/code/game/objects/structures/crates_lockers/closets/syndicate.dm b/code/game/objects/structures/crates_lockers/closets/syndicate.dm index 94d1b03fdb..b967b4efe1 100644 --- a/code/game/objects/structures/crates_lockers/closets/syndicate.dm +++ b/code/game/objects/structures/crates_lockers/closets/syndicate.dm @@ -1,120 +1,120 @@ -/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/m10mm(src) - new /obj/item/storage/belt/military(src) - new /obj/item/crowbar/red(src) - new /obj/item/clothing/glasses/night(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/m10mm(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/m10mm(src) + new /obj/item/storage/belt/military(src) + new /obj/item/crowbar/red(src) + new /obj/item/clothing/glasses/night(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/m10mm(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 6ef6f2d5ce..1c09b2fd63 100644 --- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm @@ -1,199 +1,199 @@ -/* 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" = 40, "aid" = 25, "tank" = 20, "both" = 10, "nothing" = 5))) - 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/o2(src) - new /obj/item/clothing/mask/breath(src) - - if ("tank") - new /obj/item/tank/internals/air(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 - - return - -/* - * 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() - ..() - if (prob(50)) - new /obj/item/reagent_containers/hypospray/medipen/firelocker(src) - 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() - ..() - if (prob(50)) - new /obj/item/reagent_containers/hypospray/medipen/firelocker(src) - 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/random(src) - if(prob(30)) - new /obj/item/stack/cable_coil/random(src) - if(prob(30)) - new /obj/item/stack/cable_coil/random(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() - ..() - if(prob(50)) - new /obj/item/storage/firstaid/radbgone(src) - 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() - ..() - if(prob(70)) - new /obj/item/screwdriver(src) - if(prob(50)) - new /obj/item/multitool(src) - if(prob(70)) - new /obj/item/wirecutters(src) - 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() - ..() - if(prob(90)) - new /obj/item/screwdriver(src) - if(prob(70)) - new /obj/item/multitool(src) - if(prob(90)) - new /obj/item/wirecutters(src) - new /obj/item/clothing/suit/bomb_suit/security(src) - new /obj/item/clothing/under/rank/security(src) - new /obj/item/clothing/shoes/jackboots(src) - new /obj/item/clothing/head/bomb_hood/security(src) - -/obj/structure/closet/bombcloset/white/PopulateContents() - ..() - if(prob(50)) - new /obj/item/screwdriver(src) - if(prob(20)) - new /obj/item/multitool(src) - if(prob(50)) - new /obj/item/wirecutters(src) - 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" = 40, "aid" = 25, "tank" = 20, "both" = 10, "nothing" = 5))) + 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/o2(src) + new /obj/item/clothing/mask/breath(src) + + if ("tank") + new /obj/item/tank/internals/air(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 + + return + +/* + * 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() + ..() + if (prob(50)) + new /obj/item/reagent_containers/hypospray/medipen/firelocker(src) + 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() + ..() + if (prob(50)) + new /obj/item/reagent_containers/hypospray/medipen/firelocker(src) + 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/random(src) + if(prob(30)) + new /obj/item/stack/cable_coil/random(src) + if(prob(30)) + new /obj/item/stack/cable_coil/random(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() + ..() + if(prob(50)) + new /obj/item/storage/firstaid/radbgone(src) + 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() + ..() + if(prob(70)) + new /obj/item/screwdriver(src) + if(prob(50)) + new /obj/item/multitool(src) + if(prob(70)) + new /obj/item/wirecutters(src) + 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() + ..() + if(prob(90)) + new /obj/item/screwdriver(src) + if(prob(70)) + new /obj/item/multitool(src) + if(prob(90)) + new /obj/item/wirecutters(src) + new /obj/item/clothing/suit/bomb_suit/security(src) + new /obj/item/clothing/under/rank/security(src) + new /obj/item/clothing/shoes/jackboots(src) + new /obj/item/clothing/head/bomb_hood/security(src) + +/obj/structure/closet/bombcloset/white/PopulateContents() + ..() + if(prob(50)) + new /obj/item/screwdriver(src) + if(prob(20)) + new /obj/item/multitool(src) + if(prob(50)) + new /obj/item/wirecutters(src) + 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/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index e416b2b436..eaa9074bd8 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -1,223 +1,223 @@ -/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" - var/obj/item/paper/fluff/jobs/cargo/manifest/manifest - -/obj/structure/closet/crate/New() - ..() - if(icon_state == "[initial(icon_state)]open") - opened = TRUE - update_icon() - -/obj/structure/closet/crate/CanPass(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 1 - if(!locatedcrate.opened) //otherwise, if the located crate is closed, allow entering - return 1 - return !density - -/obj/structure/closet/crate/update_icon() - icon_state = "[initial(icon_state)][opened ? "open" : ""]" - - cut_overlays() - if(manifest) - add_overlay("manifest") - -/obj/structure/closet/crate/attack_hand(mob/user) - . = ..() - if(.) - return - if(manifest) - tear_manifest(user) - -/obj/structure/closet/crate/open(mob/living/user) - . = ..() - if(. && manifest) - to_chat(user, "The manifest is torn off [src].") - playsound(src, 'sound/items/poster_ripped.ogg', 75, 1) - manifest.forceMove(get_turf(src)) - manifest = null - update_icon() - -/obj/structure/closet/crate/handle_lock_addition() - return - -/obj/structure/closet/crate/handle_lock_removal() - return - -/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, 1) - - 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 - -/obj/structure/closet/crate/coffin/examine(mob/user) - . = ..() - if(user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)) - . += {"This is a coffin which you can use to regenerate your burns and other wounds faster."} - . += {"You can also thicken your blood if you survive the day, and hide from the sun safely while inside."} - /* if(user.mind.has_antag_datum(ANTAG_DATUM_VASSAL) - . += {"This is a coffin which your master can use to shield himself from the unforgiving sun.\n - You yourself are still human and dont need it. Yet."} */ - -/obj/structure/closet/crate/internals - desc = "An internals crate." - name = "internals crate" - icon_state = "o2crate" - -/obj/structure/closet/crate/trashcart - desc = "A heavy, metal trashcart with wheels." - name = "trash cart" - icon_state = "trashcart" - -/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() - 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." - icon_state = "surgery" - -/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/AMinus(src) - new /obj/item/reagent_containers/blood/BMinus(src) - new /obj/item/reagent_containers/blood/BPlus(src) - new /obj/item/reagent_containers/blood/OMinus(src) - new /obj/item/reagent_containers/blood/OPlus(src) - new /obj/item/reagent_containers/blood/lizard(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" + var/obj/item/paper/fluff/jobs/cargo/manifest/manifest + +/obj/structure/closet/crate/New() + ..() + if(icon_state == "[initial(icon_state)]open") + opened = TRUE + update_icon() + +/obj/structure/closet/crate/CanPass(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 1 + if(!locatedcrate.opened) //otherwise, if the located crate is closed, allow entering + return 1 + return !density + +/obj/structure/closet/crate/update_icon() + icon_state = "[initial(icon_state)][opened ? "open" : ""]" + + cut_overlays() + if(manifest) + add_overlay("manifest") + +/obj/structure/closet/crate/attack_hand(mob/user) + . = ..() + if(.) + return + if(manifest) + tear_manifest(user) + +/obj/structure/closet/crate/open(mob/living/user) + . = ..() + if(. && manifest) + to_chat(user, "The manifest is torn off [src].") + playsound(src, 'sound/items/poster_ripped.ogg', 75, 1) + manifest.forceMove(get_turf(src)) + manifest = null + update_icon() + +/obj/structure/closet/crate/handle_lock_addition() + return + +/obj/structure/closet/crate/handle_lock_removal() + return + +/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, 1) + + 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 + +/obj/structure/closet/crate/coffin/examine(mob/user) + . = ..() + if(user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)) + . += {"This is a coffin which you can use to regenerate your burns and other wounds faster."} + . += {"You can also thicken your blood if you survive the day, and hide from the sun safely while inside."} + /* if(user.mind.has_antag_datum(ANTAG_DATUM_VASSAL) + . += {"This is a coffin which your master can use to shield himself from the unforgiving sun.\n + You yourself are still human and dont need it. Yet."} */ + +/obj/structure/closet/crate/internals + desc = "An internals crate." + name = "internals crate" + icon_state = "o2crate" + +/obj/structure/closet/crate/trashcart + desc = "A heavy, metal trashcart with wheels." + name = "trash cart" + icon_state = "trashcart" + +/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() + 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." + icon_state = "surgery" + +/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/AMinus(src) + new /obj/item/reagent_containers/blood/BMinus(src) + new /obj/item/reagent_containers/blood/BPlus(src) + new /obj/item/reagent_containers/blood/OMinus(src) + new /obj/item/reagent_containers/blood/OPlus(src) + new /obj/item/reagent_containers/blood/lizard(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 1580c0104e..7646fdb35f 100644 --- a/code/game/objects/structures/crates_lockers/crates/bins.dm +++ b/code/game/objects/structures/crates_lockers/crates/bins.dm @@ -1,44 +1,44 @@ -/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' - material_drop = /obj/item/stack/sheet/plastic - material_drop_amount = 40 - anchored = TRUE - horizontal = FALSE - delivery_icon = null - -/obj/structure/closet/crate/bin/New() - ..() - update_icon() - -/obj/structure/closet/crate/bin/update_icon() - ..() - cut_overlays() - if(contents.len == 0) - add_overlay("largebing") - else if(contents.len >= storage_capacity) - add_overlay("largebinr") - else - add_overlay("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, 1, -3) - flick("animate_largebins", src) - spawn(13) - playsound(loc, close_sound, 15, 1, -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' + material_drop = /obj/item/stack/sheet/plastic + material_drop_amount = 40 + anchored = TRUE + horizontal = FALSE + delivery_icon = null + +/obj/structure/closet/crate/bin/New() + ..() + update_icon() + +/obj/structure/closet/crate/bin/update_icon() + ..() + cut_overlays() + if(contents.len == 0) + add_overlay("largebing") + else if(contents.len >= storage_capacity) + add_overlay("largebinr") + else + add_overlay("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, 1, -3) + flick("animate_largebins", src) + spawn(13) + playsound(loc, close_sound, 15, 1, -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 b9f0f4b4a7..3c0021b513 100644 --- a/code/game/objects/structures/crates_lockers/crates/critter.dm +++ b/code/game/objects/structures/crates_lockers/crates/critter.dm @@ -1,38 +1,38 @@ -/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" - var/obj/item/tank/internals/emergency_oxygen/tank - -/obj/structure/closet/crate/critter/New() - ..() - 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() - cut_overlays() - if(opened) - add_overlay("crittercrate_door_open") - else - add_overlay("crittercrate_door") - if(manifest) - add_overlay("manifest") - -/obj/structure/closet/crate/critter/return_air() - if(tank) - return tank.air_contents - else +/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" + var/obj/item/tank/internals/emergency_oxygen/tank + +/obj/structure/closet/crate/critter/New() + ..() + 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() + cut_overlays() + if(opened) + add_overlay("crittercrate_door_open") + else + add_overlay("crittercrate_door") + if(manifest) + add_overlay("manifest") + +/obj/structure/closet/crate/critter/return_air() + if(tank) + return tank.air_contents + else return loc.return_air() \ No newline at end of file diff --git a/code/game/objects/structures/crates_lockers/crates/large.dm b/code/game/objects/structures/crates_lockers/crates/large.dm index 6c8feae50b..880460a23c 100644 --- a/code/game/objects/structures/crates_lockers/crates/large.dm +++ b/code/game/objects/structures/crates_lockers/crates/large.dm @@ -1,43 +1,43 @@ -/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. - -/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(istype(W, /obj/item/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, 1) - - 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. +/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. + +/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(istype(W, /obj/item/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, 1) + + 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. \ No newline at end of file diff --git a/code/game/objects/structures/crates_lockers/crates/secure.dm b/code/game/objects/structures/crates_lockers/crates/secure.dm index 8c12f67cec..290e69370b 100644 --- a/code/game/objects/structures/crates_lockers/crates/secure.dm +++ b/code/game/objects/structures/crates_lockers/crates/secure.dm @@ -1,72 +1,72 @@ -/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 - -/obj/structure/closet/crate/secure/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(damage_flag == "melee" && damage_amount < 25) - return 0 - . = ..() - -/obj/structure/closet/crate/secure/update_icon() - ..() - if(broken) - add_overlay("securecrateemag") - else if(locked) - add_overlay("securecrater") - else - add_overlay("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 - ..() - - -/obj/structure/closet/crate/secure/proc/boom(mob/user) - if(user) - to_chat(user, "The crate's anti-tamper system activates!") - var/message = "[ADMIN_LOOKUPFLW(user)] has detonated [src.name]." - GLOB.bombers += message - message_admins(message) - log_game("[key_name(user)] has detonated [src.name].") - 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 + 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 + +/obj/structure/closet/crate/secure/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) + if(damage_flag == "melee" && damage_amount < 25) + return 0 + . = ..() + +/obj/structure/closet/crate/secure/update_icon() + ..() + if(broken) + add_overlay("securecrateemag") + else if(locked) + add_overlay("securecrater") + else + add_overlay("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 + ..() + + +/obj/structure/closet/crate/secure/proc/boom(mob/user) + if(user) + to_chat(user, "The crate's anti-tamper system activates!") + var/message = "[ADMIN_LOOKUPFLW(user)] has detonated [src.name]." + GLOB.bombers += message + message_admins(message) + log_game("[key_name(user)] has detonated [src.name].") + 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" diff --git a/code/game/objects/structures/displaycase.dm b/code/game/objects/structures/displaycase.dm index 8b078802f0..8c25a2ca5c 100644 --- a/code/game/objects/structures/displaycase.dm +++ b/code/game/objects/structures/displaycase.dm @@ -1,349 +1,349 @@ -/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 = 50 - var/obj/item/showpiece = null - 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 = "" - -/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:" - . += 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, 1) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, 1) - -/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, 1) - 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, 1) - -/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(istype(W, /obj/item/weldingtool) && 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 && istype(W, /obj/item/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(user.transferItemToLoc(W, src)) - showpiece = W - to_chat(user, "You put [W] on display") - update_icon() - else if(istype(W, /obj/item/stack/sheet/glass) && broken) - 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 - 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(istype(I, /obj/item/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, 1) - 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/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/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 captains display case requiring specops ID access is intentional. -//The lab cage and captains display case do not spawn with electronics, which is why req_access is needed. -/obj/structure/displaycase/captain - alert = TRUE - start_showpiece_type = /obj/item/gun/energy/laser/captain - req_access = list(ACCESS_CENT_SPECOPS) - -/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/clown - desc = "In the event of clown, honk glass." - alert = TRUE - start_showpiece_type = /obj/item/bikehorn - req_access = list(ACCESS_CENT_GENERAL) - -/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 - - alert = 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 + 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 = 50 + var/obj/item/showpiece = null + 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 = "" + +/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:" + . += 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, 1) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, 1) + +/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, 1) + 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, 1) + +/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(istype(W, /obj/item/weldingtool) && 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 && istype(W, /obj/item/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(user.transferItemToLoc(W, src)) + showpiece = W + to_chat(user, "You put [W] on display") + update_icon() + else if(istype(W, /obj/item/stack/sheet/glass) && broken) + 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 + 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(istype(I, /obj/item/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, 1) + 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/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/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 captains display case requiring specops ID access is intentional. +//The lab cage and captains display case do not spawn with electronics, which is why req_access is needed. +/obj/structure/displaycase/captain + alert = TRUE + start_showpiece_type = /obj/item/gun/energy/laser/captain + req_access = list(ACCESS_CENT_SPECOPS) + +/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/clown + desc = "In the event of clown, honk glass." + alert = TRUE + start_showpiece_type = /obj/item/bikehorn + req_access = list(ACCESS_CENT_GENERAL) + +/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 + + alert = 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) diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm index 7f0c3c6eb8..ecacf49fc2 100644 --- a/code/game/objects/structures/dresser.dm +++ b/code/game/objects/structures/dresser.dm @@ -1,82 +1,82 @@ -/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(istype(I, /obj/item/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(. || !ishuman(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - var/mob/living/carbon/human/H = user - - if(H.dna && H.dna.species && (NO_UNDERWEAR in H.dna.species.species_traits)) - to_chat(H, "You are not capable of wearing underwear.") - return - - var/list/undergarment_choices = list("Underwear", "Underwear Color", "Undershirt", "Undershirt Color", "Socks", "Socks Color") - if(!(GLOB.underwear_list[H.underwear]?.has_color)) - undergarment_choices -= "Underwear Color" - if(!(GLOB.undershirt_list[H.undershirt]?.has_color)) - undergarment_choices -= "Undershirt Color" - if(!(GLOB.socks_list[H.socks]?.has_color)) - undergarment_choices -= "Socks Color" - - var/choice = input(H, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in undergarment_choices - if(!H.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - var/dye_undie = FALSE - var/dye_shirt = FALSE - var/dye_socks = FALSE - switch(choice) - if("Underwear") - var/new_undies = input(H, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list - if(H.underwear) - H.underwear = new_undies - H.saved_underwear = new_undies - var/datum/sprite_accessory/underwear/bottom/B = GLOB.underwear_list[new_undies] - dye_undie = B?.has_color - if("Undershirt") - var/new_undershirt = input(H, "Select your undershirt", "Changing") as null|anything in GLOB.undershirt_list - if(new_undershirt) - H.undershirt = new_undershirt - H.saved_undershirt = new_undershirt - var/datum/sprite_accessory/underwear/top/T = GLOB.undershirt_list[new_undershirt] - dye_shirt = T?.has_color - if("Socks") - var/new_socks = input(H, "Select your socks", "Changing") as null|anything in GLOB.socks_list - if(new_socks) - H.socks = new_socks - H.saved_socks = new_socks - var/datum/sprite_accessory/underwear/socks/S = GLOB.socks_list[new_socks] - dye_socks = S?.has_color - if(dye_undie || choice == "Underwear Color") - H.undie_color = recolor_undergarment(H, "underwear", H.undie_color) - if(dye_shirt || choice == "Undershirt Color") - H.shirt_color = recolor_undergarment(H, "undershirt", H.shirt_color) - if(dye_socks || choice == "Socks Color") - H.socks_color = recolor_undergarment(H, "socks", H.socks_color) - - add_fingerprint(H) - H.update_body() - -/obj/structure/dresser/proc/recolor_undergarment(mob/living/carbon/human/H, garment_type = "underwear", default_color) - var/n_color = input(H, "Choose your [garment_type]'\s color.", "Character Preference", default_color) as color|null - if(!n_color || !H.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return default_color - return sanitize_hexcolor(n_color, 3, FALSE, default_color) +/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(istype(I, /obj/item/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(. || !ishuman(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + var/mob/living/carbon/human/H = user + + if(H.dna && H.dna.species && (NO_UNDERWEAR in H.dna.species.species_traits)) + to_chat(H, "You are not capable of wearing underwear.") + return + + var/list/undergarment_choices = list("Underwear", "Underwear Color", "Undershirt", "Undershirt Color", "Socks", "Socks Color") + if(!(GLOB.underwear_list[H.underwear]?.has_color)) + undergarment_choices -= "Underwear Color" + if(!(GLOB.undershirt_list[H.undershirt]?.has_color)) + undergarment_choices -= "Undershirt Color" + if(!(GLOB.socks_list[H.socks]?.has_color)) + undergarment_choices -= "Socks Color" + + var/choice = input(H, "Underwear, Undershirt, or Socks?", "Changing") as null|anything in undergarment_choices + if(!H.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + var/dye_undie = FALSE + var/dye_shirt = FALSE + var/dye_socks = FALSE + switch(choice) + if("Underwear") + var/new_undies = input(H, "Select your underwear", "Changing") as null|anything in GLOB.underwear_list + if(H.underwear) + H.underwear = new_undies + H.saved_underwear = new_undies + var/datum/sprite_accessory/underwear/bottom/B = GLOB.underwear_list[new_undies] + dye_undie = B?.has_color + if("Undershirt") + var/new_undershirt = input(H, "Select your undershirt", "Changing") as null|anything in GLOB.undershirt_list + if(new_undershirt) + H.undershirt = new_undershirt + H.saved_undershirt = new_undershirt + var/datum/sprite_accessory/underwear/top/T = GLOB.undershirt_list[new_undershirt] + dye_shirt = T?.has_color + if("Socks") + var/new_socks = input(H, "Select your socks", "Changing") as null|anything in GLOB.socks_list + if(new_socks) + H.socks = new_socks + H.saved_socks = new_socks + var/datum/sprite_accessory/underwear/socks/S = GLOB.socks_list[new_socks] + dye_socks = S?.has_color + if(dye_undie || choice == "Underwear Color") + H.undie_color = recolor_undergarment(H, "underwear", H.undie_color) + if(dye_shirt || choice == "Undershirt Color") + H.shirt_color = recolor_undergarment(H, "undershirt", H.shirt_color) + if(dye_socks || choice == "Socks Color") + H.socks_color = recolor_undergarment(H, "socks", H.socks_color) + + add_fingerprint(H) + H.update_body() + +/obj/structure/dresser/proc/recolor_undergarment(mob/living/carbon/human/H, garment_type = "underwear", default_color) + var/n_color = input(H, "Choose your [garment_type]'\s color.", "Character Preference", default_color) as color|null + if(!n_color || !H.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return default_color + return sanitize_hexcolor(n_color, 3, FALSE, default_color) diff --git a/code/game/objects/structures/electricchair.dm b/code/game/objects/structures/electricchair.dm index 1e84ec3acf..6f1d56c4a5 100644 --- a/code/game/objects/structures/electricchair.dm +++ b/code/game/objects/structures/electricchair.dm @@ -1,46 +1,46 @@ -/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/New() - ..() - 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(istype(W, /obj/item/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(EQUIP)) - return - A.use_power(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 + 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/New() + ..() + 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(istype(W, /obj/item/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(EQUIP)) + return + A.use_power(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!") diff --git a/code/game/objects/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm index d6beaa2628..7014081042 100644 --- a/code/game/objects/structures/extinguisher.dm +++ b/code/game/objects/structures/extinguisher.dm @@ -1,156 +1,156 @@ -/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 = 50 - 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) - stored_extinguisher.ex_act(severity, target) - -/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(istype(I, /obj/item/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, 1) - 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, 1, -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, 1, -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) - return TRUE - -/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, 1, -3) - opened = !opened - update_icon() - -/obj/structure/extinguisher_cabinet/update_icon() - if(!opened) - icon_state = "extinguisher_closed" - return - 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 = 50 + 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) + stored_extinguisher.ex_act(severity, target) + +/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(istype(I, /obj/item/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, 1) + 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, 1, -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, 1, -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) + return TRUE + +/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, 1, -3) + opened = !opened + update_icon() + +/obj/structure/extinguisher_cabinet/update_icon() + if(!opened) + icon_state = "extinguisher_closed" + return + 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 12529fb9e0..261dae1ea2 100644 --- a/code/game/objects/structures/flora.dm +++ b/code/game/objects/structures/flora.dm @@ -1,427 +1,427 @@ -/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.sharpness && W.force > 0) - if(W.hitsound) - playsound(get_turf(src), W.hitsound, 100, 0, 0) - 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 , 0, 0) - 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 - -/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/list/ckeys_that_took = list() - -/obj/structure/flora/tree/pine/xmas/presents/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!user.ckey) - return - - if(ckeys_that_took[user.ckey]) - 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!") - ckeys_that_took[user.ckey] = TRUE - var/obj/item/G = new gift_type(src) - user.put_in_hands(G) - -/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/twohanded/required/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/twohanded/required/kirbyplants/Initialize() - . = ..() - AddComponent(/datum/component/tactical) - -/obj/item/twohanded/required/kirbyplants/random - icon = 'icons/obj/flora/_flora.dmi' - icon_state = "random_plant" - var/list/static/states - -/obj/item/twohanded/required/kirbyplants/random/Initialize() - . = ..() - icon = 'icons/obj/flora/plants.dmi' - if(!states) - generate_states() - icon_state = pick(states) - -/obj/item/twohanded/required/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/twohanded/required/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/twohanded/required/kirbyplants/photosynthetic - name = "photosynthetic potted plant" - desc = "A bioluminescent plant." - icon_state = "plant-09" - light_color = "#2cb2e8" - light_range = 3 - - -//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 - -/obj/structure/flora/rock/Initialize() - . = ..() - icon_state = "[icon_state][rand(1,3)]" - -/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 = "pile of rocks" - desc = "A pile of rocks." - icon_state = "rock" - 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.sharpness && W.force > 0) + if(W.hitsound) + playsound(get_turf(src), W.hitsound, 100, 0, 0) + 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 , 0, 0) + 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 + +/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/list/ckeys_that_took = list() + +/obj/structure/flora/tree/pine/xmas/presents/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!user.ckey) + return + + if(ckeys_that_took[user.ckey]) + 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!") + ckeys_that_took[user.ckey] = TRUE + var/obj/item/G = new gift_type(src) + user.put_in_hands(G) + +/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/twohanded/required/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/twohanded/required/kirbyplants/Initialize() + . = ..() + AddComponent(/datum/component/tactical) + +/obj/item/twohanded/required/kirbyplants/random + icon = 'icons/obj/flora/_flora.dmi' + icon_state = "random_plant" + var/list/static/states + +/obj/item/twohanded/required/kirbyplants/random/Initialize() + . = ..() + icon = 'icons/obj/flora/plants.dmi' + if(!states) + generate_states() + icon_state = pick(states) + +/obj/item/twohanded/required/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/twohanded/required/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/twohanded/required/kirbyplants/photosynthetic + name = "photosynthetic potted plant" + desc = "A bioluminescent plant." + icon_state = "plant-09" + light_color = "#2cb2e8" + light_range = 3 + + +//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 + +/obj/structure/flora/rock/Initialize() + . = ..() + icon_state = "[icon_state][rand(1,3)]" + +/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 = "pile of rocks" + desc = "A pile of rocks." + icon_state = "rock" + 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 de3c07b410..f58ba5d617 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/New() - ..() - 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, 1) - 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, 1) - 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/New() + ..() + 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, 1) + 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, 1) + qdel(src) + return diff --git a/code/game/objects/structures/kitchen_spike.dm b/code/game/objects/structures/kitchen_spike.dm index b9a75f88af..21622519c9 100644 --- a/code/game/objects/structures/kitchen_spike.dm +++ b/code/game/objects/structures/kitchen_spike.dm @@ -1,156 +1,156 @@ -//////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(istype(I, /obj/item/weldingtool)) - 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, 1) - 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") - if(iscarbon(L)) - var/mob/living/carbon/C = L - C.bleed(30) - else - 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.AdjustKnockdown(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(istype(I, /obj/item/weldingtool)) + 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, 1) + 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") + if(iscarbon(L)) + var/mob/living/carbon/C = L + C.bleed(30) + else + 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.AdjustKnockdown(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 d7a5a55ec7..b3f415bafe 100644 --- a/code/game/objects/structures/ladders.dm +++ b/code/game/objects/structures/ladders.dm @@ -1,231 +1,231 @@ -// 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/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() - 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 - - if (up && down) - var/result = alert("Go up or down [src]?", "Ladder", "Up", "Down", "Cancel") - 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/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() - -/obj/structure/ladder/unbreakable/binary - name = "mysterious ladder" - desc = "Where does it go?" - height = 0 - id = "lavaland_binary" - var/area_to_place = /area/lavaland/surface/outdoors - var/active = FALSE - -/obj/structure/ladder/unbreakable/binary/proc/ActivateAlmonds() - if(area_to_place && !active) - var/turf/T = getTargetTurf() - if(T) - var/obj/structure/ladder/unbreakable/U = new (T) - U.id = id - U.height = height+1 - LateInitialize() // LateInit both of these to build the links. It's fine. - U.LateInitialize() - for(var/turf/TT in range(2,U)) - TT.TerraformTurf(/turf/open/indestructible/binary, /turf/open/indestructible/binary, CHANGETURF_INHERIT_AIR) - active = TRUE - -/obj/structure/ladder/unbreakable/binary/proc/getTargetTurf() - var/list/turfList = get_area_turfs(area_to_place) - while (turfList.len && !.) - var/i = rand(1, turfList.len) - var/turf/potentialTurf = turfList[i] - if (is_centcom_level(potentialTurf.z)) // These ladders don't lead to centcom. - turfList.Cut(i,i+1) - continue - if(!istype(potentialTurf, /turf/open/lava) && !potentialTurf.density) // Or inside dense turfs or lava - var/clear = TRUE - for(var/obj/O in potentialTurf) // Let's not place these on dense objects either. Might be funny though. - if(O.density) - clear = FALSE - break - if(clear) - . = potentialTurf - if (!.) - turfList.Cut(i,i+1) - -/obj/structure/ladder/unbreakable/binary/space - id = "space_binary" - area_to_place = /area/space - -/obj/structure/ladder/unbreakable/binary/unlinked //Crew gets to complete one - id = "unlinked_binary" - area_to_place = null +// 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/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() + 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 + + if (up && down) + var/result = alert("Go up or down [src]?", "Ladder", "Up", "Down", "Cancel") + 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/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() + +/obj/structure/ladder/unbreakable/binary + name = "mysterious ladder" + desc = "Where does it go?" + height = 0 + id = "lavaland_binary" + var/area_to_place = /area/lavaland/surface/outdoors + var/active = FALSE + +/obj/structure/ladder/unbreakable/binary/proc/ActivateAlmonds() + if(area_to_place && !active) + var/turf/T = getTargetTurf() + if(T) + var/obj/structure/ladder/unbreakable/U = new (T) + U.id = id + U.height = height+1 + LateInitialize() // LateInit both of these to build the links. It's fine. + U.LateInitialize() + for(var/turf/TT in range(2,U)) + TT.TerraformTurf(/turf/open/indestructible/binary, /turf/open/indestructible/binary, CHANGETURF_INHERIT_AIR) + active = TRUE + +/obj/structure/ladder/unbreakable/binary/proc/getTargetTurf() + var/list/turfList = get_area_turfs(area_to_place) + while (turfList.len && !.) + var/i = rand(1, turfList.len) + var/turf/potentialTurf = turfList[i] + if (is_centcom_level(potentialTurf.z)) // These ladders don't lead to centcom. + turfList.Cut(i,i+1) + continue + if(!istype(potentialTurf, /turf/open/lava) && !potentialTurf.density) // Or inside dense turfs or lava + var/clear = TRUE + for(var/obj/O in potentialTurf) // Let's not place these on dense objects either. Might be funny though. + if(O.density) + clear = FALSE + break + if(clear) + . = potentialTurf + if (!.) + turfList.Cut(i,i+1) + +/obj/structure/ladder/unbreakable/binary/space + id = "space_binary" + area_to_place = /area/space + +/obj/structure/ladder/unbreakable/binary/unlinked //Crew gets to complete one + id = "unlinked_binary" + area_to_place = null diff --git a/code/game/objects/structures/loom.dm b/code/game/objects/structures/loom.dm index 28427469ba..2244742f3a 100644 --- a/code/game/objects/structures/loom.dm +++ b/code/game/objects/structures/loom.dm @@ -1,44 +1,44 @@ -#define FABRIC_PER_SHEET 4 - - -///This is a loom. It's usually made out of wood and used to weave fabric like durathread or cotton into their respective cloth types. -/obj/structure/loom - name = "loom" - desc = "A simple device used to weave cloth and other thread-based fabrics together into usable material." - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "loom" - density = TRUE - anchored = TRUE - -/obj/structure/loom/attackby(obj/item/I, mob/user) - if(weave(I, user)) - return - return ..() - -/obj/structure/loom/wrench_act(mob/living/user, obj/item/I) - ..() - default_unfasten_wrench(user, I, 5) - return TRUE - -///Handles the weaving. -/obj/structure/loom/proc/weave(obj/item/stack/sheet/S, mob/user) - if(!istype(S) || !S.is_fabric) - return FALSE - if(!anchored) - user.show_message("The loom needs to be wrenched down.", MSG_VISUAL) - return FALSE - if(S.amount < FABRIC_PER_SHEET) - user.show_message("You need at least [FABRIC_PER_SHEET] units of fabric before using this.", 1) - return FALSE - user.show_message("You start weaving \the [S.name] through the loom..", MSG_VISUAL) - if(S.use_tool(src, user, S.pull_effort)) - if(S.amount >= FABRIC_PER_SHEET) - new S.loom_result(drop_location()) - S.use(FABRIC_PER_SHEET) - user.show_message("You weave \the [S.name] into a workable fabric.", MSG_VISUAL) - return TRUE - -/obj/structure/loom/unanchored - anchored = FALSE - +#define FABRIC_PER_SHEET 4 + + +///This is a loom. It's usually made out of wood and used to weave fabric like durathread or cotton into their respective cloth types. +/obj/structure/loom + name = "loom" + desc = "A simple device used to weave cloth and other thread-based fabrics together into usable material." + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "loom" + density = TRUE + anchored = TRUE + +/obj/structure/loom/attackby(obj/item/I, mob/user) + if(weave(I, user)) + return + return ..() + +/obj/structure/loom/wrench_act(mob/living/user, obj/item/I) + ..() + default_unfasten_wrench(user, I, 5) + return TRUE + +///Handles the weaving. +/obj/structure/loom/proc/weave(obj/item/stack/sheet/S, mob/user) + if(!istype(S) || !S.is_fabric) + return FALSE + if(!anchored) + user.show_message("The loom needs to be wrenched down.", MSG_VISUAL) + return FALSE + if(S.amount < FABRIC_PER_SHEET) + user.show_message("You need at least [FABRIC_PER_SHEET] units of fabric before using this.", 1) + return FALSE + user.show_message("You start weaving \the [S.name] through the loom..", MSG_VISUAL) + if(S.use_tool(src, user, S.pull_effort)) + if(S.amount >= FABRIC_PER_SHEET) + new S.loom_result(drop_location()) + S.use(FABRIC_PER_SHEET) + user.show_message("You weave \the [S.name] into a workable fabric.", MSG_VISUAL) + return TRUE + +/obj/structure/loom/unanchored + anchored = FALSE + #undef FABRIC_PER_SHEET \ No newline at end of file diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm index 80ffced5a2..79489e4ae6 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 = 10 - var/cooldown = 0 - var/projectile_type = /obj/item/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, 1) - 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.change_view(CONFIG_GET(string/default_view)) - 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, 1) - anchored = TRUE - if(M.client) - M.client.change_view(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, 1) - 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/item/projectile/P = new projectile_type(targets_from) - P.starting = targets_from - P.firer = user - P.original = target - playsound(src, 'sound/weapons/gunshot_smg.ogg', 75, 1) - 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/item/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 = 10 + var/cooldown = 0 + var/projectile_type = /obj/item/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, 1) + 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.change_view(CONFIG_GET(string/default_view)) + 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, 1) + anchored = TRUE + if(M.client) + M.client.change_view(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, 1) + 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/item/projectile/P = new projectile_type(targets_from) + P.starting = targets_from + P.firer = user + P.original = target + playsound(src, 'sound/weapons/gunshot_smg.ogg', 75, 1) + 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/item/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 efb12ae34a..caad9e7bd4 100644 --- a/code/game/objects/structures/mineral_doors.dm +++ b/code/game/objects/structures/mineral_doors.dm @@ -1,258 +1,258 @@ -//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" - - var/initial_state - var/state = 0 //closed, 1 == open - var/isSwitchingStates = 0 - var/close_delay = -1 //-1 if does not auto close. - max_integrity = 200 - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50) - var/sheetType = /obj/item/stack/sheet/metal - var/sheetAmount = 7 - var/openSound = 'sound/effects/stonedoor_openclose.ogg' - var/closeSound = 'sound/effects/stonedoor_openclose.ogg' - CanAtmosPass = ATMOS_PASS_DENSITY - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - rad_insulation = RAD_MEDIUM_INSULATION - -/obj/structure/mineral_door/Initialize() - . = ..() - initial_state = icon_state - 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(!state) - 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/CanPass(atom/movable/mover, turf/target) - if(istype(mover, /obj/effect/beam)) - return !opacity - return !density - -/obj/structure/mineral_door/proc/TryToSwitchState(atom/user) - if(isSwitchingStates) - 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(state) - Close() - else - Open() - -/obj/structure/mineral_door/proc/Open() - isSwitchingStates = 1 - playsound(src, openSound, 100, 1) - set_opacity(FALSE) - flick("[initial_state]opening",src) - sleep(10) - density = FALSE - layer = OPEN_DOOR_LAYER - state = 1 - air_update_turf(1) - update_icon() - isSwitchingStates = 0 - - if(close_delay != -1) - addtimer(CALLBACK(src, .proc/Close), close_delay) - -/obj/structure/mineral_door/proc/Close() - if(isSwitchingStates || state != 1) - return - var/turf/T = get_turf(src) - for(var/mob/living/L in T) - return - isSwitchingStates = 1 - playsound(loc, closeSound, 100, 1) - flick("[initial_state]closing",src) - sleep(10) - density = TRUE - set_opacity(TRUE) - state = 0 - layer = initial(layer) - air_update_turf(1) - update_icon() - isSwitchingStates = 0 - -/obj/structure/mineral_door/update_icon() - if(state) - icon_state = "[initial_state]open" - else - icon_state = initial_state - -/obj/structure/mineral_door/attackby(obj/item/I, mob/user, params) - if(I.tool_behaviour == TOOL_MINING) - to_chat(user, "You start digging the [name]...") - if(I.use_tool(src, user, 40, volume=50)) - to_chat(user, "You finish digging.") - deconstruct(TRUE) - else if(user.a_intent != INTENT_HARM) - return attack_hand(user) - else - return ..() - -/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/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/woodrustic - name = "rustic wood door" - icon_state = "woodrustic" - openSound = 'sound/effects/doorcreaky.ogg' - closeSound = 'sound/effects/doorcreaky.ogg' - sheetType = /obj/item/stack/sheet/mineral/wood - sheetAmount = 10 - max_integrity = 200 - rad_insulation = RAD_VERY_LIGHT_INSULATION - -/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/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" + + var/initial_state + var/state = 0 //closed, 1 == open + var/isSwitchingStates = 0 + var/close_delay = -1 //-1 if does not auto close. + max_integrity = 200 + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 10, "bio" = 100, "rad" = 100, "fire" = 50, "acid" = 50) + var/sheetType = /obj/item/stack/sheet/metal + var/sheetAmount = 7 + var/openSound = 'sound/effects/stonedoor_openclose.ogg' + var/closeSound = 'sound/effects/stonedoor_openclose.ogg' + CanAtmosPass = ATMOS_PASS_DENSITY + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + rad_insulation = RAD_MEDIUM_INSULATION + +/obj/structure/mineral_door/Initialize() + . = ..() + initial_state = icon_state + 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(!state) + 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/CanPass(atom/movable/mover, turf/target) + if(istype(mover, /obj/effect/beam)) + return !opacity + return !density + +/obj/structure/mineral_door/proc/TryToSwitchState(atom/user) + if(isSwitchingStates) + 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(state) + Close() + else + Open() + +/obj/structure/mineral_door/proc/Open() + isSwitchingStates = 1 + playsound(src, openSound, 100, 1) + set_opacity(FALSE) + flick("[initial_state]opening",src) + sleep(10) + density = FALSE + layer = OPEN_DOOR_LAYER + state = 1 + air_update_turf(1) + update_icon() + isSwitchingStates = 0 + + if(close_delay != -1) + addtimer(CALLBACK(src, .proc/Close), close_delay) + +/obj/structure/mineral_door/proc/Close() + if(isSwitchingStates || state != 1) + return + var/turf/T = get_turf(src) + for(var/mob/living/L in T) + return + isSwitchingStates = 1 + playsound(loc, closeSound, 100, 1) + flick("[initial_state]closing",src) + sleep(10) + density = TRUE + set_opacity(TRUE) + state = 0 + layer = initial(layer) + air_update_turf(1) + update_icon() + isSwitchingStates = 0 + +/obj/structure/mineral_door/update_icon() + if(state) + icon_state = "[initial_state]open" + else + icon_state = initial_state + +/obj/structure/mineral_door/attackby(obj/item/I, mob/user, params) + if(I.tool_behaviour == TOOL_MINING) + to_chat(user, "You start digging the [name]...") + if(I.use_tool(src, user, 40, volume=50)) + to_chat(user, "You finish digging.") + deconstruct(TRUE) + else if(user.a_intent != INTENT_HARM) + return attack_hand(user) + else + return ..() + +/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/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/woodrustic + name = "rustic wood door" + icon_state = "woodrustic" + openSound = 'sound/effects/doorcreaky.ogg' + closeSound = 'sound/effects/doorcreaky.ogg' + sheetType = /obj/item/stack/sheet/mineral/wood + sheetAmount = 10 + max_integrity = 200 + rad_insulation = RAD_VERY_LIGHT_INSULATION + +/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/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 ba45ae5ef0..0a5794c59d 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -1,239 +1,239 @@ -//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 = 100 - -/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 == MALE) - var/new_style = input(user, "Select a facial hair style", "Grooming") as null|anything in GLOB.facial_hair_styles_list - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return //no tele-grooming - if(new_style) - H.facial_hair_style = new_style - else - H.facial_hair_style = "Shaved" - - //handle normal hair - var/new_style = input(user, "Select a hair style", "Grooming") as null|anything in GLOB.hair_styles_list - if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return //no tele-grooming - if(new_style) - H.hair_style = new_style - - H.update_hair() - -/obj/structure/mirror/examine_status(mob/user) - if(broken) - return // no message spam - ..() - -/obj/structure/mirror/obj_break(damage_flag, mapload) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - icon_state = "mirror_broke" - if(!mapload) - playsound(src, "shatter", 70, 1) - 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, 1) - if(BURN) - playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1) - - -/obj/structure/mirror/magic - name = "magic mirror" - desc = "Turn and face the strange... face." - icon_state = "magic_mirror" - var/list/races_blacklist = list("skeleton", "agent", "angel", "military_synth", "memezombies", "clockwork golem servant", "android", "synth", "mush", "zombie", "memezombie") - 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 = new speciestype() - if(!(S.id in races_blacklist)) - choosable_races += S.id - ..() - -/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 = new speciestype() - choosable_races += 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 = copytext(sanitize(input(H, "Who are we again?", "Name change", H.name) as null|text),1,MAX_NAME_LEN) - - 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(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(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(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - if(H.gender == "male") - if(alert(H, "Become a Witch?", "Confirmation", "Yes", "No") == "Yes") - H.gender = "female" - to_chat(H, "Man, you feel like a woman!") - else - return - - else - if(alert(H, "Become a Warlock?", "Confirmation", "Yes", "No") == "Yes") - H.gender = "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, "Hair style 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(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) - var/n_color = sanitize_hexcolor(new_eye_color) - var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES) - if(eyes) - eyes.eye_color = n_color - H.eye_color = n_color - H.dna.update_ui_block(DNA_EYE_COLOR_BLOCK) - H.dna.species.handle_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 = 100 + +/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 == MALE) + var/new_style = input(user, "Select a facial hair style", "Grooming") as null|anything in GLOB.facial_hair_styles_list + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return //no tele-grooming + if(new_style) + H.facial_hair_style = new_style + else + H.facial_hair_style = "Shaved" + + //handle normal hair + var/new_style = input(user, "Select a hair style", "Grooming") as null|anything in GLOB.hair_styles_list + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return //no tele-grooming + if(new_style) + H.hair_style = new_style + + H.update_hair() + +/obj/structure/mirror/examine_status(mob/user) + if(broken) + return // no message spam + ..() + +/obj/structure/mirror/obj_break(damage_flag, mapload) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + icon_state = "mirror_broke" + if(!mapload) + playsound(src, "shatter", 70, 1) + 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, 1) + if(BURN) + playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1) + + +/obj/structure/mirror/magic + name = "magic mirror" + desc = "Turn and face the strange... face." + icon_state = "magic_mirror" + var/list/races_blacklist = list("skeleton", "agent", "angel", "military_synth", "memezombies", "clockwork golem servant", "android", "synth", "mush", "zombie", "memezombie") + 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 = new speciestype() + if(!(S.id in races_blacklist)) + choosable_races += S.id + ..() + +/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 = new speciestype() + choosable_races += 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 = copytext(sanitize(input(H, "Who are we again?", "Name change", H.name) as null|text),1,MAX_NAME_LEN) + + 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(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(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(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(H.gender == "male") + if(alert(H, "Become a Witch?", "Confirmation", "Yes", "No") == "Yes") + H.gender = "female" + to_chat(H, "Man, you feel like a woman!") + else + return + + else + if(alert(H, "Become a Warlock?", "Confirmation", "Yes", "No") == "Yes") + H.gender = "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, "Hair style 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(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) + var/n_color = sanitize_hexcolor(new_eye_color) + var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES) + if(eyes) + eyes.eye_color = n_color + H.eye_color = n_color + H.dna.update_ui_block(DNA_EYE_COLOR_BLOCK) + H.dna.species.handle_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 e56cf3b09d..a9e12c1404 100644 --- a/code/game/objects/structures/mop_bucket.dm +++ b/code/game/objects/structures/mop_bucket.dm @@ -1,29 +1,29 @@ -/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) - to_chat(user, "You wet [I] in [src].") - playsound(loc, 'sound/effects/slosh.ogg', 25, 1) - update_icon() - else - return ..() - -/obj/structure/mopbucket/update_icon() - cut_overlays() - if(reagents.total_volume > 0) +/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) + to_chat(user, "You wet [I] in [src].") + playsound(loc, 'sound/effects/slosh.ogg', 25, 1) + update_icon() + else + return ..() + +/obj/structure/mopbucket/update_icon() + cut_overlays() + if(reagents.total_volume > 0) add_overlay("mopbucket_water") \ No newline at end of file diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm index cdd4cb5a31..7696a13bb2 100644 --- a/code/game/objects/structures/morgue.dm +++ b/code/game/objects/structures/morgue.dm @@ -1,386 +1,386 @@ -/* 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, 1) - playsound(src, 'sound/effects/roll.ogg', 5, 1) - 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, 1) - playsound(src, 'sound/items/deconstruct.ogg', 50, 1) - 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/New() - 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"].") - return TRUE - -/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 = recursive_mob_check(src, 0, 0) // 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_NOCLONE)) && !mob_occupant.hellbound) - icon_state = "morgue4" // Cloneable - if(mob_occupant.stat == DEAD && beeper) - if(world.time > next_beep) - playsound(src, 'sound/machines/beeping_alarm.ogg', 50, 0) //Clone 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 cloned.

                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() - connected = new/obj/structure/tray/c_tray(src) - connected.connected = src - - GLOB.crematoriums.Add(src) - ..() - -/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, 1) //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/mob/living/i_scream in GetAllContents()) - 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 - layer = TRAY_LAYER - 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/MouseDrop_T(atom/movable/O as mob|obj, mob/user) - if(!ismovableatom(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.lying || user.incapacitated()) - 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/CanPass(atom/movable/mover, turf/target) - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return 1 - if(locate(/obj/structure/table) in get_turf(mover)) - return 1 - else - return 0 - -/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) - . = !density - if(ismovableatom(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, 1) + playsound(src, 'sound/effects/roll.ogg', 5, 1) + 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, 1) + playsound(src, 'sound/items/deconstruct.ogg', 50, 1) + 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/New() + 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"].") + return TRUE + +/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 = recursive_mob_check(src, 0, 0) // 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_NOCLONE)) && !mob_occupant.hellbound) + icon_state = "morgue4" // Cloneable + if(mob_occupant.stat == DEAD && beeper) + if(world.time > next_beep) + playsound(src, 'sound/machines/beeping_alarm.ogg', 50, 0) //Clone 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 cloned.

                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() + connected = new/obj/structure/tray/c_tray(src) + connected.connected = src + + GLOB.crematoriums.Add(src) + ..() + +/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, 1) //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/mob/living/i_scream in GetAllContents()) + 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 + layer = TRAY_LAYER + 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/MouseDrop_T(atom/movable/O as mob|obj, mob/user) + if(!ismovableatom(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.lying || user.incapacitated()) + 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/CanPass(atom/movable/mover, turf/target) + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return 1 + if(locate(/obj/structure/table) in get_turf(mover)) + return 1 + else + return 0 + +/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(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 36dbcc1e28..4c85e7464e 100644 --- a/code/game/objects/structures/musician.dm +++ b/code/game/objects/structures/musician.dm @@ -1,381 +1,381 @@ - -#define MUSICIAN_HEARCHECK_MINDELAY 4 -#define MUSIC_MAXLINES 600 -#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/obj/instrumentObj = null // the associated obj playing the sound - var/last_hearcheck = 0 - var/list/hearing_mobs - -/datum/song/New(dir, obj, ext = "ogg") - tempo = sanitize_tempo(tempo) - instrumentDir = dir - instrumentObj = obj - instrumentExt = ext - -/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(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/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(15, source)) - if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS)) - continue - LAZYADD(hearing_mobs, M) - last_hearcheck = world.time - - var/sound/music_played = sound(soundfile) - for(var/i in hearing_mobs) - var/mob/M = i - 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)) - 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 - playing = FALSE - hearing_mobs = null - return - if(!length(note)) - continue - var/cur_note = text2ascii(note) - 96 - if(cur_note < 1 || cur_note > 7) - continue - for(var/i=2 to length(note)) - var/ni = copytext(note,i,i+1) - 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(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-- - hearing_mobs = null - playing = 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) - if(copytext(lines[1],1,6) == "BPM: ") - tempo = sanitize_tempo(600 / text2num(copytext(lines[1],6))) - 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(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)) - 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(!in_range(instrumentObj, usr)) - 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(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"]) - playing = TRUE - spawn() - playsong(usr) - - else if(href_list["newline"]) - var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) - if(!newline || !in_range(instrumentObj, usr)) - return - if(lines.len > MUSIC_MAXLINES) - return - if(length(newline) > MUSIC_MAXLINECHARS) - newline = copytext(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 = html_encode(input("Enter your line: ", instrumentObj.name, lines[num]) as text|null) - if(!content || !in_range(instrumentObj, usr)) - return - if(length(content) > MUSIC_MAXLINECHARS) - content = copytext(content, 1, MUSIC_MAXLINECHARS) - if(num > lines.len || num < 1) - return - lines[num] = content - - else if(href_list["stop"]) - playing = FALSE - hearing_mobs = null - - 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) - -// 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/New() - ..() - 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 600 +#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/obj/instrumentObj = null // the associated obj playing the sound + var/last_hearcheck = 0 + var/list/hearing_mobs + +/datum/song/New(dir, obj, ext = "ogg") + tempo = sanitize_tempo(tempo) + instrumentDir = dir + instrumentObj = obj + instrumentExt = ext + +/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(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/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(15, source)) + if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS)) + continue + LAZYADD(hearing_mobs, M) + last_hearcheck = world.time + + var/sound/music_played = sound(soundfile) + for(var/i in hearing_mobs) + var/mob/M = i + 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)) + 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 + playing = FALSE + hearing_mobs = null + return + if(!length(note)) + continue + var/cur_note = text2ascii(note) - 96 + if(cur_note < 1 || cur_note > 7) + continue + for(var/i=2 to length(note)) + var/ni = copytext(note,i,i+1) + 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(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-- + hearing_mobs = null + playing = 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) + if(copytext(lines[1],1,6) == "BPM: ") + tempo = sanitize_tempo(600 / text2num(copytext(lines[1],6))) + 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(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)) + 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(!in_range(instrumentObj, usr)) + 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(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"]) + playing = TRUE + spawn() + playsong(usr) + + else if(href_list["newline"]) + var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) + if(!newline || !in_range(instrumentObj, usr)) + return + if(lines.len > MUSIC_MAXLINES) + return + if(length(newline) > MUSIC_MAXLINECHARS) + newline = copytext(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 = html_encode(input("Enter your line: ", instrumentObj.name, lines[num]) as text|null) + if(!content || !in_range(instrumentObj, usr)) + return + if(length(content) > MUSIC_MAXLINECHARS) + content = copytext(content, 1, MUSIC_MAXLINECHARS) + if(num > lines.len || num < 1) + return + lines[num] = content + + else if(href_list["stop"]) + playing = FALSE + hearing_mobs = null + + 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) + +// 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/New() + ..() + 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 b43b7292f2..f4bca1f900 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 f256b6bd4c..92c4d0a066 100644 --- a/code/game/objects/structures/safe.dm +++ b/code/game/objects/structures/safe.dm @@ -1,211 +1,211 @@ -/* -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/New() - ..() - 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() - 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)) - 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 - level = 1 //underfloor - layer = LOW_OBJ_LAYER - - -/obj/structure/safe/floor/Initialize(mapload) - . = ..() - if(mapload) - var/turf/T = loc - hide(T.intact) - - -/obj/structure/safe/floor/hide(var/intact) - invisibility = intact ? INVISIBILITY_MAXIMUM : 0 +/* +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/New() + ..() + 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() + 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)) + 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 + level = 1 //underfloor + layer = LOW_OBJ_LAYER + + +/obj/structure/safe/floor/Initialize(mapload) + . = ..() + if(mapload) + var/turf/T = loc + hide(T.intact) + + +/obj/structure/safe/floor/hide(var/intact) + invisibility = intact ? INVISIBILITY_MAXIMUM : 0 diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 3c6a277307..f64f740a27 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -1,700 +1,700 @@ -/* 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 - obj_flags = CAN_BE_HIT|SHOVABLE_ONTO - 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 - max_integrity = 100 - integrity_failure = 30 - smooth = SMOOTH_TRUE - canSmoothWith = list(/obj/structure/table, /obj/structure/table/reinforced) - -/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/ratvar_act() - var/atom/A = loc - qdel(src) - new /obj/structure/table/reinforced/brass(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 - 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/CanPass(atom/movable/mover, turf/target) - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return 1 - if(mover.throwing) - return 1 - if(locate(/obj/structure/table) in get_turf(mover)) - return 1 - else - return !density - -/obj/structure/table/CanAStarPass(ID, dir, caller) - . = !density - if(ismovableatom(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(src.loc) - pushed_mob.resting = TRUE - pushed_mob.update_canmove() - pushed_mob.visible_message("[user] places [pushed_mob] onto [src].", \ - "[user] places [pushed_mob] onto [src].") - log_combat(user, pushed_mob, "placed") - -/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(40) - pushed_mob.visible_message("[user] slams [pushed_mob] onto [src]!", \ - "[user] slams you onto [src]!") - log_combat(user, pushed_mob, "tabled", null, "onto [src]") - if(!ishuman(pushed_mob)) - return - var/mob/living/carbon/human/H = pushed_mob - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table) - -/obj/structure/table/shove_act(mob/living/target, mob/living/user) - if(!target.resting) - target.Knockdown(SHOVE_KNOCKDOWN_TABLE) - user.visible_message("[user.name] shoves [target.name] onto \the [src]!", - "You shove [target.name] onto \the [src]!", null, COMBAT_MESSAGE_RANGE) - target.forceMove(src.loc) - log_combat(user, target, "shoved", "onto [src] (table)") - return TRUE - -/obj/structure/table/attackby(obj/item/I, mob/user, params) - if(!(flags_1 & NODECONSTRUCT_1)) - if(istype(I, /obj/item/screwdriver) && deconstruction_ready) - to_chat(user, "You start disassembling [src]...") - if(I.use_tool(src, user, 20, volume=50)) - deconstruct(TRUE) - return - - if(istype(I, /obj/item/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, 1) - 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 - 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(user.a_intent != INTENT_HARM && !(I.item_flags & ABSTRACT)) - if(user.transferItemToLoc(I, drop_location())) - 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) - return 1 - else - return ..() - -/obj/structure/table/alt_attack_hand(mob/user) - if(user && Adjacent(user) && !user.incapacitated()) - user.setClickCooldown(4) - if(istype(user) && user.a_intent == INTENT_HARM) - user.visible_message("[user] slams [user.p_their()] palms down on [src].", "You slam your palms down on [src].") - playsound(src, 'sound/weapons/sonic_jackhammer.ogg', 50, 1) - else - user.visible_message("[user] slaps [user.p_their()] hands on [src].", "You slap your hands on [src].") - playsound(src, 'sound/weapons/tap.ogg', 50, 1) - user.do_attack_animation(src) - return TRUE - -/obj/structure/table/deconstruct(disassembled = TRUE, wrench_disassembly = 0) - if(!(flags_1 & NODECONSTRUCT_1)) - var/turf/T = get_turf(src) - new buildstack(T, buildstackamount) - if(!wrench_disassembly) - new frame(T) - else - new framestack(T, framestackamount) - qdel(src) - - -/* - * 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/New() - . = ..() - 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, 1) - 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.Knockdown(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, 1) - 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 - -/* - * Plasmaglass tables - */ -/obj/structure/table/plasmaglass - name = "plasmaglass table" - desc = "A glasstable, but it's pink and more sturdy. What will Nanotrasen design next with plasma?" - icon = 'icons/obj/smooth_structures/plasmaglass_table.dmi' - icon_state = "plasmaglass_table" - climbable = TRUE - buildstack = /obj/item/stack/sheet/plasmaglass - canSmoothWith = null - max_integrity = 270 - resistance_flags = ACID_PROOF - armor = list("melee" = 10, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - var/list/debris = list() - -/obj/structure/table/plasmaglass/New() - . = ..() - debris += new frame - debris += new /obj/item/shard/plasma - -/obj/structure/table/plasmaglass/Destroy() - QDEL_LIST(debris) - . = ..() - -/obj/structure/table/plasmaglass/proc/check_break(mob/living/M) - return - -/obj/structure/table/plasmaglass/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, 1) - for(var/X in debris) - var/atom/movable/AM = X - AM.forceMove(T) - debris -= AM - qdel(src) - -/obj/structure/table/plasmaglass/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/blackred, - /obj/structure/table/wood/fancy/monochrome, - /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/blackred - icon_state = "fancy_table_blackred" - buildstack = /obj/item/stack/tile/carpet/blackred - smooth_icon = 'icons/obj/smooth_structures/fancy_table_blackred.dmi' - -/obj/structure/table/wood/fancy/monochrome - icon_state = "fancy_table_monochrome" - buildstack = /obj/item/stack/tile/carpet/monochrome - smooth_icon = 'icons/obj/smooth_structures/fancy_table_monochrome.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) - max_integrity = 200 - integrity_failure = 50 - 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." - return "The top cover is firmly welded on." - -/obj/structure/table/reinforced/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/weldingtool)) - 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/reinforced/brass - name = "brass table" - desc = "A solid, slightly beveled brass table." - icon = 'icons/obj/smooth_structures/brass_table.dmi' - icon_state = "brass_table" - resistance_flags = FIRE_PROOF | ACID_PROOF - frame = /obj/structure/table_frame/brass - framestack = /obj/item/stack/tile/brass - buildstack = /obj/item/stack/tile/brass - framestackamount = 1 - buildstackamount = 1 - canSmoothWith = list(/obj/structure/table/reinforced/brass, /obj/structure/table/bronze) - -/obj/structure/table/reinforced/brass/New() - change_construction_value(2) - ..() - -/obj/structure/table/reinforced/brass/Destroy() - change_construction_value(-2) - return ..() - -/obj/structure/table/reinforced/brass/tablepush(mob/living/user, mob/living/pushed_mob) - .= ..() - playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 50, TRUE) - -/obj/structure/table/reinforced/brass/narsie_act() - take_damage(rand(15, 45), BRUTE) - if(src) //do we still exist? - var/previouscolor = color - color = "#960000" - animate(src, color = previouscolor, time = 8) - addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) - -/obj/structure/table/reinforced/brass/ratvar_act() - obj_integrity = max_integrity - -/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/reinforced/brass, /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/New() - ..() - for(var/direction in GLOB.cardinals) - computer = locate(/obj/machinery/computer/operating, get_step(src, direction)) - if(computer) - computer.table = src - break - -/obj/structure/table/optable/tablepush(mob/living/user, mob/living/pushed_mob) - pushed_mob.forceMove(src.loc) - pushed_mob.resting = 1 - pushed_mob.update_canmove() - visible_message("[user] has laid [pushed_mob] on [src].") - check_patient() - -/obj/structure/table/optable/proc/check_patient() - var/mob/M = locate(/mob/living/carbon/human, loc) - if(M) - if(M.resting) - patient = M - return 1 - else - patient = null - return 0 - - - -/* - * 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/CanPass(atom/movable/mover, turf/target) - if(src.density == 0) //Because broken racks -Agouri |TODO: SPRITE!| - return 1 - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return 1 - else - return 0 - -/obj/structure/rack/CanAStarPass(ID, dir, caller) - . = !density - if(ismovableatom(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 (istype(W, /obj/item/wrench) && !(flags_1&NODECONSTRUCT_1)) - 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.IsKnockdown() || user.resting || user.lying || 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, 1) - else - playsound(loc, 'sound/weapons/tap.ogg', 50, 1) - if(BURN) - playsound(loc, 'sound/items/welder.ogg', 40, 1) - -/* - * 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 - materials = list(MAT_METAL=2000) - var/building = FALSE - -/obj/item/rack_parts/attackby(obj/item/W, mob/user, params) - if (istype(W, /obj/item/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 + obj_flags = CAN_BE_HIT|SHOVABLE_ONTO + 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 + max_integrity = 100 + integrity_failure = 30 + smooth = SMOOTH_TRUE + canSmoothWith = list(/obj/structure/table, /obj/structure/table/reinforced) + +/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/ratvar_act() + var/atom/A = loc + qdel(src) + new /obj/structure/table/reinforced/brass(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 + 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/CanPass(atom/movable/mover, turf/target) + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return 1 + if(mover.throwing) + return 1 + if(locate(/obj/structure/table) in get_turf(mover)) + return 1 + else + return !density + +/obj/structure/table/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(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(src.loc) + pushed_mob.resting = TRUE + pushed_mob.update_canmove() + pushed_mob.visible_message("[user] places [pushed_mob] onto [src].", \ + "[user] places [pushed_mob] onto [src].") + log_combat(user, pushed_mob, "placed") + +/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(40) + pushed_mob.visible_message("[user] slams [pushed_mob] onto [src]!", \ + "[user] slams you onto [src]!") + log_combat(user, pushed_mob, "tabled", null, "onto [src]") + if(!ishuman(pushed_mob)) + return + var/mob/living/carbon/human/H = pushed_mob + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "table", /datum/mood_event/table) + +/obj/structure/table/shove_act(mob/living/target, mob/living/user) + if(!target.resting) + target.Knockdown(SHOVE_KNOCKDOWN_TABLE) + user.visible_message("[user.name] shoves [target.name] onto \the [src]!", + "You shove [target.name] onto \the [src]!", null, COMBAT_MESSAGE_RANGE) + target.forceMove(src.loc) + log_combat(user, target, "shoved", "onto [src] (table)") + return TRUE + +/obj/structure/table/attackby(obj/item/I, mob/user, params) + if(!(flags_1 & NODECONSTRUCT_1)) + if(istype(I, /obj/item/screwdriver) && deconstruction_ready) + to_chat(user, "You start disassembling [src]...") + if(I.use_tool(src, user, 20, volume=50)) + deconstruct(TRUE) + return + + if(istype(I, /obj/item/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, 1) + 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 + 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(user.a_intent != INTENT_HARM && !(I.item_flags & ABSTRACT)) + if(user.transferItemToLoc(I, drop_location())) + 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) + return 1 + else + return ..() + +/obj/structure/table/alt_attack_hand(mob/user) + if(user && Adjacent(user) && !user.incapacitated()) + user.setClickCooldown(4) + if(istype(user) && user.a_intent == INTENT_HARM) + user.visible_message("[user] slams [user.p_their()] palms down on [src].", "You slam your palms down on [src].") + playsound(src, 'sound/weapons/sonic_jackhammer.ogg', 50, 1) + else + user.visible_message("[user] slaps [user.p_their()] hands on [src].", "You slap your hands on [src].") + playsound(src, 'sound/weapons/tap.ogg', 50, 1) + user.do_attack_animation(src) + return TRUE + +/obj/structure/table/deconstruct(disassembled = TRUE, wrench_disassembly = 0) + if(!(flags_1 & NODECONSTRUCT_1)) + var/turf/T = get_turf(src) + new buildstack(T, buildstackamount) + if(!wrench_disassembly) + new frame(T) + else + new framestack(T, framestackamount) + qdel(src) + + +/* + * 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/New() + . = ..() + 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, 1) + 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.Knockdown(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, 1) + 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 + +/* + * Plasmaglass tables + */ +/obj/structure/table/plasmaglass + name = "plasmaglass table" + desc = "A glasstable, but it's pink and more sturdy. What will Nanotrasen design next with plasma?" + icon = 'icons/obj/smooth_structures/plasmaglass_table.dmi' + icon_state = "plasmaglass_table" + climbable = TRUE + buildstack = /obj/item/stack/sheet/plasmaglass + canSmoothWith = null + max_integrity = 270 + resistance_flags = ACID_PROOF + armor = list("melee" = 10, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + var/list/debris = list() + +/obj/structure/table/plasmaglass/New() + . = ..() + debris += new frame + debris += new /obj/item/shard/plasma + +/obj/structure/table/plasmaglass/Destroy() + QDEL_LIST(debris) + . = ..() + +/obj/structure/table/plasmaglass/proc/check_break(mob/living/M) + return + +/obj/structure/table/plasmaglass/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, 1) + for(var/X in debris) + var/atom/movable/AM = X + AM.forceMove(T) + debris -= AM + qdel(src) + +/obj/structure/table/plasmaglass/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/blackred, + /obj/structure/table/wood/fancy/monochrome, + /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/blackred + icon_state = "fancy_table_blackred" + buildstack = /obj/item/stack/tile/carpet/blackred + smooth_icon = 'icons/obj/smooth_structures/fancy_table_blackred.dmi' + +/obj/structure/table/wood/fancy/monochrome + icon_state = "fancy_table_monochrome" + buildstack = /obj/item/stack/tile/carpet/monochrome + smooth_icon = 'icons/obj/smooth_structures/fancy_table_monochrome.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) + max_integrity = 200 + integrity_failure = 50 + 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." + return "The top cover is firmly welded on." + +/obj/structure/table/reinforced/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/weldingtool)) + 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/reinforced/brass + name = "brass table" + desc = "A solid, slightly beveled brass table." + icon = 'icons/obj/smooth_structures/brass_table.dmi' + icon_state = "brass_table" + resistance_flags = FIRE_PROOF | ACID_PROOF + frame = /obj/structure/table_frame/brass + framestack = /obj/item/stack/tile/brass + buildstack = /obj/item/stack/tile/brass + framestackamount = 1 + buildstackamount = 1 + canSmoothWith = list(/obj/structure/table/reinforced/brass, /obj/structure/table/bronze) + +/obj/structure/table/reinforced/brass/New() + change_construction_value(2) + ..() + +/obj/structure/table/reinforced/brass/Destroy() + change_construction_value(-2) + return ..() + +/obj/structure/table/reinforced/brass/tablepush(mob/living/user, mob/living/pushed_mob) + .= ..() + playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 50, TRUE) + +/obj/structure/table/reinforced/brass/narsie_act() + take_damage(rand(15, 45), BRUTE) + if(src) //do we still exist? + var/previouscolor = color + color = "#960000" + animate(src, color = previouscolor, time = 8) + addtimer(CALLBACK(src, /atom/proc/update_atom_colour), 8) + +/obj/structure/table/reinforced/brass/ratvar_act() + obj_integrity = max_integrity + +/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/reinforced/brass, /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/New() + ..() + for(var/direction in GLOB.cardinals) + computer = locate(/obj/machinery/computer/operating, get_step(src, direction)) + if(computer) + computer.table = src + break + +/obj/structure/table/optable/tablepush(mob/living/user, mob/living/pushed_mob) + pushed_mob.forceMove(src.loc) + pushed_mob.resting = 1 + pushed_mob.update_canmove() + visible_message("[user] has laid [pushed_mob] on [src].") + check_patient() + +/obj/structure/table/optable/proc/check_patient() + var/mob/M = locate(/mob/living/carbon/human, loc) + if(M) + if(M.resting) + patient = M + return 1 + else + patient = null + return 0 + + + +/* + * 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/CanPass(atom/movable/mover, turf/target) + if(src.density == 0) //Because broken racks -Agouri |TODO: SPRITE!| + return 1 + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return 1 + else + return 0 + +/obj/structure/rack/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(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 (istype(W, /obj/item/wrench) && !(flags_1&NODECONSTRUCT_1)) + 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.IsKnockdown() || user.resting || user.lying || 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, 1) + else + playsound(loc, 'sound/weapons/tap.ogg', 50, 1) + if(BURN) + playsound(loc, 'sound/items/welder.ogg', 40, 1) + +/* + * 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 + materials = list(MAT_METAL=2000) + var/building = FALSE + +/obj/item/rack_parts/attackby(obj/item/W, mob/user, params) + if (istype(W, /obj/item/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 076f39b85f..f156287d43 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_icon() - cut_overlays() - switch(oxygentanks) - if(1 to 3) - add_overlay("oxygen-[oxygentanks]") - if(4 to TANK_DISPENSER_CAPACITY) - add_overlay("oxygen-4") - switch(plasmatanks) - if(1 to 4) - add_overlay("plasma-[plasmatanks]") - if(5 to TANK_DISPENSER_CAPACITY) - add_overlay("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(istype(I, /obj/item/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, "tank_dispenser", name, 275, 100, 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_icon() + cut_overlays() + switch(oxygentanks) + if(1 to 3) + add_overlay("oxygen-[oxygentanks]") + if(4 to TANK_DISPENSER_CAPACITY) + add_overlay("oxygen-4") + switch(plasmatanks) + if(1 to 4) + add_overlay("plasma-[plasmatanks]") + if(5 to TANK_DISPENSER_CAPACITY) + add_overlay("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(istype(I, /obj/item/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, "tank_dispenser", name, 275, 100, 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 b1ad847cc8..b69b98ceaa 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/item/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/item/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 08f16554bc..c9d9f9dd41 100644 --- a/code/game/objects/structures/transit_tubes/transit_tube_construction.dm +++ b/code/game/objects/structures/transit_tubes/transit_tube_construction.dm @@ -1,133 +1,133 @@ -// 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/const/time_to_unwrench = 2 SECONDS - 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++ - 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, time_to_unwrench, 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 - - -/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" - 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/const/time_to_unwrench = 2 SECONDS + 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++ + 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, time_to_unwrench, 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 + + +/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" + anchored = FALSE + density = FALSE diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm index 10c6e53701..7f039598cf 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() - icon_state = "[facing]_[secure ? "secure_" : ""]windoor_assembly[state]" - -/obj/structure/windoor_assembly/CanPass(atom/movable/mover, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return 1 - if(get_dir(loc, target) == dir) //Make sure looking at appropriate border - return !density - 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 - return 1 - -/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(istype(W, /obj/item/weldingtool) && !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(istype(W, /obj/item/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(istype(W, /obj/item/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(istype(W, /obj/item/wirecutters)) - 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(istype(W, /obj/item/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(istype(W, /obj/item/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() - . = ..() - AddComponent( - /datum/component/simple_rotation, - ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, - null, - CALLBACK(src, .proc/can_be_rotated), - 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.canmove || usr.restrained()) - 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() + icon_state = "[facing]_[secure ? "secure_" : ""]windoor_assembly[state]" + +/obj/structure/windoor_assembly/CanPass(atom/movable/mover, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return 1 + if(get_dir(loc, target) == dir) //Make sure looking at appropriate border + return !density + 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 + return 1 + +/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(istype(W, /obj/item/weldingtool) && !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(istype(W, /obj/item/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(istype(W, /obj/item/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(istype(W, /obj/item/wirecutters)) + 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(istype(W, /obj/item/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(istype(W, /obj/item/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() + . = ..() + AddComponent( + /datum/component/simple_rotation, + ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, + null, + CALLBACK(src, .proc/can_be_rotated), + 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.canmove || usr.restrained()) + 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 0f68e68b7b..e5d58c3e1b 100644 --- a/code/game/shuttle_engines.dm +++ b/code/game/shuttle_engines.dm @@ -1,156 +1,156 @@ -#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/world.dm b/code/game/world.dm index e68372d768..5e7629fbb4 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -1,319 +1,319 @@ -#define RESTART_COUNTER_PATH "data/round_counter.txt" - -GLOBAL_VAR(restart_counter) - -GLOBAL_VAR(topic_status_lastcache) -GLOBAL_LIST(topic_status_cache) - -//This happens after the Master subsystem new(s) (it's a global datum) -//So subsystems globals exist, but are not initialised -/world/New() - - log_world("World loaded at [TIME_STAMP("hh:mm:ss", FALSE)]!") - - SetupExternalRSC() - - 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 = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl - - make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once) - - TgsNew() - - GLOB.revdata = new - - config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) - - //SetupLogs depends on the RoundID, so lets check - //DB schema and set RoundID if we can - SSdbcore.CheckSchemaVersion() - SSdbcore.SetRoundID() - SetupLogs() - -#ifndef USE_CUSTOM_ERROR_HANDLER - world.log = file("[GLOB.log_directory]/dd.log") -#endif - - load_admins() - LoadVerbs(/datum/verbs/menu) - if(CONFIG_GET(flag/usewhitelist)) - load_whitelist() - LoadBans() - reload_custom_roundstart_items_list()//Cit change - loads donator items. Remind me to remove when I port over bay's loadout system - - 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 - - cit_initialize() - - Master.Initialize(10, FALSE, TRUE) - - if(TEST_RUN_PARAMETER in params) - HandleTestRun() - -/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("hh:mm:ss", FALSE), ":", ".") - 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_virus_log = "[GLOB.log_directory]/virus.log" - GLOB.world_attack_log = "[GLOB.log_directory]/attack.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.subsystem_log = "[GLOB.log_directory]/subsystem.log" - -#ifdef UNIT_TESTS - GLOB.test_log = file("[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_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.subsystem_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 - - if(!SSfail2topic) - return "Server not initialized." - else if(SSfail2topic.IsRateLimited(addr)) - return "Rate limited." - - if(length(T) > CONFIG_GET(number/topic_max_size)) - return "Payload too large!" - - 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, addr) - -/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) - TgsReboot() - 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 - - 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("hh:mm:ss", FALSE)]") - shutdown_logging() // See comment below. - TgsEndProcess() - - log_world("World rebooted at [TIME_STAMP("hh:mm:ss", FALSE)]") - 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) CIT CHANGE - hides the gamemode from the hub entry, removes some useless info from the hub entry - 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" CIT CHANGE - removes some useless info from the hub entry - 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. CIT CHANGE - links to cit's website on the hub - s += "Citadel" //Replace this with something else. Or ever better, delete it and uncomment the game version. CIT CHANGE - modifies the hub entry link - s += "" - s += ")\]" //CIT CHANGE - encloses the server title in brackets to make the hub entry fancier - s += "
                [CONFIG_GET(string/servertagline)]
                " //CIT CHANGE - adds a tagline! - - var/n = 0 - for (var/mob/M in GLOB.player_list) - if (M.client) - n++ - - if(SSmapping.config) // this just stops the runtime, honk. - features += "[SSmapping.config.map_name]" //CIT CHANGE - makes the hub entry display the current map - - if(get_security_level())//CIT CHANGE - makes the hub entry show the security level - features += "[get_security_level()] alert" - - if (n > 1) - features += "~[n] players" - else if (n > 0) - features += "~[n] player" - - if (!host && hostedby) - features += "hosted by [hostedby]" - - if (features) - s += "\[[jointext(features, ", ")]" //CIT CHANGE - replaces the colon here with a left bracket - - 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() +#define RESTART_COUNTER_PATH "data/round_counter.txt" + +GLOBAL_VAR(restart_counter) + +GLOBAL_VAR(topic_status_lastcache) +GLOBAL_LIST(topic_status_cache) + +//This happens after the Master subsystem new(s) (it's a global datum) +//So subsystems globals exist, but are not initialised +/world/New() + + log_world("World loaded at [TIME_STAMP("hh:mm:ss", FALSE)]!") + + SetupExternalRSC() + + 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 = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl + + make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once) + + TgsNew() + + GLOB.revdata = new + + config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) + + //SetupLogs depends on the RoundID, so lets check + //DB schema and set RoundID if we can + SSdbcore.CheckSchemaVersion() + SSdbcore.SetRoundID() + SetupLogs() + +#ifndef USE_CUSTOM_ERROR_HANDLER + world.log = file("[GLOB.log_directory]/dd.log") +#endif + + load_admins() + LoadVerbs(/datum/verbs/menu) + if(CONFIG_GET(flag/usewhitelist)) + load_whitelist() + LoadBans() + reload_custom_roundstart_items_list()//Cit change - loads donator items. Remind me to remove when I port over bay's loadout system + + 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 + + cit_initialize() + + Master.Initialize(10, FALSE, TRUE) + + if(TEST_RUN_PARAMETER in params) + HandleTestRun() + +/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("hh:mm:ss", FALSE), ":", ".") + 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_virus_log = "[GLOB.log_directory]/virus.log" + GLOB.world_attack_log = "[GLOB.log_directory]/attack.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.subsystem_log = "[GLOB.log_directory]/subsystem.log" + +#ifdef UNIT_TESTS + GLOB.test_log = file("[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_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.subsystem_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 + + if(!SSfail2topic) + return "Server not initialized." + else if(SSfail2topic.IsRateLimited(addr)) + return "Rate limited." + + if(length(T) > CONFIG_GET(number/topic_max_size)) + return "Payload too large!" + + 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, addr) + +/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) + TgsReboot() + 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 + + 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("hh:mm:ss", FALSE)]") + shutdown_logging() // See comment below. + TgsEndProcess() + + log_world("World rebooted at [TIME_STAMP("hh:mm:ss", FALSE)]") + 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) CIT CHANGE - hides the gamemode from the hub entry, removes some useless info from the hub entry + 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" CIT CHANGE - removes some useless info from the hub entry + 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. CIT CHANGE - links to cit's website on the hub + s += "Citadel" //Replace this with something else. Or ever better, delete it and uncomment the game version. CIT CHANGE - modifies the hub entry link + s += "" + s += ")\]" //CIT CHANGE - encloses the server title in brackets to make the hub entry fancier + s += "
                [CONFIG_GET(string/servertagline)]
                " //CIT CHANGE - adds a tagline! + + var/n = 0 + for (var/mob/M in GLOB.player_list) + if (M.client) + n++ + + if(SSmapping.config) // this just stops the runtime, honk. + features += "[SSmapping.config.map_name]" //CIT CHANGE - makes the hub entry display the current map + + if(get_security_level())//CIT CHANGE - makes the hub entry show the security level + features += "[get_security_level()] alert" + + if (n > 1) + features += "~[n] players" + else if (n > 0) + features += "~[n] player" + + if (!host && hostedby) + features += "hosted by [hostedby]" + + if (features) + s += "\[[jointext(features, ", ")]" //CIT CHANGE - replaces the colon here with a left bracket + + 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() diff --git a/code/modules/NTNet/services/_service.dm b/code/modules/NTNet/services/_service.dm index 3622dc3881..75059d9992 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/DB_ban/functions.dm b/code/modules/admin/DB_ban/functions.dm index 8a426b9115..39c4d2d939 100644 --- a/code/modules/admin/DB_ban/functions.dm +++ b/code/modules/admin/DB_ban/functions.dm @@ -1,561 +1,561 @@ -#define MAX_ADMIN_BANS_PER_ADMIN 1 -#define MAX_ADMIN_BANS_PER_HEADMIN 3 - -//Either pass the mob you wish to ban in the 'banned_mob' attribute, or the banckey, banip and bancid variables. If both are passed, the mob takes priority! If a mob is not passed, banckey is the minimum that needs to be passed! banip and bancid are optional. -/datum/admins/proc/DB_ban_record(bantype, mob/banned_mob, duration = -1, reason, job = "", bankey = null, banip = null, bancid = null) - - if(!check_rights(R_BAN)) - return - - if(!SSdbcore.Connect()) - to_chat(src, "Failed to establish database connection.") - return - - var/bantype_pass = 0 - var/bantype_str - var/maxadminbancheck //Used to limit the number of active bans of a certein type that each admin can give. Used to protect against abuse or mutiny. - var/announceinirc //When set, it announces the ban in irc. Intended to be a way to raise an alarm, so to speak. - var/blockselfban //Used to prevent the banning of yourself. - var/kickbannedckey //Defines whether this proc should kick the banned person, if they are connected (if banned_mob is defined). - //some ban types kick players after this proc passes (tempban, permaban), but some are specific to db_ban, so - //they should kick within this proc. - switch(bantype) - if(BANTYPE_PERMA) - bantype_str = "PERMABAN" - duration = -1 - bantype_pass = 1 - blockselfban = 1 - if(BANTYPE_TEMP) - bantype_str = "TEMPBAN" - bantype_pass = 1 - blockselfban = 1 - if(BANTYPE_JOB_PERMA) - bantype_str = "JOB_PERMABAN" - duration = -1 - bantype_pass = 1 - if(BANTYPE_JOB_TEMP) - bantype_str = "JOB_TEMPBAN" - bantype_pass = 1 - if(BANTYPE_ADMIN_PERMA) - bantype_str = "ADMIN_PERMABAN" - duration = -1 - bantype_pass = 1 - maxadminbancheck = 1 - announceinirc = 1 - blockselfban = 1 - kickbannedckey = 1 - if(BANTYPE_ADMIN_TEMP) - bantype_str = "ADMIN_TEMPBAN" - bantype_pass = 1 - maxadminbancheck = 1 - announceinirc = 1 - blockselfban = 1 - kickbannedckey = 1 - if( !bantype_pass ) - return - if( !istext(reason) ) - return - if( !isnum(duration) ) - return - - var/ckey - var/computerid - var/ip - - if(ismob(banned_mob)) - ckey = banned_mob.ckey - bankey = banned_mob.key - if(banned_mob.client) - computerid = banned_mob.client.computer_id - ip = banned_mob.client.address - else - computerid = banned_mob.computer_id - ip = banned_mob.lastKnownIP - else if(bankey) - ckey = ckey(bankey) - computerid = bancid - ip = banip - - var/had_banned_mob = banned_mob != null - var/client/banned_client = banned_mob?.client - var/banned_mob_guest_key = had_banned_mob && IsGuestKey(banned_mob.key) - banned_mob = null - var/sql_ckey = sanitizeSQL(ckey) - var/datum/DBQuery/query_add_ban_get_ckey = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") - if(!query_add_ban_get_ckey.warn_execute()) - qdel(query_add_ban_get_ckey) - return - var/seen_before = query_add_ban_get_ckey.NextRow() - qdel(query_add_ban_get_ckey) - if(!seen_before) - if(!had_banned_mob || (had_banned_mob && !banned_mob_guest_key)) - if(alert(usr, "[bankey] has not been seen before, are you sure you want to create a ban for them?", "Unknown ckey", "Yes", "No", "Cancel") != "Yes") - return - - var/a_key - var/a_ckey - var/a_computerid - var/a_ip - - if(istype(owner)) - a_key = owner.key - a_ckey = owner.ckey - a_computerid = owner.computer_id - a_ip = owner.address - - if(blockselfban) - if(a_ckey == ckey) - to_chat(usr, "You cannot apply this ban type on yourself.") - return - - var/who - for(var/client/C in GLOB.clients) - if(!who) - who = "[C]" - else - who += ", [C]" - - var/adminwho - for(var/client/C in GLOB.admins) - if(!adminwho) - adminwho = "[C]" - else - adminwho += ", [C]" - - reason = sanitizeSQL(reason) - var/sql_a_ckey = sanitizeSQL(a_ckey) - if(maxadminbancheck) - var/datum/DBQuery/query_check_adminban_amt = SSdbcore.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[sql_a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") - if(!query_check_adminban_amt.warn_execute()) - qdel(query_check_adminban_amt) - return - if(query_check_adminban_amt.NextRow()) - var/adm_bans = text2num(query_check_adminban_amt.item[1]) - var/max_bans = MAX_ADMIN_BANS_PER_ADMIN - if (check_rights(R_PERMISSIONS, FALSE)) - max_bans = MAX_ADMIN_BANS_PER_HEADMIN - if(adm_bans >= max_bans) - to_chat(usr, "You already logged [max_bans] admin ban(s) or more. Do not abuse this function!") - qdel(query_check_adminban_amt) - return - qdel(query_check_adminban_amt) - if(!computerid) - computerid = "0" - if(!ip) - ip = "0.0.0.0" - var/sql_job = sanitizeSQL(job) - var/sql_computerid = sanitizeSQL(computerid) - var/sql_ip = sanitizeSQL(ip) - var/sql_a_computerid = sanitizeSQL(a_computerid) - var/sql_a_ip = sanitizeSQL(a_ip) - var/sql = "INSERT INTO [format_table_name("ban")] (`bantime`,`server_ip`,`server_port`,`round_id`,`bantype`,`reason`,`job`,`duration`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]', '[bantype_str]', '[reason]', '[sql_job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[sql_ckey]', '[sql_computerid]', INET_ATON('[sql_ip]'), '[sql_a_ckey]', '[sql_a_computerid]', INET_ATON('[sql_a_ip]'), '[who]', '[adminwho]')" - var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql) - if(!query_add_ban.warn_execute()) - qdel(query_add_ban) - return - qdel(query_add_ban) - to_chat(usr, "Ban saved to database.") - var/msg = "[key_name_admin(usr)] has added a [bantype_str] for [bankey] [(job)?"([job])":""] [(duration > 0)?"([duration] minutes)":""] with the reason: \"[reason]\" to the ban database." - message_admins(msg,1) - var/datum/admin_help/AH = admin_ticket_log(ckey, msg) - - if(announceinirc) - send2irc("BAN ALERT","[a_key] applied a [bantype_str] on [bankey]") - - if(kickbannedckey) - if(AH) - AH.Resolve() //with prejudice - if(banned_client && banned_client.ckey == ckey) - qdel(banned_client) - return 1 - -/datum/admins/proc/DB_ban_unban(ckey, bantype, job = "") - - if(!check_rights(R_BAN)) - return - - var/bantype_str - if(bantype) - var/bantype_pass = 0 - switch(bantype) - if(BANTYPE_PERMA) - bantype_str = "PERMABAN" - bantype_pass = 1 - if(BANTYPE_TEMP) - bantype_str = "TEMPBAN" - bantype_pass = 1 - if(BANTYPE_JOB_PERMA) - bantype_str = "JOB_PERMABAN" - bantype_pass = 1 - if(BANTYPE_JOB_TEMP) - bantype_str = "JOB_TEMPBAN" - bantype_pass = 1 - if(BANTYPE_ADMIN_PERMA) - bantype_str = "ADMIN_PERMABAN" - bantype_pass = 1 - if(BANTYPE_ADMIN_TEMP) - bantype_str = "ADMIN_TEMPBAN" - bantype_pass = 1 - if(BANTYPE_ANY_FULLBAN) - bantype_str = "ANY" - bantype_pass = 1 - if(BANTYPE_ANY_JOB) - bantype_str = "ANYJOB" - bantype_pass = 1 - if( !bantype_pass ) - return - - var/bantype_sql - if(bantype_str == "ANY") - bantype_sql = "(bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now() ) )" - else if(bantype_str == "ANYJOB") - bantype_sql = "(bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now() ) )" - else - bantype_sql = "bantype = '[bantype_str]'" - var/sql_ckey = sanitizeSQL(ckey) - var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = '[sql_ckey]' AND [bantype_sql] AND (unbanned is null OR unbanned = false)" - if(job) - var/sql_job = sanitizeSQL(job) - sql += " AND job = '[sql_job]'" - - if(!SSdbcore.Connect()) - return - - var/ban_id - var/ban_number = 0 //failsafe - - var/datum/DBQuery/query_unban_get_id = SSdbcore.NewQuery(sql) - if(!query_unban_get_id.warn_execute()) - qdel(query_unban_get_id) - return - while(query_unban_get_id.NextRow()) - ban_id = query_unban_get_id.item[1] - ban_number++; - qdel(query_unban_get_id) - - if(ban_number == 0) - to_chat(usr, "Database update failed due to no bans fitting the search criteria. If this is not a legacy ban you should contact the database admin.") - return - - if(ban_number > 1) - to_chat(usr, "Database update failed due to multiple bans fitting the search criteria. Note down the ckey, job and current time and contact the database admin.") - return - - if(istext(ban_id)) - ban_id = text2num(ban_id) - if(!isnum(ban_id)) - to_chat(usr, "Database update failed due to a ban ID mismatch. Contact the database admin.") - return - - DB_ban_unban_by_id(ban_id) - -/datum/admins/proc/DB_ban_edit(banid = null, param = null) - - if(!check_rights(R_BAN)) - return - - if(!isnum(banid) || !istext(param)) - to_chat(usr, "Cancelled") - return - - var/datum/DBQuery/query_edit_ban_get_details = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), duration, reason FROM [format_table_name("ban")] WHERE id = [banid]") - if(!query_edit_ban_get_details.warn_execute()) - qdel(query_edit_ban_get_details) - return - - var/e_key = usr.key //Editing admin key - var/p_key //(banned) Player key - var/duration //Old duration - var/reason //Old reason - - if(query_edit_ban_get_details.NextRow()) - p_key = query_edit_ban_get_details.item[1] - duration = query_edit_ban_get_details.item[2] - reason = query_edit_ban_get_details.item[3] - else - to_chat(usr, "Invalid ban id. Contact the database admin") - qdel(query_edit_ban_get_details) - return - qdel(query_edit_ban_get_details) - - reason = sanitizeSQL(reason) - var/value - - switch(param) - if("reason") - if(!value) - value = input("Insert the new reason for [p_key]'s ban", "New Reason", "[reason]", null) as null|text - value = sanitizeSQL(value) - if(!value) - to_chat(usr, "Cancelled") - return - - var/datum/DBQuery/query_edit_ban_reason = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [e_key] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
                ') WHERE id = [banid]") - if(!query_edit_ban_reason.warn_execute()) - qdel(query_edit_ban_reason) - return - qdel(query_edit_ban_reason) - message_admins("[key_name_admin(usr)] has edited a ban for [p_key]'s reason from [reason] to [value]") - if("duration") - if(!value) - value = input("Insert the new duration (in minutes) for [p_key]'s ban", "New Duration", "[duration]", null) as null|num - if(!isnum(value) || !value) - to_chat(usr, "Cancelled") - return - - var/datum/DBQuery/query_edit_ban_duration = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [e_key] changed ban duration from [duration] to [value]
                '), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]") - if(!query_edit_ban_duration.warn_execute()) - qdel(query_edit_ban_duration) - return - qdel(query_edit_ban_duration) - message_admins("[key_name_admin(usr)] has edited a ban for [p_key]'s duration from [duration] to [value]") - if("unban") - if(alert("Unban [p_key]?", "Unban?", "Yes", "No") == "Yes") - DB_ban_unban_by_id(banid) - return - else - to_chat(usr, "Cancelled") - return - else - to_chat(usr, "Cancelled") - return - -/datum/admins/proc/DB_ban_unban_by_id(id) - - if(!check_rights(R_BAN)) - return - - var/sql = "SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey) FROM [format_table_name("ban")] WHERE id = [id]" - - if(!SSdbcore.Connect()) - return - - var/ban_number = 0 //failsafe - - var/p_key - var/datum/DBQuery/query_unban_get_ckey = SSdbcore.NewQuery(sql) - if(!query_unban_get_ckey.warn_execute()) - qdel(query_unban_get_ckey) - return - while(query_unban_get_ckey.NextRow()) - p_key = query_unban_get_ckey.item[1] - ban_number++; - qdel(query_unban_get_ckey) - - if(ban_number == 0) - to_chat(usr, "Database update failed due to a ban id not being present in the database.") - return - - if(ban_number > 1) - to_chat(usr, "Database update failed due to multiple bans having the same ID. Contact the database admin.") - return - - if(!istype(owner)) - return - - var/unban_ckey = owner.ckey - var/unban_computerid = owner.computer_id - var/unban_ip = owner.address - - var/sql_update = "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = '[unban_ckey]', unbanned_computerid = '[unban_computerid]', unbanned_ip = INET_ATON('[unban_ip]') WHERE id = [id]" - var/datum/DBQuery/query_unban = SSdbcore.NewQuery(sql_update) - if(!query_unban.warn_execute()) - qdel(query_unban) - return - qdel(query_unban) - message_admins("[key_name_admin(usr)] has lifted [p_key]'s ban.") - -/client/proc/DB_ban_panel() - set category = "Admin" - set name = "Banning Panel" - set desc = "Edit admin permissions" - - if(!holder) - return - - holder.DB_ban_panel() - - -/datum/admins/proc/DB_ban_panel(playerckey, adminckey, ip, cid, page = 0) - if(!usr.client) - return - - if(!check_rights(R_BAN)) - return - - if(!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") - return - - var/output = "
                " - - output += "" - - output += "" - output += "" - output += "
                " - output += "

                Banning panel

                " - output += "
                " - - output += "
                Add custom ban: (ONLY use this if you can't ban through any other method)" - output += "" - output += HrefTokenFormField() - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "
                Ban type:Key:
                IP: Computer id:
                Duration:
                Severity:Job:
                " - output += "Reason:

                " - output += "" - output += "
                " - - output += "
                " - - output += "
                Search: " - output += "" - output += HrefTokenFormField() - output += "Ckey: " - output += "Admin ckey:
                " - output += "IP: " - output += "CID: " - output += "" - output += "
                " - output += "Please note that all jobban bans or unbans are in-effect the following round." - - if(adminckey || playerckey || ip || cid) - var/list/searchlist = list() - if(playerckey) - searchlist += "ckey = '[sanitizeSQL(ckey(playerckey))]'" - if(adminckey) - searchlist += "a_ckey = '[sanitizeSQL(ckey(adminckey))]'" - if(ip) - searchlist += "ip = INET_ATON('[sanitizeSQL(ip)]')" - if(cid) - searchlist += "computerid = '[sanitizeSQL(cid)]'" - var/search = searchlist.Join(" AND ") - var/bancount = 0 - var/bansperpage = 15 - var/pagecount = 0 - page = text2num(page) - var/datum/DBQuery/query_count_bans = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE [search]") - if(!query_count_bans.warn_execute()) - qdel(query_count_bans) - return - if(query_count_bans.NextRow()) - bancount = text2num(query_count_bans.item[1]) - qdel(query_count_bans) - if(bancount > bansperpage) - output += "
                Page: " - while(bancount > 0) - output+= "|[pagecount == page ? "\[[pagecount]\]" : "\[[pagecount]\]"]" - bancount -= bansperpage - pagecount++ - output += "|" - var/blcolor = "#ffeeee" //banned light - var/bdcolor = "#ffdddd" //banned dark - var/ulcolor = "#eeffee" //unbanned light - var/udcolor = "#ddffdd" //unbanned dark - - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - var/limit = " LIMIT [bansperpage * page], [bansperpage]" - var/datum/DBQuery/query_search_bans = SSdbcore.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), unbanned, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].unbanned_ckey), unbanned_ckey), unbanned_datetime, edits, round_id FROM [format_table_name("ban")] WHERE [search] ORDER BY bantime DESC[limit]") - if(!query_search_bans.warn_execute()) - qdel(query_search_bans) - return - - while(query_search_bans.NextRow()) - var/banid = query_search_bans.item[1] - var/bantime = query_search_bans.item[2] - var/bantype = query_search_bans.item[3] - var/reason = query_search_bans.item[4] - var/job = query_search_bans.item[5] - var/duration = query_search_bans.item[6] - var/expiration = query_search_bans.item[7] - var/ban_key = query_search_bans.item[8] - var/a_key = query_search_bans.item[9] - var/unbanned = query_search_bans.item[10] - var/unban_key = query_search_bans.item[11] - var/unbantime = query_search_bans.item[12] - var/edits = query_search_bans.item[13] - var/round_id = query_search_bans.item[14] - - var/lcolor = blcolor - var/dcolor = bdcolor - if(unbanned) - lcolor = ulcolor - dcolor = udcolor - - var/typedesc ="" - switch(bantype) - if("PERMABAN") - typedesc = "PERMABAN" - if("TEMPBAN") - typedesc = "TEMPBAN
                ([duration] minutes [(unbanned) ? "" : "(Edit))"]
                Expires [expiration]
                " - if("JOB_PERMABAN") - typedesc = "JOBBAN
                ([job])" - if("JOB_TEMPBAN") - typedesc = "TEMP JOBBAN
                ([job])
                ([duration] minutes [(unbanned) ? "" : "(Edit))"]
                Expires [expiration]" - if("ADMIN_PERMABAN") - typedesc = "ADMIN PERMABAN" - if("ADMIN_TEMPBAN") - typedesc = "ADMIN TEMPBAN
                ([duration] minutes [(unbanned) ? "" : "(Edit))"]
                Expires [expiration]
                " - - output += "
                " - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - if(edits) - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - if(unbanned) - output += "" - output += "" - output += "" - output += "" - output += "" - output += "" - qdel(query_search_bans) - output += "
                TYPECKEYTIME APPLIEDADMINOPTIONS
                [typedesc][ban_key][bantime] (Round ID: [round_id])[a_key][(unbanned) ? "" : "Unban"]
                Reason: [(unbanned) ? "" : "(Edit)"] \"[reason]\"
                EDITS
                [edits]
                UNBANNED by admin [unban_key] on [unbantime]
                 
                " - - usr << browse(output,"window=lookupbans;size=900x500") +#define MAX_ADMIN_BANS_PER_ADMIN 1 +#define MAX_ADMIN_BANS_PER_HEADMIN 3 + +//Either pass the mob you wish to ban in the 'banned_mob' attribute, or the banckey, banip and bancid variables. If both are passed, the mob takes priority! If a mob is not passed, banckey is the minimum that needs to be passed! banip and bancid are optional. +/datum/admins/proc/DB_ban_record(bantype, mob/banned_mob, duration = -1, reason, job = "", bankey = null, banip = null, bancid = null) + + if(!check_rights(R_BAN)) + return + + if(!SSdbcore.Connect()) + to_chat(src, "Failed to establish database connection.") + return + + var/bantype_pass = 0 + var/bantype_str + var/maxadminbancheck //Used to limit the number of active bans of a certein type that each admin can give. Used to protect against abuse or mutiny. + var/announceinirc //When set, it announces the ban in irc. Intended to be a way to raise an alarm, so to speak. + var/blockselfban //Used to prevent the banning of yourself. + var/kickbannedckey //Defines whether this proc should kick the banned person, if they are connected (if banned_mob is defined). + //some ban types kick players after this proc passes (tempban, permaban), but some are specific to db_ban, so + //they should kick within this proc. + switch(bantype) + if(BANTYPE_PERMA) + bantype_str = "PERMABAN" + duration = -1 + bantype_pass = 1 + blockselfban = 1 + if(BANTYPE_TEMP) + bantype_str = "TEMPBAN" + bantype_pass = 1 + blockselfban = 1 + if(BANTYPE_JOB_PERMA) + bantype_str = "JOB_PERMABAN" + duration = -1 + bantype_pass = 1 + if(BANTYPE_JOB_TEMP) + bantype_str = "JOB_TEMPBAN" + bantype_pass = 1 + if(BANTYPE_ADMIN_PERMA) + bantype_str = "ADMIN_PERMABAN" + duration = -1 + bantype_pass = 1 + maxadminbancheck = 1 + announceinirc = 1 + blockselfban = 1 + kickbannedckey = 1 + if(BANTYPE_ADMIN_TEMP) + bantype_str = "ADMIN_TEMPBAN" + bantype_pass = 1 + maxadminbancheck = 1 + announceinirc = 1 + blockselfban = 1 + kickbannedckey = 1 + if( !bantype_pass ) + return + if( !istext(reason) ) + return + if( !isnum(duration) ) + return + + var/ckey + var/computerid + var/ip + + if(ismob(banned_mob)) + ckey = banned_mob.ckey + bankey = banned_mob.key + if(banned_mob.client) + computerid = banned_mob.client.computer_id + ip = banned_mob.client.address + else + computerid = banned_mob.computer_id + ip = banned_mob.lastKnownIP + else if(bankey) + ckey = ckey(bankey) + computerid = bancid + ip = banip + + var/had_banned_mob = banned_mob != null + var/client/banned_client = banned_mob?.client + var/banned_mob_guest_key = had_banned_mob && IsGuestKey(banned_mob.key) + banned_mob = null + var/sql_ckey = sanitizeSQL(ckey) + var/datum/DBQuery/query_add_ban_get_ckey = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") + if(!query_add_ban_get_ckey.warn_execute()) + qdel(query_add_ban_get_ckey) + return + var/seen_before = query_add_ban_get_ckey.NextRow() + qdel(query_add_ban_get_ckey) + if(!seen_before) + if(!had_banned_mob || (had_banned_mob && !banned_mob_guest_key)) + if(alert(usr, "[bankey] has not been seen before, are you sure you want to create a ban for them?", "Unknown ckey", "Yes", "No", "Cancel") != "Yes") + return + + var/a_key + var/a_ckey + var/a_computerid + var/a_ip + + if(istype(owner)) + a_key = owner.key + a_ckey = owner.ckey + a_computerid = owner.computer_id + a_ip = owner.address + + if(blockselfban) + if(a_ckey == ckey) + to_chat(usr, "You cannot apply this ban type on yourself.") + return + + var/who + for(var/client/C in GLOB.clients) + if(!who) + who = "[C]" + else + who += ", [C]" + + var/adminwho + for(var/client/C in GLOB.admins) + if(!adminwho) + adminwho = "[C]" + else + adminwho += ", [C]" + + reason = sanitizeSQL(reason) + var/sql_a_ckey = sanitizeSQL(a_ckey) + if(maxadminbancheck) + var/datum/DBQuery/query_check_adminban_amt = SSdbcore.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[sql_a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") + if(!query_check_adminban_amt.warn_execute()) + qdel(query_check_adminban_amt) + return + if(query_check_adminban_amt.NextRow()) + var/adm_bans = text2num(query_check_adminban_amt.item[1]) + var/max_bans = MAX_ADMIN_BANS_PER_ADMIN + if (check_rights(R_PERMISSIONS, FALSE)) + max_bans = MAX_ADMIN_BANS_PER_HEADMIN + if(adm_bans >= max_bans) + to_chat(usr, "You already logged [max_bans] admin ban(s) or more. Do not abuse this function!") + qdel(query_check_adminban_amt) + return + qdel(query_check_adminban_amt) + if(!computerid) + computerid = "0" + if(!ip) + ip = "0.0.0.0" + var/sql_job = sanitizeSQL(job) + var/sql_computerid = sanitizeSQL(computerid) + var/sql_ip = sanitizeSQL(ip) + var/sql_a_computerid = sanitizeSQL(a_computerid) + var/sql_a_ip = sanitizeSQL(a_ip) + var/sql = "INSERT INTO [format_table_name("ban")] (`bantime`,`server_ip`,`server_port`,`round_id`,`bantype`,`reason`,`job`,`duration`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]', '[bantype_str]', '[reason]', '[sql_job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[sql_ckey]', '[sql_computerid]', INET_ATON('[sql_ip]'), '[sql_a_ckey]', '[sql_a_computerid]', INET_ATON('[sql_a_ip]'), '[who]', '[adminwho]')" + var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql) + if(!query_add_ban.warn_execute()) + qdel(query_add_ban) + return + qdel(query_add_ban) + to_chat(usr, "Ban saved to database.") + var/msg = "[key_name_admin(usr)] has added a [bantype_str] for [bankey] [(job)?"([job])":""] [(duration > 0)?"([duration] minutes)":""] with the reason: \"[reason]\" to the ban database." + message_admins(msg,1) + var/datum/admin_help/AH = admin_ticket_log(ckey, msg) + + if(announceinirc) + send2irc("BAN ALERT","[a_key] applied a [bantype_str] on [bankey]") + + if(kickbannedckey) + if(AH) + AH.Resolve() //with prejudice + if(banned_client && banned_client.ckey == ckey) + qdel(banned_client) + return 1 + +/datum/admins/proc/DB_ban_unban(ckey, bantype, job = "") + + if(!check_rights(R_BAN)) + return + + var/bantype_str + if(bantype) + var/bantype_pass = 0 + switch(bantype) + if(BANTYPE_PERMA) + bantype_str = "PERMABAN" + bantype_pass = 1 + if(BANTYPE_TEMP) + bantype_str = "TEMPBAN" + bantype_pass = 1 + if(BANTYPE_JOB_PERMA) + bantype_str = "JOB_PERMABAN" + bantype_pass = 1 + if(BANTYPE_JOB_TEMP) + bantype_str = "JOB_TEMPBAN" + bantype_pass = 1 + if(BANTYPE_ADMIN_PERMA) + bantype_str = "ADMIN_PERMABAN" + bantype_pass = 1 + if(BANTYPE_ADMIN_TEMP) + bantype_str = "ADMIN_TEMPBAN" + bantype_pass = 1 + if(BANTYPE_ANY_FULLBAN) + bantype_str = "ANY" + bantype_pass = 1 + if(BANTYPE_ANY_JOB) + bantype_str = "ANYJOB" + bantype_pass = 1 + if( !bantype_pass ) + return + + var/bantype_sql + if(bantype_str == "ANY") + bantype_sql = "(bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now() ) )" + else if(bantype_str == "ANYJOB") + bantype_sql = "(bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now() ) )" + else + bantype_sql = "bantype = '[bantype_str]'" + var/sql_ckey = sanitizeSQL(ckey) + var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = '[sql_ckey]' AND [bantype_sql] AND (unbanned is null OR unbanned = false)" + if(job) + var/sql_job = sanitizeSQL(job) + sql += " AND job = '[sql_job]'" + + if(!SSdbcore.Connect()) + return + + var/ban_id + var/ban_number = 0 //failsafe + + var/datum/DBQuery/query_unban_get_id = SSdbcore.NewQuery(sql) + if(!query_unban_get_id.warn_execute()) + qdel(query_unban_get_id) + return + while(query_unban_get_id.NextRow()) + ban_id = query_unban_get_id.item[1] + ban_number++; + qdel(query_unban_get_id) + + if(ban_number == 0) + to_chat(usr, "Database update failed due to no bans fitting the search criteria. If this is not a legacy ban you should contact the database admin.") + return + + if(ban_number > 1) + to_chat(usr, "Database update failed due to multiple bans fitting the search criteria. Note down the ckey, job and current time and contact the database admin.") + return + + if(istext(ban_id)) + ban_id = text2num(ban_id) + if(!isnum(ban_id)) + to_chat(usr, "Database update failed due to a ban ID mismatch. Contact the database admin.") + return + + DB_ban_unban_by_id(ban_id) + +/datum/admins/proc/DB_ban_edit(banid = null, param = null) + + if(!check_rights(R_BAN)) + return + + if(!isnum(banid) || !istext(param)) + to_chat(usr, "Cancelled") + return + + var/datum/DBQuery/query_edit_ban_get_details = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), duration, reason FROM [format_table_name("ban")] WHERE id = [banid]") + if(!query_edit_ban_get_details.warn_execute()) + qdel(query_edit_ban_get_details) + return + + var/e_key = usr.key //Editing admin key + var/p_key //(banned) Player key + var/duration //Old duration + var/reason //Old reason + + if(query_edit_ban_get_details.NextRow()) + p_key = query_edit_ban_get_details.item[1] + duration = query_edit_ban_get_details.item[2] + reason = query_edit_ban_get_details.item[3] + else + to_chat(usr, "Invalid ban id. Contact the database admin") + qdel(query_edit_ban_get_details) + return + qdel(query_edit_ban_get_details) + + reason = sanitizeSQL(reason) + var/value + + switch(param) + if("reason") + if(!value) + value = input("Insert the new reason for [p_key]'s ban", "New Reason", "[reason]", null) as null|text + value = sanitizeSQL(value) + if(!value) + to_chat(usr, "Cancelled") + return + + var/datum/DBQuery/query_edit_ban_reason = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [e_key] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
                ') WHERE id = [banid]") + if(!query_edit_ban_reason.warn_execute()) + qdel(query_edit_ban_reason) + return + qdel(query_edit_ban_reason) + message_admins("[key_name_admin(usr)] has edited a ban for [p_key]'s reason from [reason] to [value]") + if("duration") + if(!value) + value = input("Insert the new duration (in minutes) for [p_key]'s ban", "New Duration", "[duration]", null) as null|num + if(!isnum(value) || !value) + to_chat(usr, "Cancelled") + return + + var/datum/DBQuery/query_edit_ban_duration = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [e_key] changed ban duration from [duration] to [value]
                '), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]") + if(!query_edit_ban_duration.warn_execute()) + qdel(query_edit_ban_duration) + return + qdel(query_edit_ban_duration) + message_admins("[key_name_admin(usr)] has edited a ban for [p_key]'s duration from [duration] to [value]") + if("unban") + if(alert("Unban [p_key]?", "Unban?", "Yes", "No") == "Yes") + DB_ban_unban_by_id(banid) + return + else + to_chat(usr, "Cancelled") + return + else + to_chat(usr, "Cancelled") + return + +/datum/admins/proc/DB_ban_unban_by_id(id) + + if(!check_rights(R_BAN)) + return + + var/sql = "SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey) FROM [format_table_name("ban")] WHERE id = [id]" + + if(!SSdbcore.Connect()) + return + + var/ban_number = 0 //failsafe + + var/p_key + var/datum/DBQuery/query_unban_get_ckey = SSdbcore.NewQuery(sql) + if(!query_unban_get_ckey.warn_execute()) + qdel(query_unban_get_ckey) + return + while(query_unban_get_ckey.NextRow()) + p_key = query_unban_get_ckey.item[1] + ban_number++; + qdel(query_unban_get_ckey) + + if(ban_number == 0) + to_chat(usr, "Database update failed due to a ban id not being present in the database.") + return + + if(ban_number > 1) + to_chat(usr, "Database update failed due to multiple bans having the same ID. Contact the database admin.") + return + + if(!istype(owner)) + return + + var/unban_ckey = owner.ckey + var/unban_computerid = owner.computer_id + var/unban_ip = owner.address + + var/sql_update = "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = '[unban_ckey]', unbanned_computerid = '[unban_computerid]', unbanned_ip = INET_ATON('[unban_ip]') WHERE id = [id]" + var/datum/DBQuery/query_unban = SSdbcore.NewQuery(sql_update) + if(!query_unban.warn_execute()) + qdel(query_unban) + return + qdel(query_unban) + message_admins("[key_name_admin(usr)] has lifted [p_key]'s ban.") + +/client/proc/DB_ban_panel() + set category = "Admin" + set name = "Banning Panel" + set desc = "Edit admin permissions" + + if(!holder) + return + + holder.DB_ban_panel() + + +/datum/admins/proc/DB_ban_panel(playerckey, adminckey, ip, cid, page = 0) + if(!usr.client) + return + + if(!check_rights(R_BAN)) + return + + if(!SSdbcore.Connect()) + to_chat(usr, "Failed to establish database connection.") + return + + var/output = "
                " + + output += "" + + output += "" + output += "" + output += "
                " + output += "

                Banning panel

                " + output += "
                " + + output += "
                Add custom ban: (ONLY use this if you can't ban through any other method)" + output += "" + output += HrefTokenFormField() + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "
                Ban type:Key:
                IP: Computer id:
                Duration:
                Severity:Job:
                " + output += "Reason:

                " + output += "" + output += "
                " + + output += "
                " + + output += "
                Search: " + output += "" + output += HrefTokenFormField() + output += "Ckey: " + output += "Admin ckey:
                " + output += "IP: " + output += "CID: " + output += "" + output += "
                " + output += "Please note that all jobban bans or unbans are in-effect the following round." + + if(adminckey || playerckey || ip || cid) + var/list/searchlist = list() + if(playerckey) + searchlist += "ckey = '[sanitizeSQL(ckey(playerckey))]'" + if(adminckey) + searchlist += "a_ckey = '[sanitizeSQL(ckey(adminckey))]'" + if(ip) + searchlist += "ip = INET_ATON('[sanitizeSQL(ip)]')" + if(cid) + searchlist += "computerid = '[sanitizeSQL(cid)]'" + var/search = searchlist.Join(" AND ") + var/bancount = 0 + var/bansperpage = 15 + var/pagecount = 0 + page = text2num(page) + var/datum/DBQuery/query_count_bans = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE [search]") + if(!query_count_bans.warn_execute()) + qdel(query_count_bans) + return + if(query_count_bans.NextRow()) + bancount = text2num(query_count_bans.item[1]) + qdel(query_count_bans) + if(bancount > bansperpage) + output += "
                Page: " + while(bancount > 0) + output+= "|[pagecount == page ? "\[[pagecount]\]" : "\[[pagecount]\]"]" + bancount -= bansperpage + pagecount++ + output += "|" + var/blcolor = "#ffeeee" //banned light + var/bdcolor = "#ffdddd" //banned dark + var/ulcolor = "#eeffee" //unbanned light + var/udcolor = "#ddffdd" //unbanned dark + + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + var/limit = " LIMIT [bansperpage * page], [bansperpage]" + var/datum/DBQuery/query_search_bans = SSdbcore.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), unbanned, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].unbanned_ckey), unbanned_ckey), unbanned_datetime, edits, round_id FROM [format_table_name("ban")] WHERE [search] ORDER BY bantime DESC[limit]") + if(!query_search_bans.warn_execute()) + qdel(query_search_bans) + return + + while(query_search_bans.NextRow()) + var/banid = query_search_bans.item[1] + var/bantime = query_search_bans.item[2] + var/bantype = query_search_bans.item[3] + var/reason = query_search_bans.item[4] + var/job = query_search_bans.item[5] + var/duration = query_search_bans.item[6] + var/expiration = query_search_bans.item[7] + var/ban_key = query_search_bans.item[8] + var/a_key = query_search_bans.item[9] + var/unbanned = query_search_bans.item[10] + var/unban_key = query_search_bans.item[11] + var/unbantime = query_search_bans.item[12] + var/edits = query_search_bans.item[13] + var/round_id = query_search_bans.item[14] + + var/lcolor = blcolor + var/dcolor = bdcolor + if(unbanned) + lcolor = ulcolor + dcolor = udcolor + + var/typedesc ="" + switch(bantype) + if("PERMABAN") + typedesc = "PERMABAN" + if("TEMPBAN") + typedesc = "TEMPBAN
                ([duration] minutes [(unbanned) ? "" : "(Edit))"]
                Expires [expiration]
                " + if("JOB_PERMABAN") + typedesc = "JOBBAN
                ([job])" + if("JOB_TEMPBAN") + typedesc = "TEMP JOBBAN
                ([job])
                ([duration] minutes [(unbanned) ? "" : "(Edit))"]
                Expires [expiration]" + if("ADMIN_PERMABAN") + typedesc = "ADMIN PERMABAN" + if("ADMIN_TEMPBAN") + typedesc = "ADMIN TEMPBAN
                ([duration] minutes [(unbanned) ? "" : "(Edit))"]
                Expires [expiration]
                " + + output += "
                " + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + if(edits) + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + if(unbanned) + output += "" + output += "" + output += "" + output += "" + output += "" + output += "" + qdel(query_search_bans) + output += "
                TYPECKEYTIME APPLIEDADMINOPTIONS
                [typedesc][ban_key][bantime] (Round ID: [round_id])[a_key][(unbanned) ? "" : "Unban"]
                Reason: [(unbanned) ? "" : "(Edit)"] \"[reason]\"
                EDITS
                [edits]
                UNBANNED by admin [unban_key] on [unbantime]
                 
                " + + usr << browse(output,"window=lookupbans;size=900x500") diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 03670009f8..b0152e85f2 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -1,208 +1,208 @@ -//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 20 -#define STICKYBAN_MAX_EXISTING_USER_MATCHES 5 //ie, users who were connected before the ban triggered -#define STICKYBAN_MAX_ADMIN_MATCHES 2 - -/world/IsBanned(key,address,computer_id,type,real_bans_only=FALSE) - if (!key || !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 (text2num(computer_id) == 2147483647) //this cid causes stickybans to go haywire - log_access("Failed Login (invalid cid): [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 an invalid Computer ID.)") - - if (type == "world") - return ..() //shunt world topic banchecks to purely to byond's internal ban system - - 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. - - var/admin = FALSE - if(GLOB.admin_datums[ckey] || GLOB.deadmins[ckey]) - admin = 1 - - //Whitelist - if(CONFIG_GET(flag/usewhitelist)) - if(!check_whitelist(ckey)) - if (admin) - log_admin("The admin [key] has been allowed to bypass the whitelist") - 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 && 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 && extreme_popcap && living_player_count() >= extreme_popcap && !admin) - log_access("Failed Login: [key] - Population cap reached") - return list("reason"="popcap", "desc"= "\nReason: [CONFIG_GET(string/extreme_popcap_message)]") - - if(CONFIG_GET(flag/ban_legacy_system)) - - //Ban Checking - . = CheckBan(ckey, computer_id, address ) - if(.) - if (admin) - log_admin("The admin [key] has been allowed to bypass a matching ban on [.["key"]]") - message_admins("The admin [key] has been allowed to bypass a matching ban on [.["key"]]") - addclientmessage(ckey,"You have been allowed to bypass a matching ban on [.["key"]]") - else - log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]") - return . - - else - if(!SSdbcore.Connect()) - var/msg = "Ban database connection failure. Key [ckey] not checked" - log_world(msg) - message_admins(msg) - return - - var/ipquery = "" - var/cidquery = "" - if(address) - ipquery = " OR ip = INET_ATON('[address]') " - - if(computer_id) - cidquery = " OR computerid = '[computer_id]' " - - var/datum/DBQuery/query_ban_check = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), reason, expiration_time, duration, bantime, bantype, id, round_id FROM [format_table_name("ban")] WHERE (ckey = '[ckey]' [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR bantype = 'ADMIN_PERMABAN' OR ((bantype = 'TEMPBAN' OR bantype = 'ADMIN_TEMPBAN') AND expiration_time > Now())) AND isnull(unbanned)") - if(!query_ban_check.Execute(async = TRUE)) - qdel(query_ban_check) - return - while(query_ban_check.NextRow()) - var/pkey = query_ban_check.item[1] - var/akey = query_ban_check.item[2] - var/reason = query_ban_check.item[3] - var/expiration = query_ban_check.item[4] - var/duration = query_ban_check.item[5] - var/bantime = query_ban_check.item[6] - var/bantype = query_ban_check.item[7] - var/banid = query_ban_check.item[8] - var/ban_round_id = query_ban_check.item[9] - if (bantype == "ADMIN_PERMABAN" || bantype == "ADMIN_TEMPBAN") - //admin bans MUST match on ckey to prevent cid-spoofing attacks - // as well as dynamic ip abuse - if (ckey(pkey) != ckey) - continue - if (admin) - if (bantype == "ADMIN_PERMABAN" || bantype == "ADMIN_TEMPBAN") - log_admin("The admin [key] is admin banned (#[banid]), and has been disallowed access") - message_admins("The admin [key] is admin banned (#[banid]), and has been disallowed access") - else - log_admin("The admin [key] has been allowed to bypass a matching ban on [pkey] (#[banid])") - message_admins("The admin [key] has been allowed to bypass a matching ban on [pkey] (#[banid])") - addclientmessage(ckey,"You have been allowed to bypass a matching ban on [pkey] (#[banid])") - continue - var/expires = "" - if(text2num(duration) > 0) - expires = " The ban is for [duration] minutes and expires on [expiration] (server time)." - else - expires = " The is a permanent ban." - - var/desc = "\nReason: You, or another user of this computer or connection ([pkey]) is banned from playing here. The ban reason is:\n[reason]\nThis ban (BanID #[banid]) was applied by [akey] on [bantime] during round ID [ban_round_id], [expires]" - - . = list("reason"="[bantype]", "desc"="[desc]") - - - log_access("Failed Login: [key] [computer_id] [address] - Banned (#[banid]) [.["reason"]]") - qdel(query_ban_check) - return . - qdel(query_ban_check) - - var/list/ban = ..() //default pager ban stuff - if (ban) - var/bannedckey = "ERROR" - if (ban["ckey"]) - bannedckey = ban["ckey"] - - var/newmatch = FALSE - var/cachedban = SSstickyban.cache[bannedckey] - - //rogue ban in the process of being reverted. - if (cachedban && cachedban["reverting"]) - 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/newmatches_connected = cachedban["existing_user_matches_this_round"] - var/list/newmatches_admin = cachedban["admin_matches_this_round"] - - newmatches[ckey] = ckey - if (C) - newmatches_connected[ckey] = ckey - if (admin) - newmatches_admin[ckey] = ckey - - if (\ - newmatches.len > STICKYBAN_MAX_MATCHES || \ - newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \ - newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \ - ) - if (cachedban["reverting"]) - return null - cachedban["reverting"] = TRUE - - world.SetConfig("ban", bannedckey, null) - - log_game("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state") - message_admins("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state") - //do not convert to timer. - spawn (5) - world.SetConfig("ban", bannedckey, null) - sleep(1) - world.SetConfig("ban", bannedckey, null) - cachedban["matches_this_round"] = list() - cachedban["existing_user_matches_this_round"] = list() - cachedban["admin_matches_this_round"] = list() - cachedban -= "reverting" - world.SetConfig("ban", bannedckey, list2stickyban(cachedban)) - return null - - //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]") - 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 reversed.") - - 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 . - - -#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 20 +#define STICKYBAN_MAX_EXISTING_USER_MATCHES 5 //ie, users who were connected before the ban triggered +#define STICKYBAN_MAX_ADMIN_MATCHES 2 + +/world/IsBanned(key,address,computer_id,type,real_bans_only=FALSE) + if (!key || !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 (text2num(computer_id) == 2147483647) //this cid causes stickybans to go haywire + log_access("Failed Login (invalid cid): [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 an invalid Computer ID.)") + + if (type == "world") + return ..() //shunt world topic banchecks to purely to byond's internal ban system + + 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. + + var/admin = FALSE + if(GLOB.admin_datums[ckey] || GLOB.deadmins[ckey]) + admin = 1 + + //Whitelist + if(CONFIG_GET(flag/usewhitelist)) + if(!check_whitelist(ckey)) + if (admin) + log_admin("The admin [key] has been allowed to bypass the whitelist") + 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 && 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 && extreme_popcap && living_player_count() >= extreme_popcap && !admin) + log_access("Failed Login: [key] - Population cap reached") + return list("reason"="popcap", "desc"= "\nReason: [CONFIG_GET(string/extreme_popcap_message)]") + + if(CONFIG_GET(flag/ban_legacy_system)) + + //Ban Checking + . = CheckBan(ckey, computer_id, address ) + if(.) + if (admin) + log_admin("The admin [key] has been allowed to bypass a matching ban on [.["key"]]") + message_admins("The admin [key] has been allowed to bypass a matching ban on [.["key"]]") + addclientmessage(ckey,"You have been allowed to bypass a matching ban on [.["key"]]") + else + log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]") + return . + + else + if(!SSdbcore.Connect()) + var/msg = "Ban database connection failure. Key [ckey] not checked" + log_world(msg) + message_admins(msg) + return + + var/ipquery = "" + var/cidquery = "" + if(address) + ipquery = " OR ip = INET_ATON('[address]') " + + if(computer_id) + cidquery = " OR computerid = '[computer_id]' " + + var/datum/DBQuery/query_ban_check = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), reason, expiration_time, duration, bantime, bantype, id, round_id FROM [format_table_name("ban")] WHERE (ckey = '[ckey]' [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR bantype = 'ADMIN_PERMABAN' OR ((bantype = 'TEMPBAN' OR bantype = 'ADMIN_TEMPBAN') AND expiration_time > Now())) AND isnull(unbanned)") + if(!query_ban_check.Execute(async = TRUE)) + qdel(query_ban_check) + return + while(query_ban_check.NextRow()) + var/pkey = query_ban_check.item[1] + var/akey = query_ban_check.item[2] + var/reason = query_ban_check.item[3] + var/expiration = query_ban_check.item[4] + var/duration = query_ban_check.item[5] + var/bantime = query_ban_check.item[6] + var/bantype = query_ban_check.item[7] + var/banid = query_ban_check.item[8] + var/ban_round_id = query_ban_check.item[9] + if (bantype == "ADMIN_PERMABAN" || bantype == "ADMIN_TEMPBAN") + //admin bans MUST match on ckey to prevent cid-spoofing attacks + // as well as dynamic ip abuse + if (ckey(pkey) != ckey) + continue + if (admin) + if (bantype == "ADMIN_PERMABAN" || bantype == "ADMIN_TEMPBAN") + log_admin("The admin [key] is admin banned (#[banid]), and has been disallowed access") + message_admins("The admin [key] is admin banned (#[banid]), and has been disallowed access") + else + log_admin("The admin [key] has been allowed to bypass a matching ban on [pkey] (#[banid])") + message_admins("The admin [key] has been allowed to bypass a matching ban on [pkey] (#[banid])") + addclientmessage(ckey,"You have been allowed to bypass a matching ban on [pkey] (#[banid])") + continue + var/expires = "" + if(text2num(duration) > 0) + expires = " The ban is for [duration] minutes and expires on [expiration] (server time)." + else + expires = " The is a permanent ban." + + var/desc = "\nReason: You, or another user of this computer or connection ([pkey]) is banned from playing here. The ban reason is:\n[reason]\nThis ban (BanID #[banid]) was applied by [akey] on [bantime] during round ID [ban_round_id], [expires]" + + . = list("reason"="[bantype]", "desc"="[desc]") + + + log_access("Failed Login: [key] [computer_id] [address] - Banned (#[banid]) [.["reason"]]") + qdel(query_ban_check) + return . + qdel(query_ban_check) + + var/list/ban = ..() //default pager ban stuff + if (ban) + var/bannedckey = "ERROR" + if (ban["ckey"]) + bannedckey = ban["ckey"] + + var/newmatch = FALSE + var/cachedban = SSstickyban.cache[bannedckey] + + //rogue ban in the process of being reverted. + if (cachedban && cachedban["reverting"]) + 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/newmatches_connected = cachedban["existing_user_matches_this_round"] + var/list/newmatches_admin = cachedban["admin_matches_this_round"] + + newmatches[ckey] = ckey + if (C) + newmatches_connected[ckey] = ckey + if (admin) + newmatches_admin[ckey] = ckey + + if (\ + newmatches.len > STICKYBAN_MAX_MATCHES || \ + newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \ + newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \ + ) + if (cachedban["reverting"]) + return null + cachedban["reverting"] = TRUE + + world.SetConfig("ban", bannedckey, null) + + log_game("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state") + message_admins("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state") + //do not convert to timer. + spawn (5) + world.SetConfig("ban", bannedckey, null) + sleep(1) + world.SetConfig("ban", bannedckey, null) + cachedban["matches_this_round"] = list() + cachedban["existing_user_matches_this_round"] = list() + cachedban["admin_matches_this_round"] = list() + cachedban -= "reverting" + world.SetConfig("ban", bannedckey, list2stickyban(cachedban)) + return null + + //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]") + 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 reversed.") + + 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 . + + +#undef STICKYBAN_MAX_MATCHES +#undef STICKYBAN_MAX_EXISTING_USER_MATCHES +#undef STICKYBAN_MAX_ADMIN_MATCHES diff --git a/code/modules/admin/NewBan.dm b/code/modules/admin/NewBan.dm index be79dce06e..1a6f17efdf 100644 --- a/code/modules/admin/NewBan.dm +++ b/code/modules/admin/NewBan.dm @@ -1,238 +1,238 @@ -GLOBAL_VAR(CMinutes) -GLOBAL_DATUM(Banlist, /savefile) -GLOBAL_PROTECT(Banlist) - - -/proc/CheckBan(ckey, id, address) - if(!GLOB.Banlist) // if Banlist cannot be located for some reason - LoadBans() // try to load the bans - if(!GLOB.Banlist) // uh oh, can't find bans! - return 0 // ABORT ABORT ABORT - - . = list() - var/appeal - var/bran = CONFIG_GET(string/banappeals) - if(bran) - appeal = "\nFor more information on your ban, or to appeal, head to [bran]" - GLOB.Banlist.cd = "/base" - if( "[ckey][id]" in GLOB.Banlist.dir ) - GLOB.Banlist.cd = "[ckey][id]" - if (GLOB.Banlist["temp"]) - if (!GetExp(GLOB.Banlist["minutes"])) - ClearTempbans() - return 0 - else - .["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: [GetExp(GLOB.Banlist["minutes"])]\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]" - else - GLOB.Banlist.cd = "/base/[ckey][id]" - .["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: PERMANENT\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]" - .["reason"] = "ckey/id" - return . - else - for (var/A in GLOB.Banlist.dir) - GLOB.Banlist.cd = "/base/[A]" - var/matches - if( ckey == GLOB.Banlist["key"] ) - matches += "ckey" - if( id == GLOB.Banlist["id"] ) - if(matches) - matches += "/" - matches += "id" - if( address == GLOB.Banlist["ip"] ) - if(matches) - matches += "/" - matches += "ip" - - if(matches) - if(GLOB.Banlist["temp"]) - if (!GetExp(GLOB.Banlist["minutes"])) - ClearTempbans() - return 0 - else - .["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: [GetExp(GLOB.Banlist["minutes"])]\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]" - else - .["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: PERMENANT\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]" - .["reason"] = matches - return . - return 0 - -/proc/UpdateTime() //No idea why i made this a proc. - GLOB.CMinutes = (world.realtime / 10) / 60 - return 1 - -/proc/LoadBans() - if(!CONFIG_GET(flag/ban_legacy_system)) - return - - GLOB.Banlist = new("data/banlist.bdb") - log_admin("Loading Banlist") - - if (!length(GLOB.Banlist.dir)) - log_admin("Banlist is empty.") - - if (!GLOB.Banlist.dir.Find("base")) - log_admin("Banlist missing base dir.") - GLOB.Banlist.dir.Add("base") - GLOB.Banlist.cd = "/base" - else if (GLOB.Banlist.dir.Find("base")) - GLOB.Banlist.cd = "/base" - - ClearTempbans() - return 1 - -/proc/ClearTempbans() - UpdateTime() - - GLOB.Banlist.cd = "/base" - for (var/A in GLOB.Banlist.dir) - GLOB.Banlist.cd = "/base/[A]" - if (!GLOB.Banlist["key"] || !GLOB.Banlist["id"]) - RemoveBan(A) - log_admin("Invalid Ban.") - message_admins("Invalid Ban.") - continue - - if (!GLOB.Banlist["temp"]) - continue - if (GLOB.CMinutes >= GLOB.Banlist["minutes"]) - RemoveBan(A) - - return 1 - - -/proc/AddBan(key, computerid, reason, bannedby, temp, minutes, address) - - var/bantimestamp - var/ban_ckey = ckey(key) - if (temp) - UpdateTime() - bantimestamp = GLOB.CMinutes + minutes - - GLOB.Banlist.cd = "/base" - if ( GLOB.Banlist.dir.Find("[ban_ckey][computerid]") ) - to_chat(usr, text("Ban already exists.")) - return 0 - else - GLOB.Banlist.dir.Add("[ban_ckey][computerid]") - GLOB.Banlist.cd = "/base/[ban_ckey][computerid]" - WRITE_FILE(GLOB.Banlist["key"], ban_ckey) - WRITE_FILE(GLOB.Banlist["id"], computerid) - WRITE_FILE(GLOB.Banlist["ip"], address) - WRITE_FILE(GLOB.Banlist["reason"], reason) - WRITE_FILE(GLOB.Banlist["bannedby"], bannedby) - WRITE_FILE(GLOB.Banlist["temp"], temp) - WRITE_FILE(GLOB.Banlist["roundid"], GLOB.round_id) - if (temp) - WRITE_FILE(GLOB.Banlist["minutes"], bantimestamp) - if(!temp) - create_message("note", key, bannedby, "Permanently banned - [reason]", null, null, 0, 0, null, 0, 0) - else - create_message("note", key, bannedby, "Banned for [minutes] minutes - [reason]", null, null, 0, 0, null, 0, 0) - return 1 - -/proc/RemoveBan(foldername) - var/key - var/id - - GLOB.Banlist.cd = "/base/[foldername]" - GLOB.Banlist["key"] >> key - GLOB.Banlist["id"] >> id - GLOB.Banlist.cd = "/base" - - if (!GLOB.Banlist.dir.Remove(foldername)) - return 0 - - if(!usr) - log_admin_private("Ban Expired: [key]") - message_admins("Ban Expired: [key]") - else - ban_unban_log_save("[key_name(usr)] unbanned [key]") - log_admin_private("[key_name(usr)] unbanned [key]") - message_admins("[key_name_admin(usr)] unbanned: [key]") - usr.client.holder.DB_ban_unban( ckey(key), BANTYPE_ANY_FULLBAN) - for (var/A in GLOB.Banlist.dir) - GLOB.Banlist.cd = "/base/[A]" - if (key == GLOB.Banlist["key"] /*|| id == Banlist["id"]*/) - GLOB.Banlist.cd = "/base" - GLOB.Banlist.dir.Remove(A) - continue - - return 1 - -/proc/GetExp(minutes as num) - UpdateTime() - var/exp = minutes - GLOB.CMinutes - if (exp <= 0) - return 0 - else - var/timeleftstring - if (exp >= 1440) //1440 = 1 day in minutes - timeleftstring = "[round(exp / 1440, 0.1)] Days" - else if (exp >= 60) //60 = 1 hour in minutes - timeleftstring = "[round(exp / 60, 0.1)] Hours" - else - timeleftstring = "[exp] Minutes" - return timeleftstring - -/datum/admins/proc/unbanpanel() - var/count = 0 - var/dat - GLOB.Banlist.cd = "/base" - for (var/A in GLOB.Banlist.dir) - count++ - GLOB.Banlist.cd = "/base/[A]" - var/ref = "[REF(src)]" - var/key = GLOB.Banlist["key"] - var/id = GLOB.Banlist["id"] - var/ip = GLOB.Banlist["ip"] - var/reason = GLOB.Banlist["reason"] - var/by = GLOB.Banlist["bannedby"] - var/expiry - if(GLOB.Banlist["temp"]) - expiry = GetExp(GLOB.Banlist["minutes"]) - if(!expiry) - expiry = "Removal Pending" - else - expiry = "Permaban" - - dat += text("(U)(E) Key: [key]ComputerID: [id]IP: [ip] [expiry](By: [by])(Reason: [reason])") - - dat += "" - dat = "
                Bans: (U) = Unban , (E) = Edit Ban - ([count] Bans)
                [dat]" - usr << browse(dat, "window=unbanp;size=875x400") - -//////////////////////////////////// DEBUG //////////////////////////////////// - -/proc/CreateBans() - - UpdateTime() - - var/i - var/last - - for(i=0, i<1001, i++) - var/a = pick(1,0) - var/b = pick(1,0) - if(b) - GLOB.Banlist.cd = "/base" - GLOB.Banlist.dir.Add("trash[i]trashid[i]") - GLOB.Banlist.cd = "/base/trash[i]trashid[i]" - WRITE_FILE(GLOB.Banlist["key"], "trash[i]") - else - GLOB.Banlist.cd = "/base" - GLOB.Banlist.dir.Add("[last]trashid[i]") - GLOB.Banlist.cd = "/base/[last]trashid[i]" - WRITE_FILE(GLOB.Banlist["key"], last) - WRITE_FILE(GLOB.Banlist["id"], "trashid[i]") - WRITE_FILE(GLOB.Banlist["reason"], "Trashban[i].") - WRITE_FILE(GLOB.Banlist["temp"], a) - WRITE_FILE(GLOB.Banlist["minutes"], GLOB.CMinutes + rand(1,2000)) - WRITE_FILE(GLOB.Banlist["bannedby"], "trashmin") - last = "trash[i]" - - GLOB.Banlist.cd = "/base" - -/proc/ClearAllBans() - GLOB.Banlist.cd = "/base" - for (var/A in GLOB.Banlist.dir) - RemoveBan(A) +GLOBAL_VAR(CMinutes) +GLOBAL_DATUM(Banlist, /savefile) +GLOBAL_PROTECT(Banlist) + + +/proc/CheckBan(ckey, id, address) + if(!GLOB.Banlist) // if Banlist cannot be located for some reason + LoadBans() // try to load the bans + if(!GLOB.Banlist) // uh oh, can't find bans! + return 0 // ABORT ABORT ABORT + + . = list() + var/appeal + var/bran = CONFIG_GET(string/banappeals) + if(bran) + appeal = "\nFor more information on your ban, or to appeal, head to [bran]" + GLOB.Banlist.cd = "/base" + if( "[ckey][id]" in GLOB.Banlist.dir ) + GLOB.Banlist.cd = "[ckey][id]" + if (GLOB.Banlist["temp"]) + if (!GetExp(GLOB.Banlist["minutes"])) + ClearTempbans() + return 0 + else + .["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: [GetExp(GLOB.Banlist["minutes"])]\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]" + else + GLOB.Banlist.cd = "/base/[ckey][id]" + .["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: PERMANENT\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]" + .["reason"] = "ckey/id" + return . + else + for (var/A in GLOB.Banlist.dir) + GLOB.Banlist.cd = "/base/[A]" + var/matches + if( ckey == GLOB.Banlist["key"] ) + matches += "ckey" + if( id == GLOB.Banlist["id"] ) + if(matches) + matches += "/" + matches += "id" + if( address == GLOB.Banlist["ip"] ) + if(matches) + matches += "/" + matches += "ip" + + if(matches) + if(GLOB.Banlist["temp"]) + if (!GetExp(GLOB.Banlist["minutes"])) + ClearTempbans() + return 0 + else + .["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: [GetExp(GLOB.Banlist["minutes"])]\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]" + else + .["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: PERMENANT\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]" + .["reason"] = matches + return . + return 0 + +/proc/UpdateTime() //No idea why i made this a proc. + GLOB.CMinutes = (world.realtime / 10) / 60 + return 1 + +/proc/LoadBans() + if(!CONFIG_GET(flag/ban_legacy_system)) + return + + GLOB.Banlist = new("data/banlist.bdb") + log_admin("Loading Banlist") + + if (!length(GLOB.Banlist.dir)) + log_admin("Banlist is empty.") + + if (!GLOB.Banlist.dir.Find("base")) + log_admin("Banlist missing base dir.") + GLOB.Banlist.dir.Add("base") + GLOB.Banlist.cd = "/base" + else if (GLOB.Banlist.dir.Find("base")) + GLOB.Banlist.cd = "/base" + + ClearTempbans() + return 1 + +/proc/ClearTempbans() + UpdateTime() + + GLOB.Banlist.cd = "/base" + for (var/A in GLOB.Banlist.dir) + GLOB.Banlist.cd = "/base/[A]" + if (!GLOB.Banlist["key"] || !GLOB.Banlist["id"]) + RemoveBan(A) + log_admin("Invalid Ban.") + message_admins("Invalid Ban.") + continue + + if (!GLOB.Banlist["temp"]) + continue + if (GLOB.CMinutes >= GLOB.Banlist["minutes"]) + RemoveBan(A) + + return 1 + + +/proc/AddBan(key, computerid, reason, bannedby, temp, minutes, address) + + var/bantimestamp + var/ban_ckey = ckey(key) + if (temp) + UpdateTime() + bantimestamp = GLOB.CMinutes + minutes + + GLOB.Banlist.cd = "/base" + if ( GLOB.Banlist.dir.Find("[ban_ckey][computerid]") ) + to_chat(usr, text("Ban already exists.")) + return 0 + else + GLOB.Banlist.dir.Add("[ban_ckey][computerid]") + GLOB.Banlist.cd = "/base/[ban_ckey][computerid]" + WRITE_FILE(GLOB.Banlist["key"], ban_ckey) + WRITE_FILE(GLOB.Banlist["id"], computerid) + WRITE_FILE(GLOB.Banlist["ip"], address) + WRITE_FILE(GLOB.Banlist["reason"], reason) + WRITE_FILE(GLOB.Banlist["bannedby"], bannedby) + WRITE_FILE(GLOB.Banlist["temp"], temp) + WRITE_FILE(GLOB.Banlist["roundid"], GLOB.round_id) + if (temp) + WRITE_FILE(GLOB.Banlist["minutes"], bantimestamp) + if(!temp) + create_message("note", key, bannedby, "Permanently banned - [reason]", null, null, 0, 0, null, 0, 0) + else + create_message("note", key, bannedby, "Banned for [minutes] minutes - [reason]", null, null, 0, 0, null, 0, 0) + return 1 + +/proc/RemoveBan(foldername) + var/key + var/id + + GLOB.Banlist.cd = "/base/[foldername]" + GLOB.Banlist["key"] >> key + GLOB.Banlist["id"] >> id + GLOB.Banlist.cd = "/base" + + if (!GLOB.Banlist.dir.Remove(foldername)) + return 0 + + if(!usr) + log_admin_private("Ban Expired: [key]") + message_admins("Ban Expired: [key]") + else + ban_unban_log_save("[key_name(usr)] unbanned [key]") + log_admin_private("[key_name(usr)] unbanned [key]") + message_admins("[key_name_admin(usr)] unbanned: [key]") + usr.client.holder.DB_ban_unban( ckey(key), BANTYPE_ANY_FULLBAN) + for (var/A in GLOB.Banlist.dir) + GLOB.Banlist.cd = "/base/[A]" + if (key == GLOB.Banlist["key"] /*|| id == Banlist["id"]*/) + GLOB.Banlist.cd = "/base" + GLOB.Banlist.dir.Remove(A) + continue + + return 1 + +/proc/GetExp(minutes as num) + UpdateTime() + var/exp = minutes - GLOB.CMinutes + if (exp <= 0) + return 0 + else + var/timeleftstring + if (exp >= 1440) //1440 = 1 day in minutes + timeleftstring = "[round(exp / 1440, 0.1)] Days" + else if (exp >= 60) //60 = 1 hour in minutes + timeleftstring = "[round(exp / 60, 0.1)] Hours" + else + timeleftstring = "[exp] Minutes" + return timeleftstring + +/datum/admins/proc/unbanpanel() + var/count = 0 + var/dat + GLOB.Banlist.cd = "/base" + for (var/A in GLOB.Banlist.dir) + count++ + GLOB.Banlist.cd = "/base/[A]" + var/ref = "[REF(src)]" + var/key = GLOB.Banlist["key"] + var/id = GLOB.Banlist["id"] + var/ip = GLOB.Banlist["ip"] + var/reason = GLOB.Banlist["reason"] + var/by = GLOB.Banlist["bannedby"] + var/expiry + if(GLOB.Banlist["temp"]) + expiry = GetExp(GLOB.Banlist["minutes"]) + if(!expiry) + expiry = "Removal Pending" + else + expiry = "Permaban" + + dat += text("") + + dat += "
                (U)(E) Key: [key]ComputerID: [id]IP: [ip] [expiry](By: [by])(Reason: [reason])
                " + dat = "
                Bans: (U) = Unban , (E) = Edit Ban - ([count] Bans)
                [dat]" + usr << browse(dat, "window=unbanp;size=875x400") + +//////////////////////////////////// DEBUG //////////////////////////////////// + +/proc/CreateBans() + + UpdateTime() + + var/i + var/last + + for(i=0, i<1001, i++) + var/a = pick(1,0) + var/b = pick(1,0) + if(b) + GLOB.Banlist.cd = "/base" + GLOB.Banlist.dir.Add("trash[i]trashid[i]") + GLOB.Banlist.cd = "/base/trash[i]trashid[i]" + WRITE_FILE(GLOB.Banlist["key"], "trash[i]") + else + GLOB.Banlist.cd = "/base" + GLOB.Banlist.dir.Add("[last]trashid[i]") + GLOB.Banlist.cd = "/base/[last]trashid[i]" + WRITE_FILE(GLOB.Banlist["key"], last) + WRITE_FILE(GLOB.Banlist["id"], "trashid[i]") + WRITE_FILE(GLOB.Banlist["reason"], "Trashban[i].") + WRITE_FILE(GLOB.Banlist["temp"], a) + WRITE_FILE(GLOB.Banlist["minutes"], GLOB.CMinutes + rand(1,2000)) + WRITE_FILE(GLOB.Banlist["bannedby"], "trashmin") + last = "trash[i]" + + GLOB.Banlist.cd = "/base" + +/proc/ClearAllBans() + GLOB.Banlist.cd = "/base" + for (var/A in GLOB.Banlist.dir) + RemoveBan(A) diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 1cecbbd0e5..88e68158ed 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1,1011 +1,1011 @@ - -//////////////////////////////// -/proc/message_admins(msg) - msg = "ADMIN LOG: [msg]" - to_chat(GLOB.admins, msg) - -/proc/relay_msg_admins(msg) - msg = "RELAY: [msg]" - to_chat(GLOB.admins, msg) - - -///////////////////////////////////////////////////////////////////////////////////////////////Panels - -/datum/admins/proc/show_player_panel(mob/M in GLOB.mob_list) - set category = "Admin" - 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.") - 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() + "\]" - - 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 - " - 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 | " - body += "Ban | " - body += "Jobban | " - body += "Identity Ban | " - var/rm = REF(M) - if(jobban_isbanned(M, "OOC")) - body+= "OOCBan | " - else - body+= "OOCBan | " - if(QDELETED(M) || QDELETED(usr)) - return - if(jobban_isbanned(M, "emote")) - body+= "EmoteBan | " - else - body+= "Emoteban | " - if(QDELETED(M) || QDELETED(usr)) - return - - 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 += "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 += usr.client.citaPPoptions(M) // CITADEL - - 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 = "Fun" - 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!") - 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( isemptylist(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( isemptylist(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(isemptylist(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(isemptylist(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( isemptylist(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( isemptylist(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=210x200") - 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/list/options = list("Regular Restart", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)") - if(world.TgsAvailable()) - options += "Server Restart (Kill and restart DD)"; - - var/rebootconfirm - 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") - rebootconfirm = TRUE - else - rebootconfirm = TRUE - if(rebootconfirm) - 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") - SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 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 = "Special Verbs" - 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]") - 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 = "Special Verbs" - 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]") - 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/toggleooclocal() - set category = "Server" - set desc="Toggle dat bitch" - set name="Toggle Local OOC" - toggle_looc() - log_admin("[key_name(usr)] toggled LOOC.") - message_admins("[key_name_admin(usr)] toggled LOOC.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Local 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 Dead 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/toggleaooc() - set category = "Server" - set desc="Toggle to bitch" - set name="Toggle Antag OOC" - toggle_aooc() - log_admin("[key_name(usr)] toggled Antagonist OOC.") - message_admins("[key_name_admin(usr)] toggled Antagonist OOC.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Antag OOC", "[GLOB.aooc_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) - 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 1 - else - to_chat(usr, "Error: Start Now: Game has already started.") - - return 0 - -/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.") - else - to_chat(world, "New players may now enter the game.") - 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.") - else - to_chat(world, "The AI job is chooseable now.") - 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/toggleMulticam() - set category = "Server" - set desc="Turns mutlicam on and off." - set name="Toggle Multicam" - var/almcam = CONFIG_GET(flag/allow_ai_multicam) - CONFIG_SET(flag/allow_ai_multicam, !almcam) - if (almcam) - to_chat(world, "The AI no longer has multicam.") - for(var/i in GLOB.ai_list) - var/mob/living/silicon/ai/aiPlayer = i - if(aiPlayer.multicam_on) - aiPlayer.end_multicam() - else - to_chat(world, "The AI now has multicam.") - log_admin("[key_name(usr)] toggled AI multicam.") - world.update_status() - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Multicam", "[!almcam ? "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.") - else - to_chat(world, "You may no longer respawn :(") - 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) - if(newtime < 0) - to_chat(world, "The game start has been delayed.") - log_admin("[key_name(usr)] delayed the round start.") - else - to_chat(world, "The game will start in [DisplayTimeText(newtime)].") - SEND_SOUND(world, sound(get_announcer_sound("attention"))) - 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/toggledynamicvote() - set category = "Server" - set desc="Switches between secret/extended and dynamic voting" - set name="Toggle Dynamic Vote" - var/prev_dynamic_voting = CONFIG_GET(flag/dynamic_voting) - CONFIG_SET(flag/dynamic_voting,!prev_dynamic_voting) - if (!prev_dynamic_voting) - to_chat(world, "Vote is now a ranked choice of dynamic storytellers.") - else - to_chat(world, "Vote is now between extended and secret.") - log_admin("[key_name(usr)] [prev_dynamic_voting ? "disabled" : "enabled"] dynamic voting.") - message_admins("[key_name_admin(usr)] toggled dynamic voting.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Dynamic Voting", "[prev_dynamic_voting ? "Disabled" : "Enabled"]")) //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)) - return - var/turf/T = get_turf(usr) - - var/chosen = pick_closest_path(object) - if(!chosen) - return - if(ispath(chosen, /turf)) - T.ChangeTurf(chosen) - else - var/atom/A = new chosen(T) - A.flags_1 |= ADMIN_SPAWNED_1 - - log_admin("[key_name(usr)] spawned [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/abstract/DPtarget(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/M in GLOB.mob_list) - set category = "Admin" - set desc = "Edit mobs's memory and role" - set name = "Show Traitor Panel" - - if(!istype(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(!M.mind) - to_chat(usr, "This mob has no mind!") - return - - M.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/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!") - else - to_chat(world, "The tinted_weldhelh has been disabled!") - 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.") - else - to_chat(world, "Guests may now enter the game.") - 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:") - 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:") - else if (ispAI(S)) - to_chat(usr, "pAI [key_name(S, usr)]'s laws:") - else - to_chat(usr, "SOMETHING SILICON [key_name(S, usr)]'s laws:") - - if (S.laws == null) - to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.") - else - S.laws.show_laws(usr) - if(!ai_number) - to_chat(usr, "No AIs located" ) - -/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()) - if(!devil_number) - to_chat(usr, "No Devils located" ) - -/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()) - else - to_chat(usr, "[M] is not a devil.") - -/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.
                -
                - High population limit: Current value : [GLOB.dynamic_high_pop_limit]. -
                The threshold at which "high population override" will be in effect.
                -
                - 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) - kicked_client_names.Add("[C.key]") - qdel(C) - return kicked_client_names - -//returns 1 to let the dragdrop code know we are trapping this event -//returns 0 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 0 - - if (!frommob.ckey) - return 0 - - 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 1 - - if (!frommob || !tomob) //make sure the mobs don't go away while we waited for a response - return 1 - - tomob.ghostize(0) - - 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 1 - -/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) + +/proc/relay_msg_admins(msg) + msg = "RELAY: [msg]" + to_chat(GLOB.admins, msg) + + +///////////////////////////////////////////////////////////////////////////////////////////////Panels + +/datum/admins/proc/show_player_panel(mob/M in GLOB.mob_list) + set category = "Admin" + 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.") + 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() + "\]" + + 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 - " + 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 | " + body += "Ban | " + body += "Jobban | " + body += "Identity Ban | " + var/rm = REF(M) + if(jobban_isbanned(M, "OOC")) + body+= "OOCBan | " + else + body+= "OOCBan | " + if(QDELETED(M) || QDELETED(usr)) + return + if(jobban_isbanned(M, "emote")) + body+= "EmoteBan | " + else + body+= "Emoteban | " + if(QDELETED(M) || QDELETED(usr)) + return + + 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 += "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 += usr.client.citaPPoptions(M) // CITADEL + + 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 = "Fun" + 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!") + 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( isemptylist(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( isemptylist(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(isemptylist(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(isemptylist(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( isemptylist(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( isemptylist(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=210x200") + 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/list/options = list("Regular Restart", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)") + if(world.TgsAvailable()) + options += "Server Restart (Kill and restart DD)"; + + var/rebootconfirm + 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") + rebootconfirm = TRUE + else + rebootconfirm = TRUE + if(rebootconfirm) + 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") + SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 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 = "Special Verbs" + 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]") + 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 = "Special Verbs" + 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]") + 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/toggleooclocal() + set category = "Server" + set desc="Toggle dat bitch" + set name="Toggle Local OOC" + toggle_looc() + log_admin("[key_name(usr)] toggled LOOC.") + message_admins("[key_name_admin(usr)] toggled LOOC.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Local 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 Dead 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/toggleaooc() + set category = "Server" + set desc="Toggle to bitch" + set name="Toggle Antag OOC" + toggle_aooc() + log_admin("[key_name(usr)] toggled Antagonist OOC.") + message_admins("[key_name_admin(usr)] toggled Antagonist OOC.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Antag OOC", "[GLOB.aooc_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) + 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 1 + else + to_chat(usr, "Error: Start Now: Game has already started.") + + return 0 + +/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.") + else + to_chat(world, "New players may now enter the game.") + 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.") + else + to_chat(world, "The AI job is chooseable now.") + 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/toggleMulticam() + set category = "Server" + set desc="Turns mutlicam on and off." + set name="Toggle Multicam" + var/almcam = CONFIG_GET(flag/allow_ai_multicam) + CONFIG_SET(flag/allow_ai_multicam, !almcam) + if (almcam) + to_chat(world, "The AI no longer has multicam.") + for(var/i in GLOB.ai_list) + var/mob/living/silicon/ai/aiPlayer = i + if(aiPlayer.multicam_on) + aiPlayer.end_multicam() + else + to_chat(world, "The AI now has multicam.") + log_admin("[key_name(usr)] toggled AI multicam.") + world.update_status() + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Multicam", "[!almcam ? "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.") + else + to_chat(world, "You may no longer respawn :(") + 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) + if(newtime < 0) + to_chat(world, "The game start has been delayed.") + log_admin("[key_name(usr)] delayed the round start.") + else + to_chat(world, "The game will start in [DisplayTimeText(newtime)].") + SEND_SOUND(world, sound(get_announcer_sound("attention"))) + 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/toggledynamicvote() + set category = "Server" + set desc="Switches between secret/extended and dynamic voting" + set name="Toggle Dynamic Vote" + var/prev_dynamic_voting = CONFIG_GET(flag/dynamic_voting) + CONFIG_SET(flag/dynamic_voting,!prev_dynamic_voting) + if (!prev_dynamic_voting) + to_chat(world, "Vote is now a ranked choice of dynamic storytellers.") + else + to_chat(world, "Vote is now between extended and secret.") + log_admin("[key_name(usr)] [prev_dynamic_voting ? "disabled" : "enabled"] dynamic voting.") + message_admins("[key_name_admin(usr)] toggled dynamic voting.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Dynamic Voting", "[prev_dynamic_voting ? "Disabled" : "Enabled"]")) //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)) + return + var/turf/T = get_turf(usr) + + var/chosen = pick_closest_path(object) + if(!chosen) + return + if(ispath(chosen, /turf)) + T.ChangeTurf(chosen) + else + var/atom/A = new chosen(T) + A.flags_1 |= ADMIN_SPAWNED_1 + + log_admin("[key_name(usr)] spawned [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/abstract/DPtarget(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/M in GLOB.mob_list) + set category = "Admin" + set desc = "Edit mobs's memory and role" + set name = "Show Traitor Panel" + + if(!istype(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(!M.mind) + to_chat(usr, "This mob has no mind!") + return + + M.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/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!") + else + to_chat(world, "The tinted_weldhelh has been disabled!") + 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.") + else + to_chat(world, "Guests may now enter the game.") + 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:") + 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:") + else if (ispAI(S)) + to_chat(usr, "pAI [key_name(S, usr)]'s laws:") + else + to_chat(usr, "SOMETHING SILICON [key_name(S, usr)]'s laws:") + + if (S.laws == null) + to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.") + else + S.laws.show_laws(usr) + if(!ai_number) + to_chat(usr, "No AIs located" ) + +/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()) + if(!devil_number) + to_chat(usr, "No Devils located" ) + +/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()) + else + to_chat(usr, "[M] is not a devil.") + +/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.
                +
                + High population limit: Current value : [GLOB.dynamic_high_pop_limit]. +
                The threshold at which "high population override" will be in effect.
                +
                + 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) + kicked_client_names.Add("[C.key]") + qdel(C) + return kicked_client_names + +//returns 1 to let the dragdrop code know we are trapping this event +//returns 0 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 0 + + if (!frommob.ckey) + return 0 + + 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 1 + + if (!frommob || !tomob) //make sure the mobs don't go away while we waited for a response + return 1 + + tomob.ghostize(0) + + 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 1 + +/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 ae6482abbd..55c55c42de 100644 --- a/code/modules/admin/admin_investigate.dm +++ b/code/modules/admin/admin_investigate.dm @@ -1,22 +1,22 @@ -/atom/proc/investigate_log(message, subject) - if(!message || !subject) - return - var/F = file("[GLOB.log_directory]/[subject].html") - WRITE_FILE(F, "[TIME_STAMP("hh:mm:ss", FALSE)] [REF(src)] ([x],[y],[z]) || [src] [message]
                ") - -/client/proc/investigate_show(subject in list("notes, memos, watchlist", INVESTIGATE_RCD, 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_CIRCUIT, INVESTIGATE_NANITES) ) - set name = "Investigate" - set category = "Admin" - if(!holder) - return - switch(subject) - if("notes, memos, watchlist") - if(!check_rights(R_ADMIN)) - return - browse_messages() - else - var/F = file("[GLOB.log_directory]/[subject].html") - if(!fexists(F)) - to_chat(src, "No [subject] logfile was found.") - return - src << browse(F,"window=investigate[subject];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("hh:mm:ss", FALSE)] [REF(src)] ([x],[y],[z]) || [src] [message]
                ") + +/client/proc/investigate_show(subject in list("notes, memos, watchlist", INVESTIGATE_RCD, 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_CIRCUIT, INVESTIGATE_NANITES) ) + set name = "Investigate" + set category = "Admin" + if(!holder) + return + switch(subject) + if("notes, memos, watchlist") + if(!check_rights(R_ADMIN)) + return + browse_messages() + else + var/F = file("[GLOB.log_directory]/[subject].html") + if(!fexists(F)) + to_chat(src, "No [subject] logfile was found.") + return + src << browse(F,"window=investigate[subject];size=800x300") diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index af6f3e2b2b..4f2aa35860 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -1,306 +1,306 @@ -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) - throw EXCEPTION("Admin rank created without name.") - return - 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 - -/proc/admin_keyword_to_flag(word, previous_rights=0) - var/flag = 0 - switch(ckey(word)) - if("buildmode","build") - flag = R_BUILDMODE - 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","rights") - 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","host","all") - flag = R_EVERYTHING - if("sound","sounds") - flag = R_SOUNDS - if("spawn","create") - flag = R_SPAWN - if("autologin", "autoadmin") - flag = R_AUTOLOGIN - if("dbranks") - flag = R_DBRANKS - if("@","prev") - flag = previous_rights - return flag - -// Adds/removes rights to this admin_rank -/datum/admin_rank/proc/process_keyword(word, previous_rights=0) - if(IsAdminAdvancedProcCall()) - var/msg = " has tried to elevate permissions!" - message_admins("[key_name_admin(usr)][msg]") - log_admin("[key_name(usr)][msg]") - return - var/flag = admin_keyword_to_flag(word, previous_rights) - if(flag) - switch(text2ascii(word,1)) - if(43) - rights |= flag //+ - include_rights |= flag - if(45) - rights &= ~flag //- - exclude_rights |= flag - if(42) - can_edit_rights |= flag //* - -// Checks for (keyword-formatted) rights on this admin -/datum/admins/proc/check_keyword(word) - var/flag = admin_keyword_to_flag(word) - if(flag) - return ((rank.rights & flag) == flag) //true only if right has everything in flag - -/proc/sync_ranks_with_db() - set waitfor = FALSE - - if(IsAdminAdvancedProcCall()) - to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.") - return - - var/list/sql_ranks = list() - for(var/datum/admin_rank/R in GLOB.protected_ranks) - var/sql_rank = sanitizeSQL(R.name) - var/sql_flags = sanitizeSQL(R.include_rights) - var/sql_exclude_flags = sanitizeSQL(R.exclude_rights) - var/sql_can_edit_flags = sanitizeSQL(R.can_edit_rights) - sql_ranks += list(list("rank" = "'[sql_rank]'", "flags" = "[sql_flags]", "exclude_flags" = "[sql_exclude_flags]", "can_edit_flags" = "[sql_can_edit_flags]")) - 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.") - return - GLOB.admin_ranks.Cut() - GLOB.protected_ranks.Cut() - var/previous_rights = 0 - //load text from file and process each line separately - for(var/line in world.file2list("[global.config.directory]/admin_ranks.txt")) - if(!line || findtextEx(line,"#",1,2)) - continue - var/next = findtext(line, "=") - var/datum/admin_rank/R = new(ckeyEx(copytext(line, 1, next))) - if(!R) - continue - GLOB.admin_ranks += R - GLOB.protected_ranks += R - var/prev = findchar(line, "+-*", next, 0) - while(prev) - next = findchar(line, "+-*", prev + 1, 0) - R.process_keyword(copytext(line, prev, next), previous_rights) - prev = next - previous_rights = R.rights - 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/DBQuery/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 = ckeyEx(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/list/lines = world.file2list("[global.config.directory]/admins.txt") - for(var/line in lines) - if(!length(line) || findtextEx(line, "#", 1, 2)) - continue - var/list/entry = splittext(line, "=") - if(entry.len < 2) - continue - var/ckey = ckey(entry[1]) - var/rank = ckeyEx(entry[2]) - if(!ckey || !rank) - continue - new /datum/admins(rank_names[rank], ckey, 0, 1) - if(!CONFIG_GET(flag/admin_legacy_system) || dbfail) - var/datum/DBQuery/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 = ckeyEx(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[ckeyEx(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) + throw EXCEPTION("Admin rank created without name.") + return + 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 + +/proc/admin_keyword_to_flag(word, previous_rights=0) + var/flag = 0 + switch(ckey(word)) + if("buildmode","build") + flag = R_BUILDMODE + 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","rights") + 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","host","all") + flag = R_EVERYTHING + if("sound","sounds") + flag = R_SOUNDS + if("spawn","create") + flag = R_SPAWN + if("autologin", "autoadmin") + flag = R_AUTOLOGIN + if("dbranks") + flag = R_DBRANKS + if("@","prev") + flag = previous_rights + return flag + +// Adds/removes rights to this admin_rank +/datum/admin_rank/proc/process_keyword(word, previous_rights=0) + if(IsAdminAdvancedProcCall()) + var/msg = " has tried to elevate permissions!" + message_admins("[key_name_admin(usr)][msg]") + log_admin("[key_name(usr)][msg]") + return + var/flag = admin_keyword_to_flag(word, previous_rights) + if(flag) + switch(text2ascii(word,1)) + if(43) + rights |= flag //+ + include_rights |= flag + if(45) + rights &= ~flag //- + exclude_rights |= flag + if(42) + can_edit_rights |= flag //* + +// Checks for (keyword-formatted) rights on this admin +/datum/admins/proc/check_keyword(word) + var/flag = admin_keyword_to_flag(word) + if(flag) + return ((rank.rights & flag) == flag) //true only if right has everything in flag + +/proc/sync_ranks_with_db() + set waitfor = FALSE + + if(IsAdminAdvancedProcCall()) + to_chat(usr, "Admin rank DB Sync blocked: Advanced ProcCall detected.") + return + + var/list/sql_ranks = list() + for(var/datum/admin_rank/R in GLOB.protected_ranks) + var/sql_rank = sanitizeSQL(R.name) + var/sql_flags = sanitizeSQL(R.include_rights) + var/sql_exclude_flags = sanitizeSQL(R.exclude_rights) + var/sql_can_edit_flags = sanitizeSQL(R.can_edit_rights) + sql_ranks += list(list("rank" = "'[sql_rank]'", "flags" = "[sql_flags]", "exclude_flags" = "[sql_exclude_flags]", "can_edit_flags" = "[sql_can_edit_flags]")) + 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.") + return + GLOB.admin_ranks.Cut() + GLOB.protected_ranks.Cut() + var/previous_rights = 0 + //load text from file and process each line separately + for(var/line in world.file2list("[global.config.directory]/admin_ranks.txt")) + if(!line || findtextEx(line,"#",1,2)) + continue + var/next = findtext(line, "=") + var/datum/admin_rank/R = new(ckeyEx(copytext(line, 1, next))) + if(!R) + continue + GLOB.admin_ranks += R + GLOB.protected_ranks += R + var/prev = findchar(line, "+-*", next, 0) + while(prev) + next = findchar(line, "+-*", prev + 1, 0) + R.process_keyword(copytext(line, prev, next), previous_rights) + prev = next + previous_rights = R.rights + 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/DBQuery/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 = ckeyEx(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/list/lines = world.file2list("[global.config.directory]/admins.txt") + for(var/line in lines) + if(!length(line) || findtextEx(line, "#", 1, 2)) + continue + var/list/entry = splittext(line, "=") + if(entry.len < 2) + continue + var/ckey = ckey(entry[1]) + var/rank = ckeyEx(entry[2]) + if(!ckey || !rank) + continue + new /datum/admins(rank_names[rank], ckey, 0, 1) + if(!CONFIG_GET(flag/admin_legacy_system) || dbfail) + var/datum/DBQuery/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 = ckeyEx(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[ckeyEx(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/banjob.dm b/code/modules/admin/banjob.dm index 20a7228fb5..cae1c6b22e 100644 --- a/code/modules/admin/banjob.dm +++ b/code/modules/admin/banjob.dm @@ -1,40 +1,40 @@ -//returns a reason if M is banned from rank, returns FALSE otherwise -/proc/jobban_isbanned(mob/M, rank) - if(!M || !istype(M) || !M.ckey) - return FALSE - - if(!M.client) //no cache. fallback to a datum/DBQuery - var/datum/DBQuery/query_jobban_check_ban = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(M.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = '[sanitizeSQL(rank)]'") - if(!query_jobban_check_ban.warn_execute()) - qdel(query_jobban_check_ban) - return - if(query_jobban_check_ban.NextRow()) - var/reason = query_jobban_check_ban.item[1] - qdel(query_jobban_check_ban) - return reason ? reason : TRUE //we don't want to return "" if there is no ban reason, as that would evaluate to false - qdel(query_jobban_check_ban) - return FALSE - - if(!M.client.jobbancache) - jobban_buildcache(M.client) - - if(rank in M.client.jobbancache) - var/reason = M.client.jobbancache[rank] - return (reason) ? reason : TRUE //see above for why we need to do this - return FALSE - -/proc/jobban_buildcache(client/C) - if(!SSdbcore.Connect()) - return - if(C && istype(C)) - C.jobbancache = list() - var/datum/DBQuery/query_jobban_build_cache = SSdbcore.NewQuery("SELECT job, reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(C.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") - if(!query_jobban_build_cache.warn_execute()) - qdel(query_jobban_build_cache) - return - while(query_jobban_build_cache.NextRow()) - C.jobbancache[query_jobban_build_cache.item[1]] = query_jobban_build_cache.item[2] - qdel(query_jobban_build_cache) - -/proc/ban_unban_log_save(var/formatted_log) - text2file(formatted_log,"data/ban_unban_log.txt") +//returns a reason if M is banned from rank, returns FALSE otherwise +/proc/jobban_isbanned(mob/M, rank) + if(!M || !istype(M) || !M.ckey) + return FALSE + + if(!M.client) //no cache. fallback to a datum/DBQuery + var/datum/DBQuery/query_jobban_check_ban = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(M.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = '[sanitizeSQL(rank)]'") + if(!query_jobban_check_ban.warn_execute()) + qdel(query_jobban_check_ban) + return + if(query_jobban_check_ban.NextRow()) + var/reason = query_jobban_check_ban.item[1] + qdel(query_jobban_check_ban) + return reason ? reason : TRUE //we don't want to return "" if there is no ban reason, as that would evaluate to false + qdel(query_jobban_check_ban) + return FALSE + + if(!M.client.jobbancache) + jobban_buildcache(M.client) + + if(rank in M.client.jobbancache) + var/reason = M.client.jobbancache[rank] + return (reason) ? reason : TRUE //see above for why we need to do this + return FALSE + +/proc/jobban_buildcache(client/C) + if(!SSdbcore.Connect()) + return + if(C && istype(C)) + C.jobbancache = list() + var/datum/DBQuery/query_jobban_build_cache = SSdbcore.NewQuery("SELECT job, reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(C.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)") + if(!query_jobban_build_cache.warn_execute()) + qdel(query_jobban_build_cache) + return + while(query_jobban_build_cache.NextRow()) + C.jobbancache[query_jobban_build_cache.item[1]] = query_jobban_build_cache.item[2] + qdel(query_jobban_build_cache) + +/proc/ban_unban_log_save(var/formatted_log) + text2file(formatted_log,"data/ban_unban_log.txt") diff --git a/code/modules/admin/chat_commands.dm b/code/modules/admin/chat_commands.dm index 8b824f8fb0..52ba3c521e 100644 --- a/code/modules/admin/chat_commands.dm +++ b/code/modules/admin/chat_commands.dm @@ -1,143 +1,143 @@ -#define IRC_STATUS_THROTTLE 5 - -/datum/tgs_chat_command/ircstatus - name = "status" - help_text = "Gets the admincount, playercount, gamemode, and true game mode of the server" - admin_only = TRUE - var/last_irc_status = 0 - -/datum/tgs_chat_command/ircstatus/Run(datum/tgs_chat_user/sender, params) - var/rtod = REALTIMEOFDAY - if(rtod - last_irc_status < IRC_STATUS_THROTTLE) - return - last_irc_status = rtod - 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/irccheck - name = "check" - help_text = "Gets the playercount, gamemode, and address of the server" - var/last_irc_check = 0 - -/datum/tgs_chat_command/irccheck/Run(datum/tgs_chat_user/sender, params) - var/rtod = REALTIMEOFDAY - if(rtod - last_irc_check < IRC_STATUS_THROTTLE) - return - last_irc_check = rtod - var/server = CONFIG_GET(string/server) - return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]" - //CIT CHANGE obfuscates the gamemode for TGS bot commands on discord by removing Mode:[GLOB.master_mode] -/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 = IrcPm(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 ircadminwho() - -GLOBAL_LIST(round_end_notifiees) - -/datum/tgs_chat_command/notify - name = "notify" - help_text = "Pings the invoker when the round ends" - admin_only = FALSE - -/datum/tgs_chat_command/notify/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 - if(refs) - var/list/L = list() - for(var/ref in refs) - var/atom/A = locate(ref) - if(A) - L += "[A]" - else - L += "[ref]" - refs = L - . = "[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() - -/datum/tgs_chat_command/addbunkerbypass - name = "whitelist" - help_text = "whitelist " - admin_only = TRUE - -/datum/tgs_chat_command/addbunkerbypass/Run(datum/tgs_chat_user/sender, params) - if(!CONFIG_GET(flag/sql_enabled)) - return "The Database is not enabled!" - - GLOB.bunker_passthrough |= ckey(params) - - log_admin("[sender.friendly_name] has added [params] to the current round's bunker bypass list.") - message_admins("[sender.friendly_name] has added [params] to the current round's bunker bypass list.") - return "[params] has been added to the current round's bunker bypass list." +#define IRC_STATUS_THROTTLE 5 + +/datum/tgs_chat_command/ircstatus + name = "status" + help_text = "Gets the admincount, playercount, gamemode, and true game mode of the server" + admin_only = TRUE + var/last_irc_status = 0 + +/datum/tgs_chat_command/ircstatus/Run(datum/tgs_chat_user/sender, params) + var/rtod = REALTIMEOFDAY + if(rtod - last_irc_status < IRC_STATUS_THROTTLE) + return + last_irc_status = rtod + 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/irccheck + name = "check" + help_text = "Gets the playercount, gamemode, and address of the server" + var/last_irc_check = 0 + +/datum/tgs_chat_command/irccheck/Run(datum/tgs_chat_user/sender, params) + var/rtod = REALTIMEOFDAY + if(rtod - last_irc_check < IRC_STATUS_THROTTLE) + return + last_irc_check = rtod + var/server = CONFIG_GET(string/server) + return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]" + //CIT CHANGE obfuscates the gamemode for TGS bot commands on discord by removing Mode:[GLOB.master_mode] +/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 = IrcPm(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 ircadminwho() + +GLOBAL_LIST(round_end_notifiees) + +/datum/tgs_chat_command/notify + name = "notify" + help_text = "Pings the invoker when the round ends" + admin_only = FALSE + +/datum/tgs_chat_command/notify/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 + if(refs) + var/list/L = list() + for(var/ref in refs) + var/atom/A = locate(ref) + if(A) + L += "[A]" + else + L += "[ref]" + refs = L + . = "[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() + +/datum/tgs_chat_command/addbunkerbypass + name = "whitelist" + help_text = "whitelist " + admin_only = TRUE + +/datum/tgs_chat_command/addbunkerbypass/Run(datum/tgs_chat_user/sender, params) + if(!CONFIG_GET(flag/sql_enabled)) + return "The Database is not enabled!" + + GLOB.bunker_passthrough |= ckey(params) + + log_admin("[sender.friendly_name] has added [params] to the current round's bunker bypass list.") + message_admins("[sender.friendly_name] has added [params] to the current round's bunker bypass list.") + return "[params] has been added to the current round's bunker bypass list." diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm index eae59fcb93..9784aa7cd1 100644 --- a/code/modules/admin/create_mob.dm +++ b/code/modules/admin/create_mob.dm @@ -1,46 +1,46 @@ - -/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.real_name = random_unique_name(H.gender) - H.name = H.real_name - H.underwear = random_underwear(H.gender) - H.undie_color = random_short_color() - H.undershirt = random_undershirt(H.gender) - H.shirt_color = random_short_color() - H.skin_tone = random_skin_tone() - H.hair_style = random_hair_style(H.gender) - H.facial_hair_style = random_facial_hair_style(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() - H.saved_underwear = H.underwear - H.saved_undershirt = H.undershirt - H.saved_socks = H.socks - - // Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant. - H.dna.features["mcolor"] = random_short_color() - 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["insect_wings"] = pick(GLOB.insect_wings_list) - H.dna.features["deco_wings"] = pick(GLOB.deco_wings_list) - H.dna.features["insect_fluff"] = pick(GLOB.insect_fluffs_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.real_name = random_unique_name(H.gender) + H.name = H.real_name + H.underwear = random_underwear(H.gender) + H.undie_color = random_short_color() + H.undershirt = random_undershirt(H.gender) + H.shirt_color = random_short_color() + H.skin_tone = random_skin_tone() + H.hair_style = random_hair_style(H.gender) + H.facial_hair_style = random_facial_hair_style(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() + H.saved_underwear = H.underwear + H.saved_undershirt = H.undershirt + H.saved_socks = H.socks + + // Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant. + H.dna.features["mcolor"] = random_short_color() + 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["insect_wings"] = pick(GLOB.insect_wings_list) + H.dna.features["deco_wings"] = pick(GLOB.deco_wings_list) + H.dna.features["insect_fluff"] = pick(GLOB.insect_fluffs_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 ff49944dc4..a0d7e41485 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 create_object_forms - 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 create_object_forms + 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 4742cac442..86e83f38ae 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 11fb4e7a8b..43360ad714 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -1,210 +1,210 @@ -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) - throw EXCEPTION("Admin datum created without a ckey") - return - if(!istype(R)) - QDEL_IN(src, 0) - throw EXCEPTION("Admin datum created without a rank") - return - 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_AUTOLOGIN)) - 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!") - -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," ")].") - 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) + throw EXCEPTION("Admin datum created without a ckey") + return + if(!istype(R)) + QDEL_IN(src, 0) + throw EXCEPTION("Admin datum created without a rank") + return + 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_AUTOLOGIN)) + 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!") + +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," ")].") + 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 03bd08f750..f061b9d5cd 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -1,313 +1,313 @@ -/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) - - //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) + + //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/topic.dm b/code/modules/admin/topic.dm index 5b751ffe30..88d4105124 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1,2848 +1,2848 @@ -/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 - - citaTopic(href, href_list) //CITADEL EDIT, MENTORS - - 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!") - - 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.") - 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.") - 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!") - 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. Unfortunatly there were not enough candidates available.") - log_admin("[key_name(usr)] failed to create an abductor team.") - if("clockcult") - if(src.makeClockCult()) - message_admins("[key_name(usr)] started a clockwork cult.") - log_admin("[key_name(usr)] started a clockwork cult.") - else - message_admins("[key_name_admin(usr)] tried to start a clockwork cult. Unfortunately, there were no candidates available.") - log_admin("[key_name(usr)] failed to start a clockwork cult.") - 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("Cancel") - event.kill() - return - if("No") - event.announceWhen = -1 - 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["dbsearchckey"] || href_list["dbsearchadmin"] || href_list["dbsearchip"] || href_list["dbsearchcid"]) - var/adminckey = href_list["dbsearchadmin"] - var/playerckey = href_list["dbsearchckey"] - var/ip = href_list["dbsearchip"] - var/cid = href_list["dbsearchcid"] - var/page = href_list["dbsearchpage"] - - DB_ban_panel(playerckey, adminckey, ip, cid, page) - return - - else if(href_list["dbbanedit"]) - var/banedit = href_list["dbbanedit"] - var/banid = text2num(href_list["dbbanid"]) - if(!banedit || !banid) - return - - DB_ban_edit(banid, banedit) - return - - else if(href_list["dbbanaddtype"]) - if(!check_rights(R_BAN)) - return - var/bantype = text2num(href_list["dbbanaddtype"]) - var/bankey = href_list["dbbanaddkey"] - var/banckey = ckey(bankey) - var/banip = href_list["dbbanaddip"] - var/bancid = href_list["dbbanaddcid"] - var/banduration = text2num(href_list["dbbaddduration"]) - var/banjob = href_list["dbbanaddjob"] - var/banreason = href_list["dbbanreason"] - var/banseverity = href_list["dbbanaddseverity"] - - switch(bantype) - if(BANTYPE_PERMA) - if(!banckey || !banreason || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, severity, and reason).") - return - banduration = null - banjob = null - if(BANTYPE_TEMP) - if(!banckey || !banreason || !banduration || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, reason, severity and duration).") - return - banjob = null - if(BANTYPE_JOB_PERMA) - if(!banckey || !banreason || !banjob || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, severity, reason and job).") - return - banduration = null - if(BANTYPE_JOB_TEMP) - if(!banckey || !banreason || !banjob || !banduration || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, severity, reason and job).") - return - if(BANTYPE_ADMIN_PERMA) - if(!banckey || !banreason || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, severity and reason).") - return - banduration = null - banjob = null - if(BANTYPE_ADMIN_TEMP) - if(!banckey || !banreason || !banduration || !banseverity) - to_chat(usr, "Not enough parameters (Requires ckey, severity, reason and duration).") - return - banjob = null - - var/mob/playermob - - for(var/mob/M in GLOB.player_list) - if(M.ckey == banckey) - playermob = M - break - - - banreason = "(MANUAL BAN) "+banreason - - if(!playermob) - if(banip) - banreason = "[banreason] (CUSTOM IP)" - if(bancid) - banreason = "[banreason] (CUSTOM CID)" - else - message_admins("Ban process: A mob matching [playermob.key] was found at location [playermob.x], [playermob.y], [playermob.z]. Custom ip and computer id fields replaced with the ip and computer id from the located mob.") - - if(!DB_ban_record(bantype, playermob, banduration, banreason, banjob, bankey, banip, bancid )) - to_chat(usr, "Failed to apply ban.") - return - create_message("note", bankey, null, banreason, null, null, 0, 0, null, 0, banseverity) - - 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.") - - - href_list["secrets"] = "check_antagonist" - - 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.") - href_list["secrets"] = "check_antagonist" - 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 - 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 - 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]") - href_list["secrets"] = "check_antagonist" - 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.") - return - - var/delmob = 0 - switch(alert("Delete old mob?","Message","Yes","No","Cancel")) - if("Cancel") - return - if("Yes") - delmob = 1 - - 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() - 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("constructarmored") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/armored , null, null, delmob ) - if("constructbuilder") - M.change_mob_type( /mob/living/simple_animal/hostile/construct/builder , 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 ) - - - /////////////////////////////////////new ban stuff - else if(href_list["unbanf"]) - if(!check_rights(R_BAN)) - return - - var/banfolder = href_list["unbanf"] - GLOB.Banlist.cd = "/base/[banfolder]" - var/key = GLOB.Banlist["key"] - if(alert(usr, "Are you sure you want to unban [key]?", "Confirmation", "Yes", "No") == "Yes") - if(RemoveBan(banfolder)) - unbanpanel() - else - alert(usr, "This ban has already been lifted / does not exist.", "Error", "Ok") - unbanpanel() - - else if(href_list["unbane"]) - if(!check_rights(R_BAN)) - return - - UpdateTime() - var/reason - - var/banfolder = href_list["unbane"] - GLOB.Banlist.cd = "/base/[banfolder]" - var/reason2 = GLOB.Banlist["reason"] - var/temp = GLOB.Banlist["temp"] - - var/minutes = GLOB.Banlist["minutes"] - - var/banned_key = GLOB.Banlist["key"] - GLOB.Banlist.cd = "/base" - - var/duration - - switch(alert("Temporary Ban for [banned_key]?",,"Yes","No")) - if("Yes") - temp = 1 - var/mins = 0 - if(minutes > GLOB.CMinutes) - mins = minutes - GLOB.CMinutes - mins = input(usr,"How long (in minutes)? (Default: 1440)","Ban time",mins ? mins : 1440) as num|null - if(mins <= 0) - to_chat(usr, "[mins] is not a valid duration.") - return - minutes = GLOB.CMinutes + mins - duration = GetExp(minutes) - reason = input(usr,"Please State Reason For Banning [banned_key].","Reason",reason2) as message|null - if(!reason) - return - if("No") - temp = 0 - duration = "Perma" - reason = input(usr,"Please State Reason For Banning [banned_key].","Reason",reason2) as message|null - if(!reason) - return - - log_admin_private("[key_name(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]") - ban_unban_log_save("[key_name(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]") - message_admins("[key_name_admin(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]") - GLOB.Banlist.cd = "/base/[banfolder]" - WRITE_FILE(GLOB.Banlist["reason"], reason) - WRITE_FILE(GLOB.Banlist["temp"], temp) - WRITE_FILE(GLOB.Banlist["minutes"], minutes) - WRITE_FILE(GLOB.Banlist["bannedby"], usr.ckey) - GLOB.Banlist.cd = "/base" - unbanpanel() - - /////////////////////////////////////new ban stuff - - else if(href_list["appearanceban"]) - if(!check_rights(R_BAN)) - return - var/mob/M = locate(href_list["appearanceban"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(!M.ckey) //sanity - to_chat(usr, "This mob has no ckey") - return - - - if(jobban_isbanned(M, "appearance")) - switch(alert("Remove appearance ban?","Please Confirm","Yes","No")) - if("Yes") - ban_unban_log_save("[key_name(usr)] removed [key_name(M)]'s appearance ban.") - log_admin_private("[key_name(usr)] removed [key_name(M)]'s appearance ban.") - DB_ban_unban(M.ckey, BANTYPE_ANY_JOB, "appearance") - if(M.client) - jobban_buildcache(M.client) - message_admins("[key_name_admin(usr)] removed [key_name_admin(M)]'s appearance ban.") - to_chat(M, "[usr.client.key] has removed your appearance ban.") - - else switch(alert("Appearance ban [M.key]?",,"Yes","No", "Cancel")) - if("Yes") - var/reason = input(usr,"Please State Reason.","Reason") as message|null - if(!reason) - return - var/severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") - if(!severity) - return - if(!DB_ban_record(BANTYPE_JOB_PERMA, M, -1, reason, "appearance")) - to_chat(usr, "Failed to apply ban.") - return - if(M.client) - jobban_buildcache(M.client) - ban_unban_log_save("[key_name(usr)] appearance banned [key_name(M)]. reason: [reason]") - log_admin_private("[key_name(usr)] appearance banned [key_name(M)]. \nReason: [reason]") - create_message("note", M.key, null, "Appearance banned - [reason]", null, null, 0, 0, null, 0, severity) - message_admins("[key_name_admin(usr)] appearance banned [key_name_admin(M)].") - to_chat(M, "You have been appearance banned by [usr.client.key].") - to_chat(M, "The reason is: [reason]") - to_chat(M, "Appearance ban can be lifted only upon request.") - var/bran = CONFIG_GET(string/banappeals) - if(bran) - to_chat(M, "To try to resolve this matter head to [bran]") - else - to_chat(M, "No ban appeals URL has been set.") - if("No") - return - - else if(href_list["jobban2"]) - if(!check_rights(R_BAN)) - return - var/mob/M = locate(href_list["jobban2"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob.") - return - - if(!M.ckey) //sanity - to_chat(usr, "This mob has no ckey.") - return - - var/dat = "Job-Ban Panel: [key_name(M)]" - - /***********************************WARNING!************************************ - The jobban stuff looks mangled and disgusting - But it looks beautiful in-game - -Nodrak - ************************************WARNING!***********************************/ - var/counter = 0 -//Regular jobs - //Command (Blue) - dat += "" - dat += "" - for(var/jobPos in GLOB.command_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 6) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
                Command Positions
                [jobPos][jobPos]
                " - - //Security (Red) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.security_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
                Security Positions
                [jobPos][jobPos]
                " - - //Engineering (Yellow) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.engineering_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
                Engineering Positions
                [jobPos][jobPos]
                " - - //Medical (White) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.medical_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
                Medical Positions
                [jobPos][jobPos]
                " - - //Science (Purple) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.science_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
                Science Positions
                [jobPos][jobPos]
                " - - //Supply (Brown) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.supply_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get COPYPASTE! - dat += "" - counter = 0 - dat += "
                Supply Positions
                [jobPos][jobPos]
                " - - //Civilian (Grey) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.civilian_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - dat += "
                Civilian Positions
                [jobPos][jobPos]
                " - - //Non-Human (Green) - counter = 0 - dat += "" - dat += "" - for(var/jobPos in GLOB.nonhuman_positions) - if(!jobPos) - continue - if(jobban_isbanned(M, jobPos)) - dat += "" - counter++ - else - dat += "" - counter++ - - if(counter >= 5) //So things dont get squiiiiished! - dat += "" - counter = 0 - - dat += "
                Non-human Positions
                [jobPos][jobPos]
                " - - //Ghost Roles (light light gray) - dat += "" - dat += "" - - //pAI - if(jobban_isbanned(M, ROLE_PAI)) - dat += "" - else - dat += "" - - - //Drones - if(jobban_isbanned(M, ROLE_DRONE)) - dat += "" - else - dat += "" - - - //Positronic Brains - if(jobban_isbanned(M, ROLE_POSIBRAIN)) - dat += "" - else - dat += "" - - //Sentience Potion Spawn - if(jobban_isbanned(M, ROLE_SENTIENCE)) - dat += "" - else - dat += "" - - //Deathsquad - if(jobban_isbanned(M, ROLE_DEATHSQUAD)) - dat += "" - else - dat += "" - - //Lavaland roles - if(jobban_isbanned(M, ROLE_LAVALAND)) - dat += "" - else - dat += "" - - dat += "
                Ghost Roles
                pAIpAIDroneDronePosibrainPosibrainSentience Potion SpawnSentience Potion SpawnDeathsquadDeathsquadLavalandLavaland
                " - - //Antagonist (Orange) - var/isbanned_dept = jobban_isbanned(M, ROLE_SYNDICATE) - dat += "" - dat += "" - - //Traitor - if(jobban_isbanned(M, ROLE_TRAITOR) || isbanned_dept) - dat += "" - else - dat += "" - - //Changeling - if(jobban_isbanned(M, ROLE_CHANGELING) || isbanned_dept) - dat += "" - else - dat += "" - - //Nuke Operative - if(jobban_isbanned(M, ROLE_OPERATIVE) || isbanned_dept) - dat += "" - else - dat += "" - - //Revolutionary - if(jobban_isbanned(M, ROLE_REV) || isbanned_dept) - dat += "" - else - dat += "" - - //Cultist - if(jobban_isbanned(M, ROLE_CULTIST) || isbanned_dept) - dat += "" - else - dat += "" - - dat += "" //So things dont get squished. - - //Servant of Ratvar - if(jobban_isbanned(M, ROLE_SERVANT_OF_RATVAR) || isbanned_dept) - dat += "" - else - dat += "" - - //Wizard - if(jobban_isbanned(M, ROLE_WIZARD) || isbanned_dept) - dat += "" - else - dat += "" - - //Abductor - if(jobban_isbanned(M, ROLE_ABDUCTOR) || isbanned_dept) - dat += "" - else - dat += "" - - //Alien - if(jobban_isbanned(M, ROLE_ALIEN) || isbanned_dept) - dat += "" - else - dat += "" - - //Gang - if(jobban_isbanned(M, ROLE_GANG) || isbanned_dept) - dat += "" - else - dat += "" - - - //Other Roles (black) - dat += "
                Antagonist Positions | " - dat += "Team Antagonists | " - dat += "Conversion Antagonists
                TraitorTraitorChangelingChangelingNuke OperativeNuke OperativeRevolutionaryRevolutionaryCultistCultist
                ServantServantWizardWizardAbductorAbductorAlienAlienGangGang
                " - dat += "" - - //Mind Transfer Potion - if(jobban_isbanned(M, ROLE_MIND_TRANSFER)) - dat += "" - else - dat += "" - - dat += "
                Other Roles
                Mind Transfer PotionMind Transfer Potion
                " - usr << browse(dat, "window=jobban2;size=800x450") - return - - //JOBBAN'S INNARDS - else if(href_list["jobban3"]) - if(!check_rights(R_BAN)) - return - var/mob/M = locate(href_list["jobban4"]) - if(!ismob(M)) - to_chat(usr, "This can only be used on instances of type /mob") - return - if(!SSjob) - to_chat(usr, "Jobs subsystem not initialized yet!") - return - //get jobs for department if specified, otherwise just return the one job in a list. - var/list/joblist = list() - switch(href_list["jobban3"]) - if("commanddept") - for(var/jobPos in GLOB.command_positions) - if(!jobPos) - continue - joblist += jobPos - if("securitydept") - for(var/jobPos in GLOB.security_positions) - if(!jobPos) - continue - joblist += jobPos - if("engineeringdept") - for(var/jobPos in GLOB.engineering_positions) - if(!jobPos) - continue - joblist += jobPos - if("medicaldept") - for(var/jobPos in GLOB.medical_positions) - if(!jobPos) - continue - joblist += jobPos - if("sciencedept") - for(var/jobPos in GLOB.science_positions) - if(!jobPos) - continue - joblist += jobPos - if("supplydept") - for(var/jobPos in GLOB.supply_positions) - if(!jobPos) - continue - joblist += jobPos - if("civiliandept") - for(var/jobPos in GLOB.civilian_positions) - if(!jobPos) - continue - joblist += jobPos - if("nonhumandept") - for(var/jobPos in GLOB.nonhuman_positions) - if(!jobPos) - continue - joblist += jobPos - if("ghostroles") - joblist += list(ROLE_PAI, ROLE_POSIBRAIN, ROLE_DRONE , ROLE_DEATHSQUAD, ROLE_LAVALAND, ROLE_SENTIENCE) - if("teamantags") - joblist += list(ROLE_OPERATIVE, ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_GANG) - if("convertantags") - joblist += list(ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ALIEN) - if("otherroles") - joblist += list(ROLE_MIND_TRANSFER) - else - joblist += href_list["jobban3"] - - //Create a list of unbanned jobs within joblist - var/list/notbannedlist = list() - for(var/job in joblist) - if(!jobban_isbanned(M, job)) - notbannedlist += job - - //Banning comes first - if(notbannedlist.len) //at least 1 unbanned job exists in joblist so we have stuff to ban. - var/severity = null - switch(alert("Temporary Ban for [M.key]?",,"Yes","No", "Cancel")) - if("Yes") - var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null - if(mins <= 0) - to_chat(usr, "[mins] is not a valid duration.") - return - var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null - if(!reason) - return - severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") - if(!severity) - return - var/msg - for(var/job in notbannedlist) - if(!DB_ban_record(BANTYPE_JOB_TEMP, M, mins, reason, job)) - to_chat(usr, "Failed to apply ban.") - return - if(M.client) - jobban_buildcache(M.client) - ban_unban_log_save("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes. reason: [reason]") - log_admin_private("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes.") - if(!msg) - msg = job - else - msg += ", [job]" - create_message("note", M.key, null, "Banned from [msg] - [reason]", null, null, 0, 0, null, 0, severity) - message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg] for [mins] minutes.") - to_chat(M, "You have been [(msg == ("ooc" || "appearance")) ? "banned" : "jobbanned"] by [usr.client.key] from: [msg].") - to_chat(M, "The reason is: [reason]") - to_chat(M, "This jobban will be lifted in [mins] minutes.") - href_list["jobban2"] = 1 // lets it fall through and refresh - return 1 - if("No") - var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null - severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") - if(!severity) - return - if(reason) - var/msg - for(var/job in notbannedlist) - if(!DB_ban_record(BANTYPE_JOB_PERMA, M, -1, reason, job)) - to_chat(usr, "Failed to apply ban.") - return - if(M.client) - jobban_buildcache(M.client) - ban_unban_log_save("[key_name(usr)] perma-jobbanned [key_name(M)] from [job]. reason: [reason]") - log_admin_private("[key_name(usr)] perma-banned [key_name(M)] from [job]") - if(!msg) - msg = job - else - msg += ", [job]" - create_message("note", M.key, null, "Banned from [msg] - [reason]", null, null, 0, 0, null, 0, severity) - message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg].") - to_chat(M, "You have been [(msg == ("ooc" || "appearance")) ? "banned" : "jobbanned"] by [usr.client.key] from: [msg].") - to_chat(M, "The reason is: [reason]") - to_chat(M, "Jobban can be lifted only upon request.") - href_list["jobban2"] = 1 // lets it fall through and refresh - return 1 - if("Cancel") - return - - //Unbanning joblist - //all jobs in joblist are banned already OR we didn't give a reason (implying they shouldn't be banned) - if(joblist.len) //at least 1 banned job exists in joblist so we have stuff to unban. - var/msg - for(var/job in joblist) - var/reason = jobban_isbanned(M, job) - if(!reason) - continue //skip if it isn't jobbanned anyway - switch(alert("Job: '[job]' Reason: '[reason]' Un-jobban?","Please Confirm","Yes","No")) - if("Yes") - ban_unban_log_save("[key_name(usr)] unjobbanned [key_name(M)] from [job]") - log_admin_private("[key_name(usr)] unbanned [key_name(M)] from [job]") - DB_ban_unban(M.ckey, BANTYPE_ANY_JOB, job) - if(M.client) - jobban_buildcache(M.client) - if(!msg) - msg = job - else - msg += ", [job]" - else - continue - if(msg) - message_admins("[key_name_admin(usr)] unbanned [key_name_admin(M)] from [msg].") - to_chat(M, "You have been un-jobbanned by [usr.client.key] from [msg].") - href_list["jobban2"] = 1 // lets it fall through and refresh - return 1 - return 0 //we didn't do anything! - - 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.") - return - if(alert(usr, "Kick [key_name(M)]?", "Confirm", "Yes", "No") != "Yes") - return - if(!M) - to_chat(usr, "Error: [M] no longer exists!") - return - if(!M.client) - to_chat(usr, "Error: [M] no longer has a client!") - return - to_chat(M, "You have been kicked from the server by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].") - 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/message_id = sanitizeSQL("[href_list["messageedits"]]") - var/datum/DBQuery/query_get_message_edits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("messages")] WHERE id = '[message_id]'") - 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["newban"]) - if(!check_rights(R_BAN)) - return - - var/mob/M = locate(href_list["newban"]) - if(!ismob(M)) - return - - if(M.client && M.client.holder) - return //admins cannot be banned. Even if they could, the ban doesn't affect them anyway - - switch(alert("Temporary Ban for [M.key]?",,"Yes","No", "Cancel")) - if("Yes") - var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null - if(mins <= 0) - to_chat(usr, "[mins] is not a valid duration.") - return - var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null - if(!reason) - return - if(!DB_ban_record(BANTYPE_TEMP, M, mins, reason)) - to_chat(usr, "Failed to apply ban.") - return - AddBan(M.ckey, M.computer_id, reason, usr.ckey, 1, mins) - ban_unban_log_save("[key_name(usr)] has banned [key_name(M)]. - Reason: [reason] - This will be removed in [mins] minutes.") - to_chat(M, "You have been banned by [usr.client.key].\nReason: [reason]") - to_chat(M, "This is a temporary ban, it will be removed in [mins] minutes. The round ID is [GLOB.round_id].") - var/bran = CONFIG_GET(string/banappeals) - if(bran) - to_chat(M, "To try to resolve this matter head to [bran]") - else - to_chat(M, "No ban appeals URL has been set.") - log_admin_private("[key_name(usr)] has banned [key_name(M)].\nReason: [key_name(M)]\nThis will be removed in [mins] minutes.") - var/msg = "[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis will be removed in [mins] minutes." - message_admins(msg) - var/datum/admin_help/AH = M.client ? M.client.current_ticket : null - if(AH) - AH.Resolve() - qdel(M.client) - if("No") - var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null - if(!reason) - return - switch(alert(usr,"IP ban?",,"Yes","No","Cancel")) - if("Cancel") - return - if("Yes") - AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0, M.lastKnownIP) - if("No") - AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0) - to_chat(M, "You have been banned by [usr.client.key].\nReason: [reason]") - to_chat(M, "This is a permanent ban. The round ID is [GLOB.round_id].") - var/bran = CONFIG_GET(string/banappeals) - if(bran) - to_chat(M, "To try to resolve this matter head to [bran]") - else - to_chat(M, "No ban appeals URL has been set.") - if(!DB_ban_record(BANTYPE_PERMA, M, -1, reason)) - to_chat(usr, "Failed to apply ban.") - return - ban_unban_log_save("[key_name(usr)] has permabanned [key_name(M)]. - Reason: [reason] - This is a permanent ban.") - log_admin_private("[key_name(usr)] has banned [key_name(M)].\nReason: [reason]\nThis is a permanent ban.") - var/msg = "[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis is a permanent ban." - message_admins(msg) - var/datum/admin_help/AH = M.client ? M.client.current_ticket : null - if(AH) - AH.Resolve() - qdel(M.client) - if("Cancel") - return - - 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 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 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 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 is allowed. This is adjusted by dynamic voting.", "Change curve centre", null) as num - - 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_high_pop_limit"]) - 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 high-pop override threshold for dynamic mode.", "High pop override") as num - if (new_value < 0) - return alert(usr, "Only positive values allowed!", null, null, null, null) - GLOB.dynamic_high_pop_limit = new_value - - log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") - message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_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()) - return alert(usr, "The game has already started.", null, null, null, null) - 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]") - Game() // updates the main game menu - 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.") - 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.") - 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.") - 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.") - - 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.") - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") - 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!") - - 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.") - return - - if(!M.client) - to_chat(usr, "[M] doesn't seem to have an active client.") - 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.") - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") - 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)) - spawn(50) - to_chat(L, "You have been sent to the Thunderdome.") - 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.") - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") - 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)) - spawn(50) - to_chat(L, "You have been sent to the Thunderdome.") - 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.") - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") - return - var/mob/living/L = M - - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdomeadmin)) - spawn(50) - to_chat(L, "You have been sent to the Thunderdome.") - 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.") - return - if(isAI(M)) - to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") - 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_jacket(observer), SLOT_W_UNIFORM) - observer.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/black(observer), SLOT_SHOES) - L.Unconscious(100) - sleep(5) - L.forceMove(pick(GLOB.tdomeobserve)) - spawn(50) - to_chat(L, "You have been sent to the Thunderdome.") - 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.") - return - - L.revive(full_heal = 1, admin_revive = 1) - 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.") - return - - message_admins("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!") - log_admin("[key_name(usr)] AIized [key_name(H)].") - H.AIize() - - 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.") - 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.") - 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.") - 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.") - 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.") - 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 - if(!isobserver(usr)) - C.admin_ghost() - 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.") - 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) - gender_description = "[M.gender]" - else - gender_description = "[M.gender]" - - to_chat(src.owner, "Info about [M.name]: ") - to_chat(src.owner, "Mob type = [M.type]; Gender = [gender_description] Damage = [health_description]") - to_chat(src.owner, "Name = [M.name]; Real_name = [M.real_name]; Mind_name = [M.mind?"[M.mind.name]":""]; Key = [M.key];") - to_chat(src.owner, "Location = [location_description];") - to_chat(src.owner, "[special_role_description]") - to_chat(src.owner, ADMIN_FULLMONTY_NONAME(M)) - - 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") - 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.") - return - - var/obj/item/reagent_containers/food/snacks/cookie/cookie = new(H) - if(H.put_in_hands(cookie)) - H.update_inv_hands() - else - qdel(cookie) - log_admin("[key_name(H)] has their hands full, so they did not receive their cookie, spawned by [key_name(src.owner)].") - message_admins("[key_name(H)] has their hands full, so they did not receive their cookie, spawned by [key_name(src.owner)].") - return - - log_admin("[key_name(H)] got their cookie, spawned by [key_name(src.owner)].") - message_admins("[key_name(H)] got their cookie, 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 cookie!") - 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") - 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["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.") - 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.") - 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") - return - else - D.traitor_panel() - else - show_traitor_panel(M) - - 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") - 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") - 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, 100) - 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.") - 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.") - 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.") - 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/abstract/DPtarget(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", "") - while (findtext(src.admincaster_feed_channel.channel_name," ") == 1) - src.admincaster_feed_channel.channel_name = copytext(src.admincaster_feed_channel.channel_name,2,length(src.admincaster_feed_channel.channel_name)+1) - 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 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(input(usr, "Write your Feed story.", "Network Channel Handler", "")) - while (findtext(src.admincaster_feed_message.returnBody(-1)," ") == 1) - src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.returnBody(-1),2,length(src.admincaster_feed_message.returnBody(-1))+1) - 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(input(usr, "Provide the name of the Wanted person.", "Network Security Handler", "")) - while(findtext(src.admincaster_wanted_message.criminal," ") == 1) - src.admincaster_wanted_message.criminal = copytext(admincaster_wanted_message.criminal,2,length(admincaster_wanted_message.criminal)+1) - src.access_news_network() - - else if(href_list["ac_set_wanted_desc"]) - if(!check_rights(R_ADMIN)) - return - src.admincaster_wanted_message.body = adminscrub(input(usr, "Provide the a description of the Wanted person and any other details you deem important.", "Network Security Handler", "")) - while (findtext(src.admincaster_wanted_message.body," ") == 1) - src.admincaster_wanted_message.body = copytext(src.admincaster_wanted_message.body,2,length(src.admincaster_wanted_message.body)+1) - 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") - 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.") - - else if(href_list["create_outfit"]) - if(!check_rights(R_ADMIN)) - return - - var/datum/outfit/O = new /datum/outfit - //swap this for js dropdowns sometime - O.name = href_list["outfit_name"] - O.uniform = text2path(href_list["outfit_uniform"]) - O.shoes = text2path(href_list["outfit_shoes"]) - O.gloves = text2path(href_list["outfit_gloves"]) - O.suit = text2path(href_list["outfit_suit"]) - O.head = text2path(href_list["outfit_head"]) - O.back = text2path(href_list["outfit_back"]) - O.mask = text2path(href_list["outfit_mask"]) - O.glasses = text2path(href_list["outfit_glasses"]) - O.r_hand = text2path(href_list["outfit_r_hand"]) - O.l_hand = text2path(href_list["outfit_l_hand"]) - O.suit_store = text2path(href_list["outfit_s_store"]) - O.l_pocket = text2path(href_list["outfit_l_pocket"]) - O.r_pocket = text2path(href_list["outfit_r_pocket"]) - O.id = text2path(href_list["outfit_id"]) - O.belt = text2path(href_list["outfit_belt"]) - O.ears = text2path(href_list["outfit_ears"]) - - GLOB.custom_outfits.Add(O) - message_admins("[key_name(usr)] created \"[O.name]\" outfit!") - - 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.") - 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") - -/datum/admins/proc/HandleCMode() - if(!check_rights(R_ADMIN)) - return - - if(SSticker.HasRoundStarted()) - return alert(usr, "The game has already started.", null, null, null, null) - 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 + + citaTopic(href, href_list) //CITADEL EDIT, MENTORS + + 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!") + + 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.") + 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.") + 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!") + 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. Unfortunatly there were not enough candidates available.") + log_admin("[key_name(usr)] failed to create an abductor team.") + if("clockcult") + if(src.makeClockCult()) + message_admins("[key_name(usr)] started a clockwork cult.") + log_admin("[key_name(usr)] started a clockwork cult.") + else + message_admins("[key_name_admin(usr)] tried to start a clockwork cult. Unfortunately, there were no candidates available.") + log_admin("[key_name(usr)] failed to start a clockwork cult.") + 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("Cancel") + event.kill() + return + if("No") + event.announceWhen = -1 + 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["dbsearchckey"] || href_list["dbsearchadmin"] || href_list["dbsearchip"] || href_list["dbsearchcid"]) + var/adminckey = href_list["dbsearchadmin"] + var/playerckey = href_list["dbsearchckey"] + var/ip = href_list["dbsearchip"] + var/cid = href_list["dbsearchcid"] + var/page = href_list["dbsearchpage"] + + DB_ban_panel(playerckey, adminckey, ip, cid, page) + return + + else if(href_list["dbbanedit"]) + var/banedit = href_list["dbbanedit"] + var/banid = text2num(href_list["dbbanid"]) + if(!banedit || !banid) + return + + DB_ban_edit(banid, banedit) + return + + else if(href_list["dbbanaddtype"]) + if(!check_rights(R_BAN)) + return + var/bantype = text2num(href_list["dbbanaddtype"]) + var/bankey = href_list["dbbanaddkey"] + var/banckey = ckey(bankey) + var/banip = href_list["dbbanaddip"] + var/bancid = href_list["dbbanaddcid"] + var/banduration = text2num(href_list["dbbaddduration"]) + var/banjob = href_list["dbbanaddjob"] + var/banreason = href_list["dbbanreason"] + var/banseverity = href_list["dbbanaddseverity"] + + switch(bantype) + if(BANTYPE_PERMA) + if(!banckey || !banreason || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, severity, and reason).") + return + banduration = null + banjob = null + if(BANTYPE_TEMP) + if(!banckey || !banreason || !banduration || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, reason, severity and duration).") + return + banjob = null + if(BANTYPE_JOB_PERMA) + if(!banckey || !banreason || !banjob || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, severity, reason and job).") + return + banduration = null + if(BANTYPE_JOB_TEMP) + if(!banckey || !banreason || !banjob || !banduration || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, severity, reason and job).") + return + if(BANTYPE_ADMIN_PERMA) + if(!banckey || !banreason || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, severity and reason).") + return + banduration = null + banjob = null + if(BANTYPE_ADMIN_TEMP) + if(!banckey || !banreason || !banduration || !banseverity) + to_chat(usr, "Not enough parameters (Requires ckey, severity, reason and duration).") + return + banjob = null + + var/mob/playermob + + for(var/mob/M in GLOB.player_list) + if(M.ckey == banckey) + playermob = M + break + + + banreason = "(MANUAL BAN) "+banreason + + if(!playermob) + if(banip) + banreason = "[banreason] (CUSTOM IP)" + if(bancid) + banreason = "[banreason] (CUSTOM CID)" + else + message_admins("Ban process: A mob matching [playermob.key] was found at location [playermob.x], [playermob.y], [playermob.z]. Custom ip and computer id fields replaced with the ip and computer id from the located mob.") + + if(!DB_ban_record(bantype, playermob, banduration, banreason, banjob, bankey, banip, bancid )) + to_chat(usr, "Failed to apply ban.") + return + create_message("note", bankey, null, banreason, null, null, 0, 0, null, 0, banseverity) + + 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.") + + + href_list["secrets"] = "check_antagonist" + + 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.") + href_list["secrets"] = "check_antagonist" + 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 + 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 + 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]") + href_list["secrets"] = "check_antagonist" + 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.") + return + + var/delmob = 0 + switch(alert("Delete old mob?","Message","Yes","No","Cancel")) + if("Cancel") + return + if("Yes") + delmob = 1 + + 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() + 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("constructarmored") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/armored , null, null, delmob ) + if("constructbuilder") + M.change_mob_type( /mob/living/simple_animal/hostile/construct/builder , 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 ) + + + /////////////////////////////////////new ban stuff + else if(href_list["unbanf"]) + if(!check_rights(R_BAN)) + return + + var/banfolder = href_list["unbanf"] + GLOB.Banlist.cd = "/base/[banfolder]" + var/key = GLOB.Banlist["key"] + if(alert(usr, "Are you sure you want to unban [key]?", "Confirmation", "Yes", "No") == "Yes") + if(RemoveBan(banfolder)) + unbanpanel() + else + alert(usr, "This ban has already been lifted / does not exist.", "Error", "Ok") + unbanpanel() + + else if(href_list["unbane"]) + if(!check_rights(R_BAN)) + return + + UpdateTime() + var/reason + + var/banfolder = href_list["unbane"] + GLOB.Banlist.cd = "/base/[banfolder]" + var/reason2 = GLOB.Banlist["reason"] + var/temp = GLOB.Banlist["temp"] + + var/minutes = GLOB.Banlist["minutes"] + + var/banned_key = GLOB.Banlist["key"] + GLOB.Banlist.cd = "/base" + + var/duration + + switch(alert("Temporary Ban for [banned_key]?",,"Yes","No")) + if("Yes") + temp = 1 + var/mins = 0 + if(minutes > GLOB.CMinutes) + mins = minutes - GLOB.CMinutes + mins = input(usr,"How long (in minutes)? (Default: 1440)","Ban time",mins ? mins : 1440) as num|null + if(mins <= 0) + to_chat(usr, "[mins] is not a valid duration.") + return + minutes = GLOB.CMinutes + mins + duration = GetExp(minutes) + reason = input(usr,"Please State Reason For Banning [banned_key].","Reason",reason2) as message|null + if(!reason) + return + if("No") + temp = 0 + duration = "Perma" + reason = input(usr,"Please State Reason For Banning [banned_key].","Reason",reason2) as message|null + if(!reason) + return + + log_admin_private("[key_name(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]") + ban_unban_log_save("[key_name(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]") + message_admins("[key_name_admin(usr)] edited [banned_key]'s ban. Reason: [reason] Duration: [duration]") + GLOB.Banlist.cd = "/base/[banfolder]" + WRITE_FILE(GLOB.Banlist["reason"], reason) + WRITE_FILE(GLOB.Banlist["temp"], temp) + WRITE_FILE(GLOB.Banlist["minutes"], minutes) + WRITE_FILE(GLOB.Banlist["bannedby"], usr.ckey) + GLOB.Banlist.cd = "/base" + unbanpanel() + + /////////////////////////////////////new ban stuff + + else if(href_list["appearanceban"]) + if(!check_rights(R_BAN)) + return + var/mob/M = locate(href_list["appearanceban"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(!M.ckey) //sanity + to_chat(usr, "This mob has no ckey") + return + + + if(jobban_isbanned(M, "appearance")) + switch(alert("Remove appearance ban?","Please Confirm","Yes","No")) + if("Yes") + ban_unban_log_save("[key_name(usr)] removed [key_name(M)]'s appearance ban.") + log_admin_private("[key_name(usr)] removed [key_name(M)]'s appearance ban.") + DB_ban_unban(M.ckey, BANTYPE_ANY_JOB, "appearance") + if(M.client) + jobban_buildcache(M.client) + message_admins("[key_name_admin(usr)] removed [key_name_admin(M)]'s appearance ban.") + to_chat(M, "[usr.client.key] has removed your appearance ban.") + + else switch(alert("Appearance ban [M.key]?",,"Yes","No", "Cancel")) + if("Yes") + var/reason = input(usr,"Please State Reason.","Reason") as message|null + if(!reason) + return + var/severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") + if(!severity) + return + if(!DB_ban_record(BANTYPE_JOB_PERMA, M, -1, reason, "appearance")) + to_chat(usr, "Failed to apply ban.") + return + if(M.client) + jobban_buildcache(M.client) + ban_unban_log_save("[key_name(usr)] appearance banned [key_name(M)]. reason: [reason]") + log_admin_private("[key_name(usr)] appearance banned [key_name(M)]. \nReason: [reason]") + create_message("note", M.key, null, "Appearance banned - [reason]", null, null, 0, 0, null, 0, severity) + message_admins("[key_name_admin(usr)] appearance banned [key_name_admin(M)].") + to_chat(M, "You have been appearance banned by [usr.client.key].") + to_chat(M, "The reason is: [reason]") + to_chat(M, "Appearance ban can be lifted only upon request.") + var/bran = CONFIG_GET(string/banappeals) + if(bran) + to_chat(M, "To try to resolve this matter head to [bran]") + else + to_chat(M, "No ban appeals URL has been set.") + if("No") + return + + else if(href_list["jobban2"]) + if(!check_rights(R_BAN)) + return + var/mob/M = locate(href_list["jobban2"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.") + return + + if(!M.ckey) //sanity + to_chat(usr, "This mob has no ckey.") + return + + var/dat = "Job-Ban Panel: [key_name(M)]" + + /***********************************WARNING!************************************ + The jobban stuff looks mangled and disgusting + But it looks beautiful in-game + -Nodrak + ************************************WARNING!***********************************/ + var/counter = 0 +//Regular jobs + //Command (Blue) + dat += "" + dat += "" + for(var/jobPos in GLOB.command_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 6) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
                Command Positions
                [jobPos][jobPos]
                " + + //Security (Red) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.security_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
                Security Positions
                [jobPos][jobPos]
                " + + //Engineering (Yellow) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.engineering_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
                Engineering Positions
                [jobPos][jobPos]
                " + + //Medical (White) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.medical_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
                Medical Positions
                [jobPos][jobPos]
                " + + //Science (Purple) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.science_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
                Science Positions
                [jobPos][jobPos]
                " + + //Supply (Brown) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.supply_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get COPYPASTE! + dat += "" + counter = 0 + dat += "
                Supply Positions
                [jobPos][jobPos]
                " + + //Civilian (Grey) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.civilian_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + dat += "
                Civilian Positions
                [jobPos][jobPos]
                " + + //Non-Human (Green) + counter = 0 + dat += "" + dat += "" + for(var/jobPos in GLOB.nonhuman_positions) + if(!jobPos) + continue + if(jobban_isbanned(M, jobPos)) + dat += "" + counter++ + else + dat += "" + counter++ + + if(counter >= 5) //So things dont get squiiiiished! + dat += "" + counter = 0 + + dat += "
                Non-human Positions
                [jobPos][jobPos]
                " + + //Ghost Roles (light light gray) + dat += "" + dat += "" + + //pAI + if(jobban_isbanned(M, ROLE_PAI)) + dat += "" + else + dat += "" + + + //Drones + if(jobban_isbanned(M, ROLE_DRONE)) + dat += "" + else + dat += "" + + + //Positronic Brains + if(jobban_isbanned(M, ROLE_POSIBRAIN)) + dat += "" + else + dat += "" + + //Sentience Potion Spawn + if(jobban_isbanned(M, ROLE_SENTIENCE)) + dat += "" + else + dat += "" + + //Deathsquad + if(jobban_isbanned(M, ROLE_DEATHSQUAD)) + dat += "" + else + dat += "" + + //Lavaland roles + if(jobban_isbanned(M, ROLE_LAVALAND)) + dat += "" + else + dat += "" + + dat += "
                Ghost Roles
                pAIpAIDroneDronePosibrainPosibrainSentience Potion SpawnSentience Potion SpawnDeathsquadDeathsquadLavalandLavaland
                " + + //Antagonist (Orange) + var/isbanned_dept = jobban_isbanned(M, ROLE_SYNDICATE) + dat += "" + dat += "" + + //Traitor + if(jobban_isbanned(M, ROLE_TRAITOR) || isbanned_dept) + dat += "" + else + dat += "" + + //Changeling + if(jobban_isbanned(M, ROLE_CHANGELING) || isbanned_dept) + dat += "" + else + dat += "" + + //Nuke Operative + if(jobban_isbanned(M, ROLE_OPERATIVE) || isbanned_dept) + dat += "" + else + dat += "" + + //Revolutionary + if(jobban_isbanned(M, ROLE_REV) || isbanned_dept) + dat += "" + else + dat += "" + + //Cultist + if(jobban_isbanned(M, ROLE_CULTIST) || isbanned_dept) + dat += "" + else + dat += "" + + dat += "" //So things dont get squished. + + //Servant of Ratvar + if(jobban_isbanned(M, ROLE_SERVANT_OF_RATVAR) || isbanned_dept) + dat += "" + else + dat += "" + + //Wizard + if(jobban_isbanned(M, ROLE_WIZARD) || isbanned_dept) + dat += "" + else + dat += "" + + //Abductor + if(jobban_isbanned(M, ROLE_ABDUCTOR) || isbanned_dept) + dat += "" + else + dat += "" + + //Alien + if(jobban_isbanned(M, ROLE_ALIEN) || isbanned_dept) + dat += "" + else + dat += "" + + //Gang + if(jobban_isbanned(M, ROLE_GANG) || isbanned_dept) + dat += "" + else + dat += "" + + + //Other Roles (black) + dat += "
                Antagonist Positions | " + dat += "Team Antagonists | " + dat += "Conversion Antagonists
                TraitorTraitorChangelingChangelingNuke OperativeNuke OperativeRevolutionaryRevolutionaryCultistCultist
                ServantServantWizardWizardAbductorAbductorAlienAlienGangGang
                " + dat += "" + + //Mind Transfer Potion + if(jobban_isbanned(M, ROLE_MIND_TRANSFER)) + dat += "" + else + dat += "" + + dat += "
                Other Roles
                Mind Transfer PotionMind Transfer Potion
                " + usr << browse(dat, "window=jobban2;size=800x450") + return + + //JOBBAN'S INNARDS + else if(href_list["jobban3"]) + if(!check_rights(R_BAN)) + return + var/mob/M = locate(href_list["jobban4"]) + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + if(!SSjob) + to_chat(usr, "Jobs subsystem not initialized yet!") + return + //get jobs for department if specified, otherwise just return the one job in a list. + var/list/joblist = list() + switch(href_list["jobban3"]) + if("commanddept") + for(var/jobPos in GLOB.command_positions) + if(!jobPos) + continue + joblist += jobPos + if("securitydept") + for(var/jobPos in GLOB.security_positions) + if(!jobPos) + continue + joblist += jobPos + if("engineeringdept") + for(var/jobPos in GLOB.engineering_positions) + if(!jobPos) + continue + joblist += jobPos + if("medicaldept") + for(var/jobPos in GLOB.medical_positions) + if(!jobPos) + continue + joblist += jobPos + if("sciencedept") + for(var/jobPos in GLOB.science_positions) + if(!jobPos) + continue + joblist += jobPos + if("supplydept") + for(var/jobPos in GLOB.supply_positions) + if(!jobPos) + continue + joblist += jobPos + if("civiliandept") + for(var/jobPos in GLOB.civilian_positions) + if(!jobPos) + continue + joblist += jobPos + if("nonhumandept") + for(var/jobPos in GLOB.nonhuman_positions) + if(!jobPos) + continue + joblist += jobPos + if("ghostroles") + joblist += list(ROLE_PAI, ROLE_POSIBRAIN, ROLE_DRONE , ROLE_DEATHSQUAD, ROLE_LAVALAND, ROLE_SENTIENCE) + if("teamantags") + joblist += list(ROLE_OPERATIVE, ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_GANG) + if("convertantags") + joblist += list(ROLE_REV, ROLE_CULTIST, ROLE_SERVANT_OF_RATVAR, ROLE_ALIEN) + if("otherroles") + joblist += list(ROLE_MIND_TRANSFER) + else + joblist += href_list["jobban3"] + + //Create a list of unbanned jobs within joblist + var/list/notbannedlist = list() + for(var/job in joblist) + if(!jobban_isbanned(M, job)) + notbannedlist += job + + //Banning comes first + if(notbannedlist.len) //at least 1 unbanned job exists in joblist so we have stuff to ban. + var/severity = null + switch(alert("Temporary Ban for [M.key]?",,"Yes","No", "Cancel")) + if("Yes") + var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null + if(mins <= 0) + to_chat(usr, "[mins] is not a valid duration.") + return + var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null + if(!reason) + return + severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") + if(!severity) + return + var/msg + for(var/job in notbannedlist) + if(!DB_ban_record(BANTYPE_JOB_TEMP, M, mins, reason, job)) + to_chat(usr, "Failed to apply ban.") + return + if(M.client) + jobban_buildcache(M.client) + ban_unban_log_save("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes. reason: [reason]") + log_admin_private("[key_name(usr)] temp-jobbanned [key_name(M)] from [job] for [mins] minutes.") + if(!msg) + msg = job + else + msg += ", [job]" + create_message("note", M.key, null, "Banned from [msg] - [reason]", null, null, 0, 0, null, 0, severity) + message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg] for [mins] minutes.") + to_chat(M, "You have been [(msg == ("ooc" || "appearance")) ? "banned" : "jobbanned"] by [usr.client.key] from: [msg].") + to_chat(M, "The reason is: [reason]") + to_chat(M, "This jobban will be lifted in [mins] minutes.") + href_list["jobban2"] = 1 // lets it fall through and refresh + return 1 + if("No") + var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null + severity = input("Set the severity of the note/ban.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None") + if(!severity) + return + if(reason) + var/msg + for(var/job in notbannedlist) + if(!DB_ban_record(BANTYPE_JOB_PERMA, M, -1, reason, job)) + to_chat(usr, "Failed to apply ban.") + return + if(M.client) + jobban_buildcache(M.client) + ban_unban_log_save("[key_name(usr)] perma-jobbanned [key_name(M)] from [job]. reason: [reason]") + log_admin_private("[key_name(usr)] perma-banned [key_name(M)] from [job]") + if(!msg) + msg = job + else + msg += ", [job]" + create_message("note", M.key, null, "Banned from [msg] - [reason]", null, null, 0, 0, null, 0, severity) + message_admins("[key_name_admin(usr)] banned [key_name_admin(M)] from [msg].") + to_chat(M, "You have been [(msg == ("ooc" || "appearance")) ? "banned" : "jobbanned"] by [usr.client.key] from: [msg].") + to_chat(M, "The reason is: [reason]") + to_chat(M, "Jobban can be lifted only upon request.") + href_list["jobban2"] = 1 // lets it fall through and refresh + return 1 + if("Cancel") + return + + //Unbanning joblist + //all jobs in joblist are banned already OR we didn't give a reason (implying they shouldn't be banned) + if(joblist.len) //at least 1 banned job exists in joblist so we have stuff to unban. + var/msg + for(var/job in joblist) + var/reason = jobban_isbanned(M, job) + if(!reason) + continue //skip if it isn't jobbanned anyway + switch(alert("Job: '[job]' Reason: '[reason]' Un-jobban?","Please Confirm","Yes","No")) + if("Yes") + ban_unban_log_save("[key_name(usr)] unjobbanned [key_name(M)] from [job]") + log_admin_private("[key_name(usr)] unbanned [key_name(M)] from [job]") + DB_ban_unban(M.ckey, BANTYPE_ANY_JOB, job) + if(M.client) + jobban_buildcache(M.client) + if(!msg) + msg = job + else + msg += ", [job]" + else + continue + if(msg) + message_admins("[key_name_admin(usr)] unbanned [key_name_admin(M)] from [msg].") + to_chat(M, "You have been un-jobbanned by [usr.client.key] from [msg].") + href_list["jobban2"] = 1 // lets it fall through and refresh + return 1 + return 0 //we didn't do anything! + + 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.") + return + if(alert(usr, "Kick [key_name(M)]?", "Confirm", "Yes", "No") != "Yes") + return + if(!M) + to_chat(usr, "Error: [M] no longer exists!") + return + if(!M.client) + to_chat(usr, "Error: [M] no longer has a client!") + return + to_chat(M, "You have been kicked from the server by [usr.client.holder.fakekey ? "an Administrator" : "[usr.client.key]"].") + 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/message_id = sanitizeSQL("[href_list["messageedits"]]") + var/datum/DBQuery/query_get_message_edits = SSdbcore.NewQuery("SELECT edits FROM [format_table_name("messages")] WHERE id = '[message_id]'") + 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["newban"]) + if(!check_rights(R_BAN)) + return + + var/mob/M = locate(href_list["newban"]) + if(!ismob(M)) + return + + if(M.client && M.client.holder) + return //admins cannot be banned. Even if they could, the ban doesn't affect them anyway + + switch(alert("Temporary Ban for [M.key]?",,"Yes","No", "Cancel")) + if("Yes") + var/mins = input(usr,"How long (in minutes)?","Ban time",1440) as num|null + if(mins <= 0) + to_chat(usr, "[mins] is not a valid duration.") + return + var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null + if(!reason) + return + if(!DB_ban_record(BANTYPE_TEMP, M, mins, reason)) + to_chat(usr, "Failed to apply ban.") + return + AddBan(M.ckey, M.computer_id, reason, usr.ckey, 1, mins) + ban_unban_log_save("[key_name(usr)] has banned [key_name(M)]. - Reason: [reason] - This will be removed in [mins] minutes.") + to_chat(M, "You have been banned by [usr.client.key].\nReason: [reason]") + to_chat(M, "This is a temporary ban, it will be removed in [mins] minutes. The round ID is [GLOB.round_id].") + var/bran = CONFIG_GET(string/banappeals) + if(bran) + to_chat(M, "To try to resolve this matter head to [bran]") + else + to_chat(M, "No ban appeals URL has been set.") + log_admin_private("[key_name(usr)] has banned [key_name(M)].\nReason: [key_name(M)]\nThis will be removed in [mins] minutes.") + var/msg = "[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis will be removed in [mins] minutes." + message_admins(msg) + var/datum/admin_help/AH = M.client ? M.client.current_ticket : null + if(AH) + AH.Resolve() + qdel(M.client) + if("No") + var/reason = input(usr,"Please State Reason For Banning [M.key].","Reason") as message|null + if(!reason) + return + switch(alert(usr,"IP ban?",,"Yes","No","Cancel")) + if("Cancel") + return + if("Yes") + AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0, M.lastKnownIP) + if("No") + AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0) + to_chat(M, "You have been banned by [usr.client.key].\nReason: [reason]") + to_chat(M, "This is a permanent ban. The round ID is [GLOB.round_id].") + var/bran = CONFIG_GET(string/banappeals) + if(bran) + to_chat(M, "To try to resolve this matter head to [bran]") + else + to_chat(M, "No ban appeals URL has been set.") + if(!DB_ban_record(BANTYPE_PERMA, M, -1, reason)) + to_chat(usr, "Failed to apply ban.") + return + ban_unban_log_save("[key_name(usr)] has permabanned [key_name(M)]. - Reason: [reason] - This is a permanent ban.") + log_admin_private("[key_name(usr)] has banned [key_name(M)].\nReason: [reason]\nThis is a permanent ban.") + var/msg = "[key_name_admin(usr)] has banned [key_name_admin(M)].\nReason: [reason]\nThis is a permanent ban." + message_admins(msg) + var/datum/admin_help/AH = M.client ? M.client.current_ticket : null + if(AH) + AH.Resolve() + qdel(M.client) + if("Cancel") + return + + 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 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 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 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 is allowed. This is adjusted by dynamic voting.", "Change curve centre", null) as num + + 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_high_pop_limit"]) + 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 high-pop override threshold for dynamic mode.", "High pop override") as num + if (new_value < 0) + return alert(usr, "Only positive values allowed!", null, null, null, null) + GLOB.dynamic_high_pop_limit = new_value + + log_admin("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_limit].") + message_admins("[key_name(usr)] set 'high_pop_limit' to [GLOB.dynamic_high_pop_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()) + return alert(usr, "The game has already started.", null, null, null, null) + 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]") + Game() // updates the main game menu + 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.") + 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.") + 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.") + 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.") + + 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.") + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") + 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!") + + 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.") + return + + if(!M.client) + to_chat(usr, "[M] doesn't seem to have an active client.") + 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.") + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") + 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)) + spawn(50) + to_chat(L, "You have been sent to the Thunderdome.") + 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.") + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") + 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)) + spawn(50) + to_chat(L, "You have been sent to the Thunderdome.") + 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.") + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") + return + var/mob/living/L = M + + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdomeadmin)) + spawn(50) + to_chat(L, "You have been sent to the Thunderdome.") + 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.") + return + if(isAI(M)) + to_chat(usr, "This cannot be used on instances of type /mob/living/silicon/ai.") + 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_jacket(observer), SLOT_W_UNIFORM) + observer.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/black(observer), SLOT_SHOES) + L.Unconscious(100) + sleep(5) + L.forceMove(pick(GLOB.tdomeobserve)) + spawn(50) + to_chat(L, "You have been sent to the Thunderdome.") + 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.") + return + + L.revive(full_heal = 1, admin_revive = 1) + 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.") + return + + message_admins("Admin [key_name_admin(usr)] AIized [key_name_admin(H)]!") + log_admin("[key_name(usr)] AIized [key_name(H)].") + H.AIize() + + 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.") + 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.") + 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.") + 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.") + 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.") + 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 + if(!isobserver(usr)) + C.admin_ghost() + 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.") + 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) + gender_description = "[M.gender]" + else + gender_description = "[M.gender]" + + to_chat(src.owner, "Info about [M.name]: ") + to_chat(src.owner, "Mob type = [M.type]; Gender = [gender_description] Damage = [health_description]") + to_chat(src.owner, "Name = [M.name]; Real_name = [M.real_name]; Mind_name = [M.mind?"[M.mind.name]":""]; Key = [M.key];") + to_chat(src.owner, "Location = [location_description];") + to_chat(src.owner, "[special_role_description]") + to_chat(src.owner, ADMIN_FULLMONTY_NONAME(M)) + + 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") + 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.") + return + + var/obj/item/reagent_containers/food/snacks/cookie/cookie = new(H) + if(H.put_in_hands(cookie)) + H.update_inv_hands() + else + qdel(cookie) + log_admin("[key_name(H)] has their hands full, so they did not receive their cookie, spawned by [key_name(src.owner)].") + message_admins("[key_name(H)] has their hands full, so they did not receive their cookie, spawned by [key_name(src.owner)].") + return + + log_admin("[key_name(H)] got their cookie, spawned by [key_name(src.owner)].") + message_admins("[key_name(H)] got their cookie, 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 cookie!") + 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") + 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["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.") + 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.") + 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") + return + else + D.traitor_panel() + else + show_traitor_panel(M) + + 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") + 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") + 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, 100) + 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.") + 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.") + 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.") + 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/abstract/DPtarget(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", "") + while (findtext(src.admincaster_feed_channel.channel_name," ") == 1) + src.admincaster_feed_channel.channel_name = copytext(src.admincaster_feed_channel.channel_name,2,length(src.admincaster_feed_channel.channel_name)+1) + 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 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(input(usr, "Write your Feed story.", "Network Channel Handler", "")) + while (findtext(src.admincaster_feed_message.returnBody(-1)," ") == 1) + src.admincaster_feed_message.body = copytext(src.admincaster_feed_message.returnBody(-1),2,length(src.admincaster_feed_message.returnBody(-1))+1) + 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(input(usr, "Provide the name of the Wanted person.", "Network Security Handler", "")) + while(findtext(src.admincaster_wanted_message.criminal," ") == 1) + src.admincaster_wanted_message.criminal = copytext(admincaster_wanted_message.criminal,2,length(admincaster_wanted_message.criminal)+1) + src.access_news_network() + + else if(href_list["ac_set_wanted_desc"]) + if(!check_rights(R_ADMIN)) + return + src.admincaster_wanted_message.body = adminscrub(input(usr, "Provide the a description of the Wanted person and any other details you deem important.", "Network Security Handler", "")) + while (findtext(src.admincaster_wanted_message.body," ") == 1) + src.admincaster_wanted_message.body = copytext(src.admincaster_wanted_message.body,2,length(src.admincaster_wanted_message.body)+1) + 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") + 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.") + + else if(href_list["create_outfit"]) + if(!check_rights(R_ADMIN)) + return + + var/datum/outfit/O = new /datum/outfit + //swap this for js dropdowns sometime + O.name = href_list["outfit_name"] + O.uniform = text2path(href_list["outfit_uniform"]) + O.shoes = text2path(href_list["outfit_shoes"]) + O.gloves = text2path(href_list["outfit_gloves"]) + O.suit = text2path(href_list["outfit_suit"]) + O.head = text2path(href_list["outfit_head"]) + O.back = text2path(href_list["outfit_back"]) + O.mask = text2path(href_list["outfit_mask"]) + O.glasses = text2path(href_list["outfit_glasses"]) + O.r_hand = text2path(href_list["outfit_r_hand"]) + O.l_hand = text2path(href_list["outfit_l_hand"]) + O.suit_store = text2path(href_list["outfit_s_store"]) + O.l_pocket = text2path(href_list["outfit_l_pocket"]) + O.r_pocket = text2path(href_list["outfit_r_pocket"]) + O.id = text2path(href_list["outfit_id"]) + O.belt = text2path(href_list["outfit_belt"]) + O.ears = text2path(href_list["outfit_ears"]) + + GLOB.custom_outfits.Add(O) + message_admins("[key_name(usr)] created \"[O.name]\" outfit!") + + 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.") + 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") + +/datum/admins/proc/HandleCMode() + if(!check_rights(R_ADMIN)) + return + + if(SSticker.HasRoundStarted()) + return alert(usr, "The game has already started.", null, null, null, null) + 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/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index ce4b8f7e39..2f842263c7 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -1,699 +1,699 @@ -/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.") - -//Dissasociate ticket -/datum/admin_help_tickets/proc/ClientLogout(client/C) - if(C.current_ticket) - C.current_ticket.AddInteraction("Client disconnected.") - C.current_ticket.initiator = null - C.current_ticket = 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(msg,1,MAX_MESSAGE_LEN)) - if(!msg || !C || !C.mob) - qdel(src) - return - - id = ++ticket_counter - opened_at = world.time - - name = msg - - 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 irc if nobody is on and tell us how many were on - var/admin_number_present = send2irc_adminless_only(initiator_ckey, "Ticket #[id]: [name]") - 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 to the admin irc.") - 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 - send2irc(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") - _interactions += "[TIME_STAMP("hh:mm:ss", FALSE)]: [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)" - . += " (HANDLE)" - -//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 -/datum/admin_help/proc/MessageNoRecipient(msg) - 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]") - - //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) - - //show it to the person adminhelping too - to_chat(initiator, "PM to-Admins: [msg]") - -//Reopen a closed ticket -/datum/admin_help/proc/Reopen() - if(state == AHELP_ACTIVE) - to_chat(usr, "This ticket is already open.") - return - - if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey)) - to_chat(usr, "This user already has an active ticket, cannot reopen this one.") - 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.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) - to_chat(initiator, "Ticket closed by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"].") - 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) - 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 [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"]. The Adminhelp verb will be returned to you shortly.") - if(!silent) - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "resolved") - var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name]" - message_admins(msg) - 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 by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"]! -") - to_chat(initiator, "Your admin help was rejected. The adminhelp verb has been returned to you so that you may try again.") - 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.") - - 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].") - 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 by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"]! -
                " - msg += "Losing is part of the game!
                " - msg += "It is also possible that your ahelp is unable to be answered properly, due to events occurring in the round." - if(initiator) - to_chat(initiator, msg) - - 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]") - Resolve(silent = TRUE) - -//Let the initiator know their ahelp is being handled -/datum/admin_help/proc/HandleIssue(key_name = key_name_admin(usr)) - if(state != AHELP_ACTIVE) - return - - var/msg = "Your ticket is now being handled by an admin. Please be patient." - - if(initiator) - to_chat(initiator, msg) - - SSblackbox.record_feedback("tally", "ahelp_stats", 1, "handling") - msg = "Ticket [TicketHref("#[id]")] is being handled by [key_name]" - message_admins(msg) - log_admin_private(msg) - AddInteraction("Being handled by [key_name]") - -//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("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)" - if(closed_at) - dat += "
                Closed at: [GAMETIMESTAMP("hh:mm:ss", 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("handleissue") - HandleIssue() - 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 text - adminhelp(msg) - -/client/verb/adminhelp(msg as text) - 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.") - return - - //handle muting and automuting - if(prefs.muted & MUTE_ADMINHELP) - to_chat(src, "Error: Admin-PM: You cannot send adminhelps (Muted).") - 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...") - 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/send2irc_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] " - send2irc(source,final) - send2otherserver(source,final) - - -/proc/send2irc(msg,msg2) - msg = replacetext(replacetext(msg, "\proper", ""), "\improper", "") - msg2 = replacetext(replacetext(msg2, "\proper", ""), "\improper", "") - world.TgsTargetedChatBroadcast("[msg] | [msg2]", TRUE) - -/proc/send2otherserver(source,msg,type = "Ahelp") - var/comms_key = CONFIG_GET(string/comms_key) - if(!comms_key) - return - var/list/message = list() - message["message_sender"] = source - message["message"] = msg - message["source"] = "([CONFIG_GET(string/cross_comms_name)])" - message["key"] = comms_key - message += type - - var/list/servers = CONFIG_GET(keyed_list/cross_server) - for(var/I in servers) - world.Export("[servers[I]]?[list2params(message)]") - - -/proc/ircadminwho() - 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,irc) - - //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(irc) - if(founds == "") - return "Search Failed" - else - return founds - - return msg +/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.") + +//Dissasociate ticket +/datum/admin_help_tickets/proc/ClientLogout(client/C) + if(C.current_ticket) + C.current_ticket.AddInteraction("Client disconnected.") + C.current_ticket.initiator = null + C.current_ticket = 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(msg,1,MAX_MESSAGE_LEN)) + if(!msg || !C || !C.mob) + qdel(src) + return + + id = ++ticket_counter + opened_at = world.time + + name = msg + + 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 irc if nobody is on and tell us how many were on + var/admin_number_present = send2irc_adminless_only(initiator_ckey, "Ticket #[id]: [name]") + 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 to the admin irc.") + 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 + send2irc(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]") + _interactions += "[TIME_STAMP("hh:mm:ss", FALSE)]: [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)" + . += " (HANDLE)" + +//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 +/datum/admin_help/proc/MessageNoRecipient(msg) + 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]") + + //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) + + //show it to the person adminhelping too + to_chat(initiator, "PM to-Admins: [msg]") + +//Reopen a closed ticket +/datum/admin_help/proc/Reopen() + if(state == AHELP_ACTIVE) + to_chat(usr, "This ticket is already open.") + return + + if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey)) + to_chat(usr, "This user already has an active ticket, cannot reopen this one.") + 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.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) + to_chat(initiator, "Ticket closed by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"].") + 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) + 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 [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"]. The Adminhelp verb will be returned to you shortly.") + if(!silent) + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "resolved") + var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name]" + message_admins(msg) + 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 by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"]! -") + to_chat(initiator, "Your admin help was rejected. The adminhelp verb has been returned to you so that you may try again.") + 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.") + + 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].") + 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 by [usr?.client?.holder?.fakekey? usr.client.holder.fakekey : "an administrator"]! -
                " + msg += "Losing is part of the game!
                " + msg += "It is also possible that your ahelp is unable to be answered properly, due to events occurring in the round." + if(initiator) + to_chat(initiator, msg) + + 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]") + Resolve(silent = TRUE) + +//Let the initiator know their ahelp is being handled +/datum/admin_help/proc/HandleIssue(key_name = key_name_admin(usr)) + if(state != AHELP_ACTIVE) + return + + var/msg = "Your ticket is now being handled by an admin. Please be patient." + + if(initiator) + to_chat(initiator, msg) + + SSblackbox.record_feedback("tally", "ahelp_stats", 1, "handling") + msg = "Ticket [TicketHref("#[id]")] is being handled by [key_name]" + message_admins(msg) + log_admin_private(msg) + AddInteraction("Being handled by [key_name]") + +//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("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)" + if(closed_at) + dat += "
                Closed at: [GAMETIMESTAMP("hh:mm:ss", 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("handleissue") + HandleIssue() + 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 text + adminhelp(msg) + +/client/verb/adminhelp(msg as text) + 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.") + return + + //handle muting and automuting + if(prefs.muted & MUTE_ADMINHELP) + to_chat(src, "Error: Admin-PM: You cannot send adminhelps (Muted).") + 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...") + 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/send2irc_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] " + send2irc(source,final) + send2otherserver(source,final) + + +/proc/send2irc(msg,msg2) + msg = replacetext(replacetext(msg, "\proper", ""), "\improper", "") + msg2 = replacetext(replacetext(msg2, "\proper", ""), "\improper", "") + world.TgsTargetedChatBroadcast("[msg] | [msg2]", TRUE) + +/proc/send2otherserver(source,msg,type = "Ahelp") + var/comms_key = CONFIG_GET(string/comms_key) + if(!comms_key) + return + var/list/message = list() + message["message_sender"] = source + message["message"] = msg + message["source"] = "([CONFIG_GET(string/cross_comms_name)])" + message["key"] = comms_key + message += type + + var/list/servers = CONFIG_GET(keyed_list/cross_server) + for(var/I in servers) + world.Export("[servers[I]]?[list2params(message)]") + + +/proc/ircadminwho() + 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,irc) + + //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(irc) + if(founds == "") + return "Search Failed" + else + return founds + + return msg diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm index d3631f24d0..525e4e82c0 100644 --- a/code/modules/admin/verbs/adminjump.dm +++ b/code/modules/admin/verbs/adminjump.dm @@ -1,157 +1,157 @@ -/client/proc/jumptoarea(area/A in GLOB.sortedAreas) - set name = "Jump to Area" - set desc = "Area to jump to" - set category = "Admin" - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - return - - if(!A) - return - - var/list/turfs = list() - for(var/turf/T in A) - if(T.density) - continue - turfs.Add(T) - - var/turf/T = safepick(turfs) - if(!T) - to_chat(src, "Nowhere to jump to!") - return - usr.forceMove(T) - log_admin("[key_name(usr)] jumped to [AREACOORD(A)]") - message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]") - 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! - -/client/proc/jumptoturf(turf/T in world) - set name = "Jump to Turf" - set category = "Admin" - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - 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" - set name = "Jump to Mob" - - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - 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.") - -/client/proc/jumptocoord(tx as num, ty as num, tz as num) - set category = "Admin" - set name = "Jump to Coordinate" - - if (!holder) - to_chat(src, "Only administrators may use this command.") - 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" - set name = "Jump to Key" - - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - 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.") - 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" - set name = "Get Mob" - set desc = "Mob to teleport" - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - 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" - set name = "Get Key" - set desc = "Key to teleport" - - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - 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" - set name = "Send Mob" - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - return - var/area/A = input(usr, "Pick an area.", "Pick an area") in GLOB.sortedAreas|null - if(A && istype(A)) - if(M.forceMove(safepick(get_area_turfs(A)))) - - log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(A)]") - var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [AREACOORD(A)]" - message_admins(msg) - admin_ticket_log(M, msg) - else - to_chat(src, "Failed to move mob to a valid location.") - 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" + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + return + + if(!A) + return + + var/list/turfs = list() + for(var/turf/T in A) + if(T.density) + continue + turfs.Add(T) + + var/turf/T = safepick(turfs) + if(!T) + to_chat(src, "Nowhere to jump to!") + return + usr.forceMove(T) + log_admin("[key_name(usr)] jumped to [AREACOORD(A)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]") + 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! + +/client/proc/jumptoturf(turf/T in world) + set name = "Jump to Turf" + set category = "Admin" + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + 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" + set name = "Jump to Mob" + + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + 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.") + +/client/proc/jumptocoord(tx as num, ty as num, tz as num) + set category = "Admin" + set name = "Jump to Coordinate" + + if (!holder) + to_chat(src, "Only administrators may use this command.") + 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" + set name = "Jump to Key" + + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + 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.") + 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" + set name = "Get Mob" + set desc = "Mob to teleport" + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + 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" + set name = "Get Key" + set desc = "Key to teleport" + + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + 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" + set name = "Send Mob" + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + return + var/area/A = input(usr, "Pick an area.", "Pick an area") in GLOB.sortedAreas|null + if(A && istype(A)) + if(M.forceMove(safepick(get_area_turfs(A)))) + + log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(A)]") + var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [AREACOORD(A)]" + message_admins(msg) + admin_ticket_log(M, msg) + else + to_chat(src, "Failed to move mob to a valid location.") + 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 5f3153d90f..e2b994f769 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -1,325 +1,325 @@ -#define IRCREPLYCOUNT 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.") - 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.") - 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).") - return - var/client/C - if(istext(whom)) - if(cmptext(copytext(whom,1,2),"@")) - 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.") - return - - var/datum/admin_help/AH = C.current_ticket - - if(AH) - message_admins("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help.") - var/msg = input(src,"Message:", "Private message to [key_name(C, 0, 0)]") as message|null - if (!msg) - message_admins("[key_name_admin(src)] has cancelled their reply to [key_name(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).") - 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.") - to_chat(src, "Message: [msg]") - return - - var/client/recipient - var/irc = 0 - if(istext(whom)) - if(cmptext(copytext(whom,1,2),"@")) - whom = findStealthKey(whom) - if(whom == "IRCKEY") - irc = 1 - else - recipient = GLOB.directory[whom] - else if(istype(whom, /client)) - recipient = whom - - - if(irc) - if(!ircreplyamount) //to prevent people from spamming irc - return - if(!msg) - msg = input(src,"Message:", "Private message to Administrator") as text|null - - if(!msg) - return - if(holder) - to_chat(src, "Error: Use the admin IRC channel, nerd.") - return - - - else - if(!recipient) - if(holder) - to_chat(src, "Error: Admin-PM: Client not found.") - if(msg) - to_chat(src, msg) - 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 [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).") - return - - if(!recipient) - if(holder) - to_chat(src, "Error: Admin-PM: Client not found.") - 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)||irc)//no sending html to the poor bots - msg = trim(sanitize(copytext(msg,1,MAX_MESSAGE_LEN))) - if(!msg) - return - - var/rawmsg = msg - - if(holder) - msg = emoji_parse(msg) - - var/keywordparsedmsg = keywords_lookup(msg) - - if(irc) - to_chat(src, "PM to-Admins: [rawmsg]") - var/datum/admin_help/AH = admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to IRC: [keywordparsedmsg]") - ircreplyamount-- - send2irc("[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]") - to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]") - - //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) - - 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]") - to_chat(src, "PM to-Admins: [msg]") - - //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 - if(!recipient.current_ticket) - new /datum/admin_help(msg, recipient, TRUE) - - to_chat(recipient, "-- Administrator private message --") - to_chat(recipient, "Admin PM from-[key_name(src, recipient, 0)]: [msg]") - to_chat(recipient, "Click on the administrator's name to reply.") - to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]") - - admin_ticket_log(recipient, "PM From [key_name_admin(src)]: [keywordparsedmsg]") - - //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)) - spawn() //so we don't hold the caller proc up - var/sender = src - var/sendername = key - var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as text|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 - return - - else //neither are admins - to_chat(src, "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.") - return - - if(irc) - log_admin_private("PM: [key_name(src)]->IRC: [rawmsg]") - for(var/client/X in GLOB.admins) - to_chat(X, "PM: [key_name(src, X, 0)]->IRC: [keywordparsedmsg]") - 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]" ) - - - -#define IRC_AHELP_USAGE "Usage: ticket " -/proc/IrcPm(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/irc_tagged = "[sender](IRC)" - var/list/splits = splittext(compliant_msg, " ") - if(splits.len && splits[1] == "ticket") - if(splits.len < 2) - return IRC_AHELP_USAGE - switch(splits[2]) - if("close") - if(ticket) - ticket.Close(irc_tagged) - return "Ticket #[ticket.id] successfully closed" - if("resolve") - if(ticket) - ticket.Resolve(irc_tagged) - return "Ticket #[ticket.id] successfully resolved" - if("icissue") - if(ticket) - ticket.ICIssue(irc_tagged) - return "Ticket #[ticket.id] successfully marked as IC issue" - if("reject") - if(ticket) - ticket.Reject(irc_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. [IRC_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 IRC_AHELP_USAGE - return "Error: Ticket could not be found" - - var/static/stealthkey - var/adminname = CONFIG_GET(flag/show_irc_name) ? irc_tagged : "Administrator" - - if(!C) - return "Error: No client" - - if(!stealthkey) - stealthkey = GenIrcStealthKey() - - msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN)) - if(!msg) - return "Error: No message" - - message_admins("IRC message from [sender] to [key_name_admin(C)] : [msg]") - log_admin_private("IRC PM: [sender] -> [key_name(C)] : [msg]") - msg = emoji_parse(msg) - - to_chat(C, "-- Administrator private message --") - to_chat(C, "Admin PM from-[adminname]: [msg]") - to_chat(C, "Click on the administrator's name to reply.") - - admin_ticket_log(C, "PM From [irc_tagged]: [msg]") - - window_flash(C, ignorepref = TRUE) - //always play non-admin recipients the adminhelp sound - SEND_SOUND(C, 'sound/effects/adminhelp.ogg') - - C.ircreplyamount = IRCREPLYCOUNT - - return "Message Successful" - -/proc/GenIrcStealthKey() - 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 IRCREPLYCOUNT +#define IRCREPLYCOUNT 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.") + 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.") + 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).") + return + var/client/C + if(istext(whom)) + if(cmptext(copytext(whom,1,2),"@")) + 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.") + return + + var/datum/admin_help/AH = C.current_ticket + + if(AH) + message_admins("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help.") + var/msg = input(src,"Message:", "Private message to [key_name(C, 0, 0)]") as message|null + if (!msg) + message_admins("[key_name_admin(src)] has cancelled their reply to [key_name(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).") + 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.") + to_chat(src, "Message: [msg]") + return + + var/client/recipient + var/irc = 0 + if(istext(whom)) + if(cmptext(copytext(whom,1,2),"@")) + whom = findStealthKey(whom) + if(whom == "IRCKEY") + irc = 1 + else + recipient = GLOB.directory[whom] + else if(istype(whom, /client)) + recipient = whom + + + if(irc) + if(!ircreplyamount) //to prevent people from spamming irc + return + if(!msg) + msg = input(src,"Message:", "Private message to Administrator") as text|null + + if(!msg) + return + if(holder) + to_chat(src, "Error: Use the admin IRC channel, nerd.") + return + + + else + if(!recipient) + if(holder) + to_chat(src, "Error: Admin-PM: Client not found.") + if(msg) + to_chat(src, msg) + 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 [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).") + return + + if(!recipient) + if(holder) + to_chat(src, "Error: Admin-PM: Client not found.") + 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)||irc)//no sending html to the poor bots + msg = trim(sanitize(copytext(msg,1,MAX_MESSAGE_LEN))) + if(!msg) + return + + var/rawmsg = msg + + if(holder) + msg = emoji_parse(msg) + + var/keywordparsedmsg = keywords_lookup(msg) + + if(irc) + to_chat(src, "PM to-Admins: [rawmsg]") + var/datum/admin_help/AH = admin_ticket_log(src, "Reply PM from-[key_name(src, TRUE, TRUE)] to IRC: [keywordparsedmsg]") + ircreplyamount-- + send2irc("[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]") + to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [keywordparsedmsg]") + + //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) + + 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]") + to_chat(src, "PM to-Admins: [msg]") + + //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 + if(!recipient.current_ticket) + new /datum/admin_help(msg, recipient, TRUE) + + to_chat(recipient, "-- Administrator private message --") + to_chat(recipient, "Admin PM from-[key_name(src, recipient, 0)]: [msg]") + to_chat(recipient, "Click on the administrator's name to reply.") + to_chat(src, "Admin PM to-[key_name(recipient, src, 1)]: [msg]") + + admin_ticket_log(recipient, "PM From [key_name_admin(src)]: [keywordparsedmsg]") + + //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)) + spawn() //so we don't hold the caller proc up + var/sender = src + var/sendername = key + var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as text|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 + return + + else //neither are admins + to_chat(src, "Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.") + return + + if(irc) + log_admin_private("PM: [key_name(src)]->IRC: [rawmsg]") + for(var/client/X in GLOB.admins) + to_chat(X, "PM: [key_name(src, X, 0)]->IRC: [keywordparsedmsg]") + 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]" ) + + + +#define IRC_AHELP_USAGE "Usage: ticket " +/proc/IrcPm(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/irc_tagged = "[sender](IRC)" + var/list/splits = splittext(compliant_msg, " ") + if(splits.len && splits[1] == "ticket") + if(splits.len < 2) + return IRC_AHELP_USAGE + switch(splits[2]) + if("close") + if(ticket) + ticket.Close(irc_tagged) + return "Ticket #[ticket.id] successfully closed" + if("resolve") + if(ticket) + ticket.Resolve(irc_tagged) + return "Ticket #[ticket.id] successfully resolved" + if("icissue") + if(ticket) + ticket.ICIssue(irc_tagged) + return "Ticket #[ticket.id] successfully marked as IC issue" + if("reject") + if(ticket) + ticket.Reject(irc_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. [IRC_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 IRC_AHELP_USAGE + return "Error: Ticket could not be found" + + var/static/stealthkey + var/adminname = CONFIG_GET(flag/show_irc_name) ? irc_tagged : "Administrator" + + if(!C) + return "Error: No client" + + if(!stealthkey) + stealthkey = GenIrcStealthKey() + + msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN)) + if(!msg) + return "Error: No message" + + message_admins("IRC message from [sender] to [key_name_admin(C)] : [msg]") + log_admin_private("IRC PM: [sender] -> [key_name(C)] : [msg]") + msg = emoji_parse(msg) + + to_chat(C, "-- Administrator private message --") + to_chat(C, "Admin PM from-[adminname]: [msg]") + to_chat(C, "Click on the administrator's name to reply.") + + admin_ticket_log(C, "PM From [irc_tagged]: [msg]") + + window_flash(C, ignorepref = TRUE) + //always play non-admin recipients the adminhelp sound + SEND_SOUND(C, 'sound/effects/adminhelp.ogg') + + C.ircreplyamount = IRCREPLYCOUNT + + return "Message Successful" + +/proc/GenIrcStealthKey() + 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 IRCREPLYCOUNT diff --git a/code/modules/admin/verbs/adminsay.dm b/code/modules/admin/verbs/adminsay.dm index 84264a1b36..1ebd632c23 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 = "Special Verbs" - 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 = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN) - if(!msg) - return - msg = emoji_parse(msg) - mob.log_talk(msg, LOG_ASAY) - - msg = keywords_lookup(msg) - msg = "ADMIN: [key_name(usr, 1)] [ADMIN_FLW(mob)]: [msg]" - to_chat(GLOB.admins, msg) - - 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 - cmd_admin_say(msg) +/client/proc/cmd_admin_say(msg as text) + set category = "Special Verbs" + 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 = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN) + if(!msg) + return + msg = emoji_parse(msg) + mob.log_talk(msg, LOG_ASAY) + + msg = keywords_lookup(msg) + msg = "ADMIN: [key_name(usr, 1)] [ADMIN_FLW(mob)]: [msg]" + to_chat(GLOB.admins, msg) + + 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 + cmd_admin_say(msg) diff --git a/code/modules/admin/verbs/atmosdebug.dm b/code/modules/admin/verbs/atmosdebug.dm index 75407eae9c..b075353076 100644 --- a/code/modules/admin/verbs/atmosdebug.dm +++ b/code/modules/admin/verbs/atmosdebug.dm @@ -1,41 +1,41 @@ -/client/proc/atmosscan() - set category = "Mapping" - set name = "Check Plumbing" - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - 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/plumbing in GLOB.machines) - if (plumbing.nodealert) - to_chat(usr, "Unconnected [plumbing.name] located at [ADMIN_VERBOSEJMP(plumbing)]") - - //Manifolds - for (var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines) - if (!pipe.nodes[1] || !pipe.nodes[2] || !pipe.nodes[3]) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]") - - //Pipes - for (var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines) - if (!pipe.nodes[1] || !pipe.nodes[2]) - to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]") - -/client/proc/powerdebug() - set category = "Mapping" - set name = "Check Power" - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - 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! - - 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] - to_chat(usr, "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] - to_chat(usr, "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]") +/client/proc/atmosscan() + set category = "Mapping" + set name = "Check Plumbing" + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + 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/plumbing in GLOB.machines) + if (plumbing.nodealert) + to_chat(usr, "Unconnected [plumbing.name] located at [ADMIN_VERBOSEJMP(plumbing)]") + + //Manifolds + for (var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines) + if (!pipe.nodes[1] || !pipe.nodes[2] || !pipe.nodes[3]) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]") + + //Pipes + for (var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines) + if (!pipe.nodes[1] || !pipe.nodes[2]) + to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]") + +/client/proc/powerdebug() + set category = "Mapping" + set name = "Check Power" + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + 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! + + 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] + to_chat(usr, "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] + to_chat(usr, "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]") diff --git a/code/modules/admin/verbs/cinematic.dm b/code/modules/admin/verbs/cinematic.dm index 5aa3258f07..9b27a8d8e9 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 subtypesof(/datum/cinematic) - if(choice) +/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 subtypesof(/datum/cinematic) + if(choice) Cinematic(initial(choice.id),world,null) \ No newline at end of file diff --git a/code/modules/admin/verbs/deadsay.dm b/code/modules/admin/verbs/deadsay.dm index 30afa659f5..52c5284cbb 100644 --- a/code/modules/admin/verbs/deadsay.dm +++ b/code/modules/admin/verbs/deadsay.dm @@ -1,36 +1,36 @@ -/client/proc/dsay(msg as text) - set category = "Special Verbs" - set name = "Dsay" - set hidden = 1 - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - return - if(!src.mob) - return - if(prefs.muted & MUTE_DEADCHAT) - to_chat(src, "You cannot send DSAY messages (muted).") - return - - if (src.handle_spam_prevention(msg,MUTE_DEADCHAT)) - return - - msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN) - mob.log_talk(msg, LOG_DSAY) - - if (!msg) - return - var/static/nicknames = world.file2list("[global.config.directory]/admin_nicknames.txt") - - var/rendered = "DEAD: [uppertext(holder.rank)]([src.holder.fakekey ? pick(nicknames) : src.key]) says, \"[emoji_parse(msg)]\"" - - for (var/mob/M in GLOB.player_list) - if(isnewplayer(M)) - continue - if (M.stat == DEAD || (M.client && 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) - - 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 - dsay(msg) +/client/proc/dsay(msg as text) + set category = "Special Verbs" + set name = "Dsay" + set hidden = 1 + if(!src.holder) + to_chat(src, "Only administrators may use this command.") + return + if(!src.mob) + return + if(prefs.muted & MUTE_DEADCHAT) + to_chat(src, "You cannot send DSAY messages (muted).") + return + + if (src.handle_spam_prevention(msg,MUTE_DEADCHAT)) + return + + msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN) + mob.log_talk(msg, LOG_DSAY) + + if (!msg) + return + var/static/nicknames = world.file2list("[global.config.directory]/admin_nicknames.txt") + + var/rendered = "DEAD: [uppertext(holder.rank)]([src.holder.fakekey ? pick(nicknames) : src.key]) says, \"[emoji_parse(msg)]\"" + + for (var/mob/M in GLOB.player_list) + if(isnewplayer(M)) + continue + if (M.stat == DEAD || (M.client && 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) + + 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 + dsay(msg) diff --git a/code/modules/admin/verbs/fps.dm b/code/modules/admin/verbs/fps.dm index ce72889a20..2b65d2a44c 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.") - 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.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.") + 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.fps = new_fps diff --git a/code/modules/admin/verbs/getlogs.dm b/code/modules/admin/verbs/getlogs.dm index 5f7b7becc6..21a722d32f 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("[GLOB.log_directory]/") - -/client/proc/browseserverlogs(path = "data/logs/") - path = browse_files(path) - 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.") +//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("[GLOB.log_directory]/") + +/client/proc/browseserverlogs(path = "data/logs/") + path = browse_files(path) + 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.") return \ No newline at end of file diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index acd320856a..24e80fd8f3 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/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(5,6,9,10) ) - 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.") - else - for(var/t in GLOB.active_turfs_startlist) - new /obj/effect/abstract/marker/at(t) - count++ - to_chat(usr, "[count] AT markers placed.") - - 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 - 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 - 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]") - 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 - 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") - 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("")) +//- 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/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(5,6,9,10) ) + 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.") + else + for(var/t in GLOB.active_turfs_startlist) + new /obj/effect/abstract/marker/at(t) + count++ + to_chat(usr, "[count] AT markers placed.") + + 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 + 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 + 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]") + 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 + 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") + 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("")) diff --git a/code/modules/admin/verbs/massmodvar.dm b/code/modules/admin/verbs/massmodvar.dm index 75050b5e62..ba199350e3 100644 --- a/code/modules/admin/verbs/massmodvar.dm +++ b/code/modules/admin/verbs/massmodvar.dm @@ -1,265 +1,265 @@ -/client/proc/cmd_mass_modify_object_variables(atom/A, var_name) - set category = "Debug" - set name = "Mass Edit Variables" - set desc="(target) Edit all instances of a target item's variables" - - var/method = 0 //0 means strict type detection while 1 means this type and all subtypes (IE: /obj/item with this set to 1 will set it to ALL items) - - if(!check_rights(R_VAREDIT)) - return - - if(A && A.type) - method = vv_subtype_prompt(A.type) - - src.massmodify_variables(A, var_name, method) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Edit Variables") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -/client/proc/massmodify_variables(datum/O, var_name = "", method = 0) - if(!check_rights(R_VAREDIT)) - return - if(!istype(O)) - return - - var/variable = "" - if(!var_name) - var/list/names = list() - for (var/V in O.vars) - names += V - - names = sortList(names) - - variable = input("Which var?", "Var") as null|anything in names - else - variable = var_name - - if(!variable || !O.can_vv_get(variable)) - return - var/default - var/var_value = O.vars[variable] - - if(variable in GLOB.VVckey_edit) - to_chat(src, "It's forbidden to mass-modify ckeys. It'll crash everyone's client you dummy.") - return - if(variable in GLOB.VVlocked) - if(!check_rights(R_DEBUG)) - return - if(variable in GLOB.VVicon_edit_lock) - if(!check_rights(R_FUN|R_DEBUG)) - return - if(variable in GLOB.VVpixelmovement) - if(!check_rights(R_DEBUG)) - return - var/prompt = alert(src, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT") - if (prompt != "Continue") - return - - default = vv_get_class(variable, var_value) - - if(isnull(default)) - to_chat(src, "Unable to determine variable type.") - else - to_chat(src, "Variable appears to be [uppertext(default)].") - - to_chat(src, "Variable contains: [var_value]") - - if(default == VV_NUM) - var/dir_text = "" - if(var_value > 0 && var_value < 16) - if(var_value & 1) - dir_text += "NORTH" - if(var_value & 2) - dir_text += "SOUTH" - if(var_value & 4) - dir_text += "EAST" - if(var_value & 8) - dir_text += "WEST" - - if(dir_text) - to_chat(src, "If a direction, direction is: [dir_text]") - - var/value = vv_get_value(default_class = default) - var/new_value = value["value"] - var/class = value["class"] - - if(!class || !new_value == null && class != VV_NULL) - return - - if (class == VV_MESSAGE) - class = VV_TEXT - - if (value["type"]) - class = VV_NEW_TYPE - - var/original_name = "[O]" - - var/rejected = 0 - var/accepted = 0 - - switch(class) - if(VV_RESTORE_DEFAULT) - to_chat(src, "Finding items...") - var/list/items = get_all_of_type(O.type, method) - to_chat(src, "Changing [items.len] items...") - for(var/thing in items) - if (!thing) - continue - var/datum/D = thing - if (D.vv_edit_var(variable, initial(D.vars[variable])) != FALSE) - accepted++ - else - rejected++ - CHECK_TICK - - if(VV_TEXT) - var/list/varsvars = vv_parse_text(O, new_value) - var/pre_processing = new_value - var/unique - if (varsvars && varsvars.len) - unique = alert(usr, "Process vars unique to each instance, or same for all?", "Variable Association", "Unique", "Same") - if(unique == "Unique") - unique = TRUE - else - unique = FALSE - for(var/V in varsvars) - new_value = replacetext(new_value,"\[[V]]","[O.vars[V]]") - - to_chat(src, "Finding items...") - var/list/items = get_all_of_type(O.type, method) - to_chat(src, "Changing [items.len] items...") - for(var/thing in items) - if (!thing) - continue - var/datum/D = thing - if(unique) - new_value = pre_processing - for(var/V in varsvars) - new_value = replacetext(new_value,"\[[V]]","[D.vars[V]]") - - if (D.vv_edit_var(variable, new_value) != FALSE) - accepted++ - else - rejected++ - CHECK_TICK - - if (VV_NEW_TYPE) - var/many = alert(src, "Create only one [value["type"]] and assign each or a new one for each thing", "How Many", "One", "Many", "Cancel") - if (many == "Cancel") - return - if (many == "Many") - many = TRUE - else - many = FALSE - - var/type = value["type"] - to_chat(src, "Finding items...") - var/list/items = get_all_of_type(O.type, method) - to_chat(src, "Changing [items.len] items...") - for(var/thing in items) - if (!thing) - continue - var/datum/D = thing - if(many && !new_value) - new_value = new type() - - if (D.vv_edit_var(variable, new_value) != FALSE) - accepted++ - else - rejected++ - new_value = null - CHECK_TICK - - else - to_chat(src, "Finding items...") - var/list/items = get_all_of_type(O.type, method) - to_chat(src, "Changing [items.len] items...") - for(var/thing in items) - if (!thing) - continue - var/datum/D = thing - if (D.vv_edit_var(variable, new_value) != FALSE) - accepted++ - else - rejected++ - CHECK_TICK - - - var/count = rejected+accepted - if (!count) - to_chat(src, "No objects found") - return - if (!accepted) - to_chat(src, "Every object rejected your edit") - return - if (rejected) - to_chat(src, "[rejected] out of [count] objects rejected your edit") - - log_world("### MassVarEdit by [src]: [O.type] (A/R [accepted]/[rejected]) [variable]=[html_encode("[O.vars[variable]]")]([list2params(value)])") - log_admin("[key_name(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)") - message_admins("[key_name_admin(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)") - - -/proc/get_all_of_type(var/T, subtypes = TRUE) - var/list/typecache = list() - typecache[T] = 1 - if (subtypes) - typecache = typecacheof(typecache) - . = list() - if (ispath(T, /mob)) - for(var/mob/thing in GLOB.mob_list) - if (typecache[thing.type]) - . += thing - CHECK_TICK - - else if (ispath(T, /obj/machinery/door)) - for(var/obj/machinery/door/thing in GLOB.airlocks) - if (typecache[thing.type]) - . += thing - CHECK_TICK - - else if (ispath(T, /obj/machinery)) - for(var/obj/machinery/thing in GLOB.machines) - if (typecache[thing.type]) - . += thing - CHECK_TICK - - else if (ispath(T, /obj)) - for(var/obj/thing in world) - if (typecache[thing.type]) - . += thing - CHECK_TICK - - else if (ispath(T, /atom/movable)) - for(var/atom/movable/thing in world) - if (typecache[thing.type]) - . += thing - CHECK_TICK - - else if (ispath(T, /turf)) - for(var/turf/thing in world) - if (typecache[thing.type]) - . += thing - CHECK_TICK - - else if (ispath(T, /atom)) - for(var/atom/thing in world) - if (typecache[thing.type]) - . += thing - CHECK_TICK - - else if (ispath(T, /client)) - for(var/client/thing in GLOB.clients) - if (typecache[thing.type]) - . += thing - CHECK_TICK - - else if (ispath(T, /datum)) - for(var/datum/thing) - if (typecache[thing.type]) - . += thing - CHECK_TICK - - else - for(var/datum/thing in world) - if (typecache[thing.type]) - . += thing - CHECK_TICK +/client/proc/cmd_mass_modify_object_variables(atom/A, var_name) + set category = "Debug" + set name = "Mass Edit Variables" + set desc="(target) Edit all instances of a target item's variables" + + var/method = 0 //0 means strict type detection while 1 means this type and all subtypes (IE: /obj/item with this set to 1 will set it to ALL items) + + if(!check_rights(R_VAREDIT)) + return + + if(A && A.type) + method = vv_subtype_prompt(A.type) + + src.massmodify_variables(A, var_name, method) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Edit Variables") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/massmodify_variables(datum/O, var_name = "", method = 0) + if(!check_rights(R_VAREDIT)) + return + if(!istype(O)) + return + + var/variable = "" + if(!var_name) + var/list/names = list() + for (var/V in O.vars) + names += V + + names = sortList(names) + + variable = input("Which var?", "Var") as null|anything in names + else + variable = var_name + + if(!variable || !O.can_vv_get(variable)) + return + var/default + var/var_value = O.vars[variable] + + if(variable in GLOB.VVckey_edit) + to_chat(src, "It's forbidden to mass-modify ckeys. It'll crash everyone's client you dummy.") + return + if(variable in GLOB.VVlocked) + if(!check_rights(R_DEBUG)) + return + if(variable in GLOB.VVicon_edit_lock) + if(!check_rights(R_FUN|R_DEBUG)) + return + if(variable in GLOB.VVpixelmovement) + if(!check_rights(R_DEBUG)) + return + var/prompt = alert(src, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT") + if (prompt != "Continue") + return + + default = vv_get_class(variable, var_value) + + if(isnull(default)) + to_chat(src, "Unable to determine variable type.") + else + to_chat(src, "Variable appears to be [uppertext(default)].") + + to_chat(src, "Variable contains: [var_value]") + + if(default == VV_NUM) + var/dir_text = "" + if(var_value > 0 && var_value < 16) + if(var_value & 1) + dir_text += "NORTH" + if(var_value & 2) + dir_text += "SOUTH" + if(var_value & 4) + dir_text += "EAST" + if(var_value & 8) + dir_text += "WEST" + + if(dir_text) + to_chat(src, "If a direction, direction is: [dir_text]") + + var/value = vv_get_value(default_class = default) + var/new_value = value["value"] + var/class = value["class"] + + if(!class || !new_value == null && class != VV_NULL) + return + + if (class == VV_MESSAGE) + class = VV_TEXT + + if (value["type"]) + class = VV_NEW_TYPE + + var/original_name = "[O]" + + var/rejected = 0 + var/accepted = 0 + + switch(class) + if(VV_RESTORE_DEFAULT) + to_chat(src, "Finding items...") + var/list/items = get_all_of_type(O.type, method) + to_chat(src, "Changing [items.len] items...") + for(var/thing in items) + if (!thing) + continue + var/datum/D = thing + if (D.vv_edit_var(variable, initial(D.vars[variable])) != FALSE) + accepted++ + else + rejected++ + CHECK_TICK + + if(VV_TEXT) + var/list/varsvars = vv_parse_text(O, new_value) + var/pre_processing = new_value + var/unique + if (varsvars && varsvars.len) + unique = alert(usr, "Process vars unique to each instance, or same for all?", "Variable Association", "Unique", "Same") + if(unique == "Unique") + unique = TRUE + else + unique = FALSE + for(var/V in varsvars) + new_value = replacetext(new_value,"\[[V]]","[O.vars[V]]") + + to_chat(src, "Finding items...") + var/list/items = get_all_of_type(O.type, method) + to_chat(src, "Changing [items.len] items...") + for(var/thing in items) + if (!thing) + continue + var/datum/D = thing + if(unique) + new_value = pre_processing + for(var/V in varsvars) + new_value = replacetext(new_value,"\[[V]]","[D.vars[V]]") + + if (D.vv_edit_var(variable, new_value) != FALSE) + accepted++ + else + rejected++ + CHECK_TICK + + if (VV_NEW_TYPE) + var/many = alert(src, "Create only one [value["type"]] and assign each or a new one for each thing", "How Many", "One", "Many", "Cancel") + if (many == "Cancel") + return + if (many == "Many") + many = TRUE + else + many = FALSE + + var/type = value["type"] + to_chat(src, "Finding items...") + var/list/items = get_all_of_type(O.type, method) + to_chat(src, "Changing [items.len] items...") + for(var/thing in items) + if (!thing) + continue + var/datum/D = thing + if(many && !new_value) + new_value = new type() + + if (D.vv_edit_var(variable, new_value) != FALSE) + accepted++ + else + rejected++ + new_value = null + CHECK_TICK + + else + to_chat(src, "Finding items...") + var/list/items = get_all_of_type(O.type, method) + to_chat(src, "Changing [items.len] items...") + for(var/thing in items) + if (!thing) + continue + var/datum/D = thing + if (D.vv_edit_var(variable, new_value) != FALSE) + accepted++ + else + rejected++ + CHECK_TICK + + + var/count = rejected+accepted + if (!count) + to_chat(src, "No objects found") + return + if (!accepted) + to_chat(src, "Every object rejected your edit") + return + if (rejected) + to_chat(src, "[rejected] out of [count] objects rejected your edit") + + log_world("### MassVarEdit by [src]: [O.type] (A/R [accepted]/[rejected]) [variable]=[html_encode("[O.vars[variable]]")]([list2params(value)])") + log_admin("[key_name(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)") + message_admins("[key_name_admin(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)") + + +/proc/get_all_of_type(var/T, subtypes = TRUE) + var/list/typecache = list() + typecache[T] = 1 + if (subtypes) + typecache = typecacheof(typecache) + . = list() + if (ispath(T, /mob)) + for(var/mob/thing in GLOB.mob_list) + if (typecache[thing.type]) + . += thing + CHECK_TICK + + else if (ispath(T, /obj/machinery/door)) + for(var/obj/machinery/door/thing in GLOB.airlocks) + if (typecache[thing.type]) + . += thing + CHECK_TICK + + else if (ispath(T, /obj/machinery)) + for(var/obj/machinery/thing in GLOB.machines) + if (typecache[thing.type]) + . += thing + CHECK_TICK + + else if (ispath(T, /obj)) + for(var/obj/thing in world) + if (typecache[thing.type]) + . += thing + CHECK_TICK + + else if (ispath(T, /atom/movable)) + for(var/atom/movable/thing in world) + if (typecache[thing.type]) + . += thing + CHECK_TICK + + else if (ispath(T, /turf)) + for(var/turf/thing in world) + if (typecache[thing.type]) + . += thing + CHECK_TICK + + else if (ispath(T, /atom)) + for(var/atom/thing in world) + if (typecache[thing.type]) + . += thing + CHECK_TICK + + else if (ispath(T, /client)) + for(var/client/thing in GLOB.clients) + if (typecache[thing.type]) + . += thing + CHECK_TICK + + else if (ispath(T, /datum)) + for(var/datum/thing) + if (typecache[thing.type]) + . += thing + CHECK_TICK + + else + for(var/datum/thing in world) + if (typecache[thing.type]) + . += thing + CHECK_TICK diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index 644b2729e0..611ebcf7bf 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -1,644 +1,644 @@ -GLOBAL_LIST_INIT(VVlocked, list("vars", "datum_flags", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "force_ending")) -GLOBAL_PROTECT(VVlocked) -GLOBAL_LIST_INIT(VVicon_edit_lock, list("icon", "icon_state", "overlays", "underlays", "resize")) -GLOBAL_PROTECT(VVicon_edit_lock) -GLOBAL_LIST_INIT(VVckey_edit, list("key", "ckey")) -GLOBAL_PROTECT(VVckey_edit) -GLOBAL_LIST_INIT(VVpixelmovement, list("step_x", "step_y", "bound_height", "bound_width", "bound_x", "bound_y")) -GLOBAL_PROTECT(VVpixelmovement) - - -/client/proc/vv_get_class(var/var_name, var/var_value) - if(isnull(var_value)) - . = VV_NULL - - else if (isnum(var_value)) - if (var_name in GLOB.bitfields) - . = VV_BITFIELD - else - . = VV_NUM - - else if (istext(var_value)) - if (findtext(var_value, "\n")) - . = VV_MESSAGE - else - . = VV_TEXT - - else if (isicon(var_value)) - . = VV_ICON - - else if (ismob(var_value)) - . = VV_MOB_REFERENCE - - else if (isloc(var_value)) - . = VV_ATOM_REFERENCE - - else if (istype(var_value, /client)) - . = VV_CLIENT - - else if (istype(var_value, /datum)) - . = VV_DATUM_REFERENCE - - else if (ispath(var_value)) - if (ispath(var_value, /atom)) - . = VV_ATOM_TYPE - else if (ispath(var_value, /datum)) - . = VV_DATUM_TYPE - else - . = VV_TYPE - - else if (islist(var_value)) - . = VV_LIST - - else if (isfile(var_value)) - . = VV_FILE - else - . = VV_NULL - -/client/proc/vv_get_value(class, default_class, current_value, list/restricted_classes, list/extra_classes, list/classes, var_name) - . = list("class" = class, "value" = null) - if (!class) - if (!classes) - classes = list ( - VV_NUM, - VV_TEXT, - VV_MESSAGE, - VV_ICON, - VV_ATOM_REFERENCE, - VV_DATUM_REFERENCE, - VV_MOB_REFERENCE, - VV_CLIENT, - VV_ATOM_TYPE, - VV_DATUM_TYPE, - VV_TYPE, - VV_FILE, - VV_NEW_ATOM, - VV_NEW_DATUM, - VV_NEW_TYPE, - VV_NEW_LIST, - VV_NULL, - VV_RESTORE_DEFAULT - ) - - if(holder && holder.marked_datum && !(VV_MARKED_DATUM in restricted_classes)) - classes += "[VV_MARKED_DATUM] ([holder.marked_datum.type])" - if (restricted_classes) - classes -= restricted_classes - - if (extra_classes) - classes += extra_classes - - .["class"] = input(src, "What kind of data?", "Variable Type", default_class) as null|anything in classes - if (holder && holder.marked_datum && .["class"] == "[VV_MARKED_DATUM] ([holder.marked_datum.type])") - .["class"] = VV_MARKED_DATUM - - - switch(.["class"]) - if (VV_TEXT) - .["value"] = input("Enter new text:", "Text", current_value) as null|text - if (.["value"] == null) - .["class"] = null - return - if (VV_MESSAGE) - .["value"] = input("Enter new text:", "Text", current_value) as null|message - if (.["value"] == null) - .["class"] = null - return - - - if (VV_NUM) - .["value"] = input("Enter new number:", "Num", current_value) as null|num - if (.["value"] == null) - .["class"] = null - return - - if (VV_BITFIELD) - .["value"] = input_bitfield(usr, "Editing bitfield: [var_name]", var_name, current_value) - if (.["value"] == null) - .["class"] = null - return - - if (VV_ATOM_TYPE) - .["value"] = pick_closest_path(FALSE) - if (.["value"] == null) - .["class"] = null - return - - if (VV_DATUM_TYPE) - .["value"] = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) - if (.["value"] == null) - .["class"] = null - return - - if (VV_TYPE) - var/type = current_value - var/error = "" - do - type = input("Enter type:[error]", "Type", type) as null|text - if (!type) - break - type = text2path(type) - error = "\nType not found, Please try again" - while(!type) - if (!type) - .["class"] = null - return - .["value"] = type - - - if (VV_ATOM_REFERENCE) - var/type = pick_closest_path(FALSE) - var/subtypes = vv_subtype_prompt(type) - if (subtypes == null) - .["class"] = null - return - var/list/things = vv_reference_list(type, subtypes) - var/value = input("Select reference:", "Reference", current_value) as null|anything in things - if (!value) - .["class"] = null - return - .["value"] = things[value] - - if (VV_DATUM_REFERENCE) - var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) - var/subtypes = vv_subtype_prompt(type) - if (subtypes == null) - .["class"] = null - return - var/list/things = vv_reference_list(type, subtypes) - var/value = input("Select reference:", "Reference", current_value) as null|anything in things - if (!value) - .["class"] = null - return - .["value"] = things[value] - - if (VV_MOB_REFERENCE) - var/type = pick_closest_path(FALSE, make_types_fancy(typesof(/mob))) - var/subtypes = vv_subtype_prompt(type) - if (subtypes == null) - .["class"] = null - return - var/list/things = vv_reference_list(type, subtypes) - var/value = input("Select reference:", "Reference", current_value) as null|anything in things - if (!value) - .["class"] = null - return - .["value"] = things[value] - - - - if (VV_CLIENT) - .["value"] = input("Select reference:", "Reference", current_value) as null|anything in GLOB.clients - if (.["value"] == null) - .["class"] = null - return - - - if (VV_FILE) - .["value"] = input("Pick file:", "File") as null|file - if (.["value"] == null) - .["class"] = null - return - - - if (VV_ICON) - .["value"] = input("Pick icon:", "Icon") as null|icon - if (.["value"] == null) - .["class"] = null - return - - - if (VV_MARKED_DATUM) - .["value"] = holder.marked_datum - if (.["value"] == null) - .["class"] = null - return - - - if (VV_NEW_ATOM) - var/type = pick_closest_path(FALSE) - if (!type) - .["class"] = null - return - .["type"] = type - var/atom/newguy = new type() - newguy.datum_flags |= DF_VAR_EDITED - .["value"] = newguy - - if (VV_NEW_DATUM) - var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) - if (!type) - .["class"] = null - return - .["type"] = type - var/datum/newguy = new type() - newguy.datum_flags |= DF_VAR_EDITED - .["value"] = newguy - - if (VV_NEW_TYPE) - var/type = current_value - var/error = "" - do - type = input("Enter type:[error]", "Type", type) as null|text - if (!type) - break - type = text2path(type) - error = "\nType not found, Please try again" - while(!type) - if (!type) - .["class"] = null - return - .["type"] = type - var/datum/newguy = new type() - if(istype(newguy)) - newguy.datum_flags |= DF_VAR_EDITED - .["value"] = newguy - - - if (VV_NEW_LIST) - .["value"] = list() - .["type"] = /list - -/client/proc/vv_parse_text(O, new_var) - if(O && findtext(new_var,"\[")) - var/process_vars = alert(usr,"\[] detected in string, process as variables?","Process Variables?","Yes","No") - if(process_vars == "Yes") - . = string2listofvars(new_var, O) - -//do they want you to include subtypes? -//FALSE = no subtypes, strict exact type pathing (or the type doesn't have subtypes) -//TRUE = Yes subtypes -//NULL = User cancelled at the prompt or invalid type given -/client/proc/vv_subtype_prompt(var/type) - if (!ispath(type)) - return - var/list/subtypes = subtypesof(type) - if (!subtypes || !subtypes.len) - return FALSE - if (subtypes && subtypes.len) - switch(alert("Strict object type detection?", "Type detection", "Strictly this type","This type and subtypes", "Cancel")) - if("Strictly this type") - return FALSE - if("This type and subtypes") - return TRUE - else - return - -/client/proc/vv_reference_list(type, subtypes) - . = list() - var/list/types = list(type) - if (subtypes) - types = typesof(type) - - var/list/fancytypes = make_types_fancy(types) - - for(var/fancytype in fancytypes) //swap the assoication - types[fancytypes[fancytype]] = fancytype - - var/things = get_all_of_type(type, subtypes) - - var/i = 0 - for(var/thing in things) - var/datum/D = thing - i++ - //try one of 3 methods to shorten the type text: - // fancy type, - // fancy type with the base type removed from the begaining, - // the type with the base type removed from the begaining - var/fancytype = types[D.type] - if (findtext(fancytype, types[type])) - fancytype = copytext(fancytype, length(types[type])+1) - var/shorttype = copytext("[D.type]", length("[type]")+1) - if (length(shorttype) > length(fancytype)) - shorttype = fancytype - if (!length(shorttype)) - shorttype = "/" - - .["[D]([shorttype])[REF(D)]#[i]"] = D - -/client/proc/mod_list_add_ass(atom/O) //hehe - - var/list/L = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT)) - var/class = L["class"] - if (!class) - return - var/var_value = L["value"] - - if(class == VV_TEXT || class == VV_MESSAGE) - var/list/varsvars = vv_parse_text(O, var_value) - for(var/V in varsvars) - var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]") - - return var_value - - -/client/proc/mod_list_add(list/L, atom/O, original_name, objectvar) - var/list/LL = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT)) - var/class = LL["class"] - if (!class) - return - var/var_value = LL["value"] - - if(class == VV_TEXT || class == VV_MESSAGE) - var/list/varsvars = vv_parse_text(O, var_value) - for(var/V in varsvars) - var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]") - - if (O) - L = L.Copy() - - L += var_value - - switch(alert("Would you like to associate a value with the list entry?",,"Yes","No")) - if("Yes") - L[var_value] = mod_list_add_ass(O) //hehe - if (O) - if (O.vv_edit_var(objectvar, L) == FALSE) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: ADDED=[var_value]") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]") - message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]") - -/client/proc/mod_list(list/L, atom/O, original_name, objectvar, index, autodetect_class = FALSE) - if(!check_rights(R_VAREDIT)) - return - if(!istype(L, /list)) - to_chat(src, "Not a List.") - return - - if(L.len > 1000) - var/confirm = alert(src, "The list you're trying to edit is very long, continuing may crash the server.", "Warning", "Continue", "Abort") - if(confirm != "Continue") - return - - - - var/list/names = list() - for (var/i in 1 to L.len) - var/key = L[i] - var/value - if (IS_NORMAL_LIST(L) && !isnum(key)) - value = L[key] - if (value == null) - value = "null" - names["#[i] [key] = [value]"] = i - if (!index) - var/variable = input("Which var?","Var") as null|anything in names + "(ADD VAR)" + "(CLEAR NULLS)" + "(CLEAR DUPES)" + "(SHUFFLE)" - - if(variable == null) - return - - if(variable == "(ADD VAR)") - mod_list_add(L, O, original_name, objectvar) - return - - if(variable == "(CLEAR NULLS)") - L = L.Copy() - listclearnulls(L) - if (!O.vv_edit_var(objectvar, L)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR NULLS") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR NULLS") - message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR NULLS") - return - - if(variable == "(CLEAR DUPES)") - L = uniqueList(L) - if (!O.vv_edit_var(objectvar, L)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR DUPES") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR DUPES") - message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR DUPES") - return - - if(variable == "(SHUFFLE)") - L = shuffle(L) - if (!O.vv_edit_var(objectvar, L)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [O.type] [objectvar]: SHUFFLE") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: SHUFFLE") - message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: SHUFFLE") - return - - index = names[variable] - - - var/assoc_key - if (index == null) - return - var/assoc = 0 - var/prompt = alert(src, "Do you want to edit the key or its assigned value?", "Associated List", "Key", "Assigned Value", "Cancel") - if (prompt == "Cancel") - return - if (prompt == "Assigned Value") - assoc = 1 - assoc_key = L[index] - var/default - var/variable - if (assoc) - variable = L[assoc_key] - else - variable = L[index] - - default = vv_get_class(objectvar, variable) - - to_chat(src, "Variable appears to be [uppertext(default)].") - - to_chat(src, "Variable contains: [variable]") - - if(default == VV_NUM) - var/dir_text = "" - var/tdir = variable - if(tdir > 0 && tdir < 16) - if(tdir & 1) - dir_text += "NORTH" - if(tdir & 2) - dir_text += "SOUTH" - if(tdir & 4) - dir_text += "EAST" - if(tdir & 8) - dir_text += "WEST" - - if(dir_text) - to_chat(usr, "If a direction, direction is: [dir_text]") - - var/original_var = variable - - if (O) - L = L.Copy() - var/class - if(autodetect_class) - if (default == VV_TEXT) - default = VV_MESSAGE - class = default - var/list/LL = vv_get_value(default_class = default, current_value = original_var, restricted_classes = list(VV_RESTORE_DEFAULT), extra_classes = list(VV_LIST, "DELETE FROM LIST")) - class = LL["class"] - if (!class) - return - var/new_var = LL["value"] - - if(class == VV_MESSAGE) - class = VV_TEXT - - switch(class) //Spits a runtime error if you try to modify an entry in the contents list. Dunno how to fix it, yet. - if(VV_LIST) - mod_list(variable, O, original_name, objectvar) - - if("DELETE FROM LIST") - L.Cut(index, index+1) - if (O) - if (O.vv_edit_var(objectvar, L)) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [O.type] [objectvar]: REMOVED=[html_encode("[original_var]")]") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]") - message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]") - return - - if(VV_TEXT) - var/list/varsvars = vv_parse_text(O, new_var) - for(var/V in varsvars) - new_var = replacetext(new_var,"\[[V]]","[O.vars[V]]") - - - if(assoc) - L[assoc_key] = new_var - else - L[index] = new_var - if (O) - if (O.vv_edit_var(objectvar, L) == FALSE) - to_chat(src, "Your edit was rejected by the object.") - return - log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: [original_var]=[new_var]") - log_admin("[key_name(src)] modified [original_name]'s [objectvar]: [original_var]=[new_var]") - message_admins("[key_name_admin(src)] modified [original_name]'s varlist [objectvar]: [original_var]=[new_var]") - -/proc/vv_varname_lockcheck(param_var_name) - if(param_var_name in GLOB.VVlocked) - if(!check_rights(R_DEBUG)) - return FALSE - if(param_var_name in GLOB.VVckey_edit) - if(!check_rights(R_SPAWN|R_DEBUG)) - return FALSE - if(param_var_name in GLOB.VVicon_edit_lock) - if(!check_rights(R_FUN|R_DEBUG)) - return FALSE - if(param_var_name in GLOB.VVpixelmovement) - if(!check_rights(R_DEBUG)) - return FALSE - var/prompt = alert(usr, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT") - if (prompt != "Continue") - return FALSE - return TRUE - - -/client/proc/modify_variables(atom/O, param_var_name = null, autodetect_class = 0) - if(!check_rights(R_VAREDIT)) - return - - var/class - var/variable - var/var_value - - if(param_var_name) - if(!(param_var_name in O.vars)) - to_chat(src, "A variable with this name ([param_var_name]) doesn't exist in this datum ([O])") - return - variable = param_var_name - - else - var/list/names = list() - for (var/V in O.vars) - names += V - - names = sortList(names) - - variable = input("Which var?","Var") as null|anything in names - if(!variable) - return - - if(!O.can_vv_get(variable)) - return - - var_value = O.vars[variable] - if(!vv_varname_lockcheck(variable)) - return - if(istype(O, /datum/armor)) - var/prompt = alert(src, "Editing this var changes this value on potentially thousands of items that share the same combination of armor values. If you want to edit the armor of just one item, use the \"Modify armor values\" dropdown item", "DANGER", "ABORT ", "Continue", " ABORT") - if (prompt != "Continue") - return - - - var/default = vv_get_class(variable, var_value) - - if(isnull(default)) - to_chat(src, "Unable to determine variable type.") - else - to_chat(src, "Variable appears to be [uppertext(default)].") - - to_chat(src, "Variable contains: [var_value]") - - if(default == VV_NUM) - var/dir_text = "" - if(var_value > 0 && var_value < 16) - if(var_value & 1) - dir_text += "NORTH" - if(var_value & 2) - dir_text += "SOUTH" - if(var_value & 4) - dir_text += "EAST" - if(var_value & 8) - dir_text += "WEST" - - if(dir_text) - to_chat(src, "If a direction, direction is: [dir_text]") - - if(autodetect_class && default != VV_NULL) - if (default == VV_TEXT) - default = VV_MESSAGE - class = default - - var/list/value = vv_get_value(class, default, var_value, extra_classes = list(VV_LIST), var_name = variable) - class = value["class"] - - if (!class) - return - var/var_new = value["value"] - - if(class == VV_MESSAGE) - class = VV_TEXT - - var/original_name = "[O]" - - switch(class) - if(VV_LIST) - if(!islist(var_value)) - mod_list(list(), O, original_name, variable) - - mod_list(var_value, O, original_name, variable) - return - - if(VV_RESTORE_DEFAULT) - var_new = initial(O.vars[variable]) - - if(VV_TEXT) - var/list/varsvars = vv_parse_text(O, var_new) - for(var/V in varsvars) - var_new = replacetext(var_new,"\[[V]]","[O.vars[V]]") - - - if (O.vv_edit_var(variable, var_new) == FALSE) - to_chat(src, "Your edit was rejected by the object.") - return - vv_update_display(O, "varedited", VV_MSG_EDITED) - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_VAR_EDIT, args) - log_world("### VarEdit by [key_name(src)]: [O.type] [variable]=[var_value] => [var_new]") - log_admin("[key_name(src)] modified [original_name]'s [variable] from [html_encode("[var_value]")] to [html_encode("[var_new]")]") - var/msg = "[key_name_admin(src)] modified [original_name]'s [variable] from [var_value] to [var_new]" - message_admins(msg) - admin_ticket_log(O, msg) +GLOBAL_LIST_INIT(VVlocked, list("vars", "datum_flags", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "force_ending")) +GLOBAL_PROTECT(VVlocked) +GLOBAL_LIST_INIT(VVicon_edit_lock, list("icon", "icon_state", "overlays", "underlays", "resize")) +GLOBAL_PROTECT(VVicon_edit_lock) +GLOBAL_LIST_INIT(VVckey_edit, list("key", "ckey")) +GLOBAL_PROTECT(VVckey_edit) +GLOBAL_LIST_INIT(VVpixelmovement, list("step_x", "step_y", "bound_height", "bound_width", "bound_x", "bound_y")) +GLOBAL_PROTECT(VVpixelmovement) + + +/client/proc/vv_get_class(var/var_name, var/var_value) + if(isnull(var_value)) + . = VV_NULL + + else if (isnum(var_value)) + if (var_name in GLOB.bitfields) + . = VV_BITFIELD + else + . = VV_NUM + + else if (istext(var_value)) + if (findtext(var_value, "\n")) + . = VV_MESSAGE + else + . = VV_TEXT + + else if (isicon(var_value)) + . = VV_ICON + + else if (ismob(var_value)) + . = VV_MOB_REFERENCE + + else if (isloc(var_value)) + . = VV_ATOM_REFERENCE + + else if (istype(var_value, /client)) + . = VV_CLIENT + + else if (istype(var_value, /datum)) + . = VV_DATUM_REFERENCE + + else if (ispath(var_value)) + if (ispath(var_value, /atom)) + . = VV_ATOM_TYPE + else if (ispath(var_value, /datum)) + . = VV_DATUM_TYPE + else + . = VV_TYPE + + else if (islist(var_value)) + . = VV_LIST + + else if (isfile(var_value)) + . = VV_FILE + else + . = VV_NULL + +/client/proc/vv_get_value(class, default_class, current_value, list/restricted_classes, list/extra_classes, list/classes, var_name) + . = list("class" = class, "value" = null) + if (!class) + if (!classes) + classes = list ( + VV_NUM, + VV_TEXT, + VV_MESSAGE, + VV_ICON, + VV_ATOM_REFERENCE, + VV_DATUM_REFERENCE, + VV_MOB_REFERENCE, + VV_CLIENT, + VV_ATOM_TYPE, + VV_DATUM_TYPE, + VV_TYPE, + VV_FILE, + VV_NEW_ATOM, + VV_NEW_DATUM, + VV_NEW_TYPE, + VV_NEW_LIST, + VV_NULL, + VV_RESTORE_DEFAULT + ) + + if(holder && holder.marked_datum && !(VV_MARKED_DATUM in restricted_classes)) + classes += "[VV_MARKED_DATUM] ([holder.marked_datum.type])" + if (restricted_classes) + classes -= restricted_classes + + if (extra_classes) + classes += extra_classes + + .["class"] = input(src, "What kind of data?", "Variable Type", default_class) as null|anything in classes + if (holder && holder.marked_datum && .["class"] == "[VV_MARKED_DATUM] ([holder.marked_datum.type])") + .["class"] = VV_MARKED_DATUM + + + switch(.["class"]) + if (VV_TEXT) + .["value"] = input("Enter new text:", "Text", current_value) as null|text + if (.["value"] == null) + .["class"] = null + return + if (VV_MESSAGE) + .["value"] = input("Enter new text:", "Text", current_value) as null|message + if (.["value"] == null) + .["class"] = null + return + + + if (VV_NUM) + .["value"] = input("Enter new number:", "Num", current_value) as null|num + if (.["value"] == null) + .["class"] = null + return + + if (VV_BITFIELD) + .["value"] = input_bitfield(usr, "Editing bitfield: [var_name]", var_name, current_value) + if (.["value"] == null) + .["class"] = null + return + + if (VV_ATOM_TYPE) + .["value"] = pick_closest_path(FALSE) + if (.["value"] == null) + .["class"] = null + return + + if (VV_DATUM_TYPE) + .["value"] = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) + if (.["value"] == null) + .["class"] = null + return + + if (VV_TYPE) + var/type = current_value + var/error = "" + do + type = input("Enter type:[error]", "Type", type) as null|text + if (!type) + break + type = text2path(type) + error = "\nType not found, Please try again" + while(!type) + if (!type) + .["class"] = null + return + .["value"] = type + + + if (VV_ATOM_REFERENCE) + var/type = pick_closest_path(FALSE) + var/subtypes = vv_subtype_prompt(type) + if (subtypes == null) + .["class"] = null + return + var/list/things = vv_reference_list(type, subtypes) + var/value = input("Select reference:", "Reference", current_value) as null|anything in things + if (!value) + .["class"] = null + return + .["value"] = things[value] + + if (VV_DATUM_REFERENCE) + var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) + var/subtypes = vv_subtype_prompt(type) + if (subtypes == null) + .["class"] = null + return + var/list/things = vv_reference_list(type, subtypes) + var/value = input("Select reference:", "Reference", current_value) as null|anything in things + if (!value) + .["class"] = null + return + .["value"] = things[value] + + if (VV_MOB_REFERENCE) + var/type = pick_closest_path(FALSE, make_types_fancy(typesof(/mob))) + var/subtypes = vv_subtype_prompt(type) + if (subtypes == null) + .["class"] = null + return + var/list/things = vv_reference_list(type, subtypes) + var/value = input("Select reference:", "Reference", current_value) as null|anything in things + if (!value) + .["class"] = null + return + .["value"] = things[value] + + + + if (VV_CLIENT) + .["value"] = input("Select reference:", "Reference", current_value) as null|anything in GLOB.clients + if (.["value"] == null) + .["class"] = null + return + + + if (VV_FILE) + .["value"] = input("Pick file:", "File") as null|file + if (.["value"] == null) + .["class"] = null + return + + + if (VV_ICON) + .["value"] = input("Pick icon:", "Icon") as null|icon + if (.["value"] == null) + .["class"] = null + return + + + if (VV_MARKED_DATUM) + .["value"] = holder.marked_datum + if (.["value"] == null) + .["class"] = null + return + + + if (VV_NEW_ATOM) + var/type = pick_closest_path(FALSE) + if (!type) + .["class"] = null + return + .["type"] = type + var/atom/newguy = new type() + newguy.datum_flags |= DF_VAR_EDITED + .["value"] = newguy + + if (VV_NEW_DATUM) + var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types()) + if (!type) + .["class"] = null + return + .["type"] = type + var/datum/newguy = new type() + newguy.datum_flags |= DF_VAR_EDITED + .["value"] = newguy + + if (VV_NEW_TYPE) + var/type = current_value + var/error = "" + do + type = input("Enter type:[error]", "Type", type) as null|text + if (!type) + break + type = text2path(type) + error = "\nType not found, Please try again" + while(!type) + if (!type) + .["class"] = null + return + .["type"] = type + var/datum/newguy = new type() + if(istype(newguy)) + newguy.datum_flags |= DF_VAR_EDITED + .["value"] = newguy + + + if (VV_NEW_LIST) + .["value"] = list() + .["type"] = /list + +/client/proc/vv_parse_text(O, new_var) + if(O && findtext(new_var,"\[")) + var/process_vars = alert(usr,"\[] detected in string, process as variables?","Process Variables?","Yes","No") + if(process_vars == "Yes") + . = string2listofvars(new_var, O) + +//do they want you to include subtypes? +//FALSE = no subtypes, strict exact type pathing (or the type doesn't have subtypes) +//TRUE = Yes subtypes +//NULL = User cancelled at the prompt or invalid type given +/client/proc/vv_subtype_prompt(var/type) + if (!ispath(type)) + return + var/list/subtypes = subtypesof(type) + if (!subtypes || !subtypes.len) + return FALSE + if (subtypes && subtypes.len) + switch(alert("Strict object type detection?", "Type detection", "Strictly this type","This type and subtypes", "Cancel")) + if("Strictly this type") + return FALSE + if("This type and subtypes") + return TRUE + else + return + +/client/proc/vv_reference_list(type, subtypes) + . = list() + var/list/types = list(type) + if (subtypes) + types = typesof(type) + + var/list/fancytypes = make_types_fancy(types) + + for(var/fancytype in fancytypes) //swap the assoication + types[fancytypes[fancytype]] = fancytype + + var/things = get_all_of_type(type, subtypes) + + var/i = 0 + for(var/thing in things) + var/datum/D = thing + i++ + //try one of 3 methods to shorten the type text: + // fancy type, + // fancy type with the base type removed from the begaining, + // the type with the base type removed from the begaining + var/fancytype = types[D.type] + if (findtext(fancytype, types[type])) + fancytype = copytext(fancytype, length(types[type])+1) + var/shorttype = copytext("[D.type]", length("[type]")+1) + if (length(shorttype) > length(fancytype)) + shorttype = fancytype + if (!length(shorttype)) + shorttype = "/" + + .["[D]([shorttype])[REF(D)]#[i]"] = D + +/client/proc/mod_list_add_ass(atom/O) //hehe + + var/list/L = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT)) + var/class = L["class"] + if (!class) + return + var/var_value = L["value"] + + if(class == VV_TEXT || class == VV_MESSAGE) + var/list/varsvars = vv_parse_text(O, var_value) + for(var/V in varsvars) + var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]") + + return var_value + + +/client/proc/mod_list_add(list/L, atom/O, original_name, objectvar) + var/list/LL = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT)) + var/class = LL["class"] + if (!class) + return + var/var_value = LL["value"] + + if(class == VV_TEXT || class == VV_MESSAGE) + var/list/varsvars = vv_parse_text(O, var_value) + for(var/V in varsvars) + var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]") + + if (O) + L = L.Copy() + + L += var_value + + switch(alert("Would you like to associate a value with the list entry?",,"Yes","No")) + if("Yes") + L[var_value] = mod_list_add_ass(O) //hehe + if (O) + if (O.vv_edit_var(objectvar, L) == FALSE) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: ADDED=[var_value]") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]") + message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]") + +/client/proc/mod_list(list/L, atom/O, original_name, objectvar, index, autodetect_class = FALSE) + if(!check_rights(R_VAREDIT)) + return + if(!istype(L, /list)) + to_chat(src, "Not a List.") + return + + if(L.len > 1000) + var/confirm = alert(src, "The list you're trying to edit is very long, continuing may crash the server.", "Warning", "Continue", "Abort") + if(confirm != "Continue") + return + + + + var/list/names = list() + for (var/i in 1 to L.len) + var/key = L[i] + var/value + if (IS_NORMAL_LIST(L) && !isnum(key)) + value = L[key] + if (value == null) + value = "null" + names["#[i] [key] = [value]"] = i + if (!index) + var/variable = input("Which var?","Var") as null|anything in names + "(ADD VAR)" + "(CLEAR NULLS)" + "(CLEAR DUPES)" + "(SHUFFLE)" + + if(variable == null) + return + + if(variable == "(ADD VAR)") + mod_list_add(L, O, original_name, objectvar) + return + + if(variable == "(CLEAR NULLS)") + L = L.Copy() + listclearnulls(L) + if (!O.vv_edit_var(objectvar, L)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR NULLS") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR NULLS") + message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR NULLS") + return + + if(variable == "(CLEAR DUPES)") + L = uniqueList(L) + if (!O.vv_edit_var(objectvar, L)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR DUPES") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR DUPES") + message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR DUPES") + return + + if(variable == "(SHUFFLE)") + L = shuffle(L) + if (!O.vv_edit_var(objectvar, L)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [O.type] [objectvar]: SHUFFLE") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: SHUFFLE") + message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: SHUFFLE") + return + + index = names[variable] + + + var/assoc_key + if (index == null) + return + var/assoc = 0 + var/prompt = alert(src, "Do you want to edit the key or its assigned value?", "Associated List", "Key", "Assigned Value", "Cancel") + if (prompt == "Cancel") + return + if (prompt == "Assigned Value") + assoc = 1 + assoc_key = L[index] + var/default + var/variable + if (assoc) + variable = L[assoc_key] + else + variable = L[index] + + default = vv_get_class(objectvar, variable) + + to_chat(src, "Variable appears to be [uppertext(default)].") + + to_chat(src, "Variable contains: [variable]") + + if(default == VV_NUM) + var/dir_text = "" + var/tdir = variable + if(tdir > 0 && tdir < 16) + if(tdir & 1) + dir_text += "NORTH" + if(tdir & 2) + dir_text += "SOUTH" + if(tdir & 4) + dir_text += "EAST" + if(tdir & 8) + dir_text += "WEST" + + if(dir_text) + to_chat(usr, "If a direction, direction is: [dir_text]") + + var/original_var = variable + + if (O) + L = L.Copy() + var/class + if(autodetect_class) + if (default == VV_TEXT) + default = VV_MESSAGE + class = default + var/list/LL = vv_get_value(default_class = default, current_value = original_var, restricted_classes = list(VV_RESTORE_DEFAULT), extra_classes = list(VV_LIST, "DELETE FROM LIST")) + class = LL["class"] + if (!class) + return + var/new_var = LL["value"] + + if(class == VV_MESSAGE) + class = VV_TEXT + + switch(class) //Spits a runtime error if you try to modify an entry in the contents list. Dunno how to fix it, yet. + if(VV_LIST) + mod_list(variable, O, original_name, objectvar) + + if("DELETE FROM LIST") + L.Cut(index, index+1) + if (O) + if (O.vv_edit_var(objectvar, L)) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [O.type] [objectvar]: REMOVED=[html_encode("[original_var]")]") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]") + message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]") + return + + if(VV_TEXT) + var/list/varsvars = vv_parse_text(O, new_var) + for(var/V in varsvars) + new_var = replacetext(new_var,"\[[V]]","[O.vars[V]]") + + + if(assoc) + L[assoc_key] = new_var + else + L[index] = new_var + if (O) + if (O.vv_edit_var(objectvar, L) == FALSE) + to_chat(src, "Your edit was rejected by the object.") + return + log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: [original_var]=[new_var]") + log_admin("[key_name(src)] modified [original_name]'s [objectvar]: [original_var]=[new_var]") + message_admins("[key_name_admin(src)] modified [original_name]'s varlist [objectvar]: [original_var]=[new_var]") + +/proc/vv_varname_lockcheck(param_var_name) + if(param_var_name in GLOB.VVlocked) + if(!check_rights(R_DEBUG)) + return FALSE + if(param_var_name in GLOB.VVckey_edit) + if(!check_rights(R_SPAWN|R_DEBUG)) + return FALSE + if(param_var_name in GLOB.VVicon_edit_lock) + if(!check_rights(R_FUN|R_DEBUG)) + return FALSE + if(param_var_name in GLOB.VVpixelmovement) + if(!check_rights(R_DEBUG)) + return FALSE + var/prompt = alert(usr, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT") + if (prompt != "Continue") + return FALSE + return TRUE + + +/client/proc/modify_variables(atom/O, param_var_name = null, autodetect_class = 0) + if(!check_rights(R_VAREDIT)) + return + + var/class + var/variable + var/var_value + + if(param_var_name) + if(!(param_var_name in O.vars)) + to_chat(src, "A variable with this name ([param_var_name]) doesn't exist in this datum ([O])") + return + variable = param_var_name + + else + var/list/names = list() + for (var/V in O.vars) + names += V + + names = sortList(names) + + variable = input("Which var?","Var") as null|anything in names + if(!variable) + return + + if(!O.can_vv_get(variable)) + return + + var_value = O.vars[variable] + if(!vv_varname_lockcheck(variable)) + return + if(istype(O, /datum/armor)) + var/prompt = alert(src, "Editing this var changes this value on potentially thousands of items that share the same combination of armor values. If you want to edit the armor of just one item, use the \"Modify armor values\" dropdown item", "DANGER", "ABORT ", "Continue", " ABORT") + if (prompt != "Continue") + return + + + var/default = vv_get_class(variable, var_value) + + if(isnull(default)) + to_chat(src, "Unable to determine variable type.") + else + to_chat(src, "Variable appears to be [uppertext(default)].") + + to_chat(src, "Variable contains: [var_value]") + + if(default == VV_NUM) + var/dir_text = "" + if(var_value > 0 && var_value < 16) + if(var_value & 1) + dir_text += "NORTH" + if(var_value & 2) + dir_text += "SOUTH" + if(var_value & 4) + dir_text += "EAST" + if(var_value & 8) + dir_text += "WEST" + + if(dir_text) + to_chat(src, "If a direction, direction is: [dir_text]") + + if(autodetect_class && default != VV_NULL) + if (default == VV_TEXT) + default = VV_MESSAGE + class = default + + var/list/value = vv_get_value(class, default, var_value, extra_classes = list(VV_LIST), var_name = variable) + class = value["class"] + + if (!class) + return + var/var_new = value["value"] + + if(class == VV_MESSAGE) + class = VV_TEXT + + var/original_name = "[O]" + + switch(class) + if(VV_LIST) + if(!islist(var_value)) + mod_list(list(), O, original_name, variable) + + mod_list(var_value, O, original_name, variable) + return + + if(VV_RESTORE_DEFAULT) + var_new = initial(O.vars[variable]) + + if(VV_TEXT) + var/list/varsvars = vv_parse_text(O, var_new) + for(var/V in varsvars) + var_new = replacetext(var_new,"\[[V]]","[O.vars[V]]") + + + if (O.vv_edit_var(variable, var_new) == FALSE) + to_chat(src, "Your edit was rejected by the object.") + return + vv_update_display(O, "varedited", VV_MSG_EDITED) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_VAR_EDIT, args) + log_world("### VarEdit by [key_name(src)]: [O.type] [variable]=[var_value] => [var_new]") + log_admin("[key_name(src)] modified [original_name]'s [variable] from [html_encode("[var_value]")] to [html_encode("[var_new]")]") + var/msg = "[key_name_admin(src)] modified [original_name]'s [variable] from [var_value] to [var_new]" + message_admins(msg) + admin_ticket_log(O, msg) return TRUE \ No newline at end of file diff --git a/code/modules/admin/verbs/onlyone.dm b/code/modules/admin/verbs/onlyone.dm index 05f56047cb..f59d776a93 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 || !(H.client)) - 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() +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 || !(H.client)) + 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) \ No newline at end of file diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm index 3996cabe48..cbbec40f85 100644 --- a/code/modules/admin/verbs/playsound.dm +++ b/code/modules/admin/verbs/playsound.dm @@ -1,203 +1,203 @@ -/client/proc/play_sound(S as sound) - set category = "Fun" - set name = "Play Global Sound" - if(!check_rights(R_SOUNDS)) - return - - var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num - if(!vol) - return - var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num - if(!freq) - freq = 1 - 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]") - 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_SOUNDS)) - 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, 0, 0) - 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_web_sound() - set category = "Fun" - set name = "Play Internet Sound" - if(!check_rights(R_SOUNDS)) - return - - var/ytdl = CONFIG_GET(string/invoke_youtubedl) - if(!ytdl) - to_chat(src, "Youtube-dl was not configured, action unavailable") //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/pitch - 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.") - to_chat(src, "For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.") - return - var/shell_scrubbed_input = shell_url_scrub(web_sound_input) - var/list/output = world.shelleo("[ytdl] --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:") - to_chat(src, "[e]: [stdout]") - return - - if (data["url"]) - web_sound_url = data["url"] - var/title = "[data["title"]]" - var/webpage_url = title - if (data["webpage_url"]) - webpage_url = "[title]" - - var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num - if(!freq) - freq = 1 - pitch = freq - - 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]") - 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:") - to_chat(src, "[stderr]") - - 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") - to_chat(src, "The media provider returned a content URL that isn't using the HTTP or HTTPS protocol") - 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, pitch) - 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_SOUNDS)) - 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/play_web_sound_manual() - set category = "Fun" - set name = "Manual Play Internet Sound" - if(!check_rights(R_SOUNDS)) - return - - var/web_sound_input = input("Enter youtube-dl fetched content URL (supported sites only, leave blank to stop playing)", "Send youtube-dl media link") as text|null - if(!istext(web_sound_input)) - return - web_sound_input = trim(web_sound_input) - if(!length(web_sound_input)) - log_admin("[key_name(src)] stopped web sound") - message_admins("[key_name(src)] stopped web sound") - 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) - C.chatOutput.stopMusic() - return - var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num - if(!freq) - return - if(web_sound_input && !findtext(web_sound_input, GLOB.is_http_protocol)) - to_chat(src, "BLOCKED: Content URL not using http(s) protocol") - to_chat(src, "The media provider returned a content URL that isn't using the HTTP or HTTPS protocol") - return - - SSblackbox.record_feedback("nested tally", "played_url_manual", 1, list("[ckey]", "[web_sound_input]")) - log_admin("[key_name(src)] manually played web sound: [web_sound_input]") - message_admins("[key_name(src)] manually played web sound: HREF") - 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) - C.chatOutput.sendMusic(web_sound_input, freq) - -/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) - if(M.client) - 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_SOUNDS)) + return + + var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num + if(!vol) + return + var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num + if(!freq) + freq = 1 + 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]") + 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_SOUNDS)) + 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, 0, 0) + 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_web_sound() + set category = "Fun" + set name = "Play Internet Sound" + if(!check_rights(R_SOUNDS)) + return + + var/ytdl = CONFIG_GET(string/invoke_youtubedl) + if(!ytdl) + to_chat(src, "Youtube-dl was not configured, action unavailable") //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/pitch + 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.") + to_chat(src, "For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.") + return + var/shell_scrubbed_input = shell_url_scrub(web_sound_input) + var/list/output = world.shelleo("[ytdl] --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:") + to_chat(src, "[e]: [stdout]") + return + + if (data["url"]) + web_sound_url = data["url"] + var/title = "[data["title"]]" + var/webpage_url = title + if (data["webpage_url"]) + webpage_url = "[title]" + + var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num + if(!freq) + freq = 1 + pitch = freq + + 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]") + 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:") + to_chat(src, "[stderr]") + + 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") + to_chat(src, "The media provider returned a content URL that isn't using the HTTP or HTTPS protocol") + 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, pitch) + 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_SOUNDS)) + 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/play_web_sound_manual() + set category = "Fun" + set name = "Manual Play Internet Sound" + if(!check_rights(R_SOUNDS)) + return + + var/web_sound_input = input("Enter youtube-dl fetched content URL (supported sites only, leave blank to stop playing)", "Send youtube-dl media link") as text|null + if(!istext(web_sound_input)) + return + web_sound_input = trim(web_sound_input) + if(!length(web_sound_input)) + log_admin("[key_name(src)] stopped web sound") + message_admins("[key_name(src)] stopped web sound") + 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) + C.chatOutput.stopMusic() + return + var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num + if(!freq) + return + if(web_sound_input && !findtext(web_sound_input, GLOB.is_http_protocol)) + to_chat(src, "BLOCKED: Content URL not using http(s) protocol") + to_chat(src, "The media provider returned a content URL that isn't using the HTTP or HTTPS protocol") + return + + SSblackbox.record_feedback("nested tally", "played_url_manual", 1, list("[ckey]", "[web_sound_input]")) + log_admin("[key_name(src)] manually played web sound: [web_sound_input]") + message_admins("[key_name(src)] manually played web sound: HREF") + 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) + C.chatOutput.sendMusic(web_sound_input, freq) + +/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) + if(M.client) + 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 8f31dba1a2..445de4c40f 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.") - 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.") + 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 d5acb66fdd..0555f6b40a 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.") - return - - msg = copytext(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).") - 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) - 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]\"") - - 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(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "CENTCOM:[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]: [msg]" - to_chat(GLOB.admins, msg) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() - -/proc/Syndicate_announce(text , mob/Sender) - var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN) - msg = "SYNDICATE:[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]: [msg]" - to_chat(GLOB.admins, msg) - for(var/obj/machinery/computer/communications/C in GLOB.machines) - C.overrideCooldown() - -/proc/Nuke_request(text , mob/Sender) - var/msg = copytext(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) - 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.") + return + + msg = copytext(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).") + 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) + 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]\"") + + 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(sanitize(text), 1, MAX_MESSAGE_LEN) + msg = "CENTCOM:[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]: [msg]" + to_chat(GLOB.admins, msg) + for(var/obj/machinery/computer/communications/C in GLOB.machines) + C.overrideCooldown() + +/proc/Syndicate_announce(text , mob/Sender) + var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN) + msg = "SYNDICATE:[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]: [msg]" + to_chat(GLOB.admins, msg) + for(var/obj/machinery/computer/communications/C in GLOB.machines) + C.overrideCooldown() + +/proc/Nuke_request(text , mob/Sender) + var/msg = copytext(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) + 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 f97822a043..1437555af1 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -1,1393 +1,1393 @@ -/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 = "Special Verbs" - 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]") - - 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 = "Special Verbs" - 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") - return - if(!istype(H.ears, /obj/item/radio/headset)) - to_chat(usr, "The person you are trying to contact is not wearing a headset.") - 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(src, 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.\"") - - 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, var/operation) - set category = "Special Verbs" - 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)]") - 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 = "Special Verbs" - 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]") - 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 = "Special Verbs" - 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) - 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 = "Special Verbs" - 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) - - 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 = "Special Verbs" - set name = "Godmode" - if(!check_rights(R_ADMIN)) - return - - M.status_flags ^= GODMODE - to_chat(usr, "Toggled [(M.status_flags & GODMODE) ? "ON" : "OFF"]") - - 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.") - 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)].") - 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 candidates - else - to_chat(usr, "Error: create_xeno(): no suitable candidates.") - 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 = "Special Verbs" - 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.") - 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 - G_found.transfer_ckey(new_xeno, FALSE) - to_chat(new_xeno, "You have been fully respawned. Enjoy the game.") - 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 - G_found.transfer_ckey(new_monkey, FALSE) - to_chat(new_monkey, "You have been fully respawned. Enjoy the game.") - 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["sex"] - new_character.age = record_found.fields["age"] - new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], 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. - - G_found.transfer_ckey(new_character, FALSE) - - /* - 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.") - - 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 = "Fun" - 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" ? 1 : -1) - - var/datum/round_event/ion_storm/add_law_only/ion = new() - ion.announceEvent = 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 = "Special Verbs" - 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 = 1, admin_revive = 1) - - 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, "Rejuvinate") //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 = "Special Verbs" - set name = "Create Command Report" - - if(!check_rights(R_ADMIN)) - return - - var/input = input(usr, "Enter a Command Report. Ensure it makes sense IC.", "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, "commandreport") - 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 = "Special Verbs" - 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 = "Admin" - set name = "Delete" - - if(!check_rights(R_SPAWN|R_DEBUG)) - return - - admin_delete(A) - -/client/proc/admin_delete(datum/D) - var/atom/A = D - var/coords = "" - var/jmp_coords = "" - if(istype(A)) - var/turf/T = get_turf(A) - if(T) - coords = "at [COORD(T)]" - jmp_coords = "at [ADMIN_COORDJMP(T)]" - else - jmp_coords = coords = "in nullspace" - - if (alert(src, "Are you sure you want to delete:\n[D]\n[coords]?", "Confirmation", "Yes", "No") == "Yes") - log_admin("[key_name(usr)] deleted [D] [coords]") - message_admins("[key_name_admin(usr)] deleted [D] [jmp_coords]") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Delete") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - if(isturf(D)) - var/turf/T = D - T.ScrapeAway() - else - vv_update_display(D, "deleted", VV_MSG_DELETED) - qdel(D) - if(!QDELETED(D)) - vv_update_display(D, "deleted", "") - -/client/proc/cmd_admin_list_open_jobs() - set category = "Admin" - 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 = "Special Verbs" - 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 = "Special Verbs" - 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 = "Special Verbs" - 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 = "Special Verbs" - set name = "Check Contents" - - var/list/L = M.get_contents() - for(var/t in L) - to_chat(usr, "[t]") - 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 = "Special Verbs" - set name = "Change View Range" - set desc = "switches between 1x and custom views" - - if(view == CONFIG_GET(string/default_view)) - change_view(input("Select view range:", "FUCK YE", 7) in list(1,2,3,4,5,6,7,8,9,10,11,12,13,14,128)) - else - change_view(CONFIG_GET(string/default_view)) - - 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" - 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" - 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!") - 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.") - 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!") - - to_chat(usr, "Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet.") - - 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") - message_admins("Admin [key_name_admin(usr)] has enabled random events.") - else - to_chat(usr, "Random events disabled") - 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 = "Special Verbs" - 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","amber","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 = "Fun" - 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! - -GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits - -/client/proc/create_outfits() - set category = "Debug" - set name = "Create Custom Outfit" - - if(!check_rights(R_DEBUG)) - return - - holder.create_outfit() - -/datum/admins/proc/create_outfit() - var/list/uniforms = typesof(/obj/item/clothing/under) - var/list/suits = typesof(/obj/item/clothing/suit) - var/list/gloves = typesof(/obj/item/clothing/gloves) - var/list/shoes = typesof(/obj/item/clothing/shoes) - var/list/headwear = typesof(/obj/item/clothing/head) - var/list/glasses = typesof(/obj/item/clothing/glasses) - var/list/masks = typesof(/obj/item/clothing/mask) - var/list/ids = typesof(/obj/item/card/id) - - var/uniform_select = "" - - var/suit_select = "" - - var/gloves_select = "" - - var/shoes_select = "" - - var/head_select = "" - - var/glasses_select = "" - - var/mask_select = "" - - var/id_select = "" - - var/dat = {" - Create Outfit -
                - - [HrefTokenFormField()] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                Name: - -
                Uniform: - [uniform_select] -
                Suit: - [suit_select] -
                Back: - -
                Belt: - -
                Gloves: - [gloves_select] -
                Shoes: - [shoes_select] -
                Head: - [head_select] -
                Mask: - [mask_select] -
                Ears: - -
                Glasses: - [glasses_select] -
                ID: - [id_select] -
                Left Pocket: - -
                Right Pocket: - -
                Suit Store: - -
                Right Hand: - -
                Left Hand: - -
                -
                - -
                - "} - usr << browse(dat, "window=dressup;size=550x600") - -/client/proc/toggle_combo_hud() - set category = "Admin" - 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"].") - 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/open_shuttle_manipulator() - set category = "Admin" - set name = "Shuttle Manipulator" - set desc = "Opens the shuttle manipulator UI." - - for(var/obj/machinery/shuttle_manipulator/M in GLOB.machines) - M.ui_interact(usr) - -/client/proc/run_weather() - set category = "Fun" - 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 subtypesof(/datum/weather) - if(!weather_type) - return - - var/z_level = input("Z-Level to target? Leave blank to target current Z-Level.", "Z-Level") as num|null - if(!isnum(z_level)) - if(!src.mob) - return - z_level = src.mob.z - - 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/mob/living/carbon/human/H in GLOB.carbon_list) - 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, 1, -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") - - -/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/carbon/human/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_PIE, ADMIN_PUNISHMENT_FIREBALL, ADMIN_PUNISHMENT_LIGHTNING, ADMIN_PUNISHMENT_BRAINDAMAGE, ADMIN_PUNISHMENT_BSA, ADMIN_PUNISHMENT_GIB, ADMIN_PUNISHMENT_SUPPLYPOD_QUICK, ADMIN_PUNISHMENT_SUPPLYPOD, ADMIN_PUNISHMENT_MAZING, ADMIN_PUNISHMENT_ROD) - - var/punishment = input("Choose a punishment", "DIVINE SMITING") as null|anything in 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) - target.electrocution_animation(40) - to_chat(target, "The gods have punished you for your sins!") - 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.") - new delivery(pod) - new /obj/effect/abstract/DPtarget(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!") - return - if(ADMIN_PUNISHMENT_PIE) - var/obj/item/reagent_containers/food/snacks/pie/cream/nostun/creamy = new(get_turf(target)) - creamy.splat(target) - - punish_log(target, punishment) - -/client/proc/punish_log(var/whom, var/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.") - 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.") - return - if(!CONFIG_GET(flag/use_exp_tracking)) - to_chat(usr, "Tracking is disabled in the server configuration file.") - 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.") - return - - if(!C.set_db_player_flags()) - to_chat(usr, "ERROR: Unable read player flags from database. Please check logs.") - 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.") - 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)]") +/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 = "Special Verbs" + 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]") + + 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 = "Special Verbs" + 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") + return + if(!istype(H.ears, /obj/item/radio/headset)) + to_chat(usr, "The person you are trying to contact is not wearing a headset.") + 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(src, 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.\"") + + 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, var/operation) + set category = "Special Verbs" + 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)]") + 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 = "Special Verbs" + 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]") + 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 = "Special Verbs" + 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) + 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 = "Special Verbs" + 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) + + 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 = "Special Verbs" + set name = "Godmode" + if(!check_rights(R_ADMIN)) + return + + M.status_flags ^= GODMODE + to_chat(usr, "Toggled [(M.status_flags & GODMODE) ? "ON" : "OFF"]") + + 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.") + 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)].") + 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 candidates + else + to_chat(usr, "Error: create_xeno(): no suitable candidates.") + 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 = "Special Verbs" + 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.") + 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 + G_found.transfer_ckey(new_xeno, FALSE) + to_chat(new_xeno, "You have been fully respawned. Enjoy the game.") + 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 + G_found.transfer_ckey(new_monkey, FALSE) + to_chat(new_monkey, "You have been fully respawned. Enjoy the game.") + 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["sex"] + new_character.age = record_found.fields["age"] + new_character.hardset_dna(record_found.fields["identity"], record_found.fields["enzymes"], 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. + + G_found.transfer_ckey(new_character, FALSE) + + /* + 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.") + + 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 = "Fun" + 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" ? 1 : -1) + + var/datum/round_event/ion_storm/add_law_only/ion = new() + ion.announceEvent = 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 = "Special Verbs" + 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 = 1, admin_revive = 1) + + 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, "Rejuvinate") //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 = "Special Verbs" + set name = "Create Command Report" + + if(!check_rights(R_ADMIN)) + return + + var/input = input(usr, "Enter a Command Report. Ensure it makes sense IC.", "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, "commandreport") + 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 = "Special Verbs" + 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 = "Admin" + set name = "Delete" + + if(!check_rights(R_SPAWN|R_DEBUG)) + return + + admin_delete(A) + +/client/proc/admin_delete(datum/D) + var/atom/A = D + var/coords = "" + var/jmp_coords = "" + if(istype(A)) + var/turf/T = get_turf(A) + if(T) + coords = "at [COORD(T)]" + jmp_coords = "at [ADMIN_COORDJMP(T)]" + else + jmp_coords = coords = "in nullspace" + + if (alert(src, "Are you sure you want to delete:\n[D]\n[coords]?", "Confirmation", "Yes", "No") == "Yes") + log_admin("[key_name(usr)] deleted [D] [coords]") + message_admins("[key_name_admin(usr)] deleted [D] [jmp_coords]") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Delete") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + if(isturf(D)) + var/turf/T = D + T.ScrapeAway() + else + vv_update_display(D, "deleted", VV_MSG_DELETED) + qdel(D) + if(!QDELETED(D)) + vv_update_display(D, "deleted", "") + +/client/proc/cmd_admin_list_open_jobs() + set category = "Admin" + 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 = "Special Verbs" + 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 = "Special Verbs" + 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 = "Special Verbs" + 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 = "Special Verbs" + set name = "Check Contents" + + var/list/L = M.get_contents() + for(var/t in L) + to_chat(usr, "[t]") + 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 = "Special Verbs" + set name = "Change View Range" + set desc = "switches between 1x and custom views" + + if(view == CONFIG_GET(string/default_view)) + change_view(input("Select view range:", "FUCK YE", 7) in list(1,2,3,4,5,6,7,8,9,10,11,12,13,14,128)) + else + change_view(CONFIG_GET(string/default_view)) + + 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" + 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" + 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!") + 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.") + 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!") + + to_chat(usr, "Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet.") + + 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") + message_admins("Admin [key_name_admin(usr)] has enabled random events.") + else + to_chat(usr, "Random events disabled") + 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 = "Special Verbs" + 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","amber","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 = "Fun" + 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! + +GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits + +/client/proc/create_outfits() + set category = "Debug" + set name = "Create Custom Outfit" + + if(!check_rights(R_DEBUG)) + return + + holder.create_outfit() + +/datum/admins/proc/create_outfit() + var/list/uniforms = typesof(/obj/item/clothing/under) + var/list/suits = typesof(/obj/item/clothing/suit) + var/list/gloves = typesof(/obj/item/clothing/gloves) + var/list/shoes = typesof(/obj/item/clothing/shoes) + var/list/headwear = typesof(/obj/item/clothing/head) + var/list/glasses = typesof(/obj/item/clothing/glasses) + var/list/masks = typesof(/obj/item/clothing/mask) + var/list/ids = typesof(/obj/item/card/id) + + var/uniform_select = "" + + var/suit_select = "" + + var/gloves_select = "" + + var/shoes_select = "" + + var/head_select = "" + + var/glasses_select = "" + + var/mask_select = "" + + var/id_select = "" + + var/dat = {" + Create Outfit +
                + + [HrefTokenFormField()] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                Name: + +
                Uniform: + [uniform_select] +
                Suit: + [suit_select] +
                Back: + +
                Belt: + +
                Gloves: + [gloves_select] +
                Shoes: + [shoes_select] +
                Head: + [head_select] +
                Mask: + [mask_select] +
                Ears: + +
                Glasses: + [glasses_select] +
                ID: + [id_select] +
                Left Pocket: + +
                Right Pocket: + +
                Suit Store: + +
                Right Hand: + +
                Left Hand: + +
                +
                + +
                + "} + usr << browse(dat, "window=dressup;size=550x600") + +/client/proc/toggle_combo_hud() + set category = "Admin" + 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"].") + 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/open_shuttle_manipulator() + set category = "Admin" + set name = "Shuttle Manipulator" + set desc = "Opens the shuttle manipulator UI." + + for(var/obj/machinery/shuttle_manipulator/M in GLOB.machines) + M.ui_interact(usr) + +/client/proc/run_weather() + set category = "Fun" + 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 subtypesof(/datum/weather) + if(!weather_type) + return + + var/z_level = input("Z-Level to target? Leave blank to target current Z-Level.", "Z-Level") as num|null + if(!isnum(z_level)) + if(!src.mob) + return + z_level = src.mob.z + + 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/mob/living/carbon/human/H in GLOB.carbon_list) + 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, 1, -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") + + +/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/carbon/human/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_PIE, ADMIN_PUNISHMENT_FIREBALL, ADMIN_PUNISHMENT_LIGHTNING, ADMIN_PUNISHMENT_BRAINDAMAGE, ADMIN_PUNISHMENT_BSA, ADMIN_PUNISHMENT_GIB, ADMIN_PUNISHMENT_SUPPLYPOD_QUICK, ADMIN_PUNISHMENT_SUPPLYPOD, ADMIN_PUNISHMENT_MAZING, ADMIN_PUNISHMENT_ROD) + + var/punishment = input("Choose a punishment", "DIVINE SMITING") as null|anything in 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) + target.electrocution_animation(40) + to_chat(target, "The gods have punished you for your sins!") + 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.") + new delivery(pod) + new /obj/effect/abstract/DPtarget(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!") + return + if(ADMIN_PUNISHMENT_PIE) + var/obj/item/reagent_containers/food/snacks/pie/cream/nostun/creamy = new(get_turf(target)) + creamy.splat(target) + + punish_log(target, punishment) + +/client/proc/punish_log(var/whom, var/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.") + 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.") + return + if(!CONFIG_GET(flag/use_exp_tracking)) + to_chat(usr, "Tracking is disabled in the server configuration file.") + 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.") + return + + if(!C.set_db_player_flags()) + to_chat(usr, "ERROR: Unable read player flags from database. Please check logs.") + 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.") + 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)]") diff --git a/code/modules/admin/whitelist.dm b/code/modules/admin/whitelist.dm index 55206f6deb..263268a5ca 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_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index c8fbfde4d2..51c8ceac44 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -1,282 +1,282 @@ -/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.
                " - dat += "Martial Artist
                " - dat += "Your apprentice is training in ancient martial arts. They know the Plasmafist and Nuclear Fist.
                " - 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, ignore_category = POLL_IGNORE_WIZARD) - 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 - if(!user.onSyndieBase()) - to_chat(user, "[src] is out of range! It can only be used at your base!") - 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 = "Man-Machine Interface: [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, ignore_category = POLL_IGNORE_DEMON) - 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, 1) - 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.
                " + dat += "Martial Artist
                " + dat += "Your apprentice is training in ancient martial arts. They know the Plasmafist and Nuclear Fist.
                " + 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, ignore_category = POLL_IGNORE_WIZARD) + 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 + if(!user.onSyndieBase()) + to_chat(user, "[src] is out of range! It can only be used at your base!") + 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 = "Man-Machine Interface: [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, ignore_category = POLL_IGNORE_DEMON) + 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, 1) + 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/blob/blob/blobs/shield.dm b/code/modules/antagonists/blob/blob/blobs/shield.dm index da165adef2..481c5e6c8d 100644 --- a/code/modules/antagonists/blob/blob/blobs/shield.dm +++ b/code/modules/antagonists/blob/blob/blobs/shield.dm @@ -1,64 +1,64 @@ -/obj/structure/blob/shield - name = "strong blob" - icon = 'icons/mob/blob.dmi' - icon_state = "blob_shield" - desc = "A solid wall of slightly twitching tendrils." - var/damaged_desc = "A wall of twitching tendrils." - max_integrity = 150 - brute_resist = 0.25 - explosion_block = 3 - point_return = 4 - atmosblock = TRUE - armor = list("melee" = 25, "bullet" = 25, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - var/weakened - -/obj/structure/blob/shield/scannerreport() - if(atmosblock) - return "Will prevent the spread of atmospheric changes." - return "N/A" - -/obj/structure/blob/shield/core - point_return = 0 - -/obj/structure/blob/shield/update_icon() - ..() - if(obj_integrity < max_integrity * 0.5) - icon_state = "[initial(icon_state)]_damaged" - name = "weakened [initial(name)]" - desc = "[damaged_desc]" - atmosblock = FALSE - if(!weakened) - armor = armor.setRating("melee" = 15, "bullet" = 15, "laser" = 5, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - weakened = TRUE - else - icon_state = initial(icon_state) - name = initial(name) - desc = initial(desc) - atmosblock = TRUE - if(weakened) - armor = armor.setRating("melee" = 25, "bullet" = 25, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) - weakened = FALSE - air_update_turf(1) - -/obj/structure/blob/shield/reflective - name = "reflective blob" - desc = "A solid wall of slightly twitching tendrils with a reflective glow." - damaged_desc = "A wall of twitching tendrils with a reflective glow." - icon_state = "blob_glow" - flags_1 = CHECK_RICOCHET_1 - point_return = 8 - max_integrity = 50 - brute_resist = 1 - explosion_block = 2 - -/obj/structure/blob/shield/reflective/handle_ricochet(obj/item/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)) - if(abs(incidence_s) > 90 && abs(incidence_s) < 270) - return FALSE - var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) - P.setAngle(new_angle_s) - visible_message("[P] reflects off [src]!") +/obj/structure/blob/shield + name = "strong blob" + icon = 'icons/mob/blob.dmi' + icon_state = "blob_shield" + desc = "A solid wall of slightly twitching tendrils." + var/damaged_desc = "A wall of twitching tendrils." + max_integrity = 150 + brute_resist = 0.25 + explosion_block = 3 + point_return = 4 + atmosblock = TRUE + armor = list("melee" = 25, "bullet" = 25, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + var/weakened + +/obj/structure/blob/shield/scannerreport() + if(atmosblock) + return "Will prevent the spread of atmospheric changes." + return "N/A" + +/obj/structure/blob/shield/core + point_return = 0 + +/obj/structure/blob/shield/update_icon() + ..() + if(obj_integrity < max_integrity * 0.5) + icon_state = "[initial(icon_state)]_damaged" + name = "weakened [initial(name)]" + desc = "[damaged_desc]" + atmosblock = FALSE + if(!weakened) + armor = armor.setRating("melee" = 15, "bullet" = 15, "laser" = 5, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + weakened = TRUE + else + icon_state = initial(icon_state) + name = initial(name) + desc = initial(desc) + atmosblock = TRUE + if(weakened) + armor = armor.setRating("melee" = 25, "bullet" = 25, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90) + weakened = FALSE + air_update_turf(1) + +/obj/structure/blob/shield/reflective + name = "reflective blob" + desc = "A solid wall of slightly twitching tendrils with a reflective glow." + damaged_desc = "A wall of twitching tendrils with a reflective glow." + icon_state = "blob_glow" + flags_1 = CHECK_RICOCHET_1 + point_return = 8 + max_integrity = 50 + brute_resist = 1 + explosion_block = 2 + +/obj/structure/blob/shield/reflective/handle_ricochet(obj/item/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)) + if(abs(incidence_s) > 90 && abs(incidence_s) < 270) + return FALSE + var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s) + P.setAngle(new_angle_s) + visible_message("[P] reflects off [src]!") return TRUE \ No newline at end of file diff --git a/code/modules/antagonists/changeling/changeling_power.dm b/code/modules/antagonists/changeling/changeling_power.dm index c5334265fb..574edf225a 100644 --- a/code/modules/antagonists/changeling/changeling_power.dm +++ b/code/modules/antagonists/changeling/changeling_power.dm @@ -1,84 +1,84 @@ -/* - * Don't use the apostrophe in name or desc. Causes script errors. - * TODO: combine atleast some of the functionality with /proc_holder/spell - */ - -/obj/effect/proc_holder/changeling - panel = "Changeling" - name = "Prototype Sting" - desc = "" // Fluff - 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, -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/always_keep = 0 // important for abilities like revive that screw you if you lose them. - var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag - var/loudness = 0 //Determines how much having this ability will affect changeling blood tests. At 4, the blood will react violently and turn to ash, creating a unique message in the process. At 10, the blood will explode when heated. - - -/obj/effect/proc_holder/changeling/proc/on_purchase(mob/user, is_respec) - action.Grant(user) - if(!is_respec) - SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name) - -/obj/effect/proc_holder/changeling/proc/on_refund(mob/user) - action.Remove(user) - return - -/obj/effect/proc_holder/changeling/Click() - var/mob/user = usr - if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling)) - return - try_to_sting(user) - -/obj/effect/proc_holder/changeling/proc/try_to_sting(mob/user, mob/target) - if(!can_sting(user, target)) - return - var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(sting_action(user, target)) - SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]")) - sting_feedback(user, target) - c.chem_charges -= chemical_cost - -/obj/effect/proc_holder/changeling/proc/sting_action(mob/user, mob/target) - return 0 - -/obj/effect/proc_holder/changeling/proc/sting_feedback(mob/user, mob/target) - return 0 - -//Fairly important to remember to return 1 on success >.< -/obj/effect/proc_holder/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 0 - if(req_human && !ishuman(user)) - to_chat(user, "We cannot do that in this form!") - return 0 - 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 0 - if(c.absorbedcount < req_dna) - to_chat(user, "We require at least [req_dna] sample\s of compatible DNA.") - return 0 - if(c.trueabsorbs < req_absorbs) - to_chat(user, "We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability.") - if(req_stat < user.stat) - to_chat(user, "We are incapacitated.") - return 0 - if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath)) - to_chat(user, "We are incapacitated.") - return 0 - return 1 - -//used in /mob/Stat() -/obj/effect/proc_holder/changeling/proc/can_be_used_by(mob/user) - if(QDELETED(user)) - return FALSE - 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. + * TODO: combine atleast some of the functionality with /proc_holder/spell + */ + +/obj/effect/proc_holder/changeling + panel = "Changeling" + name = "Prototype Sting" + desc = "" // Fluff + 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, -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/always_keep = 0 // important for abilities like revive that screw you if you lose them. + var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag + var/loudness = 0 //Determines how much having this ability will affect changeling blood tests. At 4, the blood will react violently and turn to ash, creating a unique message in the process. At 10, the blood will explode when heated. + + +/obj/effect/proc_holder/changeling/proc/on_purchase(mob/user, is_respec) + action.Grant(user) + if(!is_respec) + SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name) + +/obj/effect/proc_holder/changeling/proc/on_refund(mob/user) + action.Remove(user) + return + +/obj/effect/proc_holder/changeling/Click() + var/mob/user = usr + if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling)) + return + try_to_sting(user) + +/obj/effect/proc_holder/changeling/proc/try_to_sting(mob/user, mob/target) + if(!can_sting(user, target)) + return + var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) + if(sting_action(user, target)) + SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]")) + sting_feedback(user, target) + c.chem_charges -= chemical_cost + +/obj/effect/proc_holder/changeling/proc/sting_action(mob/user, mob/target) + return 0 + +/obj/effect/proc_holder/changeling/proc/sting_feedback(mob/user, mob/target) + return 0 + +//Fairly important to remember to return 1 on success >.< +/obj/effect/proc_holder/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 0 + if(req_human && !ishuman(user)) + to_chat(user, "We cannot do that in this form!") + return 0 + 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 0 + if(c.absorbedcount < req_dna) + to_chat(user, "We require at least [req_dna] sample\s of compatible DNA.") + return 0 + if(c.trueabsorbs < req_absorbs) + to_chat(user, "We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability.") + if(req_stat < user.stat) + to_chat(user, "We are incapacitated.") + return 0 + if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath)) + to_chat(user, "We are incapacitated.") + return 0 + return 1 + +//used in /mob/Stat() +/obj/effect/proc_holder/changeling/proc/can_be_used_by(mob/user) + if(QDELETED(user)) + return FALSE + 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 34a8231960..5cb4faa86e 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -1,117 +1,117 @@ -/obj/effect/proc_holder/changeling/absorbDNA - name = "Absorb DNA" - desc = "Absorb the DNA of our victim." - chemical_cost = 0 - dna_cost = 0 - req_human = 1 - action_icon = 'icons/mob/actions/actions_changeling.dmi' - action_icon_state = "ling_absorb_dna" - action_background_icon_state = "bg_ling" - -/obj/effect/proc_holder/changeling/absorbDNA/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) - - - -/obj/effect/proc_holder/changeling/absorbDNA/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.nutrition = min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED) - - if(target.mind)//if the victim has got a mind - // Absorb a lizard, speak Draconic. - user.copy_known_languages_from(target) - - target.mind.show_memory(user, 0) //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 = target.logging[LOG_SAY] - - 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) - target_ling.geneticpoints = 0 - target_ling.canrespec = 0 - changeling.chem_storage += round(target_ling.chem_storage/2) - 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 - - - changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage) - - changeling.isabsorbing = 0 - changeling.canrespec = 1 - - target.death(0) - target.Drain() - return TRUE +/obj/effect/proc_holder/changeling/absorbDNA + name = "Absorb DNA" + desc = "Absorb the DNA of our victim." + chemical_cost = 0 + dna_cost = 0 + req_human = 1 + action_icon = 'icons/mob/actions/actions_changeling.dmi' + action_icon_state = "ling_absorb_dna" + action_background_icon_state = "bg_ling" + +/obj/effect/proc_holder/changeling/absorbDNA/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) + + + +/obj/effect/proc_holder/changeling/absorbDNA/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.nutrition = min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED) + + if(target.mind)//if the victim has got a mind + // Absorb a lizard, speak Draconic. + user.copy_known_languages_from(target) + + target.mind.show_memory(user, 0) //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 = target.logging[LOG_SAY] + + 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) + target_ling.geneticpoints = 0 + target_ling.canrespec = 0 + changeling.chem_storage += round(target_ling.chem_storage/2) + 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 + + + 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/fakedeath.dm b/code/modules/antagonists/changeling/powers/fakedeath.dm index 346d948c79..88328c582c 100644 --- a/code/modules/antagonists/changeling/powers/fakedeath.dm +++ b/code/modules/antagonists/changeling/powers/fakedeath.dm @@ -1,43 +1,43 @@ -/obj/effect/proc_holder/changeling/fakedeath - name = "Reviving Stasis" - desc = "We fall into a stasis, allowing us to regenerate and trick our enemies." - chemical_cost = 15 - dna_cost = 0 - req_dna = 1 - req_stat = DEAD - ignores_fakedeath = TRUE - action_icon = 'icons/mob/actions/actions_changeling.dmi' - action_icon_state = "ling_regenerative_stasis" - action_background_icon_state = "bg_ling" - -//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay. -/obj/effect/proc_holder/changeling/fakedeath/sting_action(mob/living/user) - to_chat(user, "We begin our stasis, preparing energy to arise once more.") - if(user.stat != DEAD) - user.emote("deathgasp") - user.tod = STATION_TIME_TIMESTAMP("hh:mm:ss") - user.fakedeath("changeling") //play dead - user.update_stat() - user.update_canmove() - - addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE) - return TRUE - -/obj/effect/proc_holder/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.") - var/obj/effect/proc_holder/changeling/revive/RV = new /obj/effect/proc_holder/changeling/revive(null) - C.purchasedpowers += RV - RV.action.Grant(user) - -/obj/effect/proc_holder/changeling/fakedeath/can_sting(mob/living/user) - if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling")) - to_chat(user, "We are already reviving.") - return - if(!user.stat) //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 ..() +/obj/effect/proc_holder/changeling/fakedeath + name = "Reviving Stasis" + desc = "We fall into a stasis, allowing us to regenerate and trick our enemies." + chemical_cost = 15 + dna_cost = 0 + req_dna = 1 + req_stat = DEAD + ignores_fakedeath = TRUE + action_icon = 'icons/mob/actions/actions_changeling.dmi' + action_icon_state = "ling_regenerative_stasis" + action_background_icon_state = "bg_ling" + +//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay. +/obj/effect/proc_holder/changeling/fakedeath/sting_action(mob/living/user) + to_chat(user, "We begin our stasis, preparing energy to arise once more.") + if(user.stat != DEAD) + user.emote("deathgasp") + user.tod = STATION_TIME_TIMESTAMP("hh:mm:ss") + user.fakedeath("changeling") //play dead + user.update_stat() + user.update_canmove() + + addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE) + return TRUE + +/obj/effect/proc_holder/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.") + var/obj/effect/proc_holder/changeling/revive/RV = new /obj/effect/proc_holder/changeling/revive(null) + C.purchasedpowers += RV + RV.action.Grant(user) + +/obj/effect/proc_holder/changeling/fakedeath/can_sting(mob/living/user) + if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling")) + to_chat(user, "We are already reviving.") + return + if(!user.stat) //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/revive.dm b/code/modules/antagonists/changeling/powers/revive.dm index 3404765628..6c2220648d 100644 --- a/code/modules/antagonists/changeling/powers/revive.dm +++ b/code/modules/antagonists/changeling/powers/revive.dm @@ -1,44 +1,44 @@ -/obj/effect/proc_holder/changeling/revive - name = "Revive" - desc = "We regenerate, healing all damage from our form." - helptext = "Does not regrow lost organs or a missing head." - req_stat = DEAD - always_keep = TRUE - ignores_fakedeath = TRUE - action_icon = 'icons/mob/actions/actions_changeling.dmi' - action_icon_state = "ling_revive" - action_background_icon_state = "bg_ling" - -//Revive from revival stasis -/obj/effect/proc_holder/changeling/revive/sting_action(mob/living/carbon/user) - user.cure_fakedeath("changeling") - user.revive(full_heal = 1) - 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, 1) - 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() - to_chat(user, "We have revived ourselves.") - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.purchasedpowers -= src - src.action.Remove(user) - return TRUE - -/obj/effect/proc_holder/changeling/revive/can_be_used_by(mob/living/user) - . = ..() - if(!.) - return - - if(HAS_TRAIT(user, CHANGELING_DRAIN) || ((user.stat != DEAD) && !(HAS_TRAIT(user, TRAIT_DEATHCOMA)))) - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - changeling.purchasedpowers -= src - return FALSE - +/obj/effect/proc_holder/changeling/revive + name = "Revive" + desc = "We regenerate, healing all damage from our form." + helptext = "Does not regrow lost organs or a missing head." + req_stat = DEAD + always_keep = TRUE + ignores_fakedeath = TRUE + action_icon = 'icons/mob/actions/actions_changeling.dmi' + action_icon_state = "ling_revive" + action_background_icon_state = "bg_ling" + +//Revive from revival stasis +/obj/effect/proc_holder/changeling/revive/sting_action(mob/living/carbon/user) + user.cure_fakedeath("changeling") + user.revive(full_heal = 1) + 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, 1) + 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() + to_chat(user, "We have revived ourselves.") + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.purchasedpowers -= src + src.action.Remove(user) + return TRUE + +/obj/effect/proc_holder/changeling/revive/can_be_used_by(mob/living/user) + . = ..() + if(!.) + return + + if(HAS_TRAIT(user, CHANGELING_DRAIN) || ((user.stat != DEAD) && !(HAS_TRAIT(user, TRAIT_DEATHCOMA)))) + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) + changeling.purchasedpowers -= src + return FALSE + diff --git a/code/modules/antagonists/changeling/powers/spiders.dm b/code/modules/antagonists/changeling/powers/spiders.dm index 489f646b39..6bd15fea92 100644 --- a/code/modules/antagonists/changeling/powers/spiders.dm +++ b/code/modules/antagonists/changeling/powers/spiders.dm @@ -1,16 +1,16 @@ -/obj/effect/proc_holder/changeling/spiders - name = "Spread Infestation" - desc = "Our form divides, creating arachnids which will grow into deadly beasts." - helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA gained through Absorb, and not through DNA sting. This ability is very loud, and will guarantee that our blood will react violently to heat." - chemical_cost = 45 - dna_cost = 1 - loudness = 4 - req_absorbs = 3 - action_icon = 'icons/effects/effects.dmi' - action_icon_state = "spiderling" - action_background_icon_state = "bg_ling" - -//Makes some spiderlings. Good for setting traps and causing general trouble. -/obj/effect/proc_holder/changeling/spiders/sting_action(mob/user) - spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, user, 2, FALSE) - return TRUE +/obj/effect/proc_holder/changeling/spiders + name = "Spread Infestation" + desc = "Our form divides, creating arachnids which will grow into deadly beasts." + helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA gained through Absorb, and not through DNA sting. This ability is very loud, and will guarantee that our blood will react violently to heat." + chemical_cost = 45 + dna_cost = 1 + loudness = 4 + req_absorbs = 3 + action_icon = 'icons/effects/effects.dmi' + action_icon_state = "spiderling" + action_background_icon_state = "bg_ling" + +//Makes some spiderlings. Good for setting traps and causing general trouble. +/obj/effect/proc_holder/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 dc7887eee3..3e19ce74fe 100644 --- a/code/modules/antagonists/changeling/powers/tiny_prick.dm +++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm @@ -1,266 +1,266 @@ -/obj/effect/proc_holder/changeling/sting - name = "Tiny Prick" - desc = "Stabby stabby." - var/sting_icon = null - -/obj/effect/proc_holder/changeling/sting/Click() - var/mob/user = usr - 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 - -/obj/effect/proc_holder/changeling/sting/proc/set_sting(mob/user) - to_chat(user, "We prepare our sting, use alt+click or middle mouse button on 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 = sting_icon - user.hud_used.lingstingdisplay.invisibility = 0 - -/obj/effect/proc_holder/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) - -/obj/effect/proc_holder/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 = 0)) - return - return 1 - -/obj/effect/proc_holder/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 - - -/obj/effect/proc_holder/changeling/sting/transformation - name = "Temporary Transformation Sting" - desc = "We silently sting a human, injecting a chemical that forces them to transform into a chosen being for a limited time. Additional stings extend the duration." - helptext = "The victim will transform much like a changeling would for a limited time. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human. This ability is loud, and might cause our blood to react violently to heat." - sting_icon = "sting_transform" - chemical_cost = 10 - dna_cost = 2 - loudness = 1 - var/datum/changelingprofile/selected_dna = null - action_icon = 'icons/mob/actions/actions_changeling.dmi' - action_icon_state = "ling_sting_transform" - action_background_icon_state = "bg_ling" - -/obj/effect/proc_holder/changeling/sting/transformation/Click() - 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 - ..() - -/obj/effect/proc_holder/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 - -/obj/effect/proc_holder/changeling/sting/transformation/sting_action(mob/user, mob/target) - - 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)) - if(C.reagents.has_reagent(/datum/reagent/changeling_string)) - C.reagents.add_reagent(/datum/reagent/changeling_string,120) - log_combat(user, target, "stung", "transformation sting", ", extending the duration.") - else - C.reagents.add_reagent(/datum/reagent/changeling_string,120,list("desired_dna" = selected_dna.dna)) - log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'") - - -/obj/effect/proc_holder/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." - helptext = "The victim will form an armblade much like a changeling would, except the armblade is dull and useless. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat." - sting_icon = "sting_armblade" - chemical_cost = 20 - dna_cost = 1 - loudness = 1 - action_icon = 'icons/mob/actions/actions_changeling.dmi' - action_icon_state = "ling_sting_fake" - action_background_icon_state = "bg_ling" - -/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 - -/obj/effect/proc_holder/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 - -/obj/effect/proc_holder/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, 1) - - addtimer(CALLBACK(src, .proc/remove_fake, target, blade), 600) - return TRUE - -/obj/effect/proc_holder/changeling/sting/false_armblade/proc/remove_fake(mob/target, obj/item/melee/arm_blade/false/blade) - playsound(target, 'sound/effects/blobattack.ogg', 30, 1) - 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 - -/obj/effect/proc_holder/changeling/sting/LSD - name = "Hallucination Sting" - desc = "Causes terror in the target and deals a minor amount of toxin damage." - helptext = "We evolve the ability to sting a target with a powerful toxic hallucinogenic chemical. The target does not notice they have been stung, and the effect begins instantaneously. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat." - sting_icon = "sting_lsd" - chemical_cost = 10 - dna_cost = 1 - loudness = 1 - action_icon = 'icons/mob/actions/actions_changeling.dmi' - action_icon_state = "ling_sting_lsd" - action_background_icon_state = "bg_ling" - -/obj/effect/proc_holder/changeling/sting/LSD/sting_action(mob/user, mob/target) - log_combat(user, target, "stung", "LSD sting") - if(target.reagents) - target.reagents.add_reagent(/datum/reagent/blob/regenerative_materia, 5) - target.reagents.add_reagent(/datum/reagent/toxin/mindbreaker, 5) - return TRUE - -/obj/effect/proc_holder/changeling/sting/cryo - name = "Cryogenic Sting" - desc = "We silently sting a human with a cocktail of chemicals that freeze them." - helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat." - sting_icon = "sting_cryo" - chemical_cost = 15 - dna_cost = 2 - loudness = 1 - action_icon = 'icons/mob/actions/actions_changeling.dmi' - action_icon_state = "ling_sting_cryo" - action_background_icon_state = "bg_ling" - -/obj/effect/proc_holder/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 +/obj/effect/proc_holder/changeling/sting + name = "Tiny Prick" + desc = "Stabby stabby." + var/sting_icon = null + +/obj/effect/proc_holder/changeling/sting/Click() + var/mob/user = usr + 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 + +/obj/effect/proc_holder/changeling/sting/proc/set_sting(mob/user) + to_chat(user, "We prepare our sting, use alt+click or middle mouse button on 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 = sting_icon + user.hud_used.lingstingdisplay.invisibility = 0 + +/obj/effect/proc_holder/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) + +/obj/effect/proc_holder/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 = 0)) + return + return 1 + +/obj/effect/proc_holder/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 + + +/obj/effect/proc_holder/changeling/sting/transformation + name = "Temporary Transformation Sting" + desc = "We silently sting a human, injecting a chemical that forces them to transform into a chosen being for a limited time. Additional stings extend the duration." + helptext = "The victim will transform much like a changeling would for a limited time. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human. This ability is loud, and might cause our blood to react violently to heat." + sting_icon = "sting_transform" + chemical_cost = 10 + dna_cost = 2 + loudness = 1 + var/datum/changelingprofile/selected_dna = null + action_icon = 'icons/mob/actions/actions_changeling.dmi' + action_icon_state = "ling_sting_transform" + action_background_icon_state = "bg_ling" + +/obj/effect/proc_holder/changeling/sting/transformation/Click() + 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 + ..() + +/obj/effect/proc_holder/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 + +/obj/effect/proc_holder/changeling/sting/transformation/sting_action(mob/user, mob/target) + + 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)) + if(C.reagents.has_reagent(/datum/reagent/changeling_string)) + C.reagents.add_reagent(/datum/reagent/changeling_string,120) + log_combat(user, target, "stung", "transformation sting", ", extending the duration.") + else + C.reagents.add_reagent(/datum/reagent/changeling_string,120,list("desired_dna" = selected_dna.dna)) + log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'") + + +/obj/effect/proc_holder/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." + helptext = "The victim will form an armblade much like a changeling would, except the armblade is dull and useless. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat." + sting_icon = "sting_armblade" + chemical_cost = 20 + dna_cost = 1 + loudness = 1 + action_icon = 'icons/mob/actions/actions_changeling.dmi' + action_icon_state = "ling_sting_fake" + action_background_icon_state = "bg_ling" + +/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 + +/obj/effect/proc_holder/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 + +/obj/effect/proc_holder/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, 1) + + addtimer(CALLBACK(src, .proc/remove_fake, target, blade), 600) + return TRUE + +/obj/effect/proc_holder/changeling/sting/false_armblade/proc/remove_fake(mob/target, obj/item/melee/arm_blade/false/blade) + playsound(target, 'sound/effects/blobattack.ogg', 30, 1) + 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 + +/obj/effect/proc_holder/changeling/sting/LSD + name = "Hallucination Sting" + desc = "Causes terror in the target and deals a minor amount of toxin damage." + helptext = "We evolve the ability to sting a target with a powerful toxic hallucinogenic chemical. The target does not notice they have been stung, and the effect begins instantaneously. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat." + sting_icon = "sting_lsd" + chemical_cost = 10 + dna_cost = 1 + loudness = 1 + action_icon = 'icons/mob/actions/actions_changeling.dmi' + action_icon_state = "ling_sting_lsd" + action_background_icon_state = "bg_ling" + +/obj/effect/proc_holder/changeling/sting/LSD/sting_action(mob/user, mob/target) + log_combat(user, target, "stung", "LSD sting") + if(target.reagents) + target.reagents.add_reagent(/datum/reagent/blob/regenerative_materia, 5) + target.reagents.add_reagent(/datum/reagent/toxin/mindbreaker, 5) + return TRUE + +/obj/effect/proc_holder/changeling/sting/cryo + name = "Cryogenic Sting" + desc = "We silently sting a human with a cocktail of chemicals that freeze them." + helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat." + sting_icon = "sting_cryo" + chemical_cost = 15 + dna_cost = 2 + loudness = 1 + action_icon = 'icons/mob/actions/actions_changeling.dmi' + action_icon_state = "ling_sting_cryo" + action_background_icon_state = "bg_ling" + +/obj/effect/proc_holder/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/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm index b98fa04504..1d6a31b072 100644 --- a/code/modules/antagonists/revenant/revenant.dm +++ b/code/modules/antagonists/revenant/revenant.dm @@ -26,7 +26,7 @@ spacewalk = TRUE sight = SEE_SELF throwforce = 0 - blood_volume = 0 + blood_volume = 0 see_in_dark = 8 lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm index a0dbc4d9b1..e6bb3f1295 100644 --- a/code/modules/antagonists/wizard/equipment/artefact.dm +++ b/code/modules/antagonists/wizard/equipment/artefact.dm @@ -1,445 +1,445 @@ - -//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" - item_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, var/spawn_type, var/spawn_amt, var/desc, var/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_pull() - 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" - -////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, staring into it gives you vision beyond mortal means." - 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/xray_granted = FALSE - -/obj/item/scrying/equipped(mob/user) - if(!xray_granted && ishuman(user)) - var/mob/living/carbon/human/H = user - if(!(H.dna.check_mutation(XRAY))) - H.dna.add_mutation(XRAY) - xray_granted = TRUE - . = ..() - -/obj/item/scrying/attack_self(mob/user) - to_chat(user, "You can see...everything!") - 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" - item_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 - - 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/space, icon_update=0) - M.revive(full_heal = 1, admin_revive = 1) - 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), SLOT_HEAD) - H.equip_to_slot_or_del(new /obj/item/clothing/under/roman(H), SLOT_W_UNIFORM) - H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), SLOT_SHOES) - 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/twohanded/spear(H), SLOT_BACK) - - -/obj/item/voodoo - name = "wicker doll" - desc = "Something creepy about it." - icon = 'icons/obj/wizard.dmi' - icon_state = "voodoo" - item_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.Knockdown(40) - GiveHint(target) - else if(istype(I, /obj/item/bikehorn)) - to_chat(target, "HONK") - SEND_SOUND(target, 'sound/items/airhorn.ogg') - target.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 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) - spawn(100) - user.reset_perspective(null) - user.unset_machine() - 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/update_targets() - LAZYINITLIST(possible) - if(!voodoo_link) - return - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - if(md5(H.dna.uni_identity) in voodoo_link.fingerprints) - 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.canmove = TRUE - -/obj/item/warpwhistle/attack_self(mob/living/carbon/user) - if(!istype(user) || on_cooldown) - return - var/turf/T = get_turf(user) - var/area/A = get_area(user) - if(!T || !A || A.noteleport) - to_chat(user, "You play \the [src], yet no sound comes out of it... Looks like it won't work here.") - return - on_cooldown = TRUE - last_user = user - playsound(T,'sound/magic/warpwhistle.ogg', 200, 1) - user.canmove = FALSE - 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) - if(!T) - end_effect(user) - return - var/turf/potential_T = find_safe_turf() - if(!potential_T) - end_effect(user) - return - 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.canmove = 0 - 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 - sleep(40) - on_cooldown = 0 - -/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" + item_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, var/spawn_type, var/spawn_amt, var/desc, var/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_pull() + 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" + +////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, staring into it gives you vision beyond mortal means." + 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/xray_granted = FALSE + +/obj/item/scrying/equipped(mob/user) + if(!xray_granted && ishuman(user)) + var/mob/living/carbon/human/H = user + if(!(H.dna.check_mutation(XRAY))) + H.dna.add_mutation(XRAY) + xray_granted = TRUE + . = ..() + +/obj/item/scrying/attack_self(mob/user) + to_chat(user, "You can see...everything!") + 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" + item_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 + + 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/space, icon_update=0) + M.revive(full_heal = 1, admin_revive = 1) + 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), SLOT_HEAD) + H.equip_to_slot_or_del(new /obj/item/clothing/under/roman(H), SLOT_W_UNIFORM) + H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), SLOT_SHOES) + 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/twohanded/spear(H), SLOT_BACK) + + +/obj/item/voodoo + name = "wicker doll" + desc = "Something creepy about it." + icon = 'icons/obj/wizard.dmi' + icon_state = "voodoo" + item_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.Knockdown(40) + GiveHint(target) + else if(istype(I, /obj/item/bikehorn)) + to_chat(target, "HONK") + SEND_SOUND(target, 'sound/items/airhorn.ogg') + target.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 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) + spawn(100) + user.reset_perspective(null) + user.unset_machine() + 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/update_targets() + LAZYINITLIST(possible) + if(!voodoo_link) + return + for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) + if(md5(H.dna.uni_identity) in voodoo_link.fingerprints) + 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.canmove = TRUE + +/obj/item/warpwhistle/attack_self(mob/living/carbon/user) + if(!istype(user) || on_cooldown) + return + var/turf/T = get_turf(user) + var/area/A = get_area(user) + if(!T || !A || A.noteleport) + to_chat(user, "You play \the [src], yet no sound comes out of it... Looks like it won't work here.") + return + on_cooldown = TRUE + last_user = user + playsound(T,'sound/magic/warpwhistle.ogg', 200, 1) + user.canmove = FALSE + 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) + if(!T) + end_effect(user) + return + var/turf/potential_T = find_safe_turf() + if(!potential_T) + end_effect(user) + return + 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.canmove = 0 + 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 + sleep(40) + on_cooldown = 0 + +/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 dfde04836d..bd4f1a8acd 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook.dm @@ -1,785 +1,785 @@ -/datum/spellbook_entry - var/name = "Entry Name" - - var/spell_type = null - var/desc = "" - var/category = "Offensive" - var/cost = 2 - var/refundable = 1 - 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 - var/dynamic_cost = 0 // How much threat the spell costs to purchase for dynamic. - var/dynamic_requirement = 0 // How high the threat level needs to be for purchasing in dynamic. - -/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 - if(istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - if(dynamic_requirement > 0 && mode.threat_level < dynamic_requirement) - return 0 - return 1 - -/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) // Specific circumstances - if(book.uses0 && istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - if(mode.threat < dynamic_cost) - return 0 - return 1 - -/datum/spellbook_entry/proc/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return 1 on success - if(!S || QDELETED(S)) - S = new spell_type() - //Check if we got the spell already - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) // Not using directly in case it was learned from one spellbook then upgraded in another - if(aspell.spell_level >= aspell.level_max) - to_chat(user, "This spell cannot be improved further.") - return 0 - 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 1 - //No same spell found - just learn it - if(dynamic_cost > 0 && istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - mode.spend_threat(dynamic_cost) - mode.log_threat("Wizard spent [dynamic_cost] on [name].") - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - user.mind.AddSpell(S) - to_chat(user, "You have learned [S.name].") - return 1 - -/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user,obj/item/spellbook/book) - if(!refundable) - return 0 - 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 1 - return 0 - -/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 - if(dynamic_cost > 0 && istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - mode.refund_threat(dynamic_cost) - mode.log_threat("Wizard refunded [dynamic_cost] on [name].") - 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?"Needs 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 = "Disintegrate" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate - -/datum/spellbook_entry/nuclearfist - name = "Nuclear Fist" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/nuclear_fist - -/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/lightningPacket - name = "Lightning bolt! Lightning bolt!" - 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/conjure/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/targeted/trigger/blind - cost = 1 - -/datum/spellbook_entry/mindswap - name = "Mindswap" - spell_type = /obj/effect/proc_holder/spell/targeted/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 = 3 - -/datum/spellbook_entry/lightningbolt/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return 1 on success - . = ..() - user.flags_1 |= TESLA_IGNORE_1 - -/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/targeted/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/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 = 0 - 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 1 - -/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 - dynamic_requirement = 60 - dynamic_cost = 20 - -/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 metal 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 ghost while alive, allowing you to spy upon the station with ease. 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 in 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/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. - new /obj/item/clothing/gloves/color/purple(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" - dynamic_requirement = 50 - dynamic_cost = 10 - -/datum/spellbook_entry/item/plasmafist - name = "Plasma Fist" - desc = "A forbidden martial art designed on the surging power of plasma. Use it to harness the ancient power." - item_path = /obj/item/book/granter/martial/plasma_fist - cost = 3 - -/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" - dynamic_requirement = 60 - -/datum/spellbook_entry/item/bloodbottle/New() - ..() - dynamic_cost = CONFIG_GET(keyed_list/dynamic_cost)["slaughter_demon"] - -/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" - dynamic_requirement = 40 - -/datum/spellbook_entry/item/hugbottle/New() - ..() - dynamic_cost = CONFIG_GET(keyed_list/dynamic_cost)["slaughter_demon"]/3 - -/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/twohanded/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/twohanded/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_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 = 0 - buy_word = "Cast" - var/active = 0 - -/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 ..() - -/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, 1) - 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. Just be careful not to stand still too long!" - dynamic_cost = 10 - dynamic_requirement = 60 - -/datum/spellbook_entry/summon/guns/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return 0 - 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, 25) - active = 1 - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) - to_chat(user, "You have cast summon guns!") - if(istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - var/threat_spent = dynamic_cost - mode.spend_threat(threat_spent) - mode.log_threat("Wizard spent [threat_spent] on summon guns.") - return 1 - -/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." - dynamic_cost = 10 - dynamic_requirement = 60 - -/datum/spellbook_entry/summon/magic/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return 0 - return (!CONFIG_GET(flag/no_summon_guns) && ..()) - -/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, 25) - active = 1 - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) - to_chat(user, "You have cast summon magic!") - if(istype(SSticker.mode,/datum/game_mode/dynamic)) - var/datum/game_mode/dynamic/mode = SSticker.mode - var/threat_spent = dynamic_cost - mode.spend_threat(threat_spent) - mode.log_threat("Wizard spent [threat_spent] on summon magic.") - return 1 - -/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." - dynamic_cost = 20 - dynamic_requirement = 60 - var/times = 0 - -/datum/spellbook_entry/summon/events/IsAvailible() - if(!SSticker.mode) // In case spellbook is placed on map - return 0 - 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() - if(istype(SSticker.mode,/datum/game_mode/dynamic) && times == 0) - var/datum/game_mode/dynamic/mode = SSticker.mode - var/threat_spent = dynamic_cost - mode.spend_threat(threat_spent) - mode.log_threat("Wizard spent [threat_spent] on summon events.") - times++ - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) - to_chat(user, "You have cast summon events.") - return 1 - -/datum/spellbook_entry/summon/events/GetInfo() - . = ..() - if(times>0) - . += "You cast it [times] times.
                " - return . - -/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++ - 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.") - uses++ - 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 1 - - 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 = 1 + 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 + var/dynamic_cost = 0 // How much threat the spell costs to purchase for dynamic. + var/dynamic_requirement = 0 // How high the threat level needs to be for purchasing in dynamic. + +/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 + if(istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + if(dynamic_requirement > 0 && mode.threat_level < dynamic_requirement) + return 0 + return 1 + +/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) // Specific circumstances + if(book.uses0 && istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + if(mode.threat < dynamic_cost) + return 0 + return 1 + +/datum/spellbook_entry/proc/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return 1 on success + if(!S || QDELETED(S)) + S = new spell_type() + //Check if we got the spell already + for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) + if(initial(S.name) == initial(aspell.name)) // Not using directly in case it was learned from one spellbook then upgraded in another + if(aspell.spell_level >= aspell.level_max) + to_chat(user, "This spell cannot be improved further.") + return 0 + 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 1 + //No same spell found - just learn it + if(dynamic_cost > 0 && istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + mode.spend_threat(dynamic_cost) + mode.log_threat("Wizard spent [dynamic_cost] on [name].") + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + user.mind.AddSpell(S) + to_chat(user, "You have learned [S.name].") + return 1 + +/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user,obj/item/spellbook/book) + if(!refundable) + return 0 + 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 1 + return 0 + +/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 + if(dynamic_cost > 0 && istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + mode.refund_threat(dynamic_cost) + mode.log_threat("Wizard refunded [dynamic_cost] on [name].") + 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?"Needs 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 = "Disintegrate" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate + +/datum/spellbook_entry/nuclearfist + name = "Nuclear Fist" + spell_type = /obj/effect/proc_holder/spell/targeted/touch/nuclear_fist + +/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/lightningPacket + name = "Lightning bolt! Lightning bolt!" + 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/conjure/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/targeted/trigger/blind + cost = 1 + +/datum/spellbook_entry/mindswap + name = "Mindswap" + spell_type = /obj/effect/proc_holder/spell/targeted/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 = 3 + +/datum/spellbook_entry/lightningbolt/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return 1 on success + . = ..() + user.flags_1 |= TESLA_IGNORE_1 + +/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/targeted/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/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 = 0 + 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 1 + +/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 + dynamic_requirement = 60 + dynamic_cost = 20 + +/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 metal 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 ghost while alive, allowing you to spy upon the station with ease. 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 in 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/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. + new /obj/item/clothing/gloves/color/purple(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" + dynamic_requirement = 50 + dynamic_cost = 10 + +/datum/spellbook_entry/item/plasmafist + name = "Plasma Fist" + desc = "A forbidden martial art designed on the surging power of plasma. Use it to harness the ancient power." + item_path = /obj/item/book/granter/martial/plasma_fist + cost = 3 + +/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" + dynamic_requirement = 60 + +/datum/spellbook_entry/item/bloodbottle/New() + ..() + dynamic_cost = CONFIG_GET(keyed_list/dynamic_cost)["slaughter_demon"] + +/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" + dynamic_requirement = 40 + +/datum/spellbook_entry/item/hugbottle/New() + ..() + dynamic_cost = CONFIG_GET(keyed_list/dynamic_cost)["slaughter_demon"]/3 + +/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/twohanded/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/twohanded/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_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 = 0 + buy_word = "Cast" + var/active = 0 + +/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 ..() + +/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, 1) + 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. Just be careful not to stand still too long!" + dynamic_cost = 10 + dynamic_requirement = 60 + +/datum/spellbook_entry/summon/guns/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return 0 + 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, 25) + active = 1 + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) + to_chat(user, "You have cast summon guns!") + if(istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + var/threat_spent = dynamic_cost + mode.spend_threat(threat_spent) + mode.log_threat("Wizard spent [threat_spent] on summon guns.") + return 1 + +/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." + dynamic_cost = 10 + dynamic_requirement = 60 + +/datum/spellbook_entry/summon/magic/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return 0 + return (!CONFIG_GET(flag/no_summon_guns) && ..()) + +/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, 25) + active = 1 + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) + to_chat(user, "You have cast summon magic!") + if(istype(SSticker.mode,/datum/game_mode/dynamic)) + var/datum/game_mode/dynamic/mode = SSticker.mode + var/threat_spent = dynamic_cost + mode.spend_threat(threat_spent) + mode.log_threat("Wizard spent [threat_spent] on summon magic.") + return 1 + +/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." + dynamic_cost = 20 + dynamic_requirement = 60 + var/times = 0 + +/datum/spellbook_entry/summon/events/IsAvailible() + if(!SSticker.mode) // In case spellbook is placed on map + return 0 + 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() + if(istype(SSticker.mode,/datum/game_mode/dynamic) && times == 0) + var/datum/game_mode/dynamic/mode = SSticker.mode + var/threat_spent = dynamic_cost + mode.spend_threat(threat_spent) + mode.log_threat("Wizard spent [threat_spent] on summon events.") + times++ + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) + to_chat(user, "You have cast summon events.") + return 1 + +/datum/spellbook_entry/summon/events/GetInfo() + . = ..() + if(times>0) + . += "You cast it [times] times.
                " + return . + +/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++ + 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.") + uses++ + 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 1 + + 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 d9a61ee2f3..2d291c580e 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 - materials = list(MAT_METAL=100) - throwforce = 2 - throw_speed = 3 - throw_range = 7 - - 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 - var/activate_cooldown = 3 SECONDS - -/obj/item/assembly/get_part_rating() - return 1 - -/obj/item/assembly/proc/on_attach() - -/obj/item/assembly/proc/on_detach() //call this when detaching it from a device. handles any special functions that need to be updated ex post facto - if(!holder) - return FALSE - forceMove(holder.drop_location()) - holder = null - return TRUE - -/obj/item/assembly/proc/holder_movement() //Called when the holder is moved - 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 + activate_cooldown - 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) +#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 + materials = list(MAT_METAL=100) + throwforce = 2 + throw_speed = 3 + throw_range = 7 + + 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 + var/activate_cooldown = 3 SECONDS + +/obj/item/assembly/get_part_rating() + return 1 + +/obj/item/assembly/proc/on_attach() + +/obj/item/assembly/proc/on_detach() //call this when detaching it from a device. handles any special functions that need to be updated ex post facto + if(!holder) + return FALSE + forceMove(holder.drop_location()) + holder = null + return TRUE + +/obj/item/assembly/proc/holder_movement() //Called when the holder is moved + 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 + activate_cooldown + 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) diff --git a/code/modules/assembly/bomb.dm b/code/modules/assembly/bomb.dm index 8ec93f978f..28bb543828 100644 --- a/code/modules/assembly/bomb.dm +++ b/code/modules/assembly/bomb.dm @@ -1,202 +1,202 @@ -/obj/item/onetankbomb - name = "bomb" - icon = 'icons/obj/tank.dmi' - item_state = "assembly" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - throwforce = 5 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 2 - throw_range = 4 - flags_1 = CONDUCT_1 - var/status = FALSE //0 - not readied //1 - bomb finished with welder - var/obj/item/assembly_holder/bombassembly = null //The first part of the bomb is an assembly holder, holding an igniter+some device - var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank - -/obj/item/onetankbomb/IsSpecialAssembly() - return TRUE - -/obj/item/onetankbomb/examine(mob/user) - bombtank.examine(user) - -/obj/item/onetankbomb/update_icon() - cut_overlays() - if(bombtank) - icon = bombtank.icon - icon_state = bombtank.icon_state - if(bombassembly) - add_overlay(bombassembly.icon_state) - copy_overlays(bombassembly) - add_overlay("bomb_assembly") - -/obj/item/onetankbomb/wrench_act(mob/living/user, obj/item/I) - to_chat(user, "You disassemble [src]!") - if(bombassembly) - bombassembly.forceMove(drop_location()) - bombassembly.master = null - bombassembly = null - if(bombtank) - bombtank.forceMove(drop_location()) - bombtank.master = null - bombtank = null - qdel(src) - return TRUE - -/obj/item/onetankbomb/welder_act(mob/living/user, obj/item/I) - . = FALSE - if(status) - to_chat(user, "[bombtank] already has a pressure hole!") - return - if(!I.tool_start_check(user, amount=0)) - return - if(I.use_tool(src, user, 0, volume=40)) - status = TRUE - GLOB.bombers += "[key_name(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]" - message_admins("[ADMIN_LOOKUPFLW(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]") - to_chat(user, "A pressure hole has been bored to [bombtank] valve. \The [bombtank] can now be ignited.") - add_fingerprint(user) - return TRUE - - -/obj/item/onetankbomb/analyzer_act(mob/living/user, obj/item/I) - bombtank.analyzer_act(user, I) - -/obj/item/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly - bombassembly.attack_self(user, TRUE) - add_fingerprint(user) - return - -/obj/item/onetankbomb/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here. - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*") - playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - sleep(10) - if(QDELETED(src)) - return - if(status) - bombtank.ignite() //if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file - else - bombtank.release() - -//Assembly / attached device memes - -/obj/item/onetankbomb/Crossed(atom/movable/AM as mob|obj) //for mousetraps - . = ..() - if(bombassembly) - bombassembly.Crossed(AM) - -/obj/item/onetankbomb/on_found(mob/finder) //for mousetraps - if(bombassembly) - bombassembly.on_found(finder) - -/obj/item/onetankbomb/attack_hand() //also for mousetraps - . = ..() - if(.) - return - if(bombassembly) - bombassembly.attack_hand() - -/obj/item/onetankbomb/Move() - . = ..() - if(bombassembly) - bombassembly.setDir(dir) - bombassembly.Move() - -/obj/item/onetankbomb/dropped() - . = ..() - if(bombassembly) - bombassembly.dropped() - - - - -// ---------- Procs below are for tanks that are used exclusively in 1-tank bombs ---------- - -//Bomb assembly proc. This turns assembly+tank into a bomb -/obj/item/tank/proc/bomb_assemble(obj/item/assembly_holder/assembly, mob/living/user) - //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it - if(isigniter(assembly.a_left) == isigniter(assembly.a_right)) - return - - if((src in user.get_equipped_items(TRUE)) && !user.canUnEquip(src)) - to_chat(user, "[src] is stuck to you!") - return - - if(!user.canUnEquip(assembly)) - to_chat(user, "[assembly] is stuck to your hand!") - return - - var/obj/item/onetankbomb/bomb = new - user.transferItemToLoc(src, bomb) - user.transferItemToLoc(assembly, bomb) - - bomb.bombassembly = assembly //Tell the bomb about its assembly part - assembly.master = bomb //Tell the assembly about its new owner - - bomb.bombtank = src //Same for tank - master = bomb - - forceMove(bomb) - bomb.update_icon() - - user.put_in_hands(bomb) //Equips the bomb if possible, or puts it on the floor. - to_chat(user, "You attach [assembly] to [src].") - return - -/obj/item/tank/proc/ignite() //This happens when a bomb is told to explode - var/fuel_moles = air_contents.gases[/datum/gas/plasma] + air_contents.gases[/datum/gas/oxygen]/6 - GAS_GARBAGE_COLLECT(air_contents.gases) - var/datum/gas_mixture/bomb_mixture = air_contents.copy() - var/strength = 1 - - var/turf/ground_zero = get_turf(loc) - - if(master) - qdel(master) - qdel(src) - - if(bomb_mixture.temperature > (T0C + 400)) - strength = (fuel_moles/15) - - if(strength >=1) - explosion(ground_zero, round(strength,1), round(strength*2,1), round(strength*3,1), round(strength*4,1)) - else if(strength >=0.5) - explosion(ground_zero, 0, 1, 2, 4) - else if(strength >=0.2) - explosion(ground_zero, -1, 0, 1, 2) - else - ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) - - else if(bomb_mixture.temperature > (T0C + 250)) - strength = (fuel_moles/20) - - if(strength >=1) - explosion(ground_zero, 0, round(strength,1), round(strength*2,1), round(strength*3,1)) - else if (strength >=0.5) - explosion(ground_zero, -1, 0, 1, 2) - else - ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) - - else if(bomb_mixture.temperature > (T0C + 100)) - strength = (fuel_moles/25) - - if (strength >=1) - explosion(ground_zero, -1, 0, round(strength,1), round(strength*3,1)) - else - ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) - - else - ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) - - ground_zero.air_update_turf() - -/obj/item/tank/proc/release() //This happens when the bomb is not welded. Tank contents are just spat out. - var/datum/gas_mixture/removed = air_contents.remove(air_contents.total_moles()) - var/turf/T = get_turf(src) - if(!T) - return - T.assume_air(removed) - air_update_turf() +/obj/item/onetankbomb + name = "bomb" + icon = 'icons/obj/tank.dmi' + item_state = "assembly" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + throwforce = 5 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 2 + throw_range = 4 + flags_1 = CONDUCT_1 + var/status = FALSE //0 - not readied //1 - bomb finished with welder + var/obj/item/assembly_holder/bombassembly = null //The first part of the bomb is an assembly holder, holding an igniter+some device + var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank + +/obj/item/onetankbomb/IsSpecialAssembly() + return TRUE + +/obj/item/onetankbomb/examine(mob/user) + bombtank.examine(user) + +/obj/item/onetankbomb/update_icon() + cut_overlays() + if(bombtank) + icon = bombtank.icon + icon_state = bombtank.icon_state + if(bombassembly) + add_overlay(bombassembly.icon_state) + copy_overlays(bombassembly) + add_overlay("bomb_assembly") + +/obj/item/onetankbomb/wrench_act(mob/living/user, obj/item/I) + to_chat(user, "You disassemble [src]!") + if(bombassembly) + bombassembly.forceMove(drop_location()) + bombassembly.master = null + bombassembly = null + if(bombtank) + bombtank.forceMove(drop_location()) + bombtank.master = null + bombtank = null + qdel(src) + return TRUE + +/obj/item/onetankbomb/welder_act(mob/living/user, obj/item/I) + . = FALSE + if(status) + to_chat(user, "[bombtank] already has a pressure hole!") + return + if(!I.tool_start_check(user, amount=0)) + return + if(I.use_tool(src, user, 0, volume=40)) + status = TRUE + GLOB.bombers += "[key_name(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]" + message_admins("[ADMIN_LOOKUPFLW(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]") + to_chat(user, "A pressure hole has been bored to [bombtank] valve. \The [bombtank] can now be ignited.") + add_fingerprint(user) + return TRUE + + +/obj/item/onetankbomb/analyzer_act(mob/living/user, obj/item/I) + bombtank.analyzer_act(user, I) + +/obj/item/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly + bombassembly.attack_self(user, TRUE) + add_fingerprint(user) + return + +/obj/item/onetankbomb/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here. + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*") + playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + sleep(10) + if(QDELETED(src)) + return + if(status) + bombtank.ignite() //if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file + else + bombtank.release() + +//Assembly / attached device memes + +/obj/item/onetankbomb/Crossed(atom/movable/AM as mob|obj) //for mousetraps + . = ..() + if(bombassembly) + bombassembly.Crossed(AM) + +/obj/item/onetankbomb/on_found(mob/finder) //for mousetraps + if(bombassembly) + bombassembly.on_found(finder) + +/obj/item/onetankbomb/attack_hand() //also for mousetraps + . = ..() + if(.) + return + if(bombassembly) + bombassembly.attack_hand() + +/obj/item/onetankbomb/Move() + . = ..() + if(bombassembly) + bombassembly.setDir(dir) + bombassembly.Move() + +/obj/item/onetankbomb/dropped() + . = ..() + if(bombassembly) + bombassembly.dropped() + + + + +// ---------- Procs below are for tanks that are used exclusively in 1-tank bombs ---------- + +//Bomb assembly proc. This turns assembly+tank into a bomb +/obj/item/tank/proc/bomb_assemble(obj/item/assembly_holder/assembly, mob/living/user) + //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it + if(isigniter(assembly.a_left) == isigniter(assembly.a_right)) + return + + if((src in user.get_equipped_items(TRUE)) && !user.canUnEquip(src)) + to_chat(user, "[src] is stuck to you!") + return + + if(!user.canUnEquip(assembly)) + to_chat(user, "[assembly] is stuck to your hand!") + return + + var/obj/item/onetankbomb/bomb = new + user.transferItemToLoc(src, bomb) + user.transferItemToLoc(assembly, bomb) + + bomb.bombassembly = assembly //Tell the bomb about its assembly part + assembly.master = bomb //Tell the assembly about its new owner + + bomb.bombtank = src //Same for tank + master = bomb + + forceMove(bomb) + bomb.update_icon() + + user.put_in_hands(bomb) //Equips the bomb if possible, or puts it on the floor. + to_chat(user, "You attach [assembly] to [src].") + return + +/obj/item/tank/proc/ignite() //This happens when a bomb is told to explode + var/fuel_moles = air_contents.gases[/datum/gas/plasma] + air_contents.gases[/datum/gas/oxygen]/6 + GAS_GARBAGE_COLLECT(air_contents.gases) + var/datum/gas_mixture/bomb_mixture = air_contents.copy() + var/strength = 1 + + var/turf/ground_zero = get_turf(loc) + + if(master) + qdel(master) + qdel(src) + + if(bomb_mixture.temperature > (T0C + 400)) + strength = (fuel_moles/15) + + if(strength >=1) + explosion(ground_zero, round(strength,1), round(strength*2,1), round(strength*3,1), round(strength*4,1)) + else if(strength >=0.5) + explosion(ground_zero, 0, 1, 2, 4) + else if(strength >=0.2) + explosion(ground_zero, -1, 0, 1, 2) + else + ground_zero.assume_air(bomb_mixture) + ground_zero.hotspot_expose(1000, 125) + + else if(bomb_mixture.temperature > (T0C + 250)) + strength = (fuel_moles/20) + + if(strength >=1) + explosion(ground_zero, 0, round(strength,1), round(strength*2,1), round(strength*3,1)) + else if (strength >=0.5) + explosion(ground_zero, -1, 0, 1, 2) + else + ground_zero.assume_air(bomb_mixture) + ground_zero.hotspot_expose(1000, 125) + + else if(bomb_mixture.temperature > (T0C + 100)) + strength = (fuel_moles/25) + + if (strength >=1) + explosion(ground_zero, -1, 0, round(strength,1), round(strength*3,1)) + else + ground_zero.assume_air(bomb_mixture) + ground_zero.hotspot_expose(1000, 125) + + else + ground_zero.assume_air(bomb_mixture) + ground_zero.hotspot_expose(1000, 125) + + ground_zero.air_update_turf() + +/obj/item/tank/proc/release() //This happens when the bomb is not welded. Tank contents are just spat out. + var/datum/gas_mixture/removed = air_contents.remove(air_contents.total_moles()) + var/turf/T = get_turf(src) + if(!T) + return + T.assume_air(removed) + air_update_turf() diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index 2ac3b34f60..56e21aa79a 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -1,365 +1,365 @@ -#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" - item_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 - materials = list(MAT_METAL = 300, MAT_GLASS = 300) - crit_fail = FALSE //Is the flash burnt out? - 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/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 (crit_fail) - user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but its burnt out!") - return SHAME - else if (user.eye_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(crit_fail) - 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(!crit_fail) - crit_fail = 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(crit_fail || (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]!") - var/toblur = 20 - M.eye_blurry - if(toblur > 0) - M.blur_eyes(toblur) - 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, 20, 1) - return TRUE - else if(issilicon(M)) - var/mob/living/silicon/robot/R = M - log_combat(user, R, "flashed", src) - update_icon(1) - R.Knockdown(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/human/H, mob/user) - if(istype(H) && ishuman(user) && 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)) - 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)) - if(. && !CONFIG_GET(flag/disable_borg_flash_knockdown) && iscarbon(M) && !M.resting && !M.get_eye_protection()) - M.Knockdown(80) - -/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" - item_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/shield - 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 = 'icons/obj/items_and_weapons.dmi' - icon_state = "flashshield" - item_state = "flashshield" - 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 - materials = list(MAT_GLASS=7500, MAT_METAL=1000) - attack_verb = list("shoved", "bashed") - block_chance = 50 - armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) - -/obj/item/assembly/flash/shield/flash_recharge(interval=10) - if(times_used >= 4) - burn_out() - return FALSE - return TRUE - -/obj/item/assembly/flash/shield/attackby(obj/item/W, mob/user) - if(istype(W, /obj/item/assembly/flash/handheld)) - var/obj/item/assembly/flash/handheld/flash = W - if(flash.crit_fail) - 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 = src)) - if(flash.crit_fail || !flash || QDELETED(flash)) - return - crit_fail = FALSE - times_used = 0 - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - update_icon() - flash.crit_fail = TRUE - flash.update_icon() - return - ..() - -/obj/item/assembly/flash/shield/update_icon(flash = FALSE) - icon_state = "flashshield" - item_state = "flashshield" - - if(crit_fail) - icon_state = "riot" - item_state = "riot" - else if(flash) - icon_state = "flashshield_flash" - item_state = "flashshield_flash" - addtimer(CALLBACK(src, /atom/.proc/update_icon), 5) - - if(holder) - holder.update_icon() - -/obj/item/assembly/flash/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) - activate() - return ..() - -//ported from tg - check to make sure it can't appear where it's not supposed to. -/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" //I cannot find this icon no matter how hard I look in tg, so I might just make my own. - 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) +#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" + item_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 + materials = list(MAT_METAL = 300, MAT_GLASS = 300) + crit_fail = FALSE //Is the flash burnt out? + 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/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 (crit_fail) + user.visible_message("[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but its burnt out!") + return SHAME + else if (user.eye_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(crit_fail) + 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(!crit_fail) + crit_fail = 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(crit_fail || (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]!") + var/toblur = 20 - M.eye_blurry + if(toblur > 0) + M.blur_eyes(toblur) + 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, 20, 1) + return TRUE + else if(issilicon(M)) + var/mob/living/silicon/robot/R = M + log_combat(user, R, "flashed", src) + update_icon(1) + R.Knockdown(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/human/H, mob/user) + if(istype(H) && ishuman(user) && 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)) + 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)) + if(. && !CONFIG_GET(flag/disable_borg_flash_knockdown) && iscarbon(M) && !M.resting && !M.get_eye_protection()) + M.Knockdown(80) + +/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" + item_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/shield + 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 = 'icons/obj/items_and_weapons.dmi' + icon_state = "flashshield" + item_state = "flashshield" + 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 + materials = list(MAT_GLASS=7500, MAT_METAL=1000) + attack_verb = list("shoved", "bashed") + block_chance = 50 + armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) + +/obj/item/assembly/flash/shield/flash_recharge(interval=10) + if(times_used >= 4) + burn_out() + return FALSE + return TRUE + +/obj/item/assembly/flash/shield/attackby(obj/item/W, mob/user) + if(istype(W, /obj/item/assembly/flash/handheld)) + var/obj/item/assembly/flash/handheld/flash = W + if(flash.crit_fail) + 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 = src)) + if(flash.crit_fail || !flash || QDELETED(flash)) + return + crit_fail = FALSE + times_used = 0 + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + update_icon() + flash.crit_fail = TRUE + flash.update_icon() + return + ..() + +/obj/item/assembly/flash/shield/update_icon(flash = FALSE) + icon_state = "flashshield" + item_state = "flashshield" + + if(crit_fail) + icon_state = "riot" + item_state = "riot" + else if(flash) + icon_state = "flashshield_flash" + item_state = "flashshield_flash" + addtimer(CALLBACK(src, /atom/.proc/update_icon), 5) + + if(holder) + holder.update_icon() + +/obj/item/assembly/flash/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) + activate() + return ..() + +//ported from tg - check to make sure it can't appear where it's not supposed to. +/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" //I cannot find this icon no matter how hard I look in tg, so I might just make my own. + 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) diff --git a/code/modules/assembly/helpers.dm b/code/modules/assembly/helpers.dm index 5ad5e129a0..3066b14851 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() +// 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 \ No newline at end of file diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm index 74e9cfeff9..d7c184de92 100644 --- a/code/modules/assembly/holder.dm +++ b/code/modules/assembly/holder.dm @@ -1,139 +1,139 @@ -/obj/item/assembly_holder - name = "Assembly" - icon = 'icons/obj/assemblies/new_assemblies.dmi' - icon_state = "holder" - item_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/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/Move() - . = ..() - 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" + item_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/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/Move() + . = ..() + 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 1f62bfaaef..36bbb509fb 100644 --- a/code/modules/assembly/igniter.dm +++ b/code/modules/assembly/igniter.dm @@ -1,41 +1,41 @@ -/obj/item/assembly/igniter - name = "igniter" - desc = "A small electronic device able to ignite combustible substances." - icon_state = "igniter" - materials = list(MAT_METAL=500, MAT_GLASS=50) - var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread - heat = 1000 - -/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/New() - ..() - sparks.set_up(2, 0, src) - sparks.attach(src) - -/obj/item/assembly/igniter/Destroy() - 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(700,10) - 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) +/obj/item/assembly/igniter + name = "igniter" + desc = "A small electronic device able to ignite combustible substances." + icon_state = "igniter" + materials = list(MAT_METAL=500, MAT_GLASS=50) + var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread + heat = 1000 + +/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/New() + ..() + sparks.set_up(2, 0, src) + sparks.attach(src) + +/obj/item/assembly/igniter/Destroy() + 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(700,10) + 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) diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm index 707ebcbb1a..9079b79c3d 100644 --- a/code/modules/assembly/infrared.dm +++ b/code/modules/assembly/infrared.dm @@ -1,239 +1,239 @@ -/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" - materials = list(MAT_METAL=1000, MAT_GLASS=500) - is_position_sensitive = TRUE - - 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() - . = ..() - AddComponent( - /datum/component/simple_rotation, - ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS, - null, - null, - 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) - . = ..() - olddir = dir - -/obj/item/assembly/infra/throw_impact() - . = ..() - 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/ui_interact(mob/user)//TODO: change this this to the wire control panel - . = ..() - if(is_secured(user)) - user.set_machine(src) - var/dat = "Infrared Laser" - dat += "
                Status: [on ? "On" : "Off"]" - dat += "
                Visibility: [visible ? "Visible" : "Invisible"]" - dat += "

                Refresh" - dat += "

                Close" - user << browse(dat, "window=infra") - onclose(user, "infra") - return - -/obj/item/assembly/infra/Topic(href, href_list) - ..() - if(usr.incapacitated() || !in_range(loc, usr)) - usr << browse(null, "window=infra") - onclose(usr, "infra") - return - if(href_list["state"]) - on = !(on) - update_icon() - refreshBeam() - if(href_list["visible"]) - visible = !(visible) - update_icon() - refreshBeam() - if(href_list["close"]) - usr << browse(null, "window=infra") - return - if(usr) - attack_self(usr) - -/obj/item/assembly/infra/setDir() - . = ..() - refreshBeam() - -/***************************IBeam*********************************/ - -/obj/effect/beam/i_beam - name = "infrared beam" - icon = 'icons/obj/projectiles.dmi' - icon_state = "ibeam" - var/obj/item/assembly/infra/master - anchored = TRUE - density = FALSE - pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE|LETPASSTHROW - -/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" + materials = list(MAT_METAL=1000, MAT_GLASS=500) + is_position_sensitive = TRUE + + 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() + . = ..() + AddComponent( + /datum/component/simple_rotation, + ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS, + null, + null, + 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) + . = ..() + olddir = dir + +/obj/item/assembly/infra/throw_impact() + . = ..() + 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/ui_interact(mob/user)//TODO: change this this to the wire control panel + . = ..() + if(is_secured(user)) + user.set_machine(src) + var/dat = "Infrared Laser" + dat += "
                Status: [on ? "On" : "Off"]" + dat += "
                Visibility: [visible ? "Visible" : "Invisible"]" + dat += "

                Refresh" + dat += "

                Close" + user << browse(dat, "window=infra") + onclose(user, "infra") + return + +/obj/item/assembly/infra/Topic(href, href_list) + ..() + if(usr.incapacitated() || !in_range(loc, usr)) + usr << browse(null, "window=infra") + onclose(usr, "infra") + return + if(href_list["state"]) + on = !(on) + update_icon() + refreshBeam() + if(href_list["visible"]) + visible = !(visible) + update_icon() + refreshBeam() + if(href_list["close"]) + usr << browse(null, "window=infra") + return + if(usr) + attack_self(usr) + +/obj/item/assembly/infra/setDir() + . = ..() + refreshBeam() + +/***************************IBeam*********************************/ + +/obj/effect/beam/i_beam + name = "infrared beam" + icon = 'icons/obj/projectiles.dmi' + icon_state = "ibeam" + var/obj/item/assembly/infra/master + anchored = TRUE + density = FALSE + pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE|LETPASSTHROW + +/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 950c0dd109..a42fad82c0 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -1,142 +1,142 @@ -/obj/item/assembly/mousetrap - name = "mousetrap" - desc = "A handy little spring-loaded trap for catching pesty rodents." - icon_state = "mousetrap" - item_state = "mousetrap" - materials = list(MAT_METAL=100) - attachable = TRUE - var/armed = FALSE - - -/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.Knockdown(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(A as mob|obj) - if(!armed) - return ..() - visible_message("[src] is triggered by [A].") - 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" + item_state = "mousetrap" + materials = list(MAT_METAL=100) + attachable = TRUE + var/armed = FALSE + + +/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.Knockdown(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(A as mob|obj) + if(!armed) + return ..() + visible_message("[src] is triggered by [A].") + 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 97359f07fc..329ea85c0e 100644 --- a/code/modules/assembly/proximity.dm +++ b/code/modules/assembly/proximity.dm @@ -1,160 +1,160 @@ -/obj/item/assembly/prox_sensor - name = "proximity sensor" - desc = "Used for scanning and alerting when someone enters a certain proximity." - icon_state = "prox" - materials = list(MAT_METAL=800, MAT_GLASS=200) - attachable = TRUE - - 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_interact(mob/user)//TODO: Change this to the wires thingy - . = ..() - if(is_secured(user)) - var/second = time % 60 - var/minute = (time - second) / 60 - var/dat = "Proximity Sensor" - if(!scanning) - dat += "
                [(timing ? "Arming" : "Not Arming")] [minute]:[second]" - dat += "
                - - + +" - dat += "
                Armed":"1'>Unarmed (Movement sensor active when armed!)"]" - dat += "
                Detection range: - [sensitivity] +" - dat += "

                Refresh" - dat += "

                Close" - user << browse(dat, "window=prox") - onclose(user, "prox") - return - - -/obj/item/assembly/prox_sensor/Topic(href, href_list) - ..() - if(usr.incapacitated() || !in_range(loc, usr)) - usr << browse(null, "window=prox") - onclose(usr, "prox") - return - - if(href_list["sense"]) - sensitivity_change(((href_list["sense"] == "up") ? 1 : -1)) - - if(href_list["scanning"]) - toggle_scan(text2num(href_list["scanning"])) - - if(href_list["time"]) - timing = text2num(href_list["time"]) - update_icon() - - if(href_list["tp"]) - var/tp = text2num(href_list["tp"]) - time += tp - time = min(max(round(time), 0), 600) - - if(href_list["close"]) - usr << browse(null, "window=prox") - return - - if(usr) - attack_self(usr) - +/obj/item/assembly/prox_sensor + name = "proximity sensor" + desc = "Used for scanning and alerting when someone enters a certain proximity." + icon_state = "prox" + materials = list(MAT_METAL=800, MAT_GLASS=200) + attachable = TRUE + + 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_interact(mob/user)//TODO: Change this to the wires thingy + . = ..() + if(is_secured(user)) + var/second = time % 60 + var/minute = (time - second) / 60 + var/dat = "Proximity Sensor" + if(!scanning) + dat += "
                [(timing ? "Arming" : "Not Arming")] [minute]:[second]" + dat += "
                - - + +" + dat += "
                Armed":"1'>Unarmed (Movement sensor active when armed!)"]" + dat += "
                Detection range: - [sensitivity] +" + dat += "

                Refresh" + dat += "

                Close" + user << browse(dat, "window=prox") + onclose(user, "prox") + return + + +/obj/item/assembly/prox_sensor/Topic(href, href_list) + ..() + if(usr.incapacitated() || !in_range(loc, usr)) + usr << browse(null, "window=prox") + onclose(usr, "prox") + return + + if(href_list["sense"]) + sensitivity_change(((href_list["sense"] == "up") ? 1 : -1)) + + if(href_list["scanning"]) + toggle_scan(text2num(href_list["scanning"])) + + if(href_list["time"]) + timing = text2num(href_list["time"]) + update_icon() + + if(href_list["tp"]) + var/tp = text2num(href_list["tp"]) + time += tp + time = min(max(round(time), 0), 600) + + if(href_list["close"]) + usr << browse(null, "window=prox") + return + + if(usr) + attack_self(usr) + diff --git a/code/modules/assembly/shock_kit.dm b/code/modules/assembly/shock_kit.dm index 8d9ba9880c..e18909c9a2 100644 --- a/code/modules/assembly/shock_kit.dm +++ b/code/modules/assembly/shock_kit.dm @@ -1,39 +1,39 @@ -/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 7377ab318e..4fba44a3f2 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -1,209 +1,209 @@ -/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" - item_state = "signaler" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - materials = list(MAT_METAL=400, MAT_GLASS=120) - wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE - attachable = TRUE - - var/code = DEFAULT_SIGNALER_CODE - var/frequency = FREQ_SIGNALER - var/delay = 0 - var/datum/radio_frequency/radio_connection - var/suicider = null - 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) - user.transferItemToLoc(src, user, TRUE) - suicider = user - return MANUAL_SUICIDE - -/obj/item/assembly/signaler/proc/manual_suicide(mob/living/carbon/user) - user.visible_message("[user]'s \the [src] receives a signal, killing [user.p_them()] instantly!") - user.adjustOxyLoss(200)//it sends an electrical pulse to their heart, killing them. or something. - user.death(0) - -/obj/item/assembly/signaler/Initialize() - . = ..() - set_frequency(frequency) - - -/obj/item/assembly/signaler/Destroy() - SSradio.remove_object(src,frequency) - . = ..() - -/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_interact(mob/user, flag1) - . = ..() - if(is_secured(user)) - var/t1 = "-------" - var/dat = {" - - -Send Signal
                -Frequency/Code for signaler:
                -Frequency: -[format_frequency(src.frequency)] -Set
                - -Code: -[src.code] -Set
                -[t1] -
                "} - user << browse(dat, "window=radio") - onclose(user, "radio") - return - - -/obj/item/assembly/signaler/Topic(href, href_list) - ..() - - if(!usr.canUseTopic(src, BE_CLOSE)) - usr << browse(null, "window=radio") - onclose(usr, "radio") - return - - if (href_list["set"]) - - if(href_list["set"] == "freq") - var/new_freq = input(usr, "Input a new signalling frequency", "Remote Signaller Frequency", format_frequency(frequency)) as num|null - if(!usr.canUseTopic(src, BE_CLOSE)) - return - new_freq = unformat_frequency(new_freq) - new_freq = sanitize_frequency(new_freq, TRUE) - set_frequency(new_freq) - - if(href_list["set"] == "code") - var/new_code = input(usr, "Input a new signalling code", "Remote Signaller Code", code) as num|null - if(!usr.canUseTopic(src, BE_CLOSE)) - return - new_code = round(new_code) - new_code = CLAMP(new_code, 1, 100) - code = new_code - - if(href_list["send"]) - spawn( 0 ) - signal() - - if(usr) - attack_self(usr) - - return - -/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]") - - - return - -/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) - 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) - - -// Embedded signaller used in anomalies. -/obj/item/assembly/signaler/anomaly - name = "anomaly core" - desc = "The neutralized core of an anomaly. It'd probably be valuable for research." - icon_state = "anomaly core" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - resistance_flags = FIRE_PROOF - var/anomaly_type = /obj/effect/anomaly - -/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal) - if(!signal) - return FALSE - if(signal.data["code"] != code) - return FALSE - for(var/obj/effect/anomaly/A in get_turf(src)) - A.anomalyNeutralize() - return TRUE - -/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) +/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" + item_state = "signaler" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + materials = list(MAT_METAL=400, MAT_GLASS=120) + wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE + attachable = TRUE + + var/code = DEFAULT_SIGNALER_CODE + var/frequency = FREQ_SIGNALER + var/delay = 0 + var/datum/radio_frequency/radio_connection + var/suicider = null + 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) + user.transferItemToLoc(src, user, TRUE) + suicider = user + return MANUAL_SUICIDE + +/obj/item/assembly/signaler/proc/manual_suicide(mob/living/carbon/user) + user.visible_message("[user]'s \the [src] receives a signal, killing [user.p_them()] instantly!") + user.adjustOxyLoss(200)//it sends an electrical pulse to their heart, killing them. or something. + user.death(0) + +/obj/item/assembly/signaler/Initialize() + . = ..() + set_frequency(frequency) + + +/obj/item/assembly/signaler/Destroy() + SSradio.remove_object(src,frequency) + . = ..() + +/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_interact(mob/user, flag1) + . = ..() + if(is_secured(user)) + var/t1 = "-------" + var/dat = {" + + +Send Signal
                +Frequency/Code for signaler:
                +Frequency: +[format_frequency(src.frequency)] +Set
                + +Code: +[src.code] +Set
                +[t1] +
                "} + user << browse(dat, "window=radio") + onclose(user, "radio") + return + + +/obj/item/assembly/signaler/Topic(href, href_list) + ..() + + if(!usr.canUseTopic(src, BE_CLOSE)) + usr << browse(null, "window=radio") + onclose(usr, "radio") + return + + if (href_list["set"]) + + if(href_list["set"] == "freq") + var/new_freq = input(usr, "Input a new signalling frequency", "Remote Signaller Frequency", format_frequency(frequency)) as num|null + if(!usr.canUseTopic(src, BE_CLOSE)) + return + new_freq = unformat_frequency(new_freq) + new_freq = sanitize_frequency(new_freq, TRUE) + set_frequency(new_freq) + + if(href_list["set"] == "code") + var/new_code = input(usr, "Input a new signalling code", "Remote Signaller Code", code) as num|null + if(!usr.canUseTopic(src, BE_CLOSE)) + return + new_code = round(new_code) + new_code = CLAMP(new_code, 1, 100) + code = new_code + + if(href_list["send"]) + spawn( 0 ) + signal() + + if(usr) + attack_self(usr) + + return + +/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]") + + + return + +/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) + 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) + + +// Embedded signaller used in anomalies. +/obj/item/assembly/signaler/anomaly + name = "anomaly core" + desc = "The neutralized core of an anomaly. It'd probably be valuable for research." + icon_state = "anomaly core" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + resistance_flags = FIRE_PROOF + var/anomaly_type = /obj/effect/anomaly + +/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal) + if(!signal) + return FALSE + if(signal.data["code"] != code) + return FALSE + for(var/obj/effect/anomaly/A in get_turf(src)) + A.anomalyNeutralize() + return TRUE + +/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 \ No newline at end of file diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm index 660ff39c6c..a5c6298910 100644 --- a/code/modules/assembly/timer.dm +++ b/code/modules/assembly/timer.dm @@ -1,135 +1,135 @@ -/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" - materials = list(MAT_METAL=500, MAT_GLASS=50) - attachable = TRUE - - 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_interact(mob/user)//TODO: Have this use the wires - . = ..() - if(is_secured(user)) - var/second = time % 60 - var/minute = (time - second) / 60 - var/dat = "Timing Unit" - dat += "
                [(timing ? "Timing" : "Not Timing")] [minute]:[second]" - dat += "
                - - + +" - dat += "

                Stop repeating" : "1'>Set to repeat")]" - dat += "

                Refresh" - dat += "

                Close" - var/datum/browser/popup = new(user, "timer", name) - popup.set_content(dat) - popup.open() - - -/obj/item/assembly/timer/Topic(href, href_list) - ..() - if(!usr.canUseTopic(src, BE_CLOSE)) - usr << browse(null, "window=timer") - onclose(usr, "timer") - return - - if(href_list["time"]) - timing = text2num(href_list["time"]) - if(timing && istype(holder, /obj/item/transfer_valve)) - var/timer_message = "[ADMIN_LOOKUPFLW(usr)] activated [src] attachment on [holder]." - message_admins(timer_message) - GLOB.bombers += timer_message - log_game("[key_name(usr)] activated [src] attachment on [holder]") - update_icon() - if(href_list["repeat"]) - loop = text2num(href_list["repeat"]) - - if(href_list["tp"]) - var/tp = text2num(href_list["tp"]) - time += tp - time = min(max(round(time), 1), 600) - saved_time = time - - if(href_list["close"]) - usr << browse(null, "window=timer") - return - - if(usr) - attack_self(usr) +/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" + materials = list(MAT_METAL=500, MAT_GLASS=50) + attachable = TRUE + + 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_interact(mob/user)//TODO: Have this use the wires + . = ..() + if(is_secured(user)) + var/second = time % 60 + var/minute = (time - second) / 60 + var/dat = "Timing Unit" + dat += "
                [(timing ? "Timing" : "Not Timing")] [minute]:[second]" + dat += "
                - - + +" + dat += "

                Stop repeating" : "1'>Set to repeat")]" + dat += "

                Refresh" + dat += "

                Close" + var/datum/browser/popup = new(user, "timer", name) + popup.set_content(dat) + popup.open() + + +/obj/item/assembly/timer/Topic(href, href_list) + ..() + if(!usr.canUseTopic(src, BE_CLOSE)) + usr << browse(null, "window=timer") + onclose(usr, "timer") + return + + if(href_list["time"]) + timing = text2num(href_list["time"]) + if(timing && istype(holder, /obj/item/transfer_valve)) + var/timer_message = "[ADMIN_LOOKUPFLW(usr)] activated [src] attachment on [holder]." + message_admins(timer_message) + GLOB.bombers += timer_message + log_game("[key_name(usr)] activated [src] attachment on [holder]") + update_icon() + if(href_list["repeat"]) + loop = text2num(href_list["repeat"]) + + if(href_list["tp"]) + var/tp = text2num(href_list["tp"]) + time += tp + time = min(max(round(time), 1), 600) + saved_time = time + + if(href_list["close"]) + usr << browse(null, "window=timer") + return + + if(usr) + attack_self(usr) diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm index 36a58b2a70..81a92f4d0f 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" - materials = list(MAT_METAL=500, MAT_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/languages // The Message's language - var/mode = 1 - var/static/list/modes = list("inclusive", - "exclusive", - "recognizer", - "voice sensor") - -/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, atom/movable/source) - . = ..() - if(speaker == src) - return - - if(listening && !radio_freq) - record_speech(speaker, raw_message, message_language) - else - if(message_language == languages) // If it isn't in the same language as the message, don't try to find the message - 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) - languages = message_language // Assign the message's language to a variable to use it elsewhere - switch(mode) - if(INCLUSIVE_MODE) - recorded = raw_message - listening = FALSE - say("Activation message is '[recorded]'.", language = languages) // Say the message in the language it was said in - if(EXCLUSIVE_MODE) - recorded = raw_message - listening = FALSE - say("Activation message is '[recorded]'.", language = languages) - if(RECOGNIZER_MODE) - recorded = speaker.GetVoice() - listening = FALSE - say("Your voice pattern is saved.", language = languages) - 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" + materials = list(MAT_METAL=500, MAT_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/languages // The Message's language + var/mode = 1 + var/static/list/modes = list("inclusive", + "exclusive", + "recognizer", + "voice sensor") + +/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, atom/movable/source) + . = ..() + if(speaker == src) + return + + if(listening && !radio_freq) + record_speech(speaker, raw_message, message_language) + else + if(message_language == languages) // If it isn't in the same language as the message, don't try to find the message + 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) + languages = message_language // Assign the message's language to a variable to use it elsewhere + switch(mode) + if(INCLUSIVE_MODE) + recorded = raw_message + listening = FALSE + say("Activation message is '[recorded]'.", language = languages) // Say the message in the language it was said in + if(EXCLUSIVE_MODE) + recorded = raw_message + listening = FALSE + say("Activation message is '[recorded]'.", language = languages) + if(RECOGNIZER_MODE) + recorded = speaker.GetVoice() + listening = FALSE + say("Your voice pattern is saved.", language = languages) + 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/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 0d9c0730c1..12efa01a38 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -1,404 +1,404 @@ - /* -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 - -//Unomos - global list inits for all of the meta gas lists. -//This setup allows procs to only look at one list instead of trying to dig around in lists-within-lists -GLOBAL_LIST_INIT(meta_gas_specific_heats, meta_gas_heat_list()) -GLOBAL_LIST_INIT(meta_gas_names, meta_gas_name_list()) -GLOBAL_LIST_INIT(meta_gas_visibility, meta_gas_visibility_list()) -GLOBAL_LIST_INIT(meta_gas_overlays, meta_gas_overlay_list()) -GLOBAL_LIST_INIT(meta_gas_dangers, meta_gas_danger_list()) -GLOBAL_LIST_INIT(meta_gas_ids, meta_gas_id_list()) -GLOBAL_LIST_INIT(meta_gas_fusions, meta_gas_fusion_list()) -/datum/gas_mixture - var/list/gases = list() - var/temperature = 0 //kelvins - var/tmp/temperature_archived = 0 - var/volume = CELL_VOLUME //liters - var/last_share = 0 - var/list/reaction_results = list() - 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) - if (!isnull(volume)) - src.volume = volume - - //PV = nRT - -/datum/gas_mixture/proc/heat_capacity() //joules per kelvin - var/list/cached_gases = gases - var/list/cached_gasheats = GLOB.meta_gas_specific_heats - . = 0 - for(var/id in cached_gases) - . += cached_gases[id] * cached_gasheats[id] - -/datum/gas_mixture/turf/heat_capacity() // Same as above except vacuums return HEAT_CAPACITY_VACUUM - var/list/cached_gases = gases - var/list/cached_gasheats = GLOB.meta_gas_specific_heats - for(var/id in cached_gases) - . += cached_gases[id] * cached_gasheats[id] - if(!.) - . += HEAT_CAPACITY_VACUUM //we want vacuums in turfs to have the same heat capacity as space - -/datum/gas_mixture/proc/total_moles() - var/cached_gases = gases - TOTAL_MOLES(cached_gases, .) - -/datum/gas_mixture/proc/return_pressure() //kilopascals - 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 - -/datum/gas_mixture/proc/return_temperature() //kelvins - return temperature - -/datum/gas_mixture/proc/return_volume() //liters - return max(0, volume) - -/datum/gas_mixture/proc/thermal_energy() //joules - return THERMAL_ENERGY(src) //see code/__DEFINES/atmospherics.dm; use the define in performance critical areas - -/datum/gas_mixture/proc/merge(datum/gas_mixture/giver) - //Merges all air from giver into self. Deletes giver. - //Returns: 1 if we are mutable, 0 otherwise - -/datum/gas_mixture/proc/remove(amount) - //Proportionally removes amount of gas from the gas_mixture - //Returns: gas_mixture with the gases removed - -/datum/gas_mixture/proc/remove_ratio(ratio) - //Proportionally removes amount of gas from the gas_mixture - //Returns: gas_mixture with the gases removed - -/datum/gas_mixture/proc/copy() - //Creates new, identical gas mixture - //Returns: duplicate gas mixture - -/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample) - //Copies variables from sample - //Returns: 1 if we are mutable, 0 otherwise - -/datum/gas_mixture/proc/copy_from_turf(turf/model) - //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/parse_gas_string(gas_string) - //Copies variables from a particularly formatted string. - //Returns: 1 if we are mutable, 0 otherwise - -/datum/gas_mixture/proc/share(datum/gas_mixture/sharer) - //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/temperature_share(datum/gas_mixture/sharer, conduction_coefficient) - //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/compare(datum/gas_mixture/sample) - //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/react(turf/open/dump_location) - //Performs various reactions such as combustion or fusion (LOL) - //Returns: 1 if any reaction took place; 0 otherwise - - -/datum/gas_mixture/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) - cached_gases[giver_id] += giver_gases[giver_id] - - return 1 - -/datum/gas_mixture/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) - removed_gases[id] = QUANTIZE((cached_gases[id] / sum) * amount) - cached_gases[id] -= removed_gases[id] - GAS_GARBAGE_COLLECT(gases) - - return removed - -/datum/gas_mixture/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) - removed_gases[id] = QUANTIZE(cached_gases[id] * ratio) - cached_gases[id] -= removed_gases[id] - - GAS_GARBAGE_COLLECT(gases) - - return removed - -/datum/gas_mixture/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) - copy_gases[id] = cached_gases[id] - - return copy - - -/datum/gas_mixture/copy_from(datum/gas_mixture/sample) - var/list/cached_gases = gases //accessing datum vars is slower than proc vars - var/list/sample_gases = sample.gases - - temperature = sample.temperature - for(var/id in sample_gases) - cached_gases[id] = sample_gases[id] - - //remove all gases not in the sample - cached_gases &= sample_gases - - return 1 - -/datum/gas_mixture/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 - -/datum/gas_mixture/parse_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 - gases[path] = text2num(gas[id]) - return 1 - -/datum/gas_mixture/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 - - //we're gonna define these vars outside of this for loop because as it turns out, var declaration is pricy - var/delta - var/gas_heat_capacity - //and also cache this shit rq because that results in sanic speed for reasons byond explanation - var/list/cached_gasheats = GLOB.meta_gas_specific_heats - //GAS TRANSFER - for(var/id in cached_gases | sharer_gases) // transfer gases - - delta = QUANTIZE(cached_gases[id] - sharer_gases[id])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures - - if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) - gas_heat_capacity = delta * cached_gasheats[id] - 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. - - cached_gases[id] -= delta - sharer_gases[id] += 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) - - if (initial(sharer.gc_share)) - GAS_GARBAGE_COLLECT(sharer.gases) - 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 - -/datum/gas_mixture/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() - sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity() - - 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 - return sharer_temperature - //thermal energy of the system (self and sharer) is unchanged - -/datum/gas_mixture/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] - var/sample_moles = sample_gases[id] - 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 "" - -/datum/gas_mixture/react(datum/holder) - . = NO_REACTION - var/list/cached_gases = gases - if(!length(cached_gases)) - return - var/list/reactions = list() - for(var/I in cached_gases) - reactions += SSair.gas_reactions[I] - 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] < min_reqs[id]) - continue reaction_loop - //at this point, all minimum requirements for the reaction are satisfied. - - /* currently no reactions have maximum requirements, so we can leave the checks commented out for a slight performance boost - PLEASE DO NOT REMOVE THIS CODE. the commenting is here only for a performance increase. - enabling these checks should be as easy as possible and the fact that they are disabled should be as clear as possible - - var/list/max_reqs = reaction.max_requirements - if((max_reqs["TEMP"] && temp > max_reqs["TEMP"]) \ - || (max_reqs["ENER"] && ener > max_reqs["ENER"])) - continue - for(var/id in max_reqs) - if(id == "TEMP" || id == "ENER") - continue - if(cached_gases[id] && cached_gases[id][MOLES] > max_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(.) - GAS_GARBAGE_COLLECT(gases) - -//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 -*/ + /* +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 + +//Unomos - global list inits for all of the meta gas lists. +//This setup allows procs to only look at one list instead of trying to dig around in lists-within-lists +GLOBAL_LIST_INIT(meta_gas_specific_heats, meta_gas_heat_list()) +GLOBAL_LIST_INIT(meta_gas_names, meta_gas_name_list()) +GLOBAL_LIST_INIT(meta_gas_visibility, meta_gas_visibility_list()) +GLOBAL_LIST_INIT(meta_gas_overlays, meta_gas_overlay_list()) +GLOBAL_LIST_INIT(meta_gas_dangers, meta_gas_danger_list()) +GLOBAL_LIST_INIT(meta_gas_ids, meta_gas_id_list()) +GLOBAL_LIST_INIT(meta_gas_fusions, meta_gas_fusion_list()) +/datum/gas_mixture + var/list/gases = list() + var/temperature = 0 //kelvins + var/tmp/temperature_archived = 0 + var/volume = CELL_VOLUME //liters + var/last_share = 0 + var/list/reaction_results = list() + 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) + if (!isnull(volume)) + src.volume = volume + + //PV = nRT + +/datum/gas_mixture/proc/heat_capacity() //joules per kelvin + var/list/cached_gases = gases + var/list/cached_gasheats = GLOB.meta_gas_specific_heats + . = 0 + for(var/id in cached_gases) + . += cached_gases[id] * cached_gasheats[id] + +/datum/gas_mixture/turf/heat_capacity() // Same as above except vacuums return HEAT_CAPACITY_VACUUM + var/list/cached_gases = gases + var/list/cached_gasheats = GLOB.meta_gas_specific_heats + for(var/id in cached_gases) + . += cached_gases[id] * cached_gasheats[id] + if(!.) + . += HEAT_CAPACITY_VACUUM //we want vacuums in turfs to have the same heat capacity as space + +/datum/gas_mixture/proc/total_moles() + var/cached_gases = gases + TOTAL_MOLES(cached_gases, .) + +/datum/gas_mixture/proc/return_pressure() //kilopascals + 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 + +/datum/gas_mixture/proc/return_temperature() //kelvins + return temperature + +/datum/gas_mixture/proc/return_volume() //liters + return max(0, volume) + +/datum/gas_mixture/proc/thermal_energy() //joules + return THERMAL_ENERGY(src) //see code/__DEFINES/atmospherics.dm; use the define in performance critical areas + +/datum/gas_mixture/proc/merge(datum/gas_mixture/giver) + //Merges all air from giver into self. Deletes giver. + //Returns: 1 if we are mutable, 0 otherwise + +/datum/gas_mixture/proc/remove(amount) + //Proportionally removes amount of gas from the gas_mixture + //Returns: gas_mixture with the gases removed + +/datum/gas_mixture/proc/remove_ratio(ratio) + //Proportionally removes amount of gas from the gas_mixture + //Returns: gas_mixture with the gases removed + +/datum/gas_mixture/proc/copy() + //Creates new, identical gas mixture + //Returns: duplicate gas mixture + +/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample) + //Copies variables from sample + //Returns: 1 if we are mutable, 0 otherwise + +/datum/gas_mixture/proc/copy_from_turf(turf/model) + //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/parse_gas_string(gas_string) + //Copies variables from a particularly formatted string. + //Returns: 1 if we are mutable, 0 otherwise + +/datum/gas_mixture/proc/share(datum/gas_mixture/sharer) + //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/temperature_share(datum/gas_mixture/sharer, conduction_coefficient) + //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/compare(datum/gas_mixture/sample) + //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/react(turf/open/dump_location) + //Performs various reactions such as combustion or fusion (LOL) + //Returns: 1 if any reaction took place; 0 otherwise + + +/datum/gas_mixture/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) + cached_gases[giver_id] += giver_gases[giver_id] + + return 1 + +/datum/gas_mixture/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) + removed_gases[id] = QUANTIZE((cached_gases[id] / sum) * amount) + cached_gases[id] -= removed_gases[id] + GAS_GARBAGE_COLLECT(gases) + + return removed + +/datum/gas_mixture/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) + removed_gases[id] = QUANTIZE(cached_gases[id] * ratio) + cached_gases[id] -= removed_gases[id] + + GAS_GARBAGE_COLLECT(gases) + + return removed + +/datum/gas_mixture/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) + copy_gases[id] = cached_gases[id] + + return copy + + +/datum/gas_mixture/copy_from(datum/gas_mixture/sample) + var/list/cached_gases = gases //accessing datum vars is slower than proc vars + var/list/sample_gases = sample.gases + + temperature = sample.temperature + for(var/id in sample_gases) + cached_gases[id] = sample_gases[id] + + //remove all gases not in the sample + cached_gases &= sample_gases + + return 1 + +/datum/gas_mixture/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 + +/datum/gas_mixture/parse_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 + gases[path] = text2num(gas[id]) + return 1 + +/datum/gas_mixture/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 + + //we're gonna define these vars outside of this for loop because as it turns out, var declaration is pricy + var/delta + var/gas_heat_capacity + //and also cache this shit rq because that results in sanic speed for reasons byond explanation + var/list/cached_gasheats = GLOB.meta_gas_specific_heats + //GAS TRANSFER + for(var/id in cached_gases | sharer_gases) // transfer gases + + delta = QUANTIZE(cached_gases[id] - sharer_gases[id])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures + + if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + gas_heat_capacity = delta * cached_gasheats[id] + 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. + + cached_gases[id] -= delta + sharer_gases[id] += 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) + + if (initial(sharer.gc_share)) + GAS_GARBAGE_COLLECT(sharer.gases) + 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 + +/datum/gas_mixture/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() + sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity() + + 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 + return sharer_temperature + //thermal energy of the system (self and sharer) is unchanged + +/datum/gas_mixture/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] + var/sample_moles = sample_gases[id] + 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 "" + +/datum/gas_mixture/react(datum/holder) + . = NO_REACTION + var/list/cached_gases = gases + if(!length(cached_gases)) + return + var/list/reactions = list() + for(var/I in cached_gases) + reactions += SSair.gas_reactions[I] + 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] < min_reqs[id]) + continue reaction_loop + //at this point, all minimum requirements for the reaction are satisfied. + + /* currently no reactions have maximum requirements, so we can leave the checks commented out for a slight performance boost + PLEASE DO NOT REMOVE THIS CODE. the commenting is here only for a performance increase. + enabling these checks should be as easy as possible and the fact that they are disabled should be as clear as possible + + var/list/max_reqs = reaction.max_requirements + if((max_reqs["TEMP"] && temp > max_reqs["TEMP"]) \ + || (max_reqs["ENER"] && ener > max_reqs["ENER"])) + continue + for(var/id in max_reqs) + if(id == "TEMP" || id == "ENER") + continue + if(cached_gases[id] && cached_gases[id][MOLES] > max_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(.) + GAS_GARBAGE_COLLECT(gases) + +//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 +*/ diff --git a/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm b/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm index 53f7ede3e6..5527ba3fef 100644 --- a/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm +++ b/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm @@ -1,74 +1,74 @@ -//"immutable" gas mixture used for immutable calculations -//it can be changed, but any changes will ultimately be undone before they can have any effect - -/datum/gas_mixture/immutable - var/initial_temperature - gc_share = TRUE - -/datum/gas_mixture/immutable/New() - ..() - temperature = initial_temperature - temperature_archived = initial_temperature - gases.Cut() - -/datum/gas_mixture/immutable/merge() - return 0 //we're immutable. - -/datum/gas_mixture/immutable/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4) - . = ..(sharer, 0) - temperature = initial_temperature - temperature_archived = initial_temperature - gases.Cut() - -/datum/gas_mixture/immutable/react() - return 0 //we're immutable. - -/datum/gas_mixture/immutable/copy() - return new type //we're immutable, so we can just return a new instance. - -/datum/gas_mixture/immutable/copy_from() - return 0 //we're immutable. - -/datum/gas_mixture/immutable/copy_from_turf() - return 0 //we're immutable. - -/datum/gas_mixture/immutable/parse_gas_string() - return 0 //we're immutable. - -/datum/gas_mixture/immutable/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity) - . = ..() - temperature = initial_temperature - -/datum/gas_mixture/immutable/proc/after_process_cell() - temperature = initial_temperature - temperature_archived = initial_temperature - gases.Cut() - -//used by space tiles -/datum/gas_mixture/immutable/space - initial_temperature = TCMB - -/datum/gas_mixture/immutable/space/heat_capacity() - return HEAT_CAPACITY_VACUUM - -/datum/gas_mixture/immutable/space/remove() - return copy() //we're always empty, so we can just return a copy. - -/datum/gas_mixture/immutable/space/remove_ratio() - return copy() //we're always empty, so we can just return a copy. - - -//used by cloners -/datum/gas_mixture/immutable/cloner - initial_temperature = T20C - -/datum/gas_mixture/immutable/cloner/New() - ..() - gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD - -/datum/gas_mixture/immutable/cloner/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4) - . = ..(sharer, 0) - gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD - -/datum/gas_mixture/immutable/cloner/heat_capacity() - return (MOLES_O2STANDARD + MOLES_N2STANDARD)*20 //specific heat of nitrogen is 20 +//"immutable" gas mixture used for immutable calculations +//it can be changed, but any changes will ultimately be undone before they can have any effect + +/datum/gas_mixture/immutable + var/initial_temperature + gc_share = TRUE + +/datum/gas_mixture/immutable/New() + ..() + temperature = initial_temperature + temperature_archived = initial_temperature + gases.Cut() + +/datum/gas_mixture/immutable/merge() + return 0 //we're immutable. + +/datum/gas_mixture/immutable/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4) + . = ..(sharer, 0) + temperature = initial_temperature + temperature_archived = initial_temperature + gases.Cut() + +/datum/gas_mixture/immutable/react() + return 0 //we're immutable. + +/datum/gas_mixture/immutable/copy() + return new type //we're immutable, so we can just return a new instance. + +/datum/gas_mixture/immutable/copy_from() + return 0 //we're immutable. + +/datum/gas_mixture/immutable/copy_from_turf() + return 0 //we're immutable. + +/datum/gas_mixture/immutable/parse_gas_string() + return 0 //we're immutable. + +/datum/gas_mixture/immutable/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity) + . = ..() + temperature = initial_temperature + +/datum/gas_mixture/immutable/proc/after_process_cell() + temperature = initial_temperature + temperature_archived = initial_temperature + gases.Cut() + +//used by space tiles +/datum/gas_mixture/immutable/space + initial_temperature = TCMB + +/datum/gas_mixture/immutable/space/heat_capacity() + return HEAT_CAPACITY_VACUUM + +/datum/gas_mixture/immutable/space/remove() + return copy() //we're always empty, so we can just return a copy. + +/datum/gas_mixture/immutable/space/remove_ratio() + return copy() //we're always empty, so we can just return a copy. + + +//used by cloners +/datum/gas_mixture/immutable/cloner + initial_temperature = T20C + +/datum/gas_mixture/immutable/cloner/New() + ..() + gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD + +/datum/gas_mixture/immutable/cloner/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4) + . = ..(sharer, 0) + gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD + +/datum/gas_mixture/immutable/cloner/heat_capacity() + return (MOLES_O2STANDARD + MOLES_N2STANDARD)*20 //specific heat of nitrogen is 20 diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index ee4d1bda11..0450e7e8dd 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -1,357 +1,357 @@ -/* -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 - idle_power_usage = 0 - active_power_usage = 0 - power_channel = ENVIRON - layer = GAS_PIPE_HIDDEN_LAYER //under wires - resistance_flags = FIRE_PROOF - max_integrity = 200 - obj_flags = CAN_BE_HIT | ON_BLUEPRINTS - var/nodealert = 0 - var/can_unwrench = 0 - var/initialize_directions = 0 - var/pipe_color - var/piping_layer = PIPING_LAYER_DEFAULT - var/pipe_flags = NONE - - 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 - - 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() - if(dir==SOUTH) - setDir(NORTH) - else if(dir==WEST) - setDir(EAST) - -//this is called just after the air controller sets up turfs -/obj/machinery/atmospherics/proc/atmosinit(var/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) - if(pipe_flags & PIPING_DEFAULT_LAYER_ONLY) - new_layer = PIPING_LAYER_DEFAULT - piping_layer = new_layer - pixel_x = (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X - pixel_y = (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y - layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE) - -/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/update_icon() - return - -/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/turf/T = get_turf(src) - if (level==1 && isturf(T) && T.intact) - to_chat(user, "You must remove the plating first!") - return TRUE - - 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() - - var/fuck_you_dir = get_dir(src, user) // Because fuck you... - if(!fuck_you_dir) - fuck_you_dir = pick(GLOB.cardinals) - var/turf/target = get_edge_target_turf(user, fuck_you_dir) - var/range = pressures/250 - var/speed = range/5 - - user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") - user.throw_at(target, range, speed) - -/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)) - - //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]" - - if((!(. = pipeimages[identifier]))) - var/image/pipe_overlay - pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) - pipe_overlay.color = col - -/obj/machinery/atmospherics/proc/icon_addintact(var/obj/machinery/atmospherics/node) - var/image/img = getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_intact", get_dir(src,node), node.pipe_color) - underlays += img - return img.dir - -/obj/machinery/atmospherics/proc/icon_addbroken(var/connected = FALSE) - var/unconnected = (~connected) & initialize_directions - for(var/direction in GLOB.cardinals) - if(unconnected & direction) - underlays += getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_exposed", direction) - -/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) - var/turf/T = get_turf(src) - level = T.intact ? 2 : 1 - 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, 1, -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.") - - user.canmove = FALSE - addtimer(VARSET_CALLBACK(user, canmove, TRUE), 1) - - -/obj/machinery/atmospherics/AltClick(mob/living/L) - if(is_type_in_typecache(src, GLOB.ventcrawl_machinery)) - return 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 +/* +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 + idle_power_usage = 0 + active_power_usage = 0 + power_channel = ENVIRON + layer = GAS_PIPE_HIDDEN_LAYER //under wires + resistance_flags = FIRE_PROOF + max_integrity = 200 + obj_flags = CAN_BE_HIT | ON_BLUEPRINTS + var/nodealert = 0 + var/can_unwrench = 0 + var/initialize_directions = 0 + var/pipe_color + var/piping_layer = PIPING_LAYER_DEFAULT + var/pipe_flags = NONE + + 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 + + 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() + if(dir==SOUTH) + setDir(NORTH) + else if(dir==WEST) + setDir(EAST) + +//this is called just after the air controller sets up turfs +/obj/machinery/atmospherics/proc/atmosinit(var/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) + if(pipe_flags & PIPING_DEFAULT_LAYER_ONLY) + new_layer = PIPING_LAYER_DEFAULT + piping_layer = new_layer + pixel_x = (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X + pixel_y = (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y + layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE) + +/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/update_icon() + return + +/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/turf/T = get_turf(src) + if (level==1 && isturf(T) && T.intact) + to_chat(user, "You must remove the plating first!") + return TRUE + + 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() + + var/fuck_you_dir = get_dir(src, user) // Because fuck you... + if(!fuck_you_dir) + fuck_you_dir = pick(GLOB.cardinals) + var/turf/target = get_edge_target_turf(user, fuck_you_dir) + var/range = pressures/250 + var/speed = range/5 + + user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") + user.throw_at(target, range, speed) + +/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)) + + //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]" + + if((!(. = pipeimages[identifier]))) + var/image/pipe_overlay + pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) + pipe_overlay.color = col + +/obj/machinery/atmospherics/proc/icon_addintact(var/obj/machinery/atmospherics/node) + var/image/img = getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_intact", get_dir(src,node), node.pipe_color) + underlays += img + return img.dir + +/obj/machinery/atmospherics/proc/icon_addbroken(var/connected = FALSE) + var/unconnected = (~connected) & initialize_directions + for(var/direction in GLOB.cardinals) + if(unconnected & direction) + underlays += getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_exposed", direction) + +/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) + var/turf/T = get_turf(src) + level = T.intact ? 2 : 1 + 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, 1, -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.") + + user.canmove = FALSE + addtimer(VARSET_CALLBACK(user, canmove, TRUE), 1) + + +/obj/machinery/atmospherics/AltClick(mob/living/L) + if(is_type_in_typecache(src, GLOB.ventcrawl_machinery)) + return 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 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 421b141f05..486a57450b 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/binary_devices.dm @@ -1,31 +1,31 @@ -/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) - initialize_directions = NORTH|SOUTH - if(SOUTH) - initialize_directions = NORTH|SOUTH - if(EAST) - initialize_directions = EAST|WEST - if(WEST) - initialize_directions = EAST|WEST -/* -Iconnery -*/ -/obj/machinery/atmospherics/components/binary/hide(intact) - update_icon() - - ..(intact) -/* -Housekeeping and pipe network stuff -*/ - -/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) + initialize_directions = NORTH|SOUTH + if(SOUTH) + initialize_directions = NORTH|SOUTH + if(EAST) + initialize_directions = EAST|WEST + if(WEST) + initialize_directions = EAST|WEST +/* +Iconnery +*/ +/obj/machinery/atmospherics/components/binary/hide(intact) + update_icon() + + ..(intact) +/* +Housekeeping and pipe network stuff +*/ + +/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 6bafba9abc..73cb8eeecb 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) - build_network() - - 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) + build_network() + + 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 879668e4d7..9f38757a89 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,253 +1,253 @@ -/* -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" - - //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." - - level = 1 - 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/layer1 - piping_layer = PIPING_LAYER_MIN - pixel_x = -PIPING_LAYER_P_X - pixel_y = -PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer3 - piping_layer = PIPING_LAYER_MAX - pixel_x = PIPING_LAYER_P_X - pixel_y = PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on - on = TRUE - icon_state = "dpvent_map_on" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer1 - piping_layer = PIPING_LAYER_MIN - pixel_x = -PIPING_LAYER_P_X - pixel_y = -PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer3 - piping_layer = PIPING_LAYER_MAX - pixel_x = PIPING_LAYER_P_X - pixel_y = PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/Destroy() - SSradio.remove_object(src, frequency) - return ..() - -/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/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 = PIPING_LAYER_MIN - pixel_x = -PIPING_LAYER_P_X - pixel_y = -PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer3 - piping_layer = PIPING_LAYER_MAX - pixel_x = PIPING_LAYER_P_X - pixel_y = PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on - on = TRUE - icon_state = "dpvent_map_on" - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer1 - piping_layer = PIPING_LAYER_MIN - pixel_x = -PIPING_LAYER_P_X - pixel_y = -PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer3 - piping_layer = PIPING_LAYER_MAX - pixel_x = PIPING_LAYER_P_X - pixel_y = PIPING_LAYER_P_Y - -/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 - -/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes() - cut_overlays() - if(showpipe) - add_overlay(getpipeimage('icons/obj/atmospherics/components/unary_devices.dmi', "dpvent_cap")) - - if(!on || !is_operational()) - icon_state = "vent_off" - return - - if(pump_direction) - icon_state = "vent_out" - else - icon_state = "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) - - if("status" in signal.data) - spawn(2) - broadcast_status() - return //do not update_icon - spawn(2) - broadcast_status() - update_icon() - -#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" + + //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." + + level = 1 + 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/layer1 + piping_layer = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer3 + piping_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on + on = TRUE + icon_state = "dpvent_map_on" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer1 + piping_layer = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer3 + piping_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/Destroy() + SSradio.remove_object(src, frequency) + return ..() + +/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/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 = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer3 + piping_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on + on = TRUE + icon_state = "dpvent_map_on" + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer1 + piping_layer = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer3 + piping_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + +/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 + +/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes() + cut_overlays() + if(showpipe) + add_overlay(getpipeimage('icons/obj/atmospherics/components/unary_devices.dmi', "dpvent_cap")) + + if(!on || !is_operational()) + icon_state = "vent_off" + return + + if(pump_direction) + icon_state = "vent_out" + else + icon_state = "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) + + if("status" in signal.data) + spawn(2) + broadcast_status() + return //do not update_icon + spawn(2) + broadcast_status() + update_icon() + +#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 8ba105fdb1..b448c21256 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 0a54503be5..4e91ee50c3 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -1,446 +1,446 @@ -/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 - pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY - occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal) - - var/autoeject = FALSE - 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 - -/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/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/Destroy() - QDEL_NULL(radio) - QDEL_NULL(beaker) - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target) - ..() - if(beaker) - beaker.ex_act(severity, target) - -/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/process() - ..() - - if(!on) - return - if(!is_operational()) - on = FALSE - update_icon() - return - if(!occupant) - return - - var/mob/living/mob_occupant = occupant - - if(mob_occupant.stat == DEAD) // We don't bother with dead people. - return - - if(mob_occupant.health >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people. - 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.reaction(occupant, VAPOR) - air1.gases[/datum/gas/oxygen] -= max(0,air1.gases[/datum/gas/oxygen] - 2 / efficiency) //Let's use gas for this - GAS_GARBAGE_COLLECT(air1.gases) - 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] < 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_thermal_protection(air1.temperature, TRUE) - - 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 = max(air1.temperature - heat / air_heat_capacity, TCMB) - mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) - - air1.gases[/datum/gas/oxygen] = max(0,air1.gases[/datum/gas/oxygen] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic. - GAS_GARBAGE_COLLECT(air1.gases) - -/obj/machinery/atmospherics/components/unary/cryo_cell/power_change() - ..() - update_icon() - -/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 = 0) - 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_canmove() - occupant = null - update_icon() - -/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user) - if((isnull(user) || istype(user)) && state_open && !panel_open) - ..(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.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) - return - if (target.IsKnockdown() || target.IsStun() || target.IsSleeping() || target.IsUnconscious()) - 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(istype(I, /obj/item/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, 400, 550, 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 - . = 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 - update_icon() - -/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !state_open) - on = !on - update_icon() - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user) - . = ..() - if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - if(state_open) - close_machine() - else - open_machine() - update_icon() - return TRUE - -/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) - build_network() - -#undef CRYOMOBS +/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 + pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY + occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal) + + var/autoeject = FALSE + 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 + +/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/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/Destroy() + QDEL_NULL(radio) + QDEL_NULL(beaker) + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target) + ..() + if(beaker) + beaker.ex_act(severity, target) + +/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/process() + ..() + + if(!on) + return + if(!is_operational()) + on = FALSE + update_icon() + return + if(!occupant) + return + + var/mob/living/mob_occupant = occupant + + if(mob_occupant.stat == DEAD) // We don't bother with dead people. + return + + if(mob_occupant.health >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people. + 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.reaction(occupant, VAPOR) + air1.gases[/datum/gas/oxygen] -= max(0,air1.gases[/datum/gas/oxygen] - 2 / efficiency) //Let's use gas for this + GAS_GARBAGE_COLLECT(air1.gases) + 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] < 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_thermal_protection(air1.temperature, TRUE) + + 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 = max(air1.temperature - heat / air_heat_capacity, TCMB) + mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) + + air1.gases[/datum/gas/oxygen] = max(0,air1.gases[/datum/gas/oxygen] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic. + GAS_GARBAGE_COLLECT(air1.gases) + +/obj/machinery/atmospherics/components/unary/cryo_cell/power_change() + ..() + update_icon() + +/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 = 0) + 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_canmove() + occupant = null + update_icon() + +/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user) + if((isnull(user) || istype(user)) && state_open && !panel_open) + ..(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.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) + return + if (target.IsKnockdown() || target.IsStun() || target.IsSleeping() || target.IsUnconscious()) + 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(istype(I, /obj/item/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, 400, 550, 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 + . = 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 + update_icon() + +/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user) + if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !state_open) + on = !on + update_icon() + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user) + . = ..() + if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + if(state_open) + close_machine() + else + open_machine() + update_icon() + return TRUE + +/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) + build_network() + +#undef CRYOMOBS diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm index 8585471ea4..9c3d07c96a 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm @@ -1,328 +1,328 @@ -#define SIPHONING 0 -#define SCRUBBING 1 - -/obj/machinery/atmospherics/components/unary/vent_scrubber - name = "air scrubber" - desc = "Has a valve and pump attached to it." - icon_state = "scrub_map" - use_power = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 60 - can_unwrench = TRUE - welded = FALSE - level = 1 - layer = GAS_SCRUBBER_LAYER - - var/id_tag = null - var/scrubbing = SCRUBBING //0 = siphoning, 1 = scrubbing - - var/filter_types = list(/datum/gas/carbon_dioxide) - var/volume_rate = 200 - var/widenet = 0 //is this scrubber acting on the 3x3 area around it. - var/list/turf/adjacent_turfs = list() - - var/frequency = FREQ_ATMOS_CONTROL - var/datum/radio_frequency/radio_connection - var/radio_filter_out - var/radio_filter_in - - pipe_state = "scrubber" - -/obj/machinery/atmospherics/components/unary/vent_scrubber/layer1 - piping_layer = PIPING_LAYER_MIN - pixel_x = -PIPING_LAYER_P_X - pixel_y = -PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/unary/vent_scrubber/layer3 - piping_layer = PIPING_LAYER_MAX - pixel_x = PIPING_LAYER_P_X - pixel_y = PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/unary/vent_scrubber/New() - ..() - if(!id_tag) - id_tag = assign_uid_vents() - - for(var/f in filter_types) - if(istext(f)) - filter_types -= f - filter_types += gas_id2path(f) - -/obj/machinery/atmospherics/components/unary/vent_scrubber/on - on = TRUE - icon_state = "scrub_map_on" - -/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer1 - piping_layer = PIPING_LAYER_MIN - pixel_x = -PIPING_LAYER_P_X - pixel_y = -PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer3 - piping_layer = PIPING_LAYER_MAX - pixel_x = PIPING_LAYER_P_X - pixel_y = PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/unary/vent_scrubber/Destroy() - var/area/A = get_area(src) - if (A) - A.air_scrub_names -= id_tag - A.air_scrub_info -= id_tag - - SSradio.remove_object(src,frequency) - radio_connection = null - adjacent_turfs.Cut() - return ..() - -/obj/machinery/atmospherics/components/unary/vent_scrubber/auto_use_power() - if(!on || welded || !is_operational() || !powered(power_channel)) - return FALSE - - var/amount = idle_power_usage - - if(scrubbing & SCRUBBING) - amount += idle_power_usage * length(filter_types) - else //scrubbing == SIPHONING - amount = active_power_usage - - if(widenet) - amount += amount * (adjacent_turfs.len * (adjacent_turfs.len / 2)) - use_power(amount, power_channel) - return TRUE - -/obj/machinery/atmospherics/components/unary/vent_scrubber/update_icon_nopipes() - cut_overlays() - if(showpipe) - add_overlay(getpipeimage(icon, "scrub_cap", initialize_directions)) - - if(welded) - icon_state = "scrub_welded" - return - - if(!nodes[1] || !on || !is_operational()) - icon_state = "scrub_off" - return - - if(scrubbing & SCRUBBING) - if(widenet) - icon_state = "scrub_wide" - else - icon_state = "scrub_on" - else //scrubbing == SIPHONING - icon_state = "scrub_purge" - -/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - radio_connection = SSradio.add_object(src, frequency, radio_filter_in) - -/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/broadcast_status() - if(!radio_connection) - return FALSE - - var/list/f_types = list() - for(var/path in GLOB.meta_gas_ids) - f_types += list(list("gas_id" = GLOB.meta_gas_ids[path], "gas_name" = GLOB.meta_gas_names[path], "enabled" = (path in filter_types))) - - var/datum/signal/signal = new(list( - "tag" = id_tag, - "frequency" = frequency, - "device" = "VS", - "timestamp" = world.time, - "power" = on, - "scrubbing" = scrubbing, - "widenet" = widenet, - "filter_types" = f_types, - "sigtype" = "status" - )) - - var/area/A = get_area(src) - if(!A.air_scrub_names[id_tag]) - name = "\improper [A.name] air scrubber #[A.air_scrub_names.len + 1]" - A.air_scrub_names[id_tag] = name - - A.air_scrub_info[id_tag] = signal.data - radio_connection.post_signal(src, signal, radio_filter_out) - - return TRUE - -/obj/machinery/atmospherics/components/unary/vent_scrubber/atmosinit() - radio_filter_in = frequency==initial(frequency)?(RADIO_FROM_AIRALARM):null - radio_filter_out = frequency==initial(frequency)?(RADIO_TO_AIRALARM):null - if(frequency) - set_frequency(frequency) - broadcast_status() - check_turfs() - ..() - -/obj/machinery/atmospherics/components/unary/vent_scrubber/process_atmos() - ..() - if(welded || !is_operational()) - return FALSE - if(!nodes[1] || !on) - on = FALSE - return FALSE - scrub(loc) - if(widenet) - for(var/turf/tile in adjacent_turfs) - scrub(tile) - return TRUE - -/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/scrub(var/turf/tile) - if(!istype(tile)) - return FALSE - var/datum/gas_mixture/environment = tile.return_air() - var/datum/gas_mixture/air_contents = airs[1] - var/list/env_gases = environment.gases - - if(air_contents.return_pressure() >= 50*ONE_ATMOSPHERE) - return FALSE - - if(scrubbing & SCRUBBING) - if(length(env_gases & filter_types)) - var/transfer_moles = min(1, volume_rate/environment.volume)*environment.total_moles() - - //Take a gas sample - var/datum/gas_mixture/removed = tile.remove_air(transfer_moles) - - //Nothing left to remove from the tile - if(isnull(removed)) - return FALSE - - var/list/removed_gases = removed.gases - - //Filter it - var/datum/gas_mixture/filtered_out = new - var/list/filtered_gases = filtered_out.gases - filtered_out.temperature = removed.temperature - - for(var/gas in filter_types & removed_gases) - filtered_gases[gas] = removed_gases[gas] - removed_gases[gas] = 0 - - GAS_GARBAGE_COLLECT(removed.gases) - - //Remix the resulting gases - air_contents.merge(filtered_out) - tile.assume_air(removed) - tile.air_update_turf() - - else //Just siphoning all air - - var/transfer_moles = environment.total_moles()*(volume_rate/environment.volume) - - var/datum/gas_mixture/removed = tile.remove_air(transfer_moles) - - air_contents.merge(removed) - tile.air_update_turf() - - update_parents() - - return TRUE - -//There is no easy way for an object to be notified of changes to atmos can pass flags -// So we check every machinery process (2 seconds) -/obj/machinery/atmospherics/components/unary/vent_scrubber/process() - if(widenet) - check_turfs() - -//we populate a list of turfs with nonatmos-blocked cardinal turfs AND -// diagonal turfs that can share atmos with *both* of the cardinal turfs - -/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/check_turfs() - adjacent_turfs.Cut() - var/turf/T = get_turf(src) - if(istype(T)) - adjacent_turfs = T.GetAtmosAdjacentTurfs(alldir = 1) - -/obj/machinery/atmospherics/components/unary/vent_scrubber/receive_signal(datum/signal/signal) - if(!is_operational() || !signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command")) - return 0 - - var/mob/signal_sender = signal.data["user"] - - if("power" in signal.data) - on = text2num(signal.data["power"]) - if("power_toggle" in signal.data) - on = !on - - if("widenet" in signal.data) - widenet = text2num(signal.data["widenet"]) - if("toggle_widenet" in signal.data) - widenet = !widenet - - var/old_scrubbing = scrubbing - if("scrubbing" in signal.data) - scrubbing = text2num(signal.data["scrubbing"]) - if("toggle_scrubbing" in signal.data) - scrubbing = !scrubbing - if(scrubbing != old_scrubbing) - investigate_log(" was toggled to [scrubbing ? "scrubbing" : "siphon"] mode by [key_name(signal_sender)]",INVESTIGATE_ATMOS) - - if("toggle_filter" in signal.data) - filter_types ^= gas_id2path(signal.data["toggle_filter"]) - - if("set_filters" in signal.data) - filter_types = list() - for(var/gas in signal.data["set_filters"]) - filter_types += gas_id2path(gas) - - if("init" in signal.data) - name = signal.data["init"] - return - - if("status" in signal.data) - broadcast_status() - return //do not update_icon - - broadcast_status() - update_icon() - return - -/obj/machinery/atmospherics/components/unary/vent_scrubber/power_change() - ..() - update_icon_nopipes() - -/obj/machinery/atmospherics/components/unary/vent_scrubber/welder_act(mob/living/user, obj/item/I) - if(!I.tool_start_check(user, amount=0)) - return TRUE - to_chat(user, "Now welding the scrubber.") - if(I.use_tool(src, user, 20, volume=50)) - if(!welded) - user.visible_message("[user] welds the scrubber shut.","You weld the scrubber shut.", "You hear welding.") - welded = TRUE - else - user.visible_message("[user] unwelds the scrubber.", "You unweld the scrubber.", "You hear welding.") - welded = FALSE - update_icon() - pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) - pipe_vision_img.plane = ABOVE_HUD_PLANE - return TRUE - -/obj/machinery/atmospherics/components/unary/vent_scrubber/can_unwrench(mob/user) - . = ..() - if(. && on && is_operational()) - to_chat(user, "You cannot unwrench [src], turn it off first!") - return FALSE - -/obj/machinery/atmospherics/components/unary/vent_scrubber/examine(mob/user) - . = ..() - if(welded) - . += "It seems welded shut." - -/obj/machinery/atmospherics/components/unary/vent_scrubber/can_crawl_through() - return !welded - -/obj/machinery/atmospherics/components/unary/vent_scrubber/attack_alien(mob/user) - if(!welded || !(do_after(user, 20, target = src))) - return - user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.") - welded = FALSE - update_icon() - pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) - pipe_vision_img.plane = ABOVE_HUD_PLANE - playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1) - - - -#undef SIPHONING -#undef SCRUBBING +#define SIPHONING 0 +#define SCRUBBING 1 + +/obj/machinery/atmospherics/components/unary/vent_scrubber + name = "air scrubber" + desc = "Has a valve and pump attached to it." + icon_state = "scrub_map" + use_power = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 60 + can_unwrench = TRUE + welded = FALSE + level = 1 + layer = GAS_SCRUBBER_LAYER + + var/id_tag = null + var/scrubbing = SCRUBBING //0 = siphoning, 1 = scrubbing + + var/filter_types = list(/datum/gas/carbon_dioxide) + var/volume_rate = 200 + var/widenet = 0 //is this scrubber acting on the 3x3 area around it. + var/list/turf/adjacent_turfs = list() + + var/frequency = FREQ_ATMOS_CONTROL + var/datum/radio_frequency/radio_connection + var/radio_filter_out + var/radio_filter_in + + pipe_state = "scrubber" + +/obj/machinery/atmospherics/components/unary/vent_scrubber/layer1 + piping_layer = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/unary/vent_scrubber/layer3 + piping_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/unary/vent_scrubber/New() + ..() + if(!id_tag) + id_tag = assign_uid_vents() + + for(var/f in filter_types) + if(istext(f)) + filter_types -= f + filter_types += gas_id2path(f) + +/obj/machinery/atmospherics/components/unary/vent_scrubber/on + on = TRUE + icon_state = "scrub_map_on" + +/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer1 + piping_layer = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer3 + piping_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/unary/vent_scrubber/Destroy() + var/area/A = get_area(src) + if (A) + A.air_scrub_names -= id_tag + A.air_scrub_info -= id_tag + + SSradio.remove_object(src,frequency) + radio_connection = null + adjacent_turfs.Cut() + return ..() + +/obj/machinery/atmospherics/components/unary/vent_scrubber/auto_use_power() + if(!on || welded || !is_operational() || !powered(power_channel)) + return FALSE + + var/amount = idle_power_usage + + if(scrubbing & SCRUBBING) + amount += idle_power_usage * length(filter_types) + else //scrubbing == SIPHONING + amount = active_power_usage + + if(widenet) + amount += amount * (adjacent_turfs.len * (adjacent_turfs.len / 2)) + use_power(amount, power_channel) + return TRUE + +/obj/machinery/atmospherics/components/unary/vent_scrubber/update_icon_nopipes() + cut_overlays() + if(showpipe) + add_overlay(getpipeimage(icon, "scrub_cap", initialize_directions)) + + if(welded) + icon_state = "scrub_welded" + return + + if(!nodes[1] || !on || !is_operational()) + icon_state = "scrub_off" + return + + if(scrubbing & SCRUBBING) + if(widenet) + icon_state = "scrub_wide" + else + icon_state = "scrub_on" + else //scrubbing == SIPHONING + icon_state = "scrub_purge" + +/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + radio_connection = SSradio.add_object(src, frequency, radio_filter_in) + +/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/broadcast_status() + if(!radio_connection) + return FALSE + + var/list/f_types = list() + for(var/path in GLOB.meta_gas_ids) + f_types += list(list("gas_id" = GLOB.meta_gas_ids[path], "gas_name" = GLOB.meta_gas_names[path], "enabled" = (path in filter_types))) + + var/datum/signal/signal = new(list( + "tag" = id_tag, + "frequency" = frequency, + "device" = "VS", + "timestamp" = world.time, + "power" = on, + "scrubbing" = scrubbing, + "widenet" = widenet, + "filter_types" = f_types, + "sigtype" = "status" + )) + + var/area/A = get_area(src) + if(!A.air_scrub_names[id_tag]) + name = "\improper [A.name] air scrubber #[A.air_scrub_names.len + 1]" + A.air_scrub_names[id_tag] = name + + A.air_scrub_info[id_tag] = signal.data + radio_connection.post_signal(src, signal, radio_filter_out) + + return TRUE + +/obj/machinery/atmospherics/components/unary/vent_scrubber/atmosinit() + radio_filter_in = frequency==initial(frequency)?(RADIO_FROM_AIRALARM):null + radio_filter_out = frequency==initial(frequency)?(RADIO_TO_AIRALARM):null + if(frequency) + set_frequency(frequency) + broadcast_status() + check_turfs() + ..() + +/obj/machinery/atmospherics/components/unary/vent_scrubber/process_atmos() + ..() + if(welded || !is_operational()) + return FALSE + if(!nodes[1] || !on) + on = FALSE + return FALSE + scrub(loc) + if(widenet) + for(var/turf/tile in adjacent_turfs) + scrub(tile) + return TRUE + +/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/scrub(var/turf/tile) + if(!istype(tile)) + return FALSE + var/datum/gas_mixture/environment = tile.return_air() + var/datum/gas_mixture/air_contents = airs[1] + var/list/env_gases = environment.gases + + if(air_contents.return_pressure() >= 50*ONE_ATMOSPHERE) + return FALSE + + if(scrubbing & SCRUBBING) + if(length(env_gases & filter_types)) + var/transfer_moles = min(1, volume_rate/environment.volume)*environment.total_moles() + + //Take a gas sample + var/datum/gas_mixture/removed = tile.remove_air(transfer_moles) + + //Nothing left to remove from the tile + if(isnull(removed)) + return FALSE + + var/list/removed_gases = removed.gases + + //Filter it + var/datum/gas_mixture/filtered_out = new + var/list/filtered_gases = filtered_out.gases + filtered_out.temperature = removed.temperature + + for(var/gas in filter_types & removed_gases) + filtered_gases[gas] = removed_gases[gas] + removed_gases[gas] = 0 + + GAS_GARBAGE_COLLECT(removed.gases) + + //Remix the resulting gases + air_contents.merge(filtered_out) + tile.assume_air(removed) + tile.air_update_turf() + + else //Just siphoning all air + + var/transfer_moles = environment.total_moles()*(volume_rate/environment.volume) + + var/datum/gas_mixture/removed = tile.remove_air(transfer_moles) + + air_contents.merge(removed) + tile.air_update_turf() + + update_parents() + + return TRUE + +//There is no easy way for an object to be notified of changes to atmos can pass flags +// So we check every machinery process (2 seconds) +/obj/machinery/atmospherics/components/unary/vent_scrubber/process() + if(widenet) + check_turfs() + +//we populate a list of turfs with nonatmos-blocked cardinal turfs AND +// diagonal turfs that can share atmos with *both* of the cardinal turfs + +/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/check_turfs() + adjacent_turfs.Cut() + var/turf/T = get_turf(src) + if(istype(T)) + adjacent_turfs = T.GetAtmosAdjacentTurfs(alldir = 1) + +/obj/machinery/atmospherics/components/unary/vent_scrubber/receive_signal(datum/signal/signal) + if(!is_operational() || !signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command")) + return 0 + + var/mob/signal_sender = signal.data["user"] + + if("power" in signal.data) + on = text2num(signal.data["power"]) + if("power_toggle" in signal.data) + on = !on + + if("widenet" in signal.data) + widenet = text2num(signal.data["widenet"]) + if("toggle_widenet" in signal.data) + widenet = !widenet + + var/old_scrubbing = scrubbing + if("scrubbing" in signal.data) + scrubbing = text2num(signal.data["scrubbing"]) + if("toggle_scrubbing" in signal.data) + scrubbing = !scrubbing + if(scrubbing != old_scrubbing) + investigate_log(" was toggled to [scrubbing ? "scrubbing" : "siphon"] mode by [key_name(signal_sender)]",INVESTIGATE_ATMOS) + + if("toggle_filter" in signal.data) + filter_types ^= gas_id2path(signal.data["toggle_filter"]) + + if("set_filters" in signal.data) + filter_types = list() + for(var/gas in signal.data["set_filters"]) + filter_types += gas_id2path(gas) + + if("init" in signal.data) + name = signal.data["init"] + return + + if("status" in signal.data) + broadcast_status() + return //do not update_icon + + broadcast_status() + update_icon() + return + +/obj/machinery/atmospherics/components/unary/vent_scrubber/power_change() + ..() + update_icon_nopipes() + +/obj/machinery/atmospherics/components/unary/vent_scrubber/welder_act(mob/living/user, obj/item/I) + if(!I.tool_start_check(user, amount=0)) + return TRUE + to_chat(user, "Now welding the scrubber.") + if(I.use_tool(src, user, 20, volume=50)) + if(!welded) + user.visible_message("[user] welds the scrubber shut.","You weld the scrubber shut.", "You hear welding.") + welded = TRUE + else + user.visible_message("[user] unwelds the scrubber.", "You unweld the scrubber.", "You hear welding.") + welded = FALSE + update_icon() + pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) + pipe_vision_img.plane = ABOVE_HUD_PLANE + return TRUE + +/obj/machinery/atmospherics/components/unary/vent_scrubber/can_unwrench(mob/user) + . = ..() + if(. && on && is_operational()) + to_chat(user, "You cannot unwrench [src], turn it off first!") + return FALSE + +/obj/machinery/atmospherics/components/unary/vent_scrubber/examine(mob/user) + . = ..() + if(welded) + . += "It seems welded shut." + +/obj/machinery/atmospherics/components/unary/vent_scrubber/can_crawl_through() + return !welded + +/obj/machinery/atmospherics/components/unary/vent_scrubber/attack_alien(mob/user) + if(!welded || !(do_after(user, 20, target = src))) + return + user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.") + welded = FALSE + update_icon() + pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) + pipe_vision_img.plane = ABOVE_HUD_PLANE + playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1) + + + +#undef SIPHONING +#undef SCRUBBING diff --git a/code/modules/atmospherics/machinery/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm index 329c77ba14..eb7d9534c1 100644 --- a/code/modules/atmospherics/machinery/other/meter.dm +++ b/code/modules/atmospherics/machinery/other/meter.dm @@ -1,148 +1,148 @@ -/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 = 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(pipe.level == 2) - break - if(candidate) - target = candidate - setAttachLayer(candidate.piping_layer) - -/obj/machinery/meter/proc/setAttachLayer(var/new_layer) - target_layer = new_layer - pixel_x = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X - pixel_y = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y - -/obj/machinery/meter/process_atmos() - if(!(target?.flags_1 & INITIALIZED_1)) - icon_state = "meterX" - return 0 - - if(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(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 = 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(pipe.level == 2) + break + if(candidate) + target = candidate + setAttachLayer(candidate.piping_layer) + +/obj/machinery/meter/proc/setAttachLayer(var/new_layer) + target_layer = new_layer + pixel_x = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X + pixel_y = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y + +/obj/machinery/meter/process_atmos() + if(!(target?.flags_1 & INITIALIZED_1)) + icon_state = "meterX" + return 0 + + if(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(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 b193a7a8bc..acafc7436a 100644 --- a/code/modules/atmospherics/machinery/pipes/layermanifold.dm +++ b/code/modules/atmospherics/machinery/pipes/layermanifold.dm @@ -1,127 +1,127 @@ -/obj/machinery/atmospherics/pipe/layer_manifold - name = "pipe-layer manifold" - 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 - var/list/front_nodes - var/list/back_nodes - construction_type = /obj/item/pipe/binary - pipe_state = "layer_manifold" - -/obj/machinery/atmospherics/pipe/layer_manifold/Initialize() - front_nodes = list() - back_nodes = list() - 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) - A.build_network() - -/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!! - layer = (initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE)) //This is above everything else. - var/invis = invisibility ? "-f" : "" - icon_state = "[initial(icon_state)][invis]" - cut_overlays() - for(var/obj/machinery/atmospherics/A in front_nodes) - add_attached_image(A) - for(var/obj/machinery/atmospherics/A in back_nodes) - add_attached_image(A) - -/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_image(obj/machinery/atmospherics/A) - var/invis = A.invisibility ? "-f" : "" - if(istype(A, /obj/machinery/atmospherics/pipe/layer_manifold)) - for(var/i = PIPING_LAYER_MIN, i <= PIPING_LAYER_MAX, i++) - var/image/I = getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full_layer_long[invis]", get_dir(src, A), A.pipe_color) - I.pixel_x = (i - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X - I.pixel_y = (i - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y - I.layer = layer - 0.01 - add_overlay(I) - else - var/image/I = getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full_layer_long[invis]", get_dir(src, A), A.pipe_color) - I.pixel_x = A.pixel_x - I.pixel_y = A.pixel_y - 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() - var/turf/T = loc // hide if turf is not intact - hide(T.intact) - -/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 + name = "pipe-layer manifold" + 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 + var/list/front_nodes + var/list/back_nodes + construction_type = /obj/item/pipe/binary + pipe_state = "layer_manifold" + +/obj/machinery/atmospherics/pipe/layer_manifold/Initialize() + front_nodes = list() + back_nodes = list() + 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) + A.build_network() + +/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!! + layer = (initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE)) //This is above everything else. + var/invis = invisibility ? "-f" : "" + icon_state = "[initial(icon_state)][invis]" + cut_overlays() + for(var/obj/machinery/atmospherics/A in front_nodes) + add_attached_image(A) + for(var/obj/machinery/atmospherics/A in back_nodes) + add_attached_image(A) + +/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_image(obj/machinery/atmospherics/A) + var/invis = A.invisibility ? "-f" : "" + if(istype(A, /obj/machinery/atmospherics/pipe/layer_manifold)) + for(var/i = PIPING_LAYER_MIN, i <= PIPING_LAYER_MAX, i++) + var/image/I = getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full_layer_long[invis]", get_dir(src, A), A.pipe_color) + I.pixel_x = (i - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X + I.pixel_y = (i - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y + I.layer = layer - 0.01 + add_overlay(I) + else + var/image/I = getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full_layer_long[invis]", get_dir(src, A), A.pipe_color) + I.pixel_x = A.pixel_x + I.pixel_y = A.pixel_y + 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() + var/turf/T = loc // hide if turf is not intact + hide(T.intact) + +/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.") diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm index b47b6b42c1..8bf0554070 100644 --- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm +++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm @@ -1,156 +1,156 @@ -/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 - - air_contents = new - air_contents.volume = volume - air_contents.temperature = T20C - - return 1 - -/obj/machinery/portable_atmospherics/Destroy() - SSair.atmos_machinery -= src - - disconnect() - qdel(air_contents) - air_contents = null - - return ..() - -/obj/machinery/portable_atmospherics/process_atmos() - if(!connected_port) // Pipe network handles reactions if connected. - air_contents.react(src) - else - update_icon() - -/obj/machinery/portable_atmospherics/return_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 - 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 - return TRUE - -/obj/machinery/portable_atmospherics/portableConnectorReturnAir() - return air_contents - -/obj/machinery/portable_atmospherics/AltClick(mob/living/user) - . = ..() - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user))) - return - if(holding) - to_chat(user, "You remove [holding] from [src].") - replace_tank(user, TRUE) - return 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(holding) - holding.forceMove(drop_location()) - if(Adjacent(user) && !issilicon(user)) - user.put_in_hands(holding) - if(new_tank) - holding = new_tank - else - holding = null - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/tank)) - if(!(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]"].") - replace_tank(user, FALSE, T) - update_icon() - else if(istype(W, /obj/item/wrench)) - if(!(stat & BROKEN)) - if(connected_port) - 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() - else - return ..() - -/obj/machinery/portable_atmospherics/analyzer_act(mob/living/user, obj/item/I) - atmosanalyzer_scan(air_contents, user, src) - -/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user) - if(I.force < 10 && !(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 + + air_contents = new + air_contents.volume = volume + air_contents.temperature = T20C + + return 1 + +/obj/machinery/portable_atmospherics/Destroy() + SSair.atmos_machinery -= src + + disconnect() + qdel(air_contents) + air_contents = null + + return ..() + +/obj/machinery/portable_atmospherics/process_atmos() + if(!connected_port) // Pipe network handles reactions if connected. + air_contents.react(src) + else + update_icon() + +/obj/machinery/portable_atmospherics/return_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 + 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 + return TRUE + +/obj/machinery/portable_atmospherics/portableConnectorReturnAir() + return air_contents + +/obj/machinery/portable_atmospherics/AltClick(mob/living/user) + . = ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user))) + return + if(holding) + to_chat(user, "You remove [holding] from [src].") + replace_tank(user, TRUE) + return 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(holding) + holding.forceMove(drop_location()) + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(holding) + if(new_tank) + holding = new_tank + else + holding = null + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/tank)) + if(!(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]"].") + replace_tank(user, FALSE, T) + update_icon() + else if(istype(W, /obj/item/wrench)) + if(!(stat & BROKEN)) + if(connected_port) + 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() + else + return ..() + +/obj/machinery/portable_atmospherics/analyzer_act(mob/living/user, obj/item/I) + atmosanalyzer_scan(air_contents, user, src) + +/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user) + if(I.force < 10 && !(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 c0658c781d..7c517720a8 100644 --- a/code/modules/atmospherics/machinery/portable/pump.dm +++ b/code/modules/atmospherics/machinery/portable/pump.dm @@ -1,156 +1,156 @@ -#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 - - var/on = FALSE - var/direction = PUMP_OUT - var/obj/machinery/atmospherics/components/binary/pump/pump - - volume = 1000 - -/obj/machinery/portable_atmospherics/pump/Initialize() - . = ..() - pump = new(src, FALSE) - pump.on = TRUE - pump.stat = 0 - pump.build_network() - -/obj/machinery/portable_atmospherics/pump/Destroy() - var/turf/T = get_turf(src) - T.assume_air(air_contents) - air_update_turf() - QDEL_NULL(pump) - return ..() - -/obj/machinery/portable_atmospherics/pump/update_icon() - icon_state = "psiphon:[on]" - - cut_overlays() - if(holding) - add_overlay("siphon-open") - if(connected_port) - add_overlay("siphon-connector") - -/obj/machinery/portable_atmospherics/pump/process_atmos() - ..() - if(!on) - pump.airs[1] = null - pump.airs[2] = null - return - - var/turf/T = get_turf(src) - if(direction == PUMP_OUT) // Hook up the internal pump. - pump.airs[1] = holding ? holding.air_contents : air_contents - pump.airs[2] = holding ? air_contents : T.return_air() - else - pump.airs[1] = holding ? air_contents : T.return_air() - pump.airs[2] = holding ? holding.air_contents : air_contents - - pump.process_atmos() // Pump gas. - if(!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 - pump.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, "portable_pump", name, 420, 415, master_ui, state) - ui.open() - -/obj/machinery/portable_atmospherics/pump/ui_data() - var/data = list() - data["on"] = on - data["direction"] = direction - data["connected"] = connected_port ? 1 : 0 - data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) - data["target_pressure"] = round(pump.target_pressure ? pump.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()) - 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(pressure == "input") - pressure = input("New release pressure ([PUMP_MIN_PRESSURE]-[PUMP_MAX_PRESSURE] kPa):", name, pump.target_pressure) as num|null - if(!isnull(pressure) && !..()) - . = TRUE - else if(text2num(pressure) != null) - pressure = text2num(pressure) - . = TRUE - if(.) - pump.target_pressure = CLAMP(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE) - investigate_log("was set to [pump.target_pressure] kPa by [key_name(usr)].", INVESTIGATE_ATMOS) - if("eject") - if(holding) - holding.forceMove(drop_location()) - holding = null - . = 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 + + var/on = FALSE + var/direction = PUMP_OUT + var/obj/machinery/atmospherics/components/binary/pump/pump + + volume = 1000 + +/obj/machinery/portable_atmospherics/pump/Initialize() + . = ..() + pump = new(src, FALSE) + pump.on = TRUE + pump.stat = 0 + pump.build_network() + +/obj/machinery/portable_atmospherics/pump/Destroy() + var/turf/T = get_turf(src) + T.assume_air(air_contents) + air_update_turf() + QDEL_NULL(pump) + return ..() + +/obj/machinery/portable_atmospherics/pump/update_icon() + icon_state = "psiphon:[on]" + + cut_overlays() + if(holding) + add_overlay("siphon-open") + if(connected_port) + add_overlay("siphon-connector") + +/obj/machinery/portable_atmospherics/pump/process_atmos() + ..() + if(!on) + pump.airs[1] = null + pump.airs[2] = null + return + + var/turf/T = get_turf(src) + if(direction == PUMP_OUT) // Hook up the internal pump. + pump.airs[1] = holding ? holding.air_contents : air_contents + pump.airs[2] = holding ? air_contents : T.return_air() + else + pump.airs[1] = holding ? air_contents : T.return_air() + pump.airs[2] = holding ? holding.air_contents : air_contents + + pump.process_atmos() // Pump gas. + if(!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 + pump.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, "portable_pump", name, 420, 415, master_ui, state) + ui.open() + +/obj/machinery/portable_atmospherics/pump/ui_data() + var/data = list() + data["on"] = on + data["direction"] = direction + data["connected"] = connected_port ? 1 : 0 + data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) + data["target_pressure"] = round(pump.target_pressure ? pump.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()) + 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(pressure == "input") + pressure = input("New release pressure ([PUMP_MIN_PRESSURE]-[PUMP_MAX_PRESSURE] kPa):", name, pump.target_pressure) as num|null + if(!isnull(pressure) && !..()) + . = TRUE + else if(text2num(pressure) != null) + pressure = text2num(pressure) + . = TRUE + if(.) + pump.target_pressure = CLAMP(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE) + investigate_log("was set to [pump.target_pressure] kPa by [key_name(usr)].", INVESTIGATE_ATMOS) + if("eject") + if(holding) + holding.forceMove(drop_location()) + holding = null + . = TRUE + update_icon() diff --git a/code/modules/atmospherics/machinery/portable/scrubber.dm b/code/modules/atmospherics/machinery/portable/scrubber.dm index 28cdb56e3d..d52f2d8356 100644 --- a/code/modules/atmospherics/machinery/portable/scrubber.dm +++ b/code/modules/atmospherics/machinery/portable/scrubber.dm @@ -1,144 +1,144 @@ -/obj/machinery/portable_atmospherics/scrubber - name = "portable air scrubber" - icon_state = "pscrubber:0" - density = TRUE - - var/on = FALSE - var/volume_rate = 1000 - volume = 1000 - - var/list/scrubbing = list(/datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz, /datum/gas/nitryl, /datum/gas/tritium, /datum/gas/hypernoblium, /datum/gas/water_vapor) - -/obj/machinery/portable_atmospherics/scrubber/Destroy() - var/turf/T = get_turf(src) - T.assume_air(air_contents) - air_update_turf() - return ..() - -/obj/machinery/portable_atmospherics/scrubber/update_icon() - icon_state = "pscrubber:[on]" - - cut_overlays() - if(holding) - add_overlay("scrubber-open") - if(connected_port) - add_overlay("scrubber-connector") - -/obj/machinery/portable_atmospherics/scrubber/process_atmos() - ..() - if(!on) - return - - if(holding) - scrub(holding.air_contents) - else - var/turf/T = get_turf(src) - scrub(T.return_air()) - -/obj/machinery/portable_atmospherics/scrubber/proc/scrub(var/datum/gas_mixture/mixture) - var/transfer_moles = min(1, volume_rate / mixture.volume) * mixture.total_moles() - - var/datum/gas_mixture/filtering = mixture.remove(transfer_moles) // Remove part of the mixture to filter. - var/datum/gas_mixture/filtered = new - if(!filtering) - return - - filtered.temperature = filtering.temperature - for(var/gas in filtering.gases & scrubbing) - filtered.gases[gas] = filtering.gases[gas] // Shuffle the "bad" gasses to the filtered mixture. - filtering.gases[gas] = 0 - GAS_GARBAGE_COLLECT(filtering.gases) - - air_contents.merge(filtered) // Store filtered out gasses. - mixture.merge(filtering) // Returned the cleaned gas. - if(!holding) - air_update_turf() - -/obj/machinery/portable_atmospherics/scrubber/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if(is_operational()) - if(prob(50 / severity)) - on = !on - update_icon() - -/obj/machinery/portable_atmospherics/scrubber/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, "portable_scrubber", name, 420, 435, master_ui, state) - ui.open() - -/obj/machinery/portable_atmospherics/scrubber/ui_data() - var/data = list() - data["on"] = on - data["connected"] = connected_port ? 1 : 0 - data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) - - data["id_tag"] = -1 //must be defined in order to reuse code between portable and vent scrubbers - data["filter_types"] = list() - for(var/path in GLOB.meta_gas_ids) - data["filter_types"] += list(list("gas_id" = GLOB.meta_gas_ids[path], "gas_name" = GLOB.meta_gas_names[path], "enabled" = (path in scrubbing))) - - if(holding) - data["holding"] = list() - data["holding"]["name"] = holding.name - data["holding"]["pressure"] = round(holding.air_contents.return_pressure()) - return data - -/obj/machinery/portable_atmospherics/scrubber/ui_act(action, params) - if(..()) - return - switch(action) - if("power") - on = !on - . = TRUE - if("eject") - if(holding) - holding.forceMove(drop_location()) - holding = null - . = TRUE - if("toggle_filter") - scrubbing ^= gas_id2path(params["val"]) - . = TRUE - update_icon() - -/obj/machinery/portable_atmospherics/scrubber/huge - name = "huge air scrubber" - icon_state = "scrubber:0" - anchored = TRUE - active_power_usage = 500 - idle_power_usage = 10 - - volume_rate = 1500 - volume = 50000 - - var/movable = FALSE - -/obj/machinery/portable_atmospherics/scrubber/huge/movable - movable = TRUE - -/obj/machinery/portable_atmospherics/scrubber/huge/update_icon() - icon_state = "scrubber:[on]" - -/obj/machinery/portable_atmospherics/scrubber/huge/process_atmos() - if((!anchored && !movable) || !is_operational()) - on = FALSE - update_icon() - use_power = on ? ACTIVE_POWER_USE : IDLE_POWER_USE - if(!on) - return - - ..() - if(!holding) - var/turf/T = get_turf(src) - for(var/turf/AT in T.GetAtmosAdjacentTurfs(alldir = TRUE)) - scrub(AT.return_air()) - -/obj/machinery/portable_atmospherics/scrubber/huge/attackby(obj/item/W, mob/user) - if(default_unfasten_wrench(user, W)) - if(!movable) - on = FALSE - else - return ..() +/obj/machinery/portable_atmospherics/scrubber + name = "portable air scrubber" + icon_state = "pscrubber:0" + density = TRUE + + var/on = FALSE + var/volume_rate = 1000 + volume = 1000 + + var/list/scrubbing = list(/datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz, /datum/gas/nitryl, /datum/gas/tritium, /datum/gas/hypernoblium, /datum/gas/water_vapor) + +/obj/machinery/portable_atmospherics/scrubber/Destroy() + var/turf/T = get_turf(src) + T.assume_air(air_contents) + air_update_turf() + return ..() + +/obj/machinery/portable_atmospherics/scrubber/update_icon() + icon_state = "pscrubber:[on]" + + cut_overlays() + if(holding) + add_overlay("scrubber-open") + if(connected_port) + add_overlay("scrubber-connector") + +/obj/machinery/portable_atmospherics/scrubber/process_atmos() + ..() + if(!on) + return + + if(holding) + scrub(holding.air_contents) + else + var/turf/T = get_turf(src) + scrub(T.return_air()) + +/obj/machinery/portable_atmospherics/scrubber/proc/scrub(var/datum/gas_mixture/mixture) + var/transfer_moles = min(1, volume_rate / mixture.volume) * mixture.total_moles() + + var/datum/gas_mixture/filtering = mixture.remove(transfer_moles) // Remove part of the mixture to filter. + var/datum/gas_mixture/filtered = new + if(!filtering) + return + + filtered.temperature = filtering.temperature + for(var/gas in filtering.gases & scrubbing) + filtered.gases[gas] = filtering.gases[gas] // Shuffle the "bad" gasses to the filtered mixture. + filtering.gases[gas] = 0 + GAS_GARBAGE_COLLECT(filtering.gases) + + air_contents.merge(filtered) // Store filtered out gasses. + mixture.merge(filtering) // Returned the cleaned gas. + if(!holding) + air_update_turf() + +/obj/machinery/portable_atmospherics/scrubber/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(is_operational()) + if(prob(50 / severity)) + on = !on + update_icon() + +/obj/machinery/portable_atmospherics/scrubber/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, "portable_scrubber", name, 420, 435, master_ui, state) + ui.open() + +/obj/machinery/portable_atmospherics/scrubber/ui_data() + var/data = list() + data["on"] = on + data["connected"] = connected_port ? 1 : 0 + data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) + + data["id_tag"] = -1 //must be defined in order to reuse code between portable and vent scrubbers + data["filter_types"] = list() + for(var/path in GLOB.meta_gas_ids) + data["filter_types"] += list(list("gas_id" = GLOB.meta_gas_ids[path], "gas_name" = GLOB.meta_gas_names[path], "enabled" = (path in scrubbing))) + + if(holding) + data["holding"] = list() + data["holding"]["name"] = holding.name + data["holding"]["pressure"] = round(holding.air_contents.return_pressure()) + return data + +/obj/machinery/portable_atmospherics/scrubber/ui_act(action, params) + if(..()) + return + switch(action) + if("power") + on = !on + . = TRUE + if("eject") + if(holding) + holding.forceMove(drop_location()) + holding = null + . = TRUE + if("toggle_filter") + scrubbing ^= gas_id2path(params["val"]) + . = TRUE + update_icon() + +/obj/machinery/portable_atmospherics/scrubber/huge + name = "huge air scrubber" + icon_state = "scrubber:0" + anchored = TRUE + active_power_usage = 500 + idle_power_usage = 10 + + volume_rate = 1500 + volume = 50000 + + var/movable = FALSE + +/obj/machinery/portable_atmospherics/scrubber/huge/movable + movable = TRUE + +/obj/machinery/portable_atmospherics/scrubber/huge/update_icon() + icon_state = "scrubber:[on]" + +/obj/machinery/portable_atmospherics/scrubber/huge/process_atmos() + if((!anchored && !movable) || !is_operational()) + on = FALSE + update_icon() + use_power = on ? ACTIVE_POWER_USE : IDLE_POWER_USE + if(!on) + return + + ..() + if(!holding) + var/turf/T = get_turf(src) + for(var/turf/AT in T.GetAtmosAdjacentTurfs(alldir = TRUE)) + scrub(AT.return_air()) + +/obj/machinery/portable_atmospherics/scrubber/huge/attackby(obj/item/W, mob/user) + if(default_unfasten_wrench(user, W)) + if(!movable) + on = FALSE + else + return ..() diff --git a/code/modules/awaymissions/bluespaceartillery.dm b/code/modules/awaymissions/bluespaceartillery.dm index 9bebcfe546..deb05920e2 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 74a9a30d7c..86610a36b8 100644 --- a/code/modules/awaymissions/exile.dm +++ b/code/modules/awaymissions/exile.dm @@ -1,13 +1,13 @@ - -/obj/structure/closet/secure_closet/exile - name = "exile implants" - req_access = list(ACCESS_HOS) - -/obj/structure/closet/secure_closet/exile/New() - ..() - new /obj/item/implanter/exile(src) - new /obj/item/implantcase/exile(src) - new /obj/item/implantcase/exile(src) - new /obj/item/implantcase/exile(src) - 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/New() + ..() + new /obj/item/implanter/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) + new /obj/item/implantcase/exile(src) new /obj/item/implantcase/exile(src) \ No newline at end of file diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm index 2cdbb41d05..e693abc8e4 100644 --- a/code/modules/awaymissions/gateway.dm +++ b/code/modules/awaymissions/gateway.dm @@ -1,255 +1,255 @@ -GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation) - -/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" - density = TRUE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/active = 0 - var/checkparts = TRUE - var/list/obj/effect/landmark/randomspawns = list() - var/calibrated = TRUE - var/list/linked = list() - var/can_link = FALSE //Is this the centerpiece? - -/obj/machinery/gateway/Initialize() - randomspawns = GLOB.awaydestinations - update_icon() - if(!istype(src, /obj/machinery/gateway/centerstation) && !istype(src, /obj/machinery/gateway/centeraway)) - switch(dir) - if(SOUTH,SOUTHEAST,SOUTHWEST) - density = FALSE - return ..() - -/obj/machinery/gateway/proc/toggleoff() - for(var/obj/machinery/gateway/G in linked) - G.active = 0 - G.update_icon() - active = 0 - update_icon() - -/obj/machinery/gateway/proc/detect() - if(!can_link) - return FALSE - linked = list() //clear the list - var/turf/T = loc - var/ready = FALSE - - for(var/i in GLOB.alldirs) - T = get_step(loc, i) - var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T - if(G) - linked.Add(G) - continue - - //this is only done if we fail to find a part - ready = FALSE - toggleoff() - break - - if((linked.len == 8) || !checkparts) - ready = TRUE - return ready - -/obj/machinery/gateway/update_icon() - if(active) - icon_state = "on" - return - icon_state = "off" - -/obj/machinery/gateway/attack_hand(mob/user) - . = ..() - if(.) - return - if(!detect()) - return - if(!active) - toggleon(user) - return - toggleoff() - -/obj/machinery/gateway/proc/toggleon(mob/user) - return FALSE - -/obj/machinery/gateway/safe_throw_at() - return - -/obj/machinery/gateway/centerstation/Initialize() - . = ..() - if(!GLOB.the_gateway) - GLOB.the_gateway = src - update_icon() - wait = world.time + CONFIG_GET(number/gateway_delay) //+ thirty minutes default - awaygate = locate(/obj/machinery/gateway/centeraway) - -/obj/machinery/gateway/centerstation/Destroy() - if(GLOB.the_gateway == src) - GLOB.the_gateway = null - return ..() - -//this is da important part wot makes things go -/obj/machinery/gateway/centerstation - density = TRUE - icon_state = "offcenter" - use_power = IDLE_POWER_USE - - //warping vars - var/wait = 0 //this just grabs world.time at world start - var/obj/machinery/gateway/centeraway/awaygate = null - can_link = TRUE - -/obj/machinery/gateway/centerstation/update_icon() - if(active) - icon_state = "oncenter" - return - icon_state = "offcenter" - -/obj/machinery/gateway/centerstation/process() - if((stat & (NOPOWER)) && use_power) - if(active) - toggleoff() - return - - if(active) - use_power(5000) - -/obj/machinery/gateway/centerstation/toggleon(mob/user) - if(!detect()) - return - if(!powered()) - return - if(!awaygate) - to_chat(user, "Error: No destination found.") - return - if(world.time < wait) - to_chat(user, "Error: Warpspace triangulation in progress. Estimated time to completion: [DisplayTimeText(wait - world.time)].") - return - - for(var/obj/machinery/gateway/G in linked) - G.active = 1 - G.update_icon() - active = 1 - update_icon() - -//okay, here's the good teleporting stuff -/obj/machinery/gateway/centerstation/Bumped(atom/movable/AM) - if(!active) - return - if(!detect()) - return - if(!awaygate || QDELETED(awaygate)) - return - - if(awaygate.calibrated) - AM.forceMove(get_step(awaygate.loc, SOUTH)) - AM.setDir(SOUTH) - if (ismob(AM)) - var/mob/M = AM - if (M.client) - M.client.move_delay = max(world.time + 5, M.client.move_delay) - return - else - var/obj/effect/landmark/dest = pick(randomspawns) - if(dest) - AM.forceMove(get_turf(dest)) - AM.setDir(SOUTH) - use_power(5000) - return - -/obj/machinery/gateway/centeraway/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/multitool)) - if(calibrated) - to_chat(user, "\black The gate is already calibrated, there is no work for you to do here.") - return - 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 - -/////////////////////////////////////Away//////////////////////// - - -/obj/machinery/gateway/centeraway - density = TRUE - icon_state = "offcenter" - use_power = NO_POWER_USE - var/obj/machinery/gateway/centerstation/stationgate = null - can_link = TRUE - - -/obj/machinery/gateway/centeraway/Initialize() - . = ..() - update_icon() - stationgate = locate(/obj/machinery/gateway/centerstation) - - -/obj/machinery/gateway/centeraway/update_icon() - if(active) - icon_state = "oncenter" - return - icon_state = "offcenter" - -/obj/machinery/gateway/centeraway/toggleon(mob/user) - if(!detect()) - return - if(!stationgate) - to_chat(user, "Error: No destination found.") - return - - for(var/obj/machinery/gateway/G in linked) - G.active = 1 - G.update_icon() - active = 1 - update_icon() - -/obj/machinery/gateway/centeraway/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, "\black The station gate has detected your exile implant and is blocking your entry.") - return TRUE - return FALSE - -/obj/machinery/gateway/centeraway/Bumped(atom/movable/AM) - if(!detect()) - return - if(!active) - return - if(!stationgate || QDELETED(stationgate)) - return - if(isliving(AM)) - if(check_exile_implant(AM)) - return - else - for(var/mob/living/L in AM.contents) - if(check_exile_implant(L)) - say("Rejecting [AM]: Exile implant detected in contained lifeform.") - return - if(AM.has_buckled_mobs()) - for(var/mob/living/L in AM.buckled_mobs) - if(check_exile_implant(L)) - say("Rejecting [AM]: Exile implant detected in close proximity lifeform.") - return - AM.forceMove(get_step(stationgate.loc, SOUTH)) - AM.setDir(SOUTH) - if (ismob(AM)) - var/mob/M = AM - if (M.client) - M.client.move_delay = max(world.time + 5, M.client.move_delay) - - -/obj/machinery/gateway/centeraway/admin - desc = "A mysterious gateway built by unknown hands, this one seems more compact." - -/obj/machinery/gateway/centeraway/admin/Initialize() - . = ..() - if(stationgate && !stationgate.awaygate) - stationgate.awaygate = src - -/obj/machinery/gateway/centeraway/admin/detect() - return TRUE - - -/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 Blue Space Research" - name = "Confidential Correspondence, Pg 1" +GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation) + +/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" + density = TRUE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/active = 0 + var/checkparts = TRUE + var/list/obj/effect/landmark/randomspawns = list() + var/calibrated = TRUE + var/list/linked = list() + var/can_link = FALSE //Is this the centerpiece? + +/obj/machinery/gateway/Initialize() + randomspawns = GLOB.awaydestinations + update_icon() + if(!istype(src, /obj/machinery/gateway/centerstation) && !istype(src, /obj/machinery/gateway/centeraway)) + switch(dir) + if(SOUTH,SOUTHEAST,SOUTHWEST) + density = FALSE + return ..() + +/obj/machinery/gateway/proc/toggleoff() + for(var/obj/machinery/gateway/G in linked) + G.active = 0 + G.update_icon() + active = 0 + update_icon() + +/obj/machinery/gateway/proc/detect() + if(!can_link) + return FALSE + linked = list() //clear the list + var/turf/T = loc + var/ready = FALSE + + for(var/i in GLOB.alldirs) + T = get_step(loc, i) + var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T + if(G) + linked.Add(G) + continue + + //this is only done if we fail to find a part + ready = FALSE + toggleoff() + break + + if((linked.len == 8) || !checkparts) + ready = TRUE + return ready + +/obj/machinery/gateway/update_icon() + if(active) + icon_state = "on" + return + icon_state = "off" + +/obj/machinery/gateway/attack_hand(mob/user) + . = ..() + if(.) + return + if(!detect()) + return + if(!active) + toggleon(user) + return + toggleoff() + +/obj/machinery/gateway/proc/toggleon(mob/user) + return FALSE + +/obj/machinery/gateway/safe_throw_at() + return + +/obj/machinery/gateway/centerstation/Initialize() + . = ..() + if(!GLOB.the_gateway) + GLOB.the_gateway = src + update_icon() + wait = world.time + CONFIG_GET(number/gateway_delay) //+ thirty minutes default + awaygate = locate(/obj/machinery/gateway/centeraway) + +/obj/machinery/gateway/centerstation/Destroy() + if(GLOB.the_gateway == src) + GLOB.the_gateway = null + return ..() + +//this is da important part wot makes things go +/obj/machinery/gateway/centerstation + density = TRUE + icon_state = "offcenter" + use_power = IDLE_POWER_USE + + //warping vars + var/wait = 0 //this just grabs world.time at world start + var/obj/machinery/gateway/centeraway/awaygate = null + can_link = TRUE + +/obj/machinery/gateway/centerstation/update_icon() + if(active) + icon_state = "oncenter" + return + icon_state = "offcenter" + +/obj/machinery/gateway/centerstation/process() + if((stat & (NOPOWER)) && use_power) + if(active) + toggleoff() + return + + if(active) + use_power(5000) + +/obj/machinery/gateway/centerstation/toggleon(mob/user) + if(!detect()) + return + if(!powered()) + return + if(!awaygate) + to_chat(user, "Error: No destination found.") + return + if(world.time < wait) + to_chat(user, "Error: Warpspace triangulation in progress. Estimated time to completion: [DisplayTimeText(wait - world.time)].") + return + + for(var/obj/machinery/gateway/G in linked) + G.active = 1 + G.update_icon() + active = 1 + update_icon() + +//okay, here's the good teleporting stuff +/obj/machinery/gateway/centerstation/Bumped(atom/movable/AM) + if(!active) + return + if(!detect()) + return + if(!awaygate || QDELETED(awaygate)) + return + + if(awaygate.calibrated) + AM.forceMove(get_step(awaygate.loc, SOUTH)) + AM.setDir(SOUTH) + if (ismob(AM)) + var/mob/M = AM + if (M.client) + M.client.move_delay = max(world.time + 5, M.client.move_delay) + return + else + var/obj/effect/landmark/dest = pick(randomspawns) + if(dest) + AM.forceMove(get_turf(dest)) + AM.setDir(SOUTH) + use_power(5000) + return + +/obj/machinery/gateway/centeraway/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/multitool)) + if(calibrated) + to_chat(user, "\black The gate is already calibrated, there is no work for you to do here.") + return + 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 + +/////////////////////////////////////Away//////////////////////// + + +/obj/machinery/gateway/centeraway + density = TRUE + icon_state = "offcenter" + use_power = NO_POWER_USE + var/obj/machinery/gateway/centerstation/stationgate = null + can_link = TRUE + + +/obj/machinery/gateway/centeraway/Initialize() + . = ..() + update_icon() + stationgate = locate(/obj/machinery/gateway/centerstation) + + +/obj/machinery/gateway/centeraway/update_icon() + if(active) + icon_state = "oncenter" + return + icon_state = "offcenter" + +/obj/machinery/gateway/centeraway/toggleon(mob/user) + if(!detect()) + return + if(!stationgate) + to_chat(user, "Error: No destination found.") + return + + for(var/obj/machinery/gateway/G in linked) + G.active = 1 + G.update_icon() + active = 1 + update_icon() + +/obj/machinery/gateway/centeraway/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, "\black The station gate has detected your exile implant and is blocking your entry.") + return TRUE + return FALSE + +/obj/machinery/gateway/centeraway/Bumped(atom/movable/AM) + if(!detect()) + return + if(!active) + return + if(!stationgate || QDELETED(stationgate)) + return + if(isliving(AM)) + if(check_exile_implant(AM)) + return + else + for(var/mob/living/L in AM.contents) + if(check_exile_implant(L)) + say("Rejecting [AM]: Exile implant detected in contained lifeform.") + return + if(AM.has_buckled_mobs()) + for(var/mob/living/L in AM.buckled_mobs) + if(check_exile_implant(L)) + say("Rejecting [AM]: Exile implant detected in close proximity lifeform.") + return + AM.forceMove(get_step(stationgate.loc, SOUTH)) + AM.setDir(SOUTH) + if (ismob(AM)) + var/mob/M = AM + if (M.client) + M.client.move_delay = max(world.time + 5, M.client.move_delay) + + +/obj/machinery/gateway/centeraway/admin + desc = "A mysterious gateway built by unknown hands, this one seems more compact." + +/obj/machinery/gateway/centeraway/admin/Initialize() + . = ..() + if(stationgate && !stationgate.awaygate) + stationgate.awaygate = src + +/obj/machinery/gateway/centeraway/admin/detect() + return TRUE + + +/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 Blue Space 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 77cdf8441b..f24cb065dd 100644 --- a/code/modules/awaymissions/mission_code/Academy.dm +++ b/code/modules/awaymissions/mission_code/Academy.dm @@ -1,348 +1,348 @@ - -//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" - item_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) - - 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 - C.transfer_ckey(current_wizard, FALSE) - -/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 = "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 - can_be_rigged = FALSE - var/reusable = 1 - var/used = 0 - -/obj/item/dice/d20/fate/one_use - reusable = 0 - -/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(rigged) - effect(user,rigged) - else - effect(user,result) - -/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) - if(!reusable) - used = 1 - visible_message("The die flare briefly.") - switch(roll) - if(1) - //Dust - user.dust() - if(2) - //Death - user.death() - if(3) - //Swarm of creatures - for(var/direction in GLOB.alldirs) - var/turf/T = get_turf(src) - new /mob/living/simple_animal/hostile/netherworld(get_step(T,direction)) - if(4) - //Destroy Equipment - for (var/obj/item/I in user) - qdel(I) - if(5) - //Monkeying - user.monkeyize() - if(6) - //Cut speed - var/datum/species/S = user.dna.species - S.speedmod += 1 - if(7) - //Throw - 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) - //Fueltank Explosion - explosion(loc,-1,0,2, flame_range = 2) - if(9) - //Cold - var/datum/disease/D = new /datum/disease/cold() - user.ForceContractDisease(D, FALSE, TRUE) - if(10) - //Nothing - visible_message("[src] roll perfectly.") - if(11) - //Cookie - var/obj/item/reagent_containers/food/snacks/cookie/C = new(drop_location()) - C.name = "Cookie of Fate" - if(12) - //Healing - user.revive(full_heal = 1, admin_revive = 1) - if(13) - //Mad Dosh - var/turf/Start = get_turf(src) - for(var/direction in GLOB.alldirs) - var/turf/T = get_step(Start,direction) - if(rand(0,1)) - new /obj/item/stack/spacecash/c1000(T) - else - var/obj/item/storage/bag/money/M = new(T) - for(var/i in 1 to rand(5,50)) - new /obj/item/coin/gold(M) - if(14) - //Free Gun - new /obj/item/gun/ballistic/revolver/mateba(drop_location()) - if(15) - //Random One-use spellbook - new /obj/item/book/granter/spell/random(drop_location()) - if(16) - //Servant & Servant Summon - var/mob/living/carbon/human/H = new(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") - C.transfer_ckey(H, FALSE) - - var/obj/effect/proc_holder/spell/targeted/summonmob/S = new - S.target_mob = H - user.mind.AddSpell(S) - - if(17) - //Tator Kit - new /obj/item/storage/box/syndicate(drop_location()) - if(18) - //Captain ID - new /obj/item/card/id/captains_spare(drop_location()) - if(19) - //Instrinct Resistance - to_chat(user, "You feel robust.") - var/datum/species/S = user.dna.species - S.brutemod *= 0.5 - S.burnmod *= 0.5 - S.coldmod *= 0.5 - if(20) - //Free wizard! - user.mind.make_Wizard() - - -/datum/outfit/butler - name = "Butler" - uniform = /obj/item/clothing/under/suit_jacket/really_black - 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 = "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/update_icon() - return - -/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" + item_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) + + 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 + C.transfer_ckey(current_wizard, FALSE) + +/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 = "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 + can_be_rigged = FALSE + var/reusable = 1 + var/used = 0 + +/obj/item/dice/d20/fate/one_use + reusable = 0 + +/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(rigged) + effect(user,rigged) + else + effect(user,result) + +/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) + if(!reusable) + used = 1 + visible_message("The die flare briefly.") + switch(roll) + if(1) + //Dust + user.dust() + if(2) + //Death + user.death() + if(3) + //Swarm of creatures + for(var/direction in GLOB.alldirs) + var/turf/T = get_turf(src) + new /mob/living/simple_animal/hostile/netherworld(get_step(T,direction)) + if(4) + //Destroy Equipment + for (var/obj/item/I in user) + qdel(I) + if(5) + //Monkeying + user.monkeyize() + if(6) + //Cut speed + var/datum/species/S = user.dna.species + S.speedmod += 1 + if(7) + //Throw + 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) + //Fueltank Explosion + explosion(loc,-1,0,2, flame_range = 2) + if(9) + //Cold + var/datum/disease/D = new /datum/disease/cold() + user.ForceContractDisease(D, FALSE, TRUE) + if(10) + //Nothing + visible_message("[src] roll perfectly.") + if(11) + //Cookie + var/obj/item/reagent_containers/food/snacks/cookie/C = new(drop_location()) + C.name = "Cookie of Fate" + if(12) + //Healing + user.revive(full_heal = 1, admin_revive = 1) + if(13) + //Mad Dosh + var/turf/Start = get_turf(src) + for(var/direction in GLOB.alldirs) + var/turf/T = get_step(Start,direction) + if(rand(0,1)) + new /obj/item/stack/spacecash/c1000(T) + else + var/obj/item/storage/bag/money/M = new(T) + for(var/i in 1 to rand(5,50)) + new /obj/item/coin/gold(M) + if(14) + //Free Gun + new /obj/item/gun/ballistic/revolver/mateba(drop_location()) + if(15) + //Random One-use spellbook + new /obj/item/book/granter/spell/random(drop_location()) + if(16) + //Servant & Servant Summon + var/mob/living/carbon/human/H = new(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") + C.transfer_ckey(H, FALSE) + + var/obj/effect/proc_holder/spell/targeted/summonmob/S = new + S.target_mob = H + user.mind.AddSpell(S) + + if(17) + //Tator Kit + new /obj/item/storage/box/syndicate(drop_location()) + if(18) + //Captain ID + new /obj/item/card/id/captains_spare(drop_location()) + if(19) + //Instrinct Resistance + to_chat(user, "You feel robust.") + var/datum/species/S = user.dna.species + S.brutemod *= 0.5 + S.burnmod *= 0.5 + S.coldmod *= 0.5 + if(20) + //Free wizard! + user.mind.make_Wizard() + + +/datum/outfit/butler + name = "Butler" + uniform = /obj/item/clothing/under/suit_jacket/really_black + 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 = "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/update_icon() + return + +/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 082d2d3c9e..3a112a5d91 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/centcomAway - name = "XCC-P5831" - icon_state = "away" - requires_power = FALSE - -/area/awaymission/centcomAway/general - name = "XCC-P5831" - music = 'sound/ambience/ambigen3.ogg' - -/area/awaymission/centcomAway/maint - name = "XCC-P5831 Maintenance" - icon_state = "away1" - music = 'sound/ambience/ambisin1.ogg' - -/area/awaymission/centcomAway/thunderdome - name = "XCC-P5831 Thunderdome" - icon_state = "away2" - music = 'sound/ambience/ambisin2.ogg' - -/area/awaymission/centcomAway/cafe - name = "XCC-P5831 Kitchen Arena" - icon_state = "away3" - music = 'sound/ambience/ambisin3.ogg' - -/area/awaymission/centcomAway/courtroom - name = "XCC-P5831 Courtroom" - icon_state = "away4" - music = 'sound/ambience/ambisin4.ogg' - -/area/awaymission/centcomAway/hangar - name = "XCC-P5831 Hangars" - icon_state = "away4" - music = '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?
                \ +//centcomAway areas + +/area/awaymission/centcomAway + name = "XCC-P5831" + icon_state = "away" + requires_power = FALSE + +/area/awaymission/centcomAway/general + name = "XCC-P5831" + music = 'sound/ambience/ambigen3.ogg' + +/area/awaymission/centcomAway/maint + name = "XCC-P5831 Maintenance" + icon_state = "away1" + music = 'sound/ambience/ambisin1.ogg' + +/area/awaymission/centcomAway/thunderdome + name = "XCC-P5831 Thunderdome" + icon_state = "away2" + music = 'sound/ambience/ambisin2.ogg' + +/area/awaymission/centcomAway/cafe + name = "XCC-P5831 Kitchen Arena" + icon_state = "away3" + music = 'sound/ambience/ambisin3.ogg' + +/area/awaymission/centcomAway/courtroom + name = "XCC-P5831 Courtroom" + icon_state = "away4" + music = 'sound/ambience/ambisin4.ogg' + +/area/awaymission/centcomAway/hangar + name = "XCC-P5831 Hangars" + icon_state = "away4" + music = '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" \ No newline at end of file diff --git a/code/modules/awaymissions/mission_code/wildwest.dm b/code/modules/awaymissions/mission_code/wildwest.dm index 4971d82053..ac1a4077ec 100644 --- a/code/modules/awaymissions/mission_code/wildwest.dm +++ b/code/modules/awaymissions/mission_code/wildwest.dm @@ -1,170 +1,170 @@ -/* 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 its just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...its 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 list("Power","Wealth","Immortality","To Kill","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(COLDRES) - 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("To Kill") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your wickedness, claiming your soul and warping your body to match the darkness in your heart.") - user.mind.add_antag_datum(/datum/antagonist/wishgranter) - 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 its just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...its 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 list("Power","Wealth","Immortality","To Kill","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(COLDRES) + 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("To Kill") + to_chat(user, "Your wish is granted, but at a terrible cost...") + to_chat(user, "The Wish Granter punishes you for your wickedness, claiming your soul and warping your body to match the darkness in your heart.") + user.mind.add_antag_datum(/datum/antagonist/wishgranter) + 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 94ebb91972..7cbbde096d 100644 --- a/code/modules/awaymissions/pamphlet.dm +++ b/code/modules/awaymissions/pamphlet.dm @@ -1,39 +1,39 @@ -/obj/item/paper/pamphlet - name = "pamphlet" - icon_state = "pamphlet" - -/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." - -//we don't want the silly text overlay! -/obj/item/paper/pamphlet/update_icon() - return +/obj/item/paper/pamphlet + name = "pamphlet" + icon_state = "pamphlet" + +/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." + +//we don't want the silly text overlay! +/obj/item/paper/pamphlet/update_icon() + return diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm index a96acb027b..744fb78ba5 100644 --- a/code/modules/awaymissions/zlevel.dm +++ b/code/modules/awaymissions/zlevel.dm @@ -1,61 +1,61 @@ -// 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.awaydestinations.len) //crude, but it saves another var! - return - - 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.") - -/proc/reset_gateway_spawns(reset = FALSE) - for(var/obj/machinery/gateway/G in world) - if(reset) - G.randomspawns = GLOB.awaydestinations - else - G.randomspawns.Add(GLOB.awaydestinations) - -/obj/effect/landmark/awaystart - name = "away mission spawn" - desc = "Randomly picked away mission spawn points." - -/obj/effect/landmark/awaystart/New() - GLOB.awaydestinations += src - ..() - -/obj/effect/landmark/awaystart/Destroy() - GLOB.awaydestinations -= src - return ..() - -/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 (copytext(t, 1, 2) == "#") - 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.awaydestinations.len) //crude, but it saves another var! + return + + 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.") + +/proc/reset_gateway_spawns(reset = FALSE) + for(var/obj/machinery/gateway/G in world) + if(reset) + G.randomspawns = GLOB.awaydestinations + else + G.randomspawns.Add(GLOB.awaydestinations) + +/obj/effect/landmark/awaystart + name = "away mission spawn" + desc = "Randomly picked away mission spawn points." + +/obj/effect/landmark/awaystart/New() + GLOB.awaydestinations += src + ..() + +/obj/effect/landmark/awaystart/Destroy() + GLOB.awaydestinations -= src + return ..() + +/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 (copytext(t, 1, 2) == "#") + 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/console.dm b/code/modules/cargo/console.dm index adb7fe73f3..526062d0f7 100644 --- a/code/modules/cargo/console.dm +++ b/code/modules/cargo/console.dm @@ -1,228 +1,228 @@ -/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 - req_access = list(ACCESS_CARGO) - var/requestonly = FALSE - var/contraband = FALSE - var/safety_warning = "For safety reasons, the automated supply shuttle \ - cannot transport live organisms, human remains, classified nuclear weaponry \ - or homing beacons." - var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible." - - 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 - req_access = list() - requestonly = TRUE - -/obj/machinery/computer/cargo/Initialize() - . = ..() - 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/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 - 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 - req_access = list() - return TRUE - -/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, 1000, 800, master_ui, state) - ui.open() - -/obj/machinery/computer/cargo/ui_data() - var/list/data = list() - data["requestonly"] = requestonly - data["location"] = SSshuttle.supply.getStatusText() - data["points"] = SSshuttle.points - data["away"] = SSshuttle.supply.getDockedId() == "supply_away" - 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["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. - )) - - 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 - )) - - 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_act(action, params, datum/tgui/ui) - if(..()) - return - if(!allowed(usr)) - to_chat(usr, "Access denied.") - return - if(action != "add" && requestonly) - 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.") - . = 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/reason = "" - if(requestonly) - reason = stripped_input("Reason:", name, "") - if(isnull(reason) || ..()) - return - - var/turf/T = get_turf(src) - var/datum/supply_order/SO = new(pack, name, rank, ckey, reason) - SO.generateRequisition(T) - if(requestonly) - SSshuttle.requestlist += SO - else - SSshuttle.shoppinglist += SO - . = TRUE - if("remove") - var/id = text2num(params["id"]) - for(var/datum/supply_order/SO in SSshuttle.shoppinglist) - if(SO.id == id) - 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(.) - 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 + req_access = list(ACCESS_CARGO) + var/requestonly = FALSE + var/contraband = FALSE + var/safety_warning = "For safety reasons, the automated supply shuttle \ + cannot transport live organisms, human remains, classified nuclear weaponry \ + or homing beacons." + var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible." + + 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 + req_access = list() + requestonly = TRUE + +/obj/machinery/computer/cargo/Initialize() + . = ..() + 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/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 + 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 + req_access = list() + return TRUE + +/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, 1000, 800, master_ui, state) + ui.open() + +/obj/machinery/computer/cargo/ui_data() + var/list/data = list() + data["requestonly"] = requestonly + data["location"] = SSshuttle.supply.getStatusText() + data["points"] = SSshuttle.points + data["away"] = SSshuttle.supply.getDockedId() == "supply_away" + 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["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. + )) + + 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 + )) + + 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_act(action, params, datum/tgui/ui) + if(..()) + return + if(!allowed(usr)) + to_chat(usr, "Access denied.") + return + if(action != "add" && requestonly) + 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.") + . = 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/reason = "" + if(requestonly) + reason = stripped_input("Reason:", name, "") + if(isnull(reason) || ..()) + return + + var/turf/T = get_turf(src) + var/datum/supply_order/SO = new(pack, name, rank, ckey, reason) + SO.generateRequisition(T) + if(requestonly) + SSshuttle.requestlist += SO + else + SSshuttle.shoppinglist += SO + . = TRUE + if("remove") + var/id = text2num(params["id"]) + for(var/datum/supply_order/SO in SSshuttle.shoppinglist) + if(SO.id == id) + 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(.) + 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 e38e5b55b9..a4ede0cad2 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -1,206 +1,206 @@ -#define MAX_EMAG_ROCKETS 8 -#define BEACON_COST 5000 -#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 - 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 - 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) - . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) - if(obj_flags & EMAGGED) - return - 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() - req_access = list() - return TRUE - -/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, "cargo_express", name, 1000, 800, 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() - 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 && SSshuttle.points >= 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["points"] = SSshuttle.points - 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") - if (SSshuttle.points >= 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]" - SSshuttle.points -= BEACON_COST - - 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) - if(!(obj_flags & EMAGGED)) - if(SO.pack.cost <= SSshuttle.points) - 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 <= SSshuttle.points && LZ)//we need to call the cost check again because of the CHECK_TICK call - SSshuttle.points -= SO.pack.cost - new /obj/effect/abstract/DPtarget(LZ, podType, SO) - . = TRUE - update_icon() - else - if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= SSshuttle.points) // 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) - SSshuttle.points -= 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/abstract/DPtarget(LZ, podType, SO) - . = TRUE - update_icon() - CHECK_TICK +#define MAX_EMAG_ROCKETS 8 +#define BEACON_COST 5000 +#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 + 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 + 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) + . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) + if(obj_flags & EMAGGED) + return + 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() + req_access = list() + return TRUE + +/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, "cargo_express", name, 1000, 800, 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() + 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 && SSshuttle.points >= 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["points"] = SSshuttle.points + 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") + if (SSshuttle.points >= 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]" + SSshuttle.points -= BEACON_COST + + 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) + if(!(obj_flags & EMAGGED)) + if(SO.pack.cost <= SSshuttle.points) + 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 <= SSshuttle.points && LZ)//we need to call the cost check again because of the CHECK_TICK call + SSshuttle.points -= SO.pack.cost + new /obj/effect/abstract/DPtarget(LZ, podType, SO) + . = TRUE + update_icon() + else + if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= SSshuttle.points) // 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) + SSshuttle.points -= 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/abstract/DPtarget(LZ, podType, SO) + . = TRUE + update_icon() + CHECK_TICK diff --git a/code/modules/cargo/order.dm b/code/modules/cargo/order.dm index b576928bfe..b78f218348 100644 --- a/code/modules/cargo/order.dm +++ b/code/modules/cargo/order.dm @@ -1,110 +1,110 @@ -/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/datum/supply_pack/pack - -/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason) - id = SSshuttle.ordernum++ - src.pack = pack - src.orderer = orderer - src.orderer_rank = orderer_rank - src.orderer_ckey = orderer_ckey - src.reason = reason - -/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 += "Item: [pack.name]
                " - P.info += "Access Restrictions: [get_access_desc(pack.access)]
                " - P.info += "Requested by: [orderer]
                " - P.info += "Rank: [orderer_rank]
                " - P.info += "Comment: [reason]
                " - - P.update_icon() - return P - -/datum/supply_order/proc/generateManifest(obj/structure/closet/crate/C) - var/obj/item/paper/fluff/jobs/cargo/manifest/P = new(C, id, pack.cost) - - var/station_name = (P.errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name() - - P.name = "shipping manifest - #[id] ([pack.name])" - P.info += "

                [command_name()] Shipping Manifest

                " - P.info += "
                " - P.info += "Order #[id]
                " - P.info += "Destination: [station_name]
                " - P.info += "Item: [pack.name]
                " - P.info += "Contents:
                " - P.info += "
                  " - for(var/atom/movable/AM in C.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:

                " - - P.update_icon() - P.forceMove(C) - C.manifest = P - C.update_icon() - - return P - -/datum/supply_order/proc/generate(atom/A) - var/obj/structure/closet/crate/C = pack.generate(A) - var/obj/item/paper/fluff/jobs/cargo/manifest/M = generateManifest(C) - - if(M.errors & MANIFEST_ERROR_ITEM) - if(istype(C, /obj/structure/closet/crate/secure) || istype(C, /obj/structure/closet/crate/large)) - M.errors &= ~MANIFEST_ERROR_ITEM - else - var/lost = max(round(C.contents.len / 10), 1) - while(--lost >= 0) - qdel(pick(C.contents)) - return C - -//Paperwork for NT -/obj/item/folder/paperwork - name = "Incomplete Paperwork" - desc = "These should've been filled out four months ago! Unfinished grant papers issued by Nanotrasen's finance department. Complete this page for additional funding." - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "docs_generic" - -/obj/item/folder/paperwork_correct - name = "Finished Paperwork" - desc = "A neat stack of filled-out forms, in triplicate and signed. Is there anything more satisfying? Make sure they get stamped." - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "docs_verified" +/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/datum/supply_pack/pack + +/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason) + id = SSshuttle.ordernum++ + src.pack = pack + src.orderer = orderer + src.orderer_rank = orderer_rank + src.orderer_ckey = orderer_ckey + src.reason = reason + +/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 += "Item: [pack.name]
                " + P.info += "Access Restrictions: [get_access_desc(pack.access)]
                " + P.info += "Requested by: [orderer]
                " + P.info += "Rank: [orderer_rank]
                " + P.info += "Comment: [reason]
                " + + P.update_icon() + return P + +/datum/supply_order/proc/generateManifest(obj/structure/closet/crate/C) + var/obj/item/paper/fluff/jobs/cargo/manifest/P = new(C, id, pack.cost) + + var/station_name = (P.errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name() + + P.name = "shipping manifest - #[id] ([pack.name])" + P.info += "

                [command_name()] Shipping Manifest

                " + P.info += "
                " + P.info += "Order #[id]
                " + P.info += "Destination: [station_name]
                " + P.info += "Item: [pack.name]
                " + P.info += "Contents:
                " + P.info += "
                  " + for(var/atom/movable/AM in C.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:

                " + + P.update_icon() + P.forceMove(C) + C.manifest = P + C.update_icon() + + return P + +/datum/supply_order/proc/generate(atom/A) + var/obj/structure/closet/crate/C = pack.generate(A) + var/obj/item/paper/fluff/jobs/cargo/manifest/M = generateManifest(C) + + if(M.errors & MANIFEST_ERROR_ITEM) + if(istype(C, /obj/structure/closet/crate/secure) || istype(C, /obj/structure/closet/crate/large)) + M.errors &= ~MANIFEST_ERROR_ITEM + else + var/lost = max(round(C.contents.len / 10), 1) + while(--lost >= 0) + qdel(pick(C.contents)) + return C + +//Paperwork for NT +/obj/item/folder/paperwork + name = "Incomplete Paperwork" + desc = "These should've been filled out four months ago! Unfinished grant papers issued by Nanotrasen's finance department. Complete this page for additional funding." + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "docs_generic" + +/obj/item/folder/paperwork_correct + name = "Finished Paperwork" + desc = "A neat stack of filled-out forms, in triplicate and signed. Is there anything more satisfying? Make sure they get stamped." + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "docs_verified" diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm index 643d072469..7dc233d20f 100644 --- a/code/modules/client/asset_cache.dm +++ b/code/modules/client/asset_cache.dm @@ -1,681 +1,681 @@ -/* -Asset cache quick users guide: - -Make a datum at the bottom of this file with your assets for your thing. -The simple subsystem 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. - -You can set verify to TRUE if you want send() to sleep until the client has the assets. -*/ - - -// Amount of time(ds) MAX to send per asset, if this get exceeded we cancel the sleeping. -// This is doubled for the first asset, then added per asset after -#define ASSET_CACHE_SEND_TIMEOUT 7 - -//When sending mutiple assets, how many before we give the client a quaint little sending resources message -#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8 - -//When passively preloading assets, how many to send at once? Too high creates noticable lag where as too low can flood the client's cache with "verify" files -#define ASSET_CACHE_PRELOAD_CONCURRENT 3 - -/client - var/list/cache = list() // List of all assets sent to this client by the asset cache. - var/list/completed_asset_jobs = list() // List of all completed jobs, awaiting acknowledgement. - var/list/sending = list() - var/last_asset_job = 0 // Last job done. - -//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(var/client/client, var/asset_name, var/verify = TRUE) - if(!istype(client)) - if(ismob(client)) - var/mob/M = client - if(M.client) - client = M.client - - else - return 0 - - else - return 0 - - if(client.cache.Find(asset_name) || client.sending.Find(asset_name)) - return 0 - - client << browse_rsc(SSassets.cache[asset_name], asset_name) - if(!verify) - client.cache += asset_name - return 1 - - client.sending |= asset_name - var/job = ++client.last_asset_job - - client << browse({" - - "}, "window=asset_cache_browser") - - var/t = 0 - var/timeout_time = (ASSET_CACHE_SEND_TIMEOUT * client.sending.len) + ASSET_CACHE_SEND_TIMEOUT - while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic() - stoplag(1) // Lock up the caller until this is received. - t++ - - if(client) - client.sending -= asset_name - client.cache |= asset_name - client.completed_asset_jobs -= job - - return 1 - -//This proc blocks(sleeps) unless verify is set to false -/proc/send_asset_list(var/client/client, var/list/asset_list, var/verify = TRUE) - if(!istype(client)) - if(ismob(client)) - var/mob/M = client - if(M.client) - client = M.client - - else - return 0 - - else - return 0 - - var/list/unreceived = asset_list - (client.cache + client.sending) - if(!unreceived || !unreceived.len) - return 0 - if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT) - to_chat(client, "Sending Resources...") - for(var/asset in unreceived) - if (asset in SSassets.cache) - client << browse_rsc(SSassets.cache[asset], asset) - - if(!verify) // Can't access the asset cache browser, rip. - client.cache += unreceived - return 1 - - client.sending |= unreceived - var/job = ++client.last_asset_job - - client << browse({" - - "}, "window=asset_cache_browser") - - var/t = 0 - var/timeout_time = ASSET_CACHE_SEND_TIMEOUT * client.sending.len - while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic() - stoplag(1) // Lock up the caller until this is received. - t++ - - if(client) - client.sending -= unreceived - client.cache |= unreceived - client.completed_asset_jobs -= job - - return 1 - -//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(var/client/client, var/list/files, var/register_asset = TRUE) - var/concurrent_tracker = 1 - for(var/file in files) - if (!client) - break - if (register_asset) - register_asset(file, files[file]) - if (concurrent_tracker >= ASSET_CACHE_PRELOAD_CONCURRENT) - concurrent_tracker = 1 - send_asset(client, file) - else - concurrent_tracker++ - send_asset(client, file, verify=FALSE) - - 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. -//if it's an icon or something be careful, you'll have to copy it before further use. -/proc/register_asset(var/asset_name, var/asset) - SSassets.cache[asset_name] = asset - -//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(var/file) - return "asset.[md5(fcopy_rsc(file))]" - - -//These datums are used to populate the asset cache, the proc "register()" does this. - -//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(var/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() - var/verify = FALSE - -/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,verify) - - -// 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) - var/verify = FALSE - -/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, verify) - -/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 {""} - -#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 - - verify = FALSE - -/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) - - -//DEFINITIONS FOR ASSET DATUMS START HERE. - -/datum/asset/simple/tgui - assets = list( - "tgui.css" = 'tgui/assets/tgui.css', - "tgui.js" = 'tgui/assets/tgui.js', - "font-awesome.min.css" = 'tgui/assets/font-awesome.min.css', - "fontawesome-webfont.eot" = 'tgui/assets/fonts/fontawesome-webfont.eot', - "fontawesome-webfont.woff2" = 'tgui/assets/fonts/fontawesome-webfont.woff2', - "fontawesome-webfont.woff" = 'tgui/assets/fonts/fontawesome-webfont.woff', - "fontawesome-webfont.ttf" = 'tgui/assets/fonts/fontawesome-webfont.ttf', - "fontawesome-webfont.svg" = 'tgui/assets/fonts/fontawesome-webfont.svg' - ) - -/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' - ) - -/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', - "status" = 'icons/pda_icons/pda_status.png', - "dronephone" = 'icons/pda_icons/pda_dronephone.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' - ) - -/datum/asset/spritesheet/simple/minesweeper - name = "minesweeper" - assets = list( - "1" = 'icons/UI_Icons/minesweeper_tiles/one.png', - "2" = 'icons/UI_Icons/minesweeper_tiles/two.png', - "3" = 'icons/UI_Icons/minesweeper_tiles/three.png', - "4" = 'icons/UI_Icons/minesweeper_tiles/four.png', - "5" = 'icons/UI_Icons/minesweeper_tiles/five.png', - "6" = 'icons/UI_Icons/minesweeper_tiles/six.png', - "7" = 'icons/UI_Icons/minesweeper_tiles/seven.png', - "8" = 'icons/UI_Icons/minesweeper_tiles/eight.png', - "empty" = 'icons/UI_Icons/minesweeper_tiles/empty.png', - "flag" = 'icons/UI_Icons/minesweeper_tiles/flag.png', - "hidden" = 'icons/UI_Icons/minesweeper_tiles/hidden.png', - "mine" = 'icons/UI_Icons/minesweeper_tiles/mine.png', - "minehit" = 'icons/UI_Icons/minesweeper_tiles/minehit.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', - ) - -/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/jquery - verify = FALSE - assets = list( - "jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js', - ) - -/datum/asset/simple/goonchat - verify = FALSE - assets = list( - "json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js', - "errorHandler.js" = 'code/modules/goonchat/browserassets/js/errorHandler.js', - "browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js', - "fontawesome-webfont.eot" = 'tgui/assets/fonts/fontawesome-webfont.eot', - "fontawesome-webfont.svg" = 'tgui/assets/fonts/fontawesome-webfont.svg', - "fontawesome-webfont.ttf" = 'tgui/assets/fonts/fontawesome-webfont.ttf', - "fontawesome-webfont.woff" = 'tgui/assets/fonts/fontawesome-webfont.woff', - "font-awesome.css" = 'code/modules/goonchat/browserassets/css/font-awesome.css', - "browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css', - "browserOutput_dark.css" = 'code/modules/goonchat/browserassets/css/browserOutput_dark.css', - "browserOutput_light.css" = 'code/modules/goonchat/browserassets/css/browserOutput_light.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', - ) - -//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')) - 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 ..() +/* +Asset cache quick users guide: + +Make a datum at the bottom of this file with your assets for your thing. +The simple subsystem 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. + +You can set verify to TRUE if you want send() to sleep until the client has the assets. +*/ + + +// Amount of time(ds) MAX to send per asset, if this get exceeded we cancel the sleeping. +// This is doubled for the first asset, then added per asset after +#define ASSET_CACHE_SEND_TIMEOUT 7 + +//When sending mutiple assets, how many before we give the client a quaint little sending resources message +#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8 + +//When passively preloading assets, how many to send at once? Too high creates noticable lag where as too low can flood the client's cache with "verify" files +#define ASSET_CACHE_PRELOAD_CONCURRENT 3 + +/client + var/list/cache = list() // List of all assets sent to this client by the asset cache. + var/list/completed_asset_jobs = list() // List of all completed jobs, awaiting acknowledgement. + var/list/sending = list() + var/last_asset_job = 0 // Last job done. + +//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(var/client/client, var/asset_name, var/verify = TRUE) + if(!istype(client)) + if(ismob(client)) + var/mob/M = client + if(M.client) + client = M.client + + else + return 0 + + else + return 0 + + if(client.cache.Find(asset_name) || client.sending.Find(asset_name)) + return 0 + + client << browse_rsc(SSassets.cache[asset_name], asset_name) + if(!verify) + client.cache += asset_name + return 1 + + client.sending |= asset_name + var/job = ++client.last_asset_job + + client << browse({" + + "}, "window=asset_cache_browser") + + var/t = 0 + var/timeout_time = (ASSET_CACHE_SEND_TIMEOUT * client.sending.len) + ASSET_CACHE_SEND_TIMEOUT + while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic() + stoplag(1) // Lock up the caller until this is received. + t++ + + if(client) + client.sending -= asset_name + client.cache |= asset_name + client.completed_asset_jobs -= job + + return 1 + +//This proc blocks(sleeps) unless verify is set to false +/proc/send_asset_list(var/client/client, var/list/asset_list, var/verify = TRUE) + if(!istype(client)) + if(ismob(client)) + var/mob/M = client + if(M.client) + client = M.client + + else + return 0 + + else + return 0 + + var/list/unreceived = asset_list - (client.cache + client.sending) + if(!unreceived || !unreceived.len) + return 0 + if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT) + to_chat(client, "Sending Resources...") + for(var/asset in unreceived) + if (asset in SSassets.cache) + client << browse_rsc(SSassets.cache[asset], asset) + + if(!verify) // Can't access the asset cache browser, rip. + client.cache += unreceived + return 1 + + client.sending |= unreceived + var/job = ++client.last_asset_job + + client << browse({" + + "}, "window=asset_cache_browser") + + var/t = 0 + var/timeout_time = ASSET_CACHE_SEND_TIMEOUT * client.sending.len + while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic() + stoplag(1) // Lock up the caller until this is received. + t++ + + if(client) + client.sending -= unreceived + client.cache |= unreceived + client.completed_asset_jobs -= job + + return 1 + +//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(var/client/client, var/list/files, var/register_asset = TRUE) + var/concurrent_tracker = 1 + for(var/file in files) + if (!client) + break + if (register_asset) + register_asset(file, files[file]) + if (concurrent_tracker >= ASSET_CACHE_PRELOAD_CONCURRENT) + concurrent_tracker = 1 + send_asset(client, file) + else + concurrent_tracker++ + send_asset(client, file, verify=FALSE) + + 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. +//if it's an icon or something be careful, you'll have to copy it before further use. +/proc/register_asset(var/asset_name, var/asset) + SSassets.cache[asset_name] = asset + +//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(var/file) + return "asset.[md5(fcopy_rsc(file))]" + + +//These datums are used to populate the asset cache, the proc "register()" does this. + +//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(var/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() + var/verify = FALSE + +/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,verify) + + +// 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) + var/verify = FALSE + +/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, verify) + +/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 {""} + +#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 + + verify = FALSE + +/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) + + +//DEFINITIONS FOR ASSET DATUMS START HERE. + +/datum/asset/simple/tgui + assets = list( + "tgui.css" = 'tgui/assets/tgui.css', + "tgui.js" = 'tgui/assets/tgui.js', + "font-awesome.min.css" = 'tgui/assets/font-awesome.min.css', + "fontawesome-webfont.eot" = 'tgui/assets/fonts/fontawesome-webfont.eot', + "fontawesome-webfont.woff2" = 'tgui/assets/fonts/fontawesome-webfont.woff2', + "fontawesome-webfont.woff" = 'tgui/assets/fonts/fontawesome-webfont.woff', + "fontawesome-webfont.ttf" = 'tgui/assets/fonts/fontawesome-webfont.ttf', + "fontawesome-webfont.svg" = 'tgui/assets/fonts/fontawesome-webfont.svg' + ) + +/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' + ) + +/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', + "status" = 'icons/pda_icons/pda_status.png', + "dronephone" = 'icons/pda_icons/pda_dronephone.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' + ) + +/datum/asset/spritesheet/simple/minesweeper + name = "minesweeper" + assets = list( + "1" = 'icons/UI_Icons/minesweeper_tiles/one.png', + "2" = 'icons/UI_Icons/minesweeper_tiles/two.png', + "3" = 'icons/UI_Icons/minesweeper_tiles/three.png', + "4" = 'icons/UI_Icons/minesweeper_tiles/four.png', + "5" = 'icons/UI_Icons/minesweeper_tiles/five.png', + "6" = 'icons/UI_Icons/minesweeper_tiles/six.png', + "7" = 'icons/UI_Icons/minesweeper_tiles/seven.png', + "8" = 'icons/UI_Icons/minesweeper_tiles/eight.png', + "empty" = 'icons/UI_Icons/minesweeper_tiles/empty.png', + "flag" = 'icons/UI_Icons/minesweeper_tiles/flag.png', + "hidden" = 'icons/UI_Icons/minesweeper_tiles/hidden.png', + "mine" = 'icons/UI_Icons/minesweeper_tiles/mine.png', + "minehit" = 'icons/UI_Icons/minesweeper_tiles/minehit.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', + ) + +/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/jquery + verify = FALSE + assets = list( + "jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js', + ) + +/datum/asset/simple/goonchat + verify = FALSE + assets = list( + "json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js', + "errorHandler.js" = 'code/modules/goonchat/browserassets/js/errorHandler.js', + "browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js', + "fontawesome-webfont.eot" = 'tgui/assets/fonts/fontawesome-webfont.eot', + "fontawesome-webfont.svg" = 'tgui/assets/fonts/fontawesome-webfont.svg', + "fontawesome-webfont.ttf" = 'tgui/assets/fonts/fontawesome-webfont.ttf', + "fontawesome-webfont.woff" = 'tgui/assets/fonts/fontawesome-webfont.woff', + "font-awesome.css" = 'code/modules/goonchat/browserassets/css/font-awesome.css', + "browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css', + "browserOutput_dark.css" = 'code/modules/goonchat/browserassets/css/browserOutput_dark.css', + "browserOutput_light.css" = 'code/modules/goonchat/browserassets/css/browserOutput_light.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', + ) + +//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')) + 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 ..() diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 361b2a7517..2bdb743fc7 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -1,82 +1,82 @@ - -/client - ////////////////////// - //BLACK MAGIC THINGS// - ////////////////////// - parent_type = /datum - //////////////// - //ADMIN THINGS// - //////////////// - var/datum/admins/holder = null - var/datum/click_intercept = null // Needs to implement InterceptClickOn(user,params,atom) proc - var/AI_Interact = 0 - - var/jobbancache = null //Used to cache this client's jobbans to save on DB queries - var/last_message = "" //Contains the last message sent by this client - used to protect against copy-paste spamming. - var/last_message_count = 0 //contins a number of how many times a message identical to last_message was sent. - var/ircreplyamount = 0 - - ///////// - //OTHER// - ///////// - var/datum/preferences/prefs = null - var/last_turn = 0 - var/move_delay = 0 - var/area = null - - /////////////// - //SOUND STUFF// - /////////////// - var/ambience_playing= null - var/played = 0 - //////////// - //SECURITY// - //////////// - // comment out the line below when debugging locally to enable the options & messages menu - control_freak = 1 - - //////////////////////////////////// - //things that require the database// - //////////////////////////////////// - var/player_age = -1 //Used to determine how old the account is - in days. - var/player_join_date = null //Date that this account was first seen in the server - 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 ip - var/related_accounts_cid = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id - var/account_join_date = null //Date of byond account creation in ISO 8601 format - var/account_age = -1 //Age of byond account in days - - preload_rsc = PRELOAD_RSC - - var/obj/screen/click_catcher/void - - //These two vars are used to make a special mouse cursor, with a unique icon for clicking - var/mouse_up_icon = null - var/mouse_down_icon = null - - var/ip_intel = "Disabled" - - //datum that controls the displaying and hiding of tooltips - var/datum/tooltip/tooltips - - var/lastping = 0 - var/avgping = 0 - var/connection_time //world.time they connected - var/connection_realtime //world.realtime they connected - var/connection_timeofday //world.timeofday they connected - - var/inprefs = FALSE - var/list/topiclimiter - var/list/clicklimiter - - var/datum/chatOutput/chatOutput - - var/list/credits //lazy list of all credit object bound to this client - - var/datum/player_details/player_details //these persist between logins/logouts during the same round. - - var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen. - - var/client_keysend_amount = 0 - var/next_keysend_reset = 0 - var/next_keysend_trip_reset = 0 - var/keysend_tripped = FALSE + +/client + ////////////////////// + //BLACK MAGIC THINGS// + ////////////////////// + parent_type = /datum + //////////////// + //ADMIN THINGS// + //////////////// + var/datum/admins/holder = null + var/datum/click_intercept = null // Needs to implement InterceptClickOn(user,params,atom) proc + var/AI_Interact = 0 + + var/jobbancache = null //Used to cache this client's jobbans to save on DB queries + var/last_message = "" //Contains the last message sent by this client - used to protect against copy-paste spamming. + var/last_message_count = 0 //contins a number of how many times a message identical to last_message was sent. + var/ircreplyamount = 0 + + ///////// + //OTHER// + ///////// + var/datum/preferences/prefs = null + var/last_turn = 0 + var/move_delay = 0 + var/area = null + + /////////////// + //SOUND STUFF// + /////////////// + var/ambience_playing= null + var/played = 0 + //////////// + //SECURITY// + //////////// + // comment out the line below when debugging locally to enable the options & messages menu + control_freak = 1 + + //////////////////////////////////// + //things that require the database// + //////////////////////////////////// + var/player_age = -1 //Used to determine how old the account is - in days. + var/player_join_date = null //Date that this account was first seen in the server + 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 ip + var/related_accounts_cid = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id + var/account_join_date = null //Date of byond account creation in ISO 8601 format + var/account_age = -1 //Age of byond account in days + + preload_rsc = PRELOAD_RSC + + var/obj/screen/click_catcher/void + + //These two vars are used to make a special mouse cursor, with a unique icon for clicking + var/mouse_up_icon = null + var/mouse_down_icon = null + + var/ip_intel = "Disabled" + + //datum that controls the displaying and hiding of tooltips + var/datum/tooltip/tooltips + + var/lastping = 0 + var/avgping = 0 + var/connection_time //world.time they connected + var/connection_realtime //world.realtime they connected + var/connection_timeofday //world.timeofday they connected + + var/inprefs = FALSE + var/list/topiclimiter + var/list/clicklimiter + + var/datum/chatOutput/chatOutput + + var/list/credits //lazy list of all credit object bound to this client + + var/datum/player_details/player_details //these persist between logins/logouts during the same round. + + var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen. + + var/client_keysend_amount = 0 + var/next_keysend_reset = 0 + var/next_keysend_trip_reset = 0 + var/keysend_tripped = FALSE diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm index d5643a61fe..8f7156863d 100644 --- a/code/modules/client/preferences_toggles.dm +++ b/code/modules/client/preferences_toggles.dm @@ -1,417 +1,417 @@ -//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 - -/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, 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 - - -TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggleprayersounds)() - set name = "Hear/Silence Prayer Sounds" - set category = "Preferences" - set desc = "Hear Prayer Sounds" - usr.client.prefs.toggles ^= SOUND_PRAYERS - usr.client.prefs.save_preferences() - if(usr.client.prefs.toggles & SOUND_PRAYERS) - to_chat(usr, "You will now hear prayer sounds.") - else - to_chat(usr, "You will no longer prayer sounds.") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]")) -/datum/verbs/menu/Settings/Sound/toggleprayersounds/Get_checked(client/C) - return C.prefs.toggles & SOUND_PRAYERS - - -/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 - - -GLOBAL_LIST_INIT(ghost_forms, 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 = "Preferences" - 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 = "Preferences" - 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 = "Preferences" - 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 = "Preferences" - set desc ="Toggles seeing deadchat" - 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 = "Preferences" - set desc = "Toggles seeing prayers" - 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! +//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 + +/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, 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 + + +TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggleprayersounds)() + set name = "Hear/Silence Prayer Sounds" + set category = "Preferences" + set desc = "Hear Prayer Sounds" + usr.client.prefs.toggles ^= SOUND_PRAYERS + usr.client.prefs.save_preferences() + if(usr.client.prefs.toggles & SOUND_PRAYERS) + to_chat(usr, "You will now hear prayer sounds.") + else + to_chat(usr, "You will no longer prayer sounds.") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]")) +/datum/verbs/menu/Settings/Sound/toggleprayersounds/Get_checked(client/C) + return C.prefs.toggles & SOUND_PRAYERS + + +/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 + + +GLOBAL_LIST_INIT(ghost_forms, 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 = "Preferences" + 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 = "Preferences" + 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 = "Preferences" + 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 = "Preferences" + set desc ="Toggles seeing deadchat" + 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 = "Preferences" + set desc = "Toggles seeing prayers" + 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! diff --git a/code/modules/client/verbs/etips.dm b/code/modules/client/verbs/etips.dm index 326f693b08..27434cddc4 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 4d441e7e2b..431947d9c1 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -1,457 +1,457 @@ -/obj/item/clothing - name = "clothing" - resistance_flags = FLAMMABLE - max_integrity = 200 - integrity_failure = 80 - var/damaged_clothes = 0 //similar to machine's BROKEN stat and structure's broken var - var/flash_protect = 0 //What level of bright light protection item has. 1 = Flashers, Flashes, & Flashbangs | 2 = Welding | -1 = OH GOD WELDING BURNT OUT MY RETINAS - 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/obj/item/flashlight/F = null - var/can_flashlight = 0 - - var/blocks_shove_knockdown = FALSE //Whether wearing the clothing item blocks the ability for shove to knock down. - - var/clothing_flags = NONE - - //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 - - //basically a restriction list. - var/list/species_restricted = null - //Basically syntax is species_restricted = list("Species Name","Species Name") - //Add a "exclude" string to do the opposite, making it only only species listed that can't wear it. - //You append this to clothing objects. - - //Polychrome stuff: - var/hasprimary = FALSE //These vars allow you to choose which overlays a clothing has - var/hassecondary = FALSE - var/hastertiary = FALSE - var/primary_color = "#FFFFFF" //RGB in hexcode - var/secondary_color = "#FFFFFF" - var/tertiary_color = "#808080" - - //No idea what this is but eh -tori - var/force_alternate_icon = FALSE - -/obj/item/clothing/Initialize() - . = ..() - if(CHECK_BITFIELD(clothing_flags, VOICEBOX_TOGGLABLE)) - actions_types += /datum/action/item_action/toggle_voice_box - if(ispath(pocket_storage_component_path)) - LoadComponent(pocket_storage_component_path) - if(hasprimary | hassecondary | hastertiary) //Checks if polychrome is enabled - update_icon() //Applies the overlays and default colors onto the clothes on spawn. - -/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 = "oops" - 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) - -/obj/item/clothing/attack(mob/M, mob/user, def_zone) - if(user.a_intent != INTENT_HARM && ismoth(M)) - 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, /obj/item/stack/sheet/cloth)) - var/obj/item/stack/sheet/cloth/C = W - C.use(1) - update_clothes_damaged_state(FALSE) - obj_integrity = max_integrity - to_chat(user, "You fix the damage on [src] with [C].") - return 1 - return ..() - -/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 - 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 & slotdefine2slotbit(slot)) //Was equipped to a valid slot for this item? - 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) - . += "It looks damaged!" - 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" - how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n" - how_cool_are_your_threads += "[src] can store items 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(hasprimary | hassecondary | hastertiary) //Checks if polychrome is enabled - . += "Alt-click to recolor it." - -/obj/item/clothing/obj_break(damage_flag) - if(!damaged_clothes) - update_clothes_damaged_state(TRUE) - if(ismob(loc)) //It's not important enough to warrant a message if nobody's wearing it - var/mob/M = loc - to_chat(M, "Your [name] starts to fall apart!") - -/obj/item/clothing/proc/update_clothes_damaged_state(damaging = TRUE) - var/index = "[REF(initial(icon))]-[initial(icon_state)]" - var/static/list/damaged_clothes_icons = list() - if(damaging) - damaged_clothes = 1 - 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 - add_overlay(damaged_clothes_icon, 1) - else - damaged_clothes = 0 - cut_overlay(damaged_clothes_icons[index], TRUE) - - -/* -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/uniform.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/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(src.has_sensor == LOCKED_SENSORS) - to_chat(usr, "The controls are locked.") - return 0 - if(src.has_sensor == BROKEN_SENSORS) - to_chat(usr, "The sensors have shorted out!") - return 0 - if(src.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 (src.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/CtrlClick(mob/user) - . = ..() - - if (!(item_flags & IN_INVENTORY)) - return - - if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - - if(has_sensor == LOCKED_SENSORS) - to_chat(user, "The controls are locked.") - return - if(has_sensor == BROKEN_SENSORS) - to_chat(user, "The sensors have shorted out!") - return - if(has_sensor <= NO_SENSORS) - to_chat(user, "This suit does not have any sensors.") - return - - sensor_mode = SENSOR_COORDS - - to_chat(user, "Your suit will now report your exact vital lifesigns as well as your coordinate position.") - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.w_uniform == src) - H.update_suit_sensors() - -/obj/item/clothing/under/AltClick(mob/user) - . = ..() - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - if(attached_accessory) - remove_accessory(user) - else - rolldown() - // Polychrome stuff: - if(hasprimary | hassecondary | hastertiary) - var/choice = input(user,"polychromic thread options", "Clothing Recolor") as null|anything in list("[hasprimary ? "Primary Color" : ""]", "[hassecondary ? "Secondary Color" : ""]", "[hastertiary ? "Tertiary Color" : ""]") //generates a list depending on the enabled overlays - switch(choice) //Lets the list's options actually lead to something - if("Primary Color") - var/primary_color_input = input(usr,"","Choose Primary Color",primary_color) as color|null //color input menu, the "|null" adds a cancel button to it. - if(primary_color_input) //Checks if the color selected is NULL, rejects it if it is NULL. - primary_color = sanitize_hexcolor(primary_color_input, desired_format=6, include_crunch=1) //formats the selected color properly - update_icon() //updates the item icon - user.regenerate_icons() //updates the worn icon. Probably a bad idea, but it works. - if("Secondary Color") - var/secondary_color_input = input(usr,"","Choose Secondary Color",secondary_color) as color|null - if(secondary_color_input) - secondary_color = sanitize_hexcolor(secondary_color_input, desired_format=6, include_crunch=1) - update_icon() - user.regenerate_icons() - if("Tertiary Color") - var/tertiary_color_input = input(usr,"","Choose Tertiary Color",tertiary_color) as color|null - if(tertiary_color_input) - tertiary_color = sanitize_hexcolor(tertiary_color_input, desired_format=6, include_crunch=1) - update_icon() - user.regenerate_icons() - return TRUE - -/obj/item/clothing/neck/AltClick(mob/user) - . = ..() - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - // Polychrome stuff: - if(hasprimary | hassecondary | hastertiary) - var/choice = input(user,"polychromic thread options", "Clothing Recolor") as null|anything in list("[hasprimary ? "Primary Color" : ""]", "[hassecondary ? "Secondary Color" : ""]", "[hastertiary ? "Tertiary Color" : ""]") //generates a list depending on the enabled overlays - switch(choice) //Lets the list's options actually lead to something - if("Primary Color") - var/primary_color_input = input(usr,"","Choose Primary Color",primary_color) as color|null //color input menu, the "|null" adds a cancel button to it. - if(primary_color_input) //Checks if the color selected is NULL, rejects it if it is NULL. - primary_color = sanitize_hexcolor(primary_color_input, desired_format=6, include_crunch=1) //formats the selected color properly - update_icon() //updates the item icon - user.regenerate_icons() //updates the worn icon. Probably a bad idea, but it works. - if("Secondary Color") - var/secondary_color_input = input(usr,"","Choose Secondary Color",secondary_color) as color|null - if(secondary_color_input) - secondary_color = sanitize_hexcolor(secondary_color_input, desired_format=6, include_crunch=1) - update_icon() - user.regenerate_icons() - if("Tertiary Color") - var/tertiary_color_input = input(usr,"","Choose Tertiary Color",tertiary_color) as color|null - if(tertiary_color_input) - tertiary_color = sanitize_hexcolor(tertiary_color_input, desired_format=6, include_crunch=1) - update_icon() - user.regenerate_icons() - return TRUE - -/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() - 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 - body_parts_covered &= ~CHEST - else - fitted = initial(fitted) - if(!alt_covers_chest) - body_parts_covered |= CHEST - - return adjusted - -/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/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" || damage_flag == "melee") - var/turf/T = get_turf(src) - spawn(1) //so the shred survives potential turf change from the explosion. - var/obj/effect/decal/cleanable/shreds/Shreds = new(T) - Shreds.desc = "The sad remains of what used to be [name]." - deconstruct(FALSE) - else - ..() - - -//Species-restricted clothing check. - Thanks Oraclestation, BS13, /vg/station etc. -/obj/item/clothing/mob_can_equip(mob/M, slot, disable_warning = TRUE) - - //if we can't equip the item anyway, don't bother with species_restricted (also cuts down on spam) - if(!..()) - return FALSE - - // Skip species restriction checks on non-equipment slots - if(slot in list(SLOT_IN_BACKPACK, SLOT_L_STORE, SLOT_R_STORE)) - return TRUE - - if(species_restricted && ishuman(M)) - - var/wearable = null - var/exclusive = null - var/mob/living/carbon/human/H = M - - if("exclude" in species_restricted) //TURNS IT INTO A BLACKLIST - AKA ALL MINUS SPECIES LISTED. - exclusive = TRUE - - if(H.dna.species) - if(exclusive) - if(!(H.dna.species.name in species_restricted)) - wearable = TRUE - else - if(H.dna.species.name in species_restricted) - wearable = TRUE - - if(!wearable) - to_chat(M, "Your species cannot wear [src].") - return FALSE - - return TRUE - -/obj/item/clothing/update_icon() // Polychrome stuff - ..() - if(hasprimary) //Checks if the overlay is enabled - var/mutable_appearance/primary_overlay = mutable_appearance(icon, "[item_color]-primary") //Automagically picks overlays - primary_overlay.color = primary_color //Colors the greyscaled overlay - add_overlay(primary_overlay) //Applies the coloured overlay onto the item sprite. but NOT the mob sprite. - if(hassecondary) - var/mutable_appearance/secondary_overlay = mutable_appearance(icon, "[item_color]-secondary") - secondary_overlay.color = secondary_color - add_overlay(secondary_overlay) - if(hastertiary) - var/mutable_appearance/tertiary_overlay = mutable_appearance(icon, "[item_color]-tertiary") - tertiary_overlay.color = tertiary_color +/obj/item/clothing + name = "clothing" + resistance_flags = FLAMMABLE + max_integrity = 200 + integrity_failure = 80 + var/damaged_clothes = 0 //similar to machine's BROKEN stat and structure's broken var + var/flash_protect = 0 //What level of bright light protection item has. 1 = Flashers, Flashes, & Flashbangs | 2 = Welding | -1 = OH GOD WELDING BURNT OUT MY RETINAS + 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/obj/item/flashlight/F = null + var/can_flashlight = 0 + + var/blocks_shove_knockdown = FALSE //Whether wearing the clothing item blocks the ability for shove to knock down. + + var/clothing_flags = NONE + + //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 + + //basically a restriction list. + var/list/species_restricted = null + //Basically syntax is species_restricted = list("Species Name","Species Name") + //Add a "exclude" string to do the opposite, making it only only species listed that can't wear it. + //You append this to clothing objects. + + //Polychrome stuff: + var/hasprimary = FALSE //These vars allow you to choose which overlays a clothing has + var/hassecondary = FALSE + var/hastertiary = FALSE + var/primary_color = "#FFFFFF" //RGB in hexcode + var/secondary_color = "#FFFFFF" + var/tertiary_color = "#808080" + + //No idea what this is but eh -tori + var/force_alternate_icon = FALSE + +/obj/item/clothing/Initialize() + . = ..() + if(CHECK_BITFIELD(clothing_flags, VOICEBOX_TOGGLABLE)) + actions_types += /datum/action/item_action/toggle_voice_box + if(ispath(pocket_storage_component_path)) + LoadComponent(pocket_storage_component_path) + if(hasprimary | hassecondary | hastertiary) //Checks if polychrome is enabled + update_icon() //Applies the overlays and default colors onto the clothes on spawn. + +/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 = "oops" + 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) + +/obj/item/clothing/attack(mob/M, mob/user, def_zone) + if(user.a_intent != INTENT_HARM && ismoth(M)) + 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, /obj/item/stack/sheet/cloth)) + var/obj/item/stack/sheet/cloth/C = W + C.use(1) + update_clothes_damaged_state(FALSE) + obj_integrity = max_integrity + to_chat(user, "You fix the damage on [src] with [C].") + return 1 + return ..() + +/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 + 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 & slotdefine2slotbit(slot)) //Was equipped to a valid slot for this item? + 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) + . += "It looks damaged!" + 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" + how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n" + how_cool_are_your_threads += "[src] can store items 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(hasprimary | hassecondary | hastertiary) //Checks if polychrome is enabled + . += "Alt-click to recolor it." + +/obj/item/clothing/obj_break(damage_flag) + if(!damaged_clothes) + update_clothes_damaged_state(TRUE) + if(ismob(loc)) //It's not important enough to warrant a message if nobody's wearing it + var/mob/M = loc + to_chat(M, "Your [name] starts to fall apart!") + +/obj/item/clothing/proc/update_clothes_damaged_state(damaging = TRUE) + var/index = "[REF(initial(icon))]-[initial(icon_state)]" + var/static/list/damaged_clothes_icons = list() + if(damaging) + damaged_clothes = 1 + 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 + add_overlay(damaged_clothes_icon, 1) + else + damaged_clothes = 0 + cut_overlay(damaged_clothes_icons[index], TRUE) + + +/* +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/uniform.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/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(src.has_sensor == LOCKED_SENSORS) + to_chat(usr, "The controls are locked.") + return 0 + if(src.has_sensor == BROKEN_SENSORS) + to_chat(usr, "The sensors have shorted out!") + return 0 + if(src.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 (src.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/CtrlClick(mob/user) + . = ..() + + if (!(item_flags & IN_INVENTORY)) + return + + if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + + if(has_sensor == LOCKED_SENSORS) + to_chat(user, "The controls are locked.") + return + if(has_sensor == BROKEN_SENSORS) + to_chat(user, "The sensors have shorted out!") + return + if(has_sensor <= NO_SENSORS) + to_chat(user, "This suit does not have any sensors.") + return + + sensor_mode = SENSOR_COORDS + + to_chat(user, "Your suit will now report your exact vital lifesigns as well as your coordinate position.") + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.w_uniform == src) + H.update_suit_sensors() + +/obj/item/clothing/under/AltClick(mob/user) + . = ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + if(attached_accessory) + remove_accessory(user) + else + rolldown() + // Polychrome stuff: + if(hasprimary | hassecondary | hastertiary) + var/choice = input(user,"polychromic thread options", "Clothing Recolor") as null|anything in list("[hasprimary ? "Primary Color" : ""]", "[hassecondary ? "Secondary Color" : ""]", "[hastertiary ? "Tertiary Color" : ""]") //generates a list depending on the enabled overlays + switch(choice) //Lets the list's options actually lead to something + if("Primary Color") + var/primary_color_input = input(usr,"","Choose Primary Color",primary_color) as color|null //color input menu, the "|null" adds a cancel button to it. + if(primary_color_input) //Checks if the color selected is NULL, rejects it if it is NULL. + primary_color = sanitize_hexcolor(primary_color_input, desired_format=6, include_crunch=1) //formats the selected color properly + update_icon() //updates the item icon + user.regenerate_icons() //updates the worn icon. Probably a bad idea, but it works. + if("Secondary Color") + var/secondary_color_input = input(usr,"","Choose Secondary Color",secondary_color) as color|null + if(secondary_color_input) + secondary_color = sanitize_hexcolor(secondary_color_input, desired_format=6, include_crunch=1) + update_icon() + user.regenerate_icons() + if("Tertiary Color") + var/tertiary_color_input = input(usr,"","Choose Tertiary Color",tertiary_color) as color|null + if(tertiary_color_input) + tertiary_color = sanitize_hexcolor(tertiary_color_input, desired_format=6, include_crunch=1) + update_icon() + user.regenerate_icons() + return TRUE + +/obj/item/clothing/neck/AltClick(mob/user) + . = ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + // Polychrome stuff: + if(hasprimary | hassecondary | hastertiary) + var/choice = input(user,"polychromic thread options", "Clothing Recolor") as null|anything in list("[hasprimary ? "Primary Color" : ""]", "[hassecondary ? "Secondary Color" : ""]", "[hastertiary ? "Tertiary Color" : ""]") //generates a list depending on the enabled overlays + switch(choice) //Lets the list's options actually lead to something + if("Primary Color") + var/primary_color_input = input(usr,"","Choose Primary Color",primary_color) as color|null //color input menu, the "|null" adds a cancel button to it. + if(primary_color_input) //Checks if the color selected is NULL, rejects it if it is NULL. + primary_color = sanitize_hexcolor(primary_color_input, desired_format=6, include_crunch=1) //formats the selected color properly + update_icon() //updates the item icon + user.regenerate_icons() //updates the worn icon. Probably a bad idea, but it works. + if("Secondary Color") + var/secondary_color_input = input(usr,"","Choose Secondary Color",secondary_color) as color|null + if(secondary_color_input) + secondary_color = sanitize_hexcolor(secondary_color_input, desired_format=6, include_crunch=1) + update_icon() + user.regenerate_icons() + if("Tertiary Color") + var/tertiary_color_input = input(usr,"","Choose Tertiary Color",tertiary_color) as color|null + if(tertiary_color_input) + tertiary_color = sanitize_hexcolor(tertiary_color_input, desired_format=6, include_crunch=1) + update_icon() + user.regenerate_icons() + return TRUE + +/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() + 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 + body_parts_covered &= ~CHEST + else + fitted = initial(fitted) + if(!alt_covers_chest) + body_parts_covered |= CHEST + + return adjusted + +/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/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" || damage_flag == "melee") + var/turf/T = get_turf(src) + spawn(1) //so the shred survives potential turf change from the explosion. + var/obj/effect/decal/cleanable/shreds/Shreds = new(T) + Shreds.desc = "The sad remains of what used to be [name]." + deconstruct(FALSE) + else + ..() + + +//Species-restricted clothing check. - Thanks Oraclestation, BS13, /vg/station etc. +/obj/item/clothing/mob_can_equip(mob/M, slot, disable_warning = TRUE) + + //if we can't equip the item anyway, don't bother with species_restricted (also cuts down on spam) + if(!..()) + return FALSE + + // Skip species restriction checks on non-equipment slots + if(slot in list(SLOT_IN_BACKPACK, SLOT_L_STORE, SLOT_R_STORE)) + return TRUE + + if(species_restricted && ishuman(M)) + + var/wearable = null + var/exclusive = null + var/mob/living/carbon/human/H = M + + if("exclude" in species_restricted) //TURNS IT INTO A BLACKLIST - AKA ALL MINUS SPECIES LISTED. + exclusive = TRUE + + if(H.dna.species) + if(exclusive) + if(!(H.dna.species.name in species_restricted)) + wearable = TRUE + else + if(H.dna.species.name in species_restricted) + wearable = TRUE + + if(!wearable) + to_chat(M, "Your species cannot wear [src].") + return FALSE + + return TRUE + +/obj/item/clothing/update_icon() // Polychrome stuff + ..() + if(hasprimary) //Checks if the overlay is enabled + var/mutable_appearance/primary_overlay = mutable_appearance(icon, "[item_color]-primary") //Automagically picks overlays + primary_overlay.color = primary_color //Colors the greyscaled overlay + add_overlay(primary_overlay) //Applies the coloured overlay onto the item sprite. but NOT the mob sprite. + if(hassecondary) + var/mutable_appearance/secondary_overlay = mutable_appearance(icon, "[item_color]-secondary") + secondary_overlay.color = secondary_color + add_overlay(secondary_overlay) + if(hastertiary) + var/mutable_appearance/tertiary_overlay = mutable_appearance(icon, "[item_color]-tertiary") + tertiary_overlay.color = tertiary_color add_overlay(tertiary_overlay) \ No newline at end of file diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index d91a2b9a2c..69c50c5461 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -1,461 +1,461 @@ -//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 - materials = list(MAT_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 its 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(loc)) - return - var/mob/living/carbon/human/H = loc - var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES) - if((!HAS_TRAIT(H, TRAIT_BLIND) || !eyes) && 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/proc/ranged_attack(mob/living/carbon/human/user,atom/A, params) - return FALSE - -/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" - item_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/prescription - name = "prescription optical meson scanner" - desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions. This one has prescription lens fitted in." - vision_correction = 1 - -/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" - item_state = "nvgmeson" - darkness_view = 8 - 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" - item_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" - item_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, mob/user, datum/action/A) - if(slot == SLOT_GLASSES) - return 1 - -/obj/item/clothing/glasses/night - name = "night vision goggles" - desc = "You can totally see in the dark now!" - icon_state = "night" - item_state = "glasses" - darkness_view = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/night/prescription - name = "prescription night vision goggles" - desc = "NVGs but for those with nearsightedness." - vision_correction = 1 - -/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" - item_state = "eyepatch" - -/obj/item/clothing/glasses/monocle - name = "monocle" - desc = "Such a dapper eyepiece!" - icon_state = "monocle" - item_state = "headset" // lol - -/obj/item/clothing/glasses/material - name = "optical material scanner" - desc = "Very confusing glasses." - icon_state = "material" - item_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" - item_state = "glasses" - darkness_view = 0 - -/obj/item/clothing/glasses/material/mining/gar - name = "gar material scanner" - icon_state = "garm" - item_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 - vision_correction = 1 - 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" - item_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" - item_state = "jamjar_glasses" - -/obj/item/clothing/glasses/regular/hipster - name = "prescription glasses" - desc = "Made by Uncool. Co." - icon_state = "hipster_glasses" - item_state = "hipster_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" - item_state = "sunglasses" - darkness_view = 1 - flash_protect = 1 - 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" - desc = "A pair of sunglasses outfitted with apparatus to scan reagents." - 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" - item_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" - item_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" - item_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" - item_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 welders; approved by the mad scientist association." - icon_state = "welding-g" - item_state = "welding-g" - actions_types = list(/datum/action/item_action/toggle) - materials = list(MAT_METAL = 250) - flash_protect = 2 - tint = 2 - visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT - flags_cover = GLASSESCOVERSEYES - visor_flags_inv = HIDEEYES - glass_colour_type = /datum/client_colour/glass_colour/gray - -/obj/item/clothing/glasses/welding/attack_self(mob/user) - weldingvisortoggle(user) - - -/obj/item/clothing/glasses/sunglasses/blindfold - name = "blindfold" - desc = "Covers the eyes, preventing sight." - icon_state = "blindfold" - item_state = "blindfold" - flash_protect = 2 - tint = 3 // to make them blind - -/obj/item/clothing/glasses/sunglasses/blindfold/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(slot == SLOT_GLASSES) - user.become_blind("blindfold_[REF(src)]") - -/obj/item/clothing/glasses/sunglasses/blindfold/dropped(mob/living/carbon/human/user) - ..() - user.cure_blind("blindfold_[REF(src)]") - -/obj/item/clothing/glasses/sunglasses/blindfold/white - name = "blind personnel blindfold" - desc = "Indicates that the wearer suffers from blindness." - icon_state = "blindfoldwhite" - item_state = "blindfoldwhite" - var/colored_before = FALSE - -/obj/item/clothing/glasses/sunglasses/blindfold/white/equipped(mob/living/carbon/human/user, slot) - if(ishuman(user) && slot == SLOT_GLASSES) - update_icon(user) - user.update_inv_glasses() //Color might have been changed by update_icon. - ..() - -/obj/item/clothing/glasses/sunglasses/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/sunglasses/blindfold/white/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) - . = list() - if(!isinhands && ishuman(loc) && !colored_before) - var/mob/living/carbon/human/H = loc - var/mutable_appearance/M = mutable_appearance('icons/mob/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" - item_state = "bigsunglasses" - -/obj/item/clothing/glasses/thermal - name = "optical thermal scanner" - desc = "Thermals in the shape of glasses." - icon_state = "thermal" - item_state = "glasses" - vision_flags = SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - flash_protect = 0 - 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/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." - flash_protect = -1 - - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/clothing/glasses/thermal/syndi/New() - ..() - 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! - var/desk = desc - if(user.gender == MALE) - desc = replacetext(desc, "person", "man") - else if(user.gender == FEMALE) - desc = replacetext(desc, "person", "woman") - . = ..() - desc = desk - -/obj/item/clothing/glasses/thermal/eyepatch - name = "optical thermal eyepatch" - desc = "An eyepatch with built-in thermal optics." - icon_state = "eyepatch" - item_state = "eyepatch" - -/obj/item/clothing/glasses/cold - name = "cold goggles" - desc = "A pair of goggles meant for low temperatures." - icon_state = "cold" - item_state = "cold" - -/obj/item/clothing/glasses/heat - name = "heat goggles" - desc = "A pair of goggles meant for high temperatures." - icon_state = "heat" - item_state = "heat" - -/obj/item/clothing/glasses/orange - name = "orange glasses" - desc = "A sweet pair of orange shades." - icon_state = "orangeglasses" - item_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" - item_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" - item_state = "godeye" - vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS - darkness_view = 8 - clothing_flags = SCAN_REAGENTS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - resistance_flags = LAVA_PROOF | FIRE_PROOF - -/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.item_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?.prefs && 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) - return TRUE - -/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) +//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 + materials = list(MAT_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 its 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(loc)) + return + var/mob/living/carbon/human/H = loc + var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES) + if((!HAS_TRAIT(H, TRAIT_BLIND) || !eyes) && 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/proc/ranged_attack(mob/living/carbon/human/user,atom/A, params) + return FALSE + +/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" + item_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/prescription + name = "prescription optical meson scanner" + desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions. This one has prescription lens fitted in." + vision_correction = 1 + +/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" + item_state = "nvgmeson" + darkness_view = 8 + 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" + item_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" + item_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, mob/user, datum/action/A) + if(slot == SLOT_GLASSES) + return 1 + +/obj/item/clothing/glasses/night + name = "night vision goggles" + desc = "You can totally see in the dark now!" + icon_state = "night" + item_state = "glasses" + darkness_view = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/night/prescription + name = "prescription night vision goggles" + desc = "NVGs but for those with nearsightedness." + vision_correction = 1 + +/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" + item_state = "eyepatch" + +/obj/item/clothing/glasses/monocle + name = "monocle" + desc = "Such a dapper eyepiece!" + icon_state = "monocle" + item_state = "headset" // lol + +/obj/item/clothing/glasses/material + name = "optical material scanner" + desc = "Very confusing glasses." + icon_state = "material" + item_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" + item_state = "glasses" + darkness_view = 0 + +/obj/item/clothing/glasses/material/mining/gar + name = "gar material scanner" + icon_state = "garm" + item_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 + vision_correction = 1 + 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" + item_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" + item_state = "jamjar_glasses" + +/obj/item/clothing/glasses/regular/hipster + name = "prescription glasses" + desc = "Made by Uncool. Co." + icon_state = "hipster_glasses" + item_state = "hipster_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" + item_state = "sunglasses" + darkness_view = 1 + flash_protect = 1 + 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" + desc = "A pair of sunglasses outfitted with apparatus to scan reagents." + 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" + item_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" + item_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" + item_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" + item_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 welders; approved by the mad scientist association." + icon_state = "welding-g" + item_state = "welding-g" + actions_types = list(/datum/action/item_action/toggle) + materials = list(MAT_METAL = 250) + flash_protect = 2 + tint = 2 + visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT + flags_cover = GLASSESCOVERSEYES + visor_flags_inv = HIDEEYES + glass_colour_type = /datum/client_colour/glass_colour/gray + +/obj/item/clothing/glasses/welding/attack_self(mob/user) + weldingvisortoggle(user) + + +/obj/item/clothing/glasses/sunglasses/blindfold + name = "blindfold" + desc = "Covers the eyes, preventing sight." + icon_state = "blindfold" + item_state = "blindfold" + flash_protect = 2 + tint = 3 // to make them blind + +/obj/item/clothing/glasses/sunglasses/blindfold/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot == SLOT_GLASSES) + user.become_blind("blindfold_[REF(src)]") + +/obj/item/clothing/glasses/sunglasses/blindfold/dropped(mob/living/carbon/human/user) + ..() + user.cure_blind("blindfold_[REF(src)]") + +/obj/item/clothing/glasses/sunglasses/blindfold/white + name = "blind personnel blindfold" + desc = "Indicates that the wearer suffers from blindness." + icon_state = "blindfoldwhite" + item_state = "blindfoldwhite" + var/colored_before = FALSE + +/obj/item/clothing/glasses/sunglasses/blindfold/white/equipped(mob/living/carbon/human/user, slot) + if(ishuman(user) && slot == SLOT_GLASSES) + update_icon(user) + user.update_inv_glasses() //Color might have been changed by update_icon. + ..() + +/obj/item/clothing/glasses/sunglasses/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/sunglasses/blindfold/white/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) + . = list() + if(!isinhands && ishuman(loc) && !colored_before) + var/mob/living/carbon/human/H = loc + var/mutable_appearance/M = mutable_appearance('icons/mob/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" + item_state = "bigsunglasses" + +/obj/item/clothing/glasses/thermal + name = "optical thermal scanner" + desc = "Thermals in the shape of glasses." + icon_state = "thermal" + item_state = "glasses" + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + flash_protect = 0 + 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/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." + flash_protect = -1 + + var/datum/action/item_action/chameleon/change/chameleon_action + +/obj/item/clothing/glasses/thermal/syndi/New() + ..() + 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! + var/desk = desc + if(user.gender == MALE) + desc = replacetext(desc, "person", "man") + else if(user.gender == FEMALE) + desc = replacetext(desc, "person", "woman") + . = ..() + desc = desk + +/obj/item/clothing/glasses/thermal/eyepatch + name = "optical thermal eyepatch" + desc = "An eyepatch with built-in thermal optics." + icon_state = "eyepatch" + item_state = "eyepatch" + +/obj/item/clothing/glasses/cold + name = "cold goggles" + desc = "A pair of goggles meant for low temperatures." + icon_state = "cold" + item_state = "cold" + +/obj/item/clothing/glasses/heat + name = "heat goggles" + desc = "A pair of goggles meant for high temperatures." + icon_state = "heat" + item_state = "heat" + +/obj/item/clothing/glasses/orange + name = "orange glasses" + desc = "A sweet pair of orange shades." + icon_state = "orangeglasses" + item_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" + item_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" + item_state = "godeye" + vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS + darkness_view = 8 + clothing_flags = SCAN_REAGENTS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + resistance_flags = LAVA_PROOF | FIRE_PROOF + +/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.item_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?.prefs && 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) + return TRUE + +/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) diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm index 387aa65d20..f2f5dd9b7a 100644 --- a/code/modules/clothing/glasses/hud.dm +++ b/code/modules/clothing/glasses/hud.dm @@ -1,227 +1,227 @@ -/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 - -/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot) - ..() - if(hud_type && slot == SLOT_GLASSES) - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) - -/obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user) - ..() - if(hud_type && istype(user) && user.glasses == src) - var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) - -/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." - return TRUE - -/obj/item/clothing/glasses/hud/health - name = "health scanner HUD" - desc = "A heads-up display that scans the humans in view and provides accurate data about their health status." - icon_state = "healthhud" - hud_type = DATA_HUD_MEDICAL_ADVANCED - glass_colour_type = /datum/client_colour/glass_colour/lightblue - -/obj/item/clothing/glasses/hud/health/prescription - name = "prescription health scanner HUD" - desc = "A heads-up display, made with a prescription lens, that scans the humans in view and provides accurate data about their health status." - icon_state = "healthhud" - hud_type = DATA_HUD_MEDICAL_ADVANCED - vision_correction = 1 - 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" - item_state = "glasses" - darkness_view = 8 - 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 = 1 - 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 - glass_colour_type = /datum/client_colour/glass_colour/lightorange - -/obj/item/clothing/glasses/hud/diagnostic/prescription - name = "prescription diagnostic HUD" - desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits. This one has a prescription lens." - icon_state = "diagnostichud" - hud_type = DATA_HUD_DIAGNOSTIC_BASIC - vision_correction = 1 - 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" - item_state = "glasses" - darkness_view = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/hud/security - name = "security HUD" - desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records." - icon_state = "securityhud" - hud_type = DATA_HUD_SECURITY_ADVANCED - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/hud/security/prescription - name = "prescription security HUD" - desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records. This one has a prescription lens so you can see the banana peal that slipped you." - icon_state = "securityhud" - hud_type = DATA_HUD_SECURITY_ADVANCED - vision_correction = 1 - 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 = 1 - - // 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/New() - ..() - 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 = 1 - 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 which provides id data and vision in complete darkness." - icon_state = "securityhudnight" - darkness_view = 8 - 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" - item_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" - item_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 + 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 + +/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot) + ..() + if(hud_type && slot == SLOT_GLASSES) + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.add_hud_to(user) + +/obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user) + ..() + if(hud_type && istype(user) && user.glasses == src) + var/datum/atom_hud/H = GLOB.huds[hud_type] + H.remove_hud_from(user) + +/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." + return TRUE + +/obj/item/clothing/glasses/hud/health + name = "health scanner HUD" + desc = "A heads-up display that scans the humans in view and provides accurate data about their health status." + icon_state = "healthhud" + hud_type = DATA_HUD_MEDICAL_ADVANCED + glass_colour_type = /datum/client_colour/glass_colour/lightblue + +/obj/item/clothing/glasses/hud/health/prescription + name = "prescription health scanner HUD" + desc = "A heads-up display, made with a prescription lens, that scans the humans in view and provides accurate data about their health status." + icon_state = "healthhud" + hud_type = DATA_HUD_MEDICAL_ADVANCED + vision_correction = 1 + 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" + item_state = "glasses" + darkness_view = 8 + 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 = 1 + 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 + glass_colour_type = /datum/client_colour/glass_colour/lightorange + +/obj/item/clothing/glasses/hud/diagnostic/prescription + name = "prescription diagnostic HUD" + desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits. This one has a prescription lens." + icon_state = "diagnostichud" + hud_type = DATA_HUD_DIAGNOSTIC_BASIC + vision_correction = 1 + 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" + item_state = "glasses" + darkness_view = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/hud/security + name = "security HUD" + desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records." + icon_state = "securityhud" + hud_type = DATA_HUD_SECURITY_ADVANCED + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/hud/security/prescription + name = "prescription security HUD" + desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records. This one has a prescription lens so you can see the banana peal that slipped you." + icon_state = "securityhud" + hud_type = DATA_HUD_SECURITY_ADVANCED + vision_correction = 1 + 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 = 1 + + // 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/New() + ..() + 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 = 1 + 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 which provides id data and vision in complete darkness." + icon_state = "securityhudnight" + darkness_view = 8 + 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" + item_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" + item_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() diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm index 87d9f359eb..8ab897f5d9 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 - var/transfer_blood = 0 - strip_delay = 20 - equip_delay_other = 40 - -/obj/item/clothing/gloves/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.proc/clean_blood) - -/obj/item/clothing/gloves/clean_blood(datum/source, strength) - . = ..() - 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, icon_file, style_flags = NONE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves") - if(blood_DNA) - . += mutable_appearance('icons/effects/blood.dmi', "bloodyhands", color = blood_DNA_to_color()) - -/obj/item/clothing/gloves/update_clothes_damaged_state(damaging = TRUE) - ..() - 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) +/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 + var/transfer_blood = 0 + strip_delay = 20 + equip_delay_other = 40 + +/obj/item/clothing/gloves/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.proc/clean_blood) + +/obj/item/clothing/gloves/clean_blood(datum/source, strength) + . = ..() + 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, icon_file, style_flags = NONE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves") + if(blood_DNA) + . += mutable_appearance('icons/effects/blood.dmi', "bloodyhands", color = blood_DNA_to_color()) + +/obj/item/clothing/gloves/update_clothes_damaged_state(damaging = TRUE) + ..() + 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 FALSE // return TRUE to cancel attack_hand() \ No newline at end of file diff --git a/code/modules/clothing/gloves/boxing.dm b/code/modules/clothing/gloves/boxing.dm index f1016b0cc1..a76ff7c583 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" - item_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" - item_state = "boxinggreen" - -/obj/item/clothing/gloves/boxing/blue - icon_state = "boxingblue" - item_state = "boxingblue" - -/obj/item/clothing/gloves/boxing/yellow - icon_state = "boxingyellow" - item_state = "boxingyellow" +/obj/item/clothing/gloves/boxing + name = "boxing gloves" + desc = "Because you really needed another excuse to punch your crewmates." + icon_state = "boxing" + item_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" + item_state = "boxinggreen" + +/obj/item/clothing/gloves/boxing/blue + icon_state = "boxingblue" + item_state = "boxingblue" + +/obj/item/clothing/gloves/boxing/yellow + icon_state = "boxingyellow" + item_state = "boxingyellow" diff --git a/code/modules/clothing/gloves/color.dm b/code/modules/clothing/gloves/color.dm index e95a836a0d..b630b121a9 100644 --- a/code/modules/clothing/gloves/color.dm +++ b/code/modules/clothing/gloves/color.dm @@ -1,220 +1,220 @@ -/obj/item/clothing/gloves/color/yellow - desc = "These gloves will protect the wearer from electric shock." - name = "insulated gloves" - icon_state = "yellow" - item_state = "ygloves" - siemens_coefficient = 0 - permeability_coefficient = 0.05 - item_color="yellow" - resistance_flags = NONE - var/can_be_cut = 1 - -/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" - item_state = "ygloves" - siemens_coefficient = 1 //Set to a default of 1, gets overridden in New() - permeability_coefficient = 0.05 - item_color="yellow" - resistance_flags = NONE - var/can_be_cut = 1 - -/obj/item/clothing/gloves/color/fyellow/New() - ..() - 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/cut - desc = "These gloves would protect the wearer from electric shock.. if the fingers were covered." - name = "fingerless insulated gloves" - icon_state = "yellowcut" - item_state = "yglovescut" - siemens_coefficient = 1 - permeability_coefficient = 1 - resistance_flags = NONE - transfer_prints = TRUE - -/obj/item/clothing/gloves/cut/family - desc = "The old gloves your great grandfather stole from Engineering, many moons ago. They've seen some tough times recently." - name = "fingerless insulated gloves" - -/obj/item/clothing/gloves/color/yellow/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/wirecutters)) - 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/cut(drop_location()) - qdel(src) - ..() - -/obj/item/clothing/gloves/color/fyellow/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/wirecutters)) - 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/cut(drop_location()) - qdel(src) - ..() - -/obj/item/clothing/gloves/color/black - desc = "These gloves are fire-resistant." - name = "black gloves" - icon_state = "black" - item_state = "blackgloves" - item_color="black" - 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 = 1 - -/obj/item/clothing/gloves/color/black/hos - item_color = "hosred" //Exists for washing machines. Is not different from black gloves in any way. - -/obj/item/clothing/gloves/color/black/ce - item_color = "chief" //Exists for washing machines. Is not different from black gloves in any way. - -/obj/item/clothing/gloves/color/black/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/wirecutters)) - 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" - item_state = "orangegloves" - item_color="orange" - -/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" - item_state = "redgloves" - item_color = "red" - - -/obj/item/clothing/gloves/color/red/insulated - name = "insulated gloves" - desc = "These gloves will protect the wearer from 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" - item_state = "rainbowgloves" - item_color = "rainbow" - -/obj/item/clothing/gloves/color/rainbow/clown - item_color = "clown" - -/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" - item_state = "bluegloves" - item_color="blue" - -/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" - item_state = "purplegloves" - item_color="purple" - -/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" - item_state = "greengloves" - item_color="green" - -/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" - item_state = "graygloves" - item_color="grey" - -/obj/item/clothing/gloves/color/grey/rd - item_color = "director" //Exists for washing machines. Is not different from gray gloves in any way. - -/obj/item/clothing/gloves/color/grey/hop - item_color = "hop" //Exists for washing machines. Is not different from gray gloves in any way. - -/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" - item_state = "lightbrowngloves" - item_color="light brown" - -/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" - item_state = "browngloves" - item_color="brown" - -/obj/item/clothing/gloves/color/brown/cargo - item_color = "cargo" //Exists for washing machines. Is not different from brown gloves in any way. - -/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" - item_state = "egloves" - item_color = "captain" - 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." - icon_state = "latex" - item_state = "lgloves" - siemens_coefficient = 0.3 - permeability_coefficient = 0.01 - item_color="mime" - transfer_prints = TRUE - resistance_flags = NONE - -/obj/item/clothing/gloves/color/latex/nitrile - name = "nitrile gloves" - desc = "Pricy sterile gloves that are stronger than latex." - icon_state = "nitrile" - item_state = "nitrilegloves" - item_color = "cmo" - transfer_prints = FALSE - -/obj/item/clothing/gloves/color/white - name = "white gloves" - desc = "These look pretty fancy." - icon_state = "white" - item_state = "wgloves" - item_color="white" - -/obj/item/clothing/gloves/color/white/redcoat - item_color = "redcoat" //Exists for washing machines. Is not different from white gloves in any way. +/obj/item/clothing/gloves/color/yellow + desc = "These gloves will protect the wearer from electric shock." + name = "insulated gloves" + icon_state = "yellow" + item_state = "ygloves" + siemens_coefficient = 0 + permeability_coefficient = 0.05 + item_color="yellow" + resistance_flags = NONE + var/can_be_cut = 1 + +/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" + item_state = "ygloves" + siemens_coefficient = 1 //Set to a default of 1, gets overridden in New() + permeability_coefficient = 0.05 + item_color="yellow" + resistance_flags = NONE + var/can_be_cut = 1 + +/obj/item/clothing/gloves/color/fyellow/New() + ..() + 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/cut + desc = "These gloves would protect the wearer from electric shock.. if the fingers were covered." + name = "fingerless insulated gloves" + icon_state = "yellowcut" + item_state = "yglovescut" + siemens_coefficient = 1 + permeability_coefficient = 1 + resistance_flags = NONE + transfer_prints = TRUE + +/obj/item/clothing/gloves/cut/family + desc = "The old gloves your great grandfather stole from Engineering, many moons ago. They've seen some tough times recently." + name = "fingerless insulated gloves" + +/obj/item/clothing/gloves/color/yellow/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/wirecutters)) + 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/cut(drop_location()) + qdel(src) + ..() + +/obj/item/clothing/gloves/color/fyellow/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/wirecutters)) + 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/cut(drop_location()) + qdel(src) + ..() + +/obj/item/clothing/gloves/color/black + desc = "These gloves are fire-resistant." + name = "black gloves" + icon_state = "black" + item_state = "blackgloves" + item_color="black" + 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 = 1 + +/obj/item/clothing/gloves/color/black/hos + item_color = "hosred" //Exists for washing machines. Is not different from black gloves in any way. + +/obj/item/clothing/gloves/color/black/ce + item_color = "chief" //Exists for washing machines. Is not different from black gloves in any way. + +/obj/item/clothing/gloves/color/black/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/wirecutters)) + 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" + item_state = "orangegloves" + item_color="orange" + +/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" + item_state = "redgloves" + item_color = "red" + + +/obj/item/clothing/gloves/color/red/insulated + name = "insulated gloves" + desc = "These gloves will protect the wearer from 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" + item_state = "rainbowgloves" + item_color = "rainbow" + +/obj/item/clothing/gloves/color/rainbow/clown + item_color = "clown" + +/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" + item_state = "bluegloves" + item_color="blue" + +/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" + item_state = "purplegloves" + item_color="purple" + +/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" + item_state = "greengloves" + item_color="green" + +/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" + item_state = "graygloves" + item_color="grey" + +/obj/item/clothing/gloves/color/grey/rd + item_color = "director" //Exists for washing machines. Is not different from gray gloves in any way. + +/obj/item/clothing/gloves/color/grey/hop + item_color = "hop" //Exists for washing machines. Is not different from gray gloves in any way. + +/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" + item_state = "lightbrowngloves" + item_color="light brown" + +/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" + item_state = "browngloves" + item_color="brown" + +/obj/item/clothing/gloves/color/brown/cargo + item_color = "cargo" //Exists for washing machines. Is not different from brown gloves in any way. + +/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" + item_state = "egloves" + item_color = "captain" + 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." + icon_state = "latex" + item_state = "lgloves" + siemens_coefficient = 0.3 + permeability_coefficient = 0.01 + item_color="mime" + transfer_prints = TRUE + resistance_flags = NONE + +/obj/item/clothing/gloves/color/latex/nitrile + name = "nitrile gloves" + desc = "Pricy sterile gloves that are stronger than latex." + icon_state = "nitrile" + item_state = "nitrilegloves" + item_color = "cmo" + transfer_prints = FALSE + +/obj/item/clothing/gloves/color/white + name = "white gloves" + desc = "These look pretty fancy." + icon_state = "white" + item_state = "wgloves" + item_color="white" + +/obj/item/clothing/gloves/color/white/redcoat + item_color = "redcoat" //Exists for washing machines. Is not different from white gloves in any way. diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index 984dddf813..efe32a1473 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -1,105 +1,105 @@ - -/obj/item/clothing/gloves/fingerless - name = "fingerless gloves" - desc = "Plain black gloves without fingertips for the hard working." - icon_state = "fingerless" - item_state = "fingerless" - item_color = null //So they don't wash. - transfer_prints = TRUE - strip_delay = 40 - equip_delay_other = 20 - cold_protection = HANDS - min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT - -/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" - item_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 shock resistant." - icon_state = "combat" - item_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" - item_state = "bracers" - item_color = null //So they don't wash. - 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" = 35, "laser" = 35, "energy" = 20, "bomb" = 35, "bio" = 35, "rad" = 35, "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. Violently." - icon_state = "rapid" - item_state = "rapid" - transfer_prints = TRUE - var/warcry = "AT" - -/obj/item/clothing/gloves/rapid/Touch(mob/living/target,proximity = TRUE) - if(!istype(target)) - return - - var/mob/living/M = loc - M.changeNext_move(CLICK_CD_RAPID) - M.adjustStaminaLoss(-3.5) // used to be -2 with some comment about stamina buffer management but *shrug -hatterhat - if(warcry) - M.say("[warcry]", ignore_spam = TRUE, forced = "north star warcry") - - .= FALSE - - -/obj/item/clothing/gloves/rapid/attack_self(mob/user) - var/input = stripped_input(user,"What do you want your battlecry to be? Max length of 6 characters.", ,"", 7) - if(input) - warcry = input - -/obj/item/clothing/gloves/rapid/hug - name = "Hugs of the North Star" - desc = "Just looking at these fills you with an urge to hug the shit out of people. In a very friendly manner." - warcry = "owo" //Shouldn't ever come into play - -/obj/item/clothing/gloves/rapid/hug/Touch(mob/living/target,proximity = TRUE) - if(!istype(target)) - return - - var/mob/living/M = loc - - if(M.a_intent == INTENT_HELP) - if(target.health >= 0 && !HAS_TRAIT(target, TRAIT_FAKEDEATH)) //Can't hug people who are dying/dead - if(target.on_fire || target.lying) //No spamming extinguishing, helping them up, or other non-hugging/patting help interactions - return - else - M.changeNext_move(CLICK_CD_RAPID) - . = FALSE - -/obj/item/clothing/gloves/rapid/hug/attack_self(mob/user) - return FALSE + +/obj/item/clothing/gloves/fingerless + name = "fingerless gloves" + desc = "Plain black gloves without fingertips for the hard working." + icon_state = "fingerless" + item_state = "fingerless" + item_color = null //So they don't wash. + transfer_prints = TRUE + strip_delay = 40 + equip_delay_other = 20 + cold_protection = HANDS + min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT + +/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" + item_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 shock resistant." + icon_state = "combat" + item_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" + item_state = "bracers" + item_color = null //So they don't wash. + 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" = 35, "laser" = 35, "energy" = 20, "bomb" = 35, "bio" = 35, "rad" = 35, "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. Violently." + icon_state = "rapid" + item_state = "rapid" + transfer_prints = TRUE + var/warcry = "AT" + +/obj/item/clothing/gloves/rapid/Touch(mob/living/target,proximity = TRUE) + if(!istype(target)) + return + + var/mob/living/M = loc + M.changeNext_move(CLICK_CD_RAPID) + M.adjustStaminaLoss(-3.5) // used to be -2 with some comment about stamina buffer management but *shrug -hatterhat + if(warcry) + M.say("[warcry]", ignore_spam = TRUE, forced = "north star warcry") + + .= FALSE + + +/obj/item/clothing/gloves/rapid/attack_self(mob/user) + var/input = stripped_input(user,"What do you want your battlecry to be? Max length of 6 characters.", ,"", 7) + if(input) + warcry = input + +/obj/item/clothing/gloves/rapid/hug + name = "Hugs of the North Star" + desc = "Just looking at these fills you with an urge to hug the shit out of people. In a very friendly manner." + warcry = "owo" //Shouldn't ever come into play + +/obj/item/clothing/gloves/rapid/hug/Touch(mob/living/target,proximity = TRUE) + if(!istype(target)) + return + + var/mob/living/M = loc + + if(M.a_intent == INTENT_HELP) + if(target.health >= 0 && !HAS_TRAIT(target, TRAIT_FAKEDEATH)) //Can't hug people who are dying/dead + if(target.on_fire || target.lying) //No spamming extinguishing, helping them up, or other non-hugging/patting help interactions + return + else + M.changeNext_move(CLICK_CD_RAPID) + . = FALSE + +/obj/item/clothing/gloves/rapid/hug/attack_self(mob/user) + return FALSE diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm index a875561056..d599fbf6f7 100644 --- a/code/modules/clothing/head/_head.dm +++ b/code/modules/clothing/head/_head.dm @@ -1,63 +1,63 @@ -/obj/item/clothing/head - name = BODY_ZONE_HEAD - icon = 'icons/obj/clothing/hats.dmi' - icon_state = "top_hat" - item_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. -/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 downsides when worn - if(clothing_flags & IGNORE_HAT_TOSS) - 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 - H.visible_message("[src] bounces off [H]'s [WH.name]!", "[src] bounces off your [WH.name], falling to the floor.") - return - if(H.equip_to_slot_if_possible(src, SLOT_HEAD, FALSE, TRUE)) - 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, R.equippable_hats) || R.hat_offset == INFINITY) - 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, icon_file, style_flags = NONE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet") - if(blood_DNA) - . += mutable_appearance('icons/effects/blood.dmi', "helmetblood", color = blood_DNA_to_color()) - -/obj/item/clothing/head/update_clothes_damaged_state(damaging = TRUE) - ..() - if(ismob(loc)) - var/mob/M = loc +/obj/item/clothing/head + name = BODY_ZONE_HEAD + icon = 'icons/obj/clothing/hats.dmi' + icon_state = "top_hat" + item_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. +/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 downsides when worn + if(clothing_flags & IGNORE_HAT_TOSS) + 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 + H.visible_message("[src] bounces off [H]'s [WH.name]!", "[src] bounces off your [WH.name], falling to the floor.") + return + if(H.equip_to_slot_if_possible(src, SLOT_HEAD, FALSE, TRUE)) + 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, R.equippable_hats) || R.hat_offset == INFINITY) + 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, icon_file, style_flags = NONE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet") + if(blood_DNA) + . += mutable_appearance('icons/effects/blood.dmi', "helmetblood", color = blood_DNA_to_color()) + +/obj/item/clothing/head/update_clothes_damaged_state(damaging = TRUE) + ..() + if(ismob(loc)) + var/mob/M = loc M.update_inv_head() \ No newline at end of file diff --git a/code/modules/clothing/head/collectable.dm b/code/modules/clothing/head/collectable.dm index dc7e1df15f..314142d0cc 100644 --- a/code/modules/clothing/head/collectable.dm +++ b/code/modules/clothing/head/collectable.dm @@ -1,154 +1,154 @@ - -//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/slime - name = "collectable slime cap!" - desc = "It just latches right in place!" - icon_state = "slime" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/collectable/xenom - name = "collectable xenomorph helmet!" - desc = "Hiss hiss hiss!" - 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" - item_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" - item_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" - item_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" - item_state = "welding" - resistance_flags = NONE - -/obj/item/clothing/head/collectable/slime - name = "collectable slime hat" - desc = "Just like a real brain slug!" - icon_state = "headslime" - item_state = "headslime" - -/obj/item/clothing/head/collectable/flatcap - name = "collectable flat cap" - desc = "A collectible farmer's flat cap!" - icon_state = "flat_cap" - item_state = "detective" - -/obj/item/clothing/head/collectable/pirate - name = "collectable pirate hat" - desc = "You'd make a great Dread Syndie Roberts!" - icon_state = "pirate" - item_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" - item_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" - item_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!" - icon_state = "hardhat0_yellow" - item_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" - item_state = "thunderdome" - resistance_flags = NONE - 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" - item_state = "swat" - resistance_flags = NONE - 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/slime + name = "collectable slime cap!" + desc = "It just latches right in place!" + icon_state = "slime" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/collectable/xenom + name = "collectable xenomorph helmet!" + desc = "Hiss hiss hiss!" + 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" + item_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" + item_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" + item_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" + item_state = "welding" + resistance_flags = NONE + +/obj/item/clothing/head/collectable/slime + name = "collectable slime hat" + desc = "Just like a real brain slug!" + icon_state = "headslime" + item_state = "headslime" + +/obj/item/clothing/head/collectable/flatcap + name = "collectable flat cap" + desc = "A collectible farmer's flat cap!" + icon_state = "flat_cap" + item_state = "detective" + +/obj/item/clothing/head/collectable/pirate + name = "collectable pirate hat" + desc = "You'd make a great Dread Syndie Roberts!" + icon_state = "pirate" + item_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" + item_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" + item_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!" + icon_state = "hardhat0_yellow" + item_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" + item_state = "thunderdome" + resistance_flags = NONE + 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" + item_state = "swat" + resistance_flags = NONE + flags_inv = HIDEHAIR diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm index 51822cab77..e7a8243889 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -1,161 +1,161 @@ -/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" - item_state = "hardhat0_yellow" - var/brightness_on = 4 //luminosity when on - light_color = "#FFCC66" - var/power_on = 0.8 - var/on = FALSE - item_color = "yellow" //Determines used sprites: hardhat[on]_[item_color] and hardhat[on]_[item_color]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) - resistance_flags = FIRE_PROOF - dynamic_hair_suffix = "+generic" - - dog_fashion = /datum/dog_fashion/head - -/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() - icon_state = "hardhat[on]_[item_color]" - item_state = "hardhat[on]_[item_color]" - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.update_inv_head() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon(force = TRUE) - -/obj/item/clothing/head/hardhat/proc/turn_on(mob/user) - set_light(brightness_on, power_on) - -/obj/item/clothing/head/hardhat/proc/turn_off(mob/user) - set_light(0) - -/obj/item/clothing/head/hardhat/orange - icon_state = "hardhat0_orange" - item_state = "hardhat0_orange" - item_color = "orange" - dog_fashion = null - -/obj/item/clothing/head/hardhat/red - icon_state = "hardhat0_red" - item_state = "hardhat0_red" - item_color = "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/white - icon_state = "hardhat0_white" - item_state = "hardhat0_white" - item_color = "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" - item_state = "hardhat0_dblue" - item_color = "dblue" - dog_fashion = null - -/obj/item/clothing/head/hardhat/atmos - icon_state = "hardhat0_atmos" - item_state = "hardhat0_atmos" - item_color = "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|HIDESNOUT - heat_protection = HEAD - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - mutantrace_variation = STYLE_MUZZLE - -/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 = 2 - 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 - -/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) - return TRUE - -/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, icon_file, style_flags = NONE) - . = ..() - if(!isinhands) - . += mutable_appearance('icons/mob/head.dmi', "weldhelmet") - if(!up) - . += mutable_appearance('icons/mob/head.dmi', "weldvisor") - -/obj/item/clothing/head/hardhat/weldhat/update_icon() - cut_overlays() - if(!up) - add_overlay("weldvisor") - ..() - -/obj/item/clothing/head/hardhat/weldhat/orange - icon_state = "hardhat0_orange" - item_state = "hardhat0_orange" - item_color = "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" - item_state = "hardhat0_white" - brightness_on = 4 //Boss always takes the best stuff - item_color = "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" - item_state = "hardhat0_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" + item_state = "hardhat0_yellow" + var/brightness_on = 4 //luminosity when on + light_color = "#FFCC66" + var/power_on = 0.8 + var/on = FALSE + item_color = "yellow" //Determines used sprites: hardhat[on]_[item_color] and hardhat[on]_[item_color]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) + resistance_flags = FIRE_PROOF + dynamic_hair_suffix = "+generic" + + dog_fashion = /datum/dog_fashion/head + +/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() + icon_state = "hardhat[on]_[item_color]" + item_state = "hardhat[on]_[item_color]" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.update_inv_head() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon(force = TRUE) + +/obj/item/clothing/head/hardhat/proc/turn_on(mob/user) + set_light(brightness_on, power_on) + +/obj/item/clothing/head/hardhat/proc/turn_off(mob/user) + set_light(0) + +/obj/item/clothing/head/hardhat/orange + icon_state = "hardhat0_orange" + item_state = "hardhat0_orange" + item_color = "orange" + dog_fashion = null + +/obj/item/clothing/head/hardhat/red + icon_state = "hardhat0_red" + item_state = "hardhat0_red" + item_color = "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/white + icon_state = "hardhat0_white" + item_state = "hardhat0_white" + item_color = "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" + item_state = "hardhat0_dblue" + item_color = "dblue" + dog_fashion = null + +/obj/item/clothing/head/hardhat/atmos + icon_state = "hardhat0_atmos" + item_state = "hardhat0_atmos" + item_color = "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|HIDESNOUT + heat_protection = HEAD + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + mutantrace_variation = STYLE_MUZZLE + +/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 = 2 + 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 + +/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) + return TRUE + +/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, icon_file, style_flags = NONE) + . = ..() + if(!isinhands) + . += mutable_appearance('icons/mob/head.dmi', "weldhelmet") + if(!up) + . += mutable_appearance('icons/mob/head.dmi', "weldvisor") + +/obj/item/clothing/head/hardhat/weldhat/update_icon() + cut_overlays() + if(!up) + add_overlay("weldvisor") + ..() + +/obj/item/clothing/head/hardhat/weldhat/orange + icon_state = "hardhat0_orange" + item_state = "hardhat0_orange" + item_color = "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" + item_state = "hardhat0_white" + brightness_on = 4 //Boss always takes the best stuff + item_color = "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" + item_state = "hardhat0_dblue" item_color = "dblue" \ No newline at end of file diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index c2992b7651..e7f20e1358 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -1,364 +1,364 @@ -/obj/item/clothing/head/helmet - name = "helmet" - desc = "Standard Security gear. Protects the head from impacts." - icon_state = "helmet" - item_state = "helmet" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - flags_inv = HIDEEARS - 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 - resistance_flags = NONE - flags_cover = HEADCOVERSEYES - flags_inv = HIDEHAIR - - dog_fashion = /datum/dog_fashion/head/helmet - -/obj/item/clothing/head/helmet/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_HEAD)) - -/obj/item/clothing/head/helmet/sec - can_flashlight = 1 - -/obj/item/clothing/head/helmet/sec/attackby(obj/item/I, mob/user, params) - if(issignaler(I)) - var/obj/item/assembly/signaler/S = I - if(F) //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" - item_state = "helmetalt" - armor = list("melee" = 15, "bullet" = 60, "laser" = 10, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - can_flashlight = 1 - 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" - item_state = "blueshift" - -/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" - item_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" = 45, "bullet" = 15, "laser" = 5,"energy" = 5, "bomb" = 5, "bio" = 2, "rad" = 0, "fire" = 50, "acid" = 50) - 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 - visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - dog_fashion = null - mutantrace_variation = STYLE_MUZZLE - -/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.loc, "[active_sound]", 100, 0, 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" - item_state = "swatsyndie" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 30, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 50, "acid" = 50) - 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 - dog_fashion = null - -/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" - item_state = "swat" - -/obj/item/clothing/head/helmet/thunderdome - name = "\improper Thunderdome helmet" - desc = "'Let the battle commence!'" - flags_inv = HIDEEARS|HIDEHAIR - icon_state = "thunderdome" - item_state = "thunderdome" - armor = list("melee" = 40, "bullet" = 30, "laser" = 25,"energy" = 10, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 50) - 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" - item_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" - item_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" - item_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 - item_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 - item_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" - item_state = "knight_green" - armor = list("melee" = 41, "bullet" = 15, "laser" = 5,"energy" = 5, "bomb" = 5, "bio" = 2, "rad" = 0, "fire" = 0, "acid" = 50) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - strip_delay = 80 - dog_fashion = null - mutantrace_variation = STYLE_MUZZLE - - -/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" - item_state = "knight_blue" - -/obj/item/clothing/head/helmet/knight/yellow - icon_state = "knight_yellow" - item_state = "knight_yellow" - -/obj/item/clothing/head/helmet/knight/red - icon_state = "knight_red" - item_state = "knight_red" - -/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" = 25, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 10, "bio" = 5, "rad" = 20, "fire" = 40, "acid" = 20) - icon_state = "skull" - item_state = "skull" - strip_delay = 100 - mutantrace_variation = STYLE_MUZZLE - -//LightToggle - -/obj/item/clothing/head/helmet/update_icon() - var/state = "[initial(icon_state)]" - if(F) - if(F.on) - state += "-flight-on" //"helmet-flight-on" // "helmet-cam-flight-on" - else - state += "-flight" //etc. - - icon_state = state - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.update_inv_head() - -/obj/item/clothing/head/helmet/ui_action_click(mob/user, action) - if(istype(action, /datum/action/item_action/toggle_helmet_flashlight)) - 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) - if(!F) - if(!user.transferItemToLoc(S, src)) - return - to_chat(user, "You click [S] into place on [src].") - if(S.on) - set_light(0) - F = S - update_icon() - update_helmlight(user) - verbs += /obj/item/clothing/head/helmet/proc/toggle_helmlight - var/datum/action/A = new /datum/action/item_action/toggle_helmet_flashlight(src) - if(loc == user) - A.Grant(user) - return - - if(istype(I, /obj/item/screwdriver)) - if(F) - for(var/obj/item/flashlight/seclite/S in src) - to_chat(user, "You unscrew the seclite from [src].") - F = null - S.forceMove(user.drop_location()) - update_helmlight(user) - S.update_brightness(user) - update_icon() - usr.update_inv_head() - verbs -= /obj/item/clothing/head/helmet/proc/toggle_helmlight - for(var/datum/action/item_action/toggle_helmet_flashlight/THL in actions) - qdel(THL) - return - - return ..() - -/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(!F) - return - - var/mob/user = usr - if(user.incapacitated()) - return - F.on = !F.on - to_chat(user, "You toggle the helmetlight [F.on ? "on":"off"].") - - playsound(user, 'sound/weapons/empty.ogg', 100, 1) - update_helmlight(user) - -/obj/item/clothing/head/helmet/proc/update_helmlight(mob/user = null) - if(F) - if(F.on) - set_light(F.brightness_on, F.flashlight_power, F.light_color) - 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/durathread - name = "makeshift helmet" - desc = "A hardhat with strips of leather and durathread for additional blunt protection." - icon_state = "durathread" - item_state = "durathread" - armor = list("melee" = 25, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 30, "bio" = 15, "rad" = 20, "fire" = 100, "acid" = 50) - -/obj/item/clothing/head/helmet/rus_helmet - name = "russian helmet" - desc = "It can hold a bottle of vodka." - icon_state = "rus_helmet" - item_state = "rus_helmet" - armor = list("melee" = 30, "bullet" = 25, "laser" = 20,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 50) - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/rushelmet - -/obj/item/clothing/head/helmet/rus_ushanka - name = "battle ushanka" - desc = "100% bear." - icon_state = "rus_ushanka" - item_state = "rus_ushanka" - clothing_flags = THICKMATERIAL - body_parts_covered = HEAD - cold_protection = HEAD - min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT - armor = list("melee" = 10, "bullet" = 5, "laser" = 5,"energy" = 5, "bomb" = 5, "bio" = 50, "rad" = 20, "fire" = -10, "acid" = 0) +/obj/item/clothing/head/helmet + name = "helmet" + desc = "Standard Security gear. Protects the head from impacts." + icon_state = "helmet" + item_state = "helmet" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + flags_inv = HIDEEARS + 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 + resistance_flags = NONE + flags_cover = HEADCOVERSEYES + flags_inv = HIDEHAIR + + dog_fashion = /datum/dog_fashion/head/helmet + +/obj/item/clothing/head/helmet/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_HEAD)) + +/obj/item/clothing/head/helmet/sec + can_flashlight = 1 + +/obj/item/clothing/head/helmet/sec/attackby(obj/item/I, mob/user, params) + if(issignaler(I)) + var/obj/item/assembly/signaler/S = I + if(F) //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" + item_state = "helmetalt" + armor = list("melee" = 15, "bullet" = 60, "laser" = 10, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + can_flashlight = 1 + 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" + item_state = "blueshift" + +/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" + item_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" = 45, "bullet" = 15, "laser" = 5,"energy" = 5, "bomb" = 5, "bio" = 2, "rad" = 0, "fire" = 50, "acid" = 50) + 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 + visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + dog_fashion = null + mutantrace_variation = STYLE_MUZZLE + +/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.loc, "[active_sound]", 100, 0, 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" + item_state = "swatsyndie" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 30, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 50, "acid" = 50) + 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 + dog_fashion = null + +/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" + item_state = "swat" + +/obj/item/clothing/head/helmet/thunderdome + name = "\improper Thunderdome helmet" + desc = "'Let the battle commence!'" + flags_inv = HIDEEARS|HIDEHAIR + icon_state = "thunderdome" + item_state = "thunderdome" + armor = list("melee" = 40, "bullet" = 30, "laser" = 25,"energy" = 10, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 50) + 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" + item_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" + item_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" + item_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 + item_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 + item_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" + item_state = "knight_green" + armor = list("melee" = 41, "bullet" = 15, "laser" = 5,"energy" = 5, "bomb" = 5, "bio" = 2, "rad" = 0, "fire" = 0, "acid" = 50) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + strip_delay = 80 + dog_fashion = null + mutantrace_variation = STYLE_MUZZLE + + +/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" + item_state = "knight_blue" + +/obj/item/clothing/head/helmet/knight/yellow + icon_state = "knight_yellow" + item_state = "knight_yellow" + +/obj/item/clothing/head/helmet/knight/red + icon_state = "knight_red" + item_state = "knight_red" + +/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" = 25, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 10, "bio" = 5, "rad" = 20, "fire" = 40, "acid" = 20) + icon_state = "skull" + item_state = "skull" + strip_delay = 100 + mutantrace_variation = STYLE_MUZZLE + +//LightToggle + +/obj/item/clothing/head/helmet/update_icon() + var/state = "[initial(icon_state)]" + if(F) + if(F.on) + state += "-flight-on" //"helmet-flight-on" // "helmet-cam-flight-on" + else + state += "-flight" //etc. + + icon_state = state + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.update_inv_head() + +/obj/item/clothing/head/helmet/ui_action_click(mob/user, action) + if(istype(action, /datum/action/item_action/toggle_helmet_flashlight)) + 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) + if(!F) + if(!user.transferItemToLoc(S, src)) + return + to_chat(user, "You click [S] into place on [src].") + if(S.on) + set_light(0) + F = S + update_icon() + update_helmlight(user) + verbs += /obj/item/clothing/head/helmet/proc/toggle_helmlight + var/datum/action/A = new /datum/action/item_action/toggle_helmet_flashlight(src) + if(loc == user) + A.Grant(user) + return + + if(istype(I, /obj/item/screwdriver)) + if(F) + for(var/obj/item/flashlight/seclite/S in src) + to_chat(user, "You unscrew the seclite from [src].") + F = null + S.forceMove(user.drop_location()) + update_helmlight(user) + S.update_brightness(user) + update_icon() + usr.update_inv_head() + verbs -= /obj/item/clothing/head/helmet/proc/toggle_helmlight + for(var/datum/action/item_action/toggle_helmet_flashlight/THL in actions) + qdel(THL) + return + + return ..() + +/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(!F) + return + + var/mob/user = usr + if(user.incapacitated()) + return + F.on = !F.on + to_chat(user, "You toggle the helmetlight [F.on ? "on":"off"].") + + playsound(user, 'sound/weapons/empty.ogg', 100, 1) + update_helmlight(user) + +/obj/item/clothing/head/helmet/proc/update_helmlight(mob/user = null) + if(F) + if(F.on) + set_light(F.brightness_on, F.flashlight_power, F.light_color) + 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/durathread + name = "makeshift helmet" + desc = "A hardhat with strips of leather and durathread for additional blunt protection." + icon_state = "durathread" + item_state = "durathread" + armor = list("melee" = 25, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 30, "bio" = 15, "rad" = 20, "fire" = 100, "acid" = 50) + +/obj/item/clothing/head/helmet/rus_helmet + name = "russian helmet" + desc = "It can hold a bottle of vodka." + icon_state = "rus_helmet" + item_state = "rus_helmet" + armor = list("melee" = 30, "bullet" = 25, "laser" = 20,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 50) + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/rushelmet + +/obj/item/clothing/head/helmet/rus_ushanka + name = "battle ushanka" + desc = "100% bear." + icon_state = "rus_ushanka" + item_state = "rus_ushanka" + clothing_flags = THICKMATERIAL + body_parts_covered = HEAD + cold_protection = HEAD + min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT + armor = list("melee" = 10, "bullet" = 5, "laser" = 5,"energy" = 5, "bomb" = 5, "bio" = 50, "rad" = 20, "fire" = -10, "acid" = 0) diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm index 05116c8b97..9253c4e9aa 100644 --- a/code/modules/clothing/head/jobs.dm +++ b/code/modules/clothing/head/jobs.dm @@ -1,373 +1,373 @@ -//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" - item_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, 1) - return(FIRELOSS) - -//Captain -/obj/item/clothing/head/caphat - name = "captain's hat" - desc = "It's good being the king." - icon_state = "captain" - item_state = "that" - flags_inv = 0 - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "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 - -/obj/item/clothing/head/caphat/beret - name = "captain's beret" - desc = "A beret fit for a leader." - icon_state = "capberet" - dynamic_hair_suffix = "" - - dog_fashion = null - -/obj/item/clothing/head/caphat/beret/white - name = "captain's white beret" - desc = "A white beret fit for a leader." - icon_state = "beret_captain_white" - -//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" = 30, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - dog_fashion = /datum/dog_fashion/head/hop - -/obj/item/clothing/head/hopcap/beret - name = "head of personnel's beret" - desc = "The symbol of true bureaucratic micromanagement, although in a fancy form." - icon_state = "hopberet" - dynamic_hair_suffix = "" - - dog_fashion = null - -/obj/item/clothing/head/hopcap/beret/white - name = "head of personnel's white beret" - desc = "The symbol of true bureaucratic micromanagement, although in a fancy form." - icon_state = "beret_white_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 - -//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" = 10, "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/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(loc == user && user.canUseTopic(src, BE_CLOSE, ismonkey(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.") - return TRUE - - -//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/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/purple - name = "purple beret" - desc = "A purple beret." - icon_state = "beret_purple" - -/obj/item/clothing/head/beret/blue - name = "blue beret" - desc = "A blue beret" - icon_state = "beret_blue" - -/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) - -//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" = 10, "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/officer - name = "head of security officer beret" - desc = "A robust beret for the Head of Security, for looking stylish while not sacrificing protection." - icon_state = "beret_centcom_officer" - -/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" = 10, "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. Has the letters 'FMJ' enscribed on its side." - icon_state = "wardendrill" - item_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 == 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" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "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" - -/obj/item/clothing/head/beret/sec/corporatewarden - name = "warden's corporate beret" - desc = "A special beret with the Warden's insignia emblazoned on it. For wardens with class." - icon_state = "beret_corporate_warden" - -/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" - -//Chief Medical Officer -/obj/item/clothing/head/beret/cmo - name = "chief medical officer's beret" - desc = "A fancy beret with a green cross, signifying your status in the station's medbay." - icon_state = "cmoberet" - -/obj/item/clothing/head/beret/cmo/blue - name = "chief medical officer's blue beret" - desc = "A fancy beret with a blue and white cross, try not to be chief malpractice officer in it." - icon_state = "beret_blue_cmo" - -//Medical -/obj/item/clothing/head/beret/med - name = "medical officer's beret" - desc = "A fancy beret with a blue cross, smells sterile" - icon_state = "beret_med" - -/obj/item/clothing/head/beret/chem - name = "chemist's beret" - desc = "A fancy beret with a orange beaker, you're not sure if you should smell it" - icon_state = "beret_chem" - -/obj/item/clothing/head/beret/viro - name = "virologist's beret" - desc = "A fancy beret with a green gross, hopefully it's virus free!" - icon_state = "beret_viro" - -//Research Director -/obj/item/clothing/head/beret/rd - name = "research director's beret" - desc = "A beret worn only by highly intelligent people." - icon_state = "rdberet" - - - -//Scientist -/obj/item/clothing/head/beret/sci - name = "scientist's beret" - desc = "A Scientist's beret, looks like it's covered in slime." - icon_state = "beret_sci" - -//Roboticist -/obj/item/clothing/head/beret/robo - name = "roboticist's beret" - desc = "A Roboticist's beret, almost more oil than hat." - icon_state = "beret_robot" - - -//Chief Engineer -/obj/item/clothing/head/beret/ce - name = "chief engineer's beret" - desc = "A beret that will surely make you look way cooler than a hard hat, although lack of protection is the price." - icon_state = "ceberet" - -/obj/item/clothing/head/beret/ce/white - name = "chief engineer's white beret" - desc = "A beret that will surely make you look way cooler than a hard hat, although lack of protection is the price." - icon_state = "beret_ce_white" - -//Atmos -/obj/item/clothing/head/beret/atmos - name = "atmospheric technician's beret" - desc = "An Atmospheric Technician's beret. Smells like plasma fire." - icon_state = "beret_atmos" - -//Engineer -/obj/item/clothing/head/beret/eng - name = "engineer's beret" - desc = "An Engineer's beret, try not to lose it to space wind." - icon_state = "beret_engineering" - -//Quartermaster -/obj/item/clothing/head/beret/qm - name = "quartermaster's beret" - desc = "This headwear shows off your Cargonian leadership" - icon_state = "qmberet" - -/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" - item_color = null - armor = list("melee" = 25, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 30, "bio" = 15, "rad" = 20, "fire" = 100, "acid" = 50) - -#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" + item_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, 1) + return(FIRELOSS) + +//Captain +/obj/item/clothing/head/caphat + name = "captain's hat" + desc = "It's good being the king." + icon_state = "captain" + item_state = "that" + flags_inv = 0 + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "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 + +/obj/item/clothing/head/caphat/beret + name = "captain's beret" + desc = "A beret fit for a leader." + icon_state = "capberet" + dynamic_hair_suffix = "" + + dog_fashion = null + +/obj/item/clothing/head/caphat/beret/white + name = "captain's white beret" + desc = "A white beret fit for a leader." + icon_state = "beret_captain_white" + +//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" = 30, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + dog_fashion = /datum/dog_fashion/head/hop + +/obj/item/clothing/head/hopcap/beret + name = "head of personnel's beret" + desc = "The symbol of true bureaucratic micromanagement, although in a fancy form." + icon_state = "hopberet" + dynamic_hair_suffix = "" + + dog_fashion = null + +/obj/item/clothing/head/hopcap/beret/white + name = "head of personnel's white beret" + desc = "The symbol of true bureaucratic micromanagement, although in a fancy form." + icon_state = "beret_white_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 + +//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" = 10, "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/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(loc == user && user.canUseTopic(src, BE_CLOSE, ismonkey(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.") + return TRUE + + +//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/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/purple + name = "purple beret" + desc = "A purple beret." + icon_state = "beret_purple" + +/obj/item/clothing/head/beret/blue + name = "blue beret" + desc = "A blue beret" + icon_state = "beret_blue" + +/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) + +//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" = 10, "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/officer + name = "head of security officer beret" + desc = "A robust beret for the Head of Security, for looking stylish while not sacrificing protection." + icon_state = "beret_centcom_officer" + +/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" = 10, "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. Has the letters 'FMJ' enscribed on its side." + icon_state = "wardendrill" + item_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 == 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" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "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" + +/obj/item/clothing/head/beret/sec/corporatewarden + name = "warden's corporate beret" + desc = "A special beret with the Warden's insignia emblazoned on it. For wardens with class." + icon_state = "beret_corporate_warden" + +/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" + +//Chief Medical Officer +/obj/item/clothing/head/beret/cmo + name = "chief medical officer's beret" + desc = "A fancy beret with a green cross, signifying your status in the station's medbay." + icon_state = "cmoberet" + +/obj/item/clothing/head/beret/cmo/blue + name = "chief medical officer's blue beret" + desc = "A fancy beret with a blue and white cross, try not to be chief malpractice officer in it." + icon_state = "beret_blue_cmo" + +//Medical +/obj/item/clothing/head/beret/med + name = "medical officer's beret" + desc = "A fancy beret with a blue cross, smells sterile" + icon_state = "beret_med" + +/obj/item/clothing/head/beret/chem + name = "chemist's beret" + desc = "A fancy beret with a orange beaker, you're not sure if you should smell it" + icon_state = "beret_chem" + +/obj/item/clothing/head/beret/viro + name = "virologist's beret" + desc = "A fancy beret with a green gross, hopefully it's virus free!" + icon_state = "beret_viro" + +//Research Director +/obj/item/clothing/head/beret/rd + name = "research director's beret" + desc = "A beret worn only by highly intelligent people." + icon_state = "rdberet" + + + +//Scientist +/obj/item/clothing/head/beret/sci + name = "scientist's beret" + desc = "A Scientist's beret, looks like it's covered in slime." + icon_state = "beret_sci" + +//Roboticist +/obj/item/clothing/head/beret/robo + name = "roboticist's beret" + desc = "A Roboticist's beret, almost more oil than hat." + icon_state = "beret_robot" + + +//Chief Engineer +/obj/item/clothing/head/beret/ce + name = "chief engineer's beret" + desc = "A beret that will surely make you look way cooler than a hard hat, although lack of protection is the price." + icon_state = "ceberet" + +/obj/item/clothing/head/beret/ce/white + name = "chief engineer's white beret" + desc = "A beret that will surely make you look way cooler than a hard hat, although lack of protection is the price." + icon_state = "beret_ce_white" + +//Atmos +/obj/item/clothing/head/beret/atmos + name = "atmospheric technician's beret" + desc = "An Atmospheric Technician's beret. Smells like plasma fire." + icon_state = "beret_atmos" + +//Engineer +/obj/item/clothing/head/beret/eng + name = "engineer's beret" + desc = "An Engineer's beret, try not to lose it to space wind." + icon_state = "beret_engineering" + +//Quartermaster +/obj/item/clothing/head/beret/qm + name = "quartermaster's beret" + desc = "This headwear shows off your Cargonian leadership" + icon_state = "qmberet" + +/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" + item_color = null + armor = list("melee" = 25, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 30, "bio" = 15, "rad" = 20, "fire" = 100, "acid" = 50) + +#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 4c6a0fa3b6..07014c8a0a 100644 --- a/code/modules/clothing/head/misc.dm +++ b/code/modules/clothing/head/misc.dm @@ -1,424 +1,424 @@ - - -/obj/item/clothing/head/centhat - name = "\improper CentCom hat" - icon_state = "centcom" - desc = "It's good to be emperor." - item_state = "that" - flags_inv = 0 - armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 80 - -/obj/item/clothing/head/powdered_wig - name = "powdered wig" - desc = "A powdered wig." - icon_state = "pwig" - item_state = "pwig" - -/obj/item/clothing/head/that - name = "top-hat" - desc = "It's an amish looking hat." - icon_state = "tophat" - item_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" - item_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" - item_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!" - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - mutantrace_variation = STYLE_MUZZLE - -/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" - item_state="cueball" - flags_cover = HEADCOVERSEYES|HEADCOVERSMOUTH - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - -/obj/item/clothing/head/snowman - name = "Snowman Head" - desc = "A ball of white styrofoam. So festive." - icon_state = "snowman_h" - item_state = "snowman_h" - flags_cover = HEADCOVERSEYES - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - -/obj/item/clothing/head/justice - name = "justice hat" - desc = "Fight for what's righteous!" - icon_state = "justicered" - item_state = "justicered" - flags_inv = HIDEHAIR|HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR|HIDESNOUT - flags_cover = HEADCOVERSEYES - -/obj/item/clothing/head/justice/blue - icon_state = "justiceblue" - item_state = "justiceblue" - -/obj/item/clothing/head/justice/yellow - icon_state = "justiceyellow" - item_state = "justiceyellow" - -/obj/item/clothing/head/justice/green - icon_state = "justicegreen" - item_state = "justicegreen" - -/obj/item/clothing/head/justice/pink - icon_state = "justicepink" - item_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/flatcap - name = "flat cap" - desc = "A working man's cap." - icon_state = "flat_cap" - item_state = "detective" - -/obj/item/clothing/head/pirate - name = "pirate hat" - desc = "Yarr." - icon_state = "pirate" - item_state = "pirate" - dog_fashion = /datum/dog_fashion/head/pirate - -/obj/item/clothing/head/pirate/captain - icon_state = "hgpiratecap" - item_state = "hgpiratecap" - -/obj/item/clothing/head/bandana - name = "pirate bandana" - desc = "Yarr." - icon_state = "bandana" - item_state = "bandana" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/bowler - name = "bowler-hat" - desc = "Gentleman, elite aboard!" - icon_state = "bowler" - item_state = "bowler" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/witchwig - name = "witch costume wig" - desc = "Eeeee~heheheheheheh!" - icon_state = "witch" - item_state = "witch" - flags_inv = HIDEHAIR - -/obj/item/clothing/head/chicken - name = "chicken suit head" - desc = "Bkaw!" - icon_state = "chickenhead" - item_state = "chickensuit" - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - -/obj/item/clothing/head/griffin - name = "griffon head" - desc = "Why not 'eagle head'? Who knows." - icon_state = "griffinhat" - item_state = "griffinhat" - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - -/obj/item/clothing/head/bearpelt - name = "bear pelt hat" - desc = "Fuzzy." - icon_state = "bearpelt" - item_state = "bearpelt" - -/obj/item/clothing/head/xenos - name = "xenos helmet" - icon_state = "xenos" - item_state = "xenos_helm" - desc = "A helmet made out of chitinous alien hide." - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - -/obj/item/clothing/head/fedora - name = "fedora" - icon_state = "fedora" - item_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 - -/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_hair_style = "Neckbeard" - return(BRUTELOSS) - -/obj/item/clothing/head/sombrero - name = "sombrero" - icon_state = "sombrero" - item_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" - item_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" - item_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/cone - desc = "This cone is trying to warn you of something!" - name = "warning cone" - icon = 'icons/obj/janitor.dmi' - icon_state = "cone" - item_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" - item_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/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|HIDESNOUT - -/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|HIDESNOUT - -/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" = 15, "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" - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - -/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 = "pharaoh_hat" - icon_state = "pharaoh_hat" - -/obj/item/clothing/head/jester/alt - name = "jester hat" - desc = "A hat with bells, to add some merriness to the suit." - icon_state = "jester_hat2" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/nemes - name = "headdress of Nemes" - desc = "Lavish space tomb not included." - icon_state = "nemes_headdress" - icon_state = "nemes_headdress" - -/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 = "beretblack" - dynamic_hair_suffix = "" - -/obj/item/clothing/head/frenchberet/equipped(mob/M, slot) - . = ..() - if (slot == 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/assu_helmet - name = "DAB helmet" - icon_state = "assu_helmet" - item_state = "assu_helmet" - desc = "A cheap replica of old riot helmet without visor. It has \"D.A.B.\" written on the front." - flags_inv = HIDEHAIR - -/obj/item/clothing/head/hotel - name = "Telegram cap" - desc = "A bright red cap warn by hotel staff. Or people who want to be a singing telegram" - icon_state = "telegram" - item_color = "telegram" - dog_fashion = /datum/dog_fashion/head/telegram - -/obj/item/clothing/head/colour - name = "Singer cap" - desc = "A light white hat that has bands of color. Just makes you want to sing and dance!" - icon_state = "colour" - item_color = "colour" - dog_fashion = /datum/dog_fashion/head/colour - -/obj/item/clothing/head/christmashat - name = "red santa hat" - desc = "A red Christmas Hat! How festive!" - icon_state = "christmashat" - item_state = "christmashat" - -/obj/item/clothing/head/christmashatg - name = "green santa hat" - desc = "A green Christmas Hat! How festive!" - icon_state = "christmashatg" - item_state = "christmashatg" - -/obj/item/clothing/head/cowboyhat - name = "cowboy hat" - desc = "A standard brown cowboy hat, yeehaw." - icon_state = "cowboyhat" - item_state= "cowboyhat" - -/obj/item/clothing/head/cowboyhat/black - name = "black cowboy hat" - desc = "A a black cowboy hat, perfect for any outlaw" - icon_state = "cowboyhat_black" - item_state= "cowboyhat_black" - -/obj/item/clothing/head/cowboyhat/white - name = "white cowboy hat" - desc = "A white cowboy hat, perfect for your every day rancher" - icon_state = "cowboyhat_white" - item_state= "cowboyhat_white" - -/obj/item/clothing/head/cowboyhat/pink - name = "pink cowboy hat" - desc = "A pink cowboy? more like cowgirl hat, just don't be a buckle bunny." - icon_state = "cowboyhat_pink" - item_state= "cowboyhat_pink" - -/obj/item/clothing/head/cowboyhat/sec - name = "security cowboy hat" - desc = "A security cowboy hat, perfect for any true lawman" - icon_state = "cowboyhat_sec" + + +/obj/item/clothing/head/centhat + name = "\improper CentCom hat" + icon_state = "centcom" + desc = "It's good to be emperor." + item_state = "that" + flags_inv = 0 + armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 80 + +/obj/item/clothing/head/powdered_wig + name = "powdered wig" + desc = "A powdered wig." + icon_state = "pwig" + item_state = "pwig" + +/obj/item/clothing/head/that + name = "top-hat" + desc = "It's an amish looking hat." + icon_state = "tophat" + item_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" + item_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" + item_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!" + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + mutantrace_variation = STYLE_MUZZLE + +/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" + item_state="cueball" + flags_cover = HEADCOVERSEYES|HEADCOVERSMOUTH + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + +/obj/item/clothing/head/snowman + name = "Snowman Head" + desc = "A ball of white styrofoam. So festive." + icon_state = "snowman_h" + item_state = "snowman_h" + flags_cover = HEADCOVERSEYES + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + +/obj/item/clothing/head/justice + name = "justice hat" + desc = "Fight for what's righteous!" + icon_state = "justicered" + item_state = "justicered" + flags_inv = HIDEHAIR|HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR|HIDESNOUT + flags_cover = HEADCOVERSEYES + +/obj/item/clothing/head/justice/blue + icon_state = "justiceblue" + item_state = "justiceblue" + +/obj/item/clothing/head/justice/yellow + icon_state = "justiceyellow" + item_state = "justiceyellow" + +/obj/item/clothing/head/justice/green + icon_state = "justicegreen" + item_state = "justicegreen" + +/obj/item/clothing/head/justice/pink + icon_state = "justicepink" + item_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/flatcap + name = "flat cap" + desc = "A working man's cap." + icon_state = "flat_cap" + item_state = "detective" + +/obj/item/clothing/head/pirate + name = "pirate hat" + desc = "Yarr." + icon_state = "pirate" + item_state = "pirate" + dog_fashion = /datum/dog_fashion/head/pirate + +/obj/item/clothing/head/pirate/captain + icon_state = "hgpiratecap" + item_state = "hgpiratecap" + +/obj/item/clothing/head/bandana + name = "pirate bandana" + desc = "Yarr." + icon_state = "bandana" + item_state = "bandana" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/bowler + name = "bowler-hat" + desc = "Gentleman, elite aboard!" + icon_state = "bowler" + item_state = "bowler" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/witchwig + name = "witch costume wig" + desc = "Eeeee~heheheheheheh!" + icon_state = "witch" + item_state = "witch" + flags_inv = HIDEHAIR + +/obj/item/clothing/head/chicken + name = "chicken suit head" + desc = "Bkaw!" + icon_state = "chickenhead" + item_state = "chickensuit" + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + +/obj/item/clothing/head/griffin + name = "griffon head" + desc = "Why not 'eagle head'? Who knows." + icon_state = "griffinhat" + item_state = "griffinhat" + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + +/obj/item/clothing/head/bearpelt + name = "bear pelt hat" + desc = "Fuzzy." + icon_state = "bearpelt" + item_state = "bearpelt" + +/obj/item/clothing/head/xenos + name = "xenos helmet" + icon_state = "xenos" + item_state = "xenos_helm" + desc = "A helmet made out of chitinous alien hide." + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + +/obj/item/clothing/head/fedora + name = "fedora" + icon_state = "fedora" + item_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 + +/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_hair_style = "Neckbeard" + return(BRUTELOSS) + +/obj/item/clothing/head/sombrero + name = "sombrero" + icon_state = "sombrero" + item_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" + item_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" + item_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/cone + desc = "This cone is trying to warn you of something!" + name = "warning cone" + icon = 'icons/obj/janitor.dmi' + icon_state = "cone" + item_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" + item_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/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|HIDESNOUT + +/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|HIDESNOUT + +/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" = 15, "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" + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + +/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 = "pharaoh_hat" + icon_state = "pharaoh_hat" + +/obj/item/clothing/head/jester/alt + name = "jester hat" + desc = "A hat with bells, to add some merriness to the suit." + icon_state = "jester_hat2" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/nemes + name = "headdress of Nemes" + desc = "Lavish space tomb not included." + icon_state = "nemes_headdress" + icon_state = "nemes_headdress" + +/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 = "beretblack" + dynamic_hair_suffix = "" + +/obj/item/clothing/head/frenchberet/equipped(mob/M, slot) + . = ..() + if (slot == 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/assu_helmet + name = "DAB helmet" + icon_state = "assu_helmet" + item_state = "assu_helmet" + desc = "A cheap replica of old riot helmet without visor. It has \"D.A.B.\" written on the front." + flags_inv = HIDEHAIR + +/obj/item/clothing/head/hotel + name = "Telegram cap" + desc = "A bright red cap warn by hotel staff. Or people who want to be a singing telegram" + icon_state = "telegram" + item_color = "telegram" + dog_fashion = /datum/dog_fashion/head/telegram + +/obj/item/clothing/head/colour + name = "Singer cap" + desc = "A light white hat that has bands of color. Just makes you want to sing and dance!" + icon_state = "colour" + item_color = "colour" + dog_fashion = /datum/dog_fashion/head/colour + +/obj/item/clothing/head/christmashat + name = "red santa hat" + desc = "A red Christmas Hat! How festive!" + icon_state = "christmashat" + item_state = "christmashat" + +/obj/item/clothing/head/christmashatg + name = "green santa hat" + desc = "A green Christmas Hat! How festive!" + icon_state = "christmashatg" + item_state = "christmashatg" + +/obj/item/clothing/head/cowboyhat + name = "cowboy hat" + desc = "A standard brown cowboy hat, yeehaw." + icon_state = "cowboyhat" + item_state= "cowboyhat" + +/obj/item/clothing/head/cowboyhat/black + name = "black cowboy hat" + desc = "A a black cowboy hat, perfect for any outlaw" + icon_state = "cowboyhat_black" + item_state= "cowboyhat_black" + +/obj/item/clothing/head/cowboyhat/white + name = "white cowboy hat" + desc = "A white cowboy hat, perfect for your every day rancher" + icon_state = "cowboyhat_white" + item_state= "cowboyhat_white" + +/obj/item/clothing/head/cowboyhat/pink + name = "pink cowboy hat" + desc = "A pink cowboy? more like cowgirl hat, just don't be a buckle bunny." + icon_state = "cowboyhat_pink" + item_state= "cowboyhat_pink" + +/obj/item/clothing/head/cowboyhat/sec + name = "security cowboy hat" + desc = "A security cowboy hat, perfect for any true lawman" + icon_state = "cowboyhat_sec" item_state= "cowboyhat_sec" \ No newline at end of file diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index a18d68cc72..1000decc87 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -1,312 +1,312 @@ -/* - * 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 - item_state = "welding" - materials = list(MAT_METAL=1750, MAT_GLASS=400) - flash_protect = 2 - 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 - resistance_flags = FIRE_PROOF - mutantrace_variation = STYLE_MUZZLE - -/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" - item_state = "hardhat0_cakehat" - item_color = "cakehat" - hitsound = 'sound/weapons/tap.ogg' - 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 = 1000 - -/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() - ..() - force = 15 - throwforce = 15 - damtype = BURN - hitsound = 'sound/items/welder.ogg' - START_PROCESSING(SSobj, src) - -/obj/item/clothing/head/hardhat/cakehat/turn_off() - ..() - force = 0 - throwforce = 0 - damtype = BRUTE - hitsound = 'sound/weapons/tap.ogg' - STOP_PROCESSING(SSobj, src) - -/obj/item/clothing/head/hardhat/cakehat/get_temperature() - return on * heat -/* - * Ushanka - */ -/obj/item/clothing/head/ushanka - name = "ushanka" - desc = "Perfect for winter in Siberia, da?" - icon_state = "ushankadown" - item_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.item_state = "ushankaup" - earflaps = 0 - to_chat(user, "You raise the ear flaps on the ushanka.") - else - src.icon_state = "ushankadown" - src.item_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" - item_state = "hardhat0_pumpkin" - item_color = "pumpkin" - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - 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 == 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" - item_state = "hardhat0_reindeer" - item_color = "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" - item_state = "cardborg_h" - flags_cover = HEADCOVERSEYES - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - - dog_fashion = /datum/dog_fashion/head/cardborg - -/obj/item/clothing/head/cardborg/equipped(mob/living/user, slot) - ..() - if(ishuman(user) && slot == 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_state = "" - item_state = "pwig" - flags_inv = HIDEHAIR - var/hair_style = "Very Long Hair" - var/hair_color = "#000" - -/obj/item/clothing/head/wig/Initialize(mapload) - . = ..() - update_icon() - -/obj/item/clothing/head/wig/update_icon() - cut_overlays() - var/datum/sprite_accessory/S = GLOB.hair_styles_list[hair_style] - if(!S) - icon_state = "pwig" - else - var/mutable_appearance/M = mutable_appearance(S.icon,S.icon_state) - M.appearance_flags |= RESET_COLOR - M.color = hair_color - add_overlay(M) - -/obj/item/clothing/head/wig/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) - . = list() - if(!isinhands) - var/datum/sprite_accessory/S = GLOB.hair_styles_list[hair_style] - if(!S) - return - var/mutable_appearance/M = mutable_appearance(S.icon, S.icon_state,layer = -HAIR_LAYER) - M.appearance_flags |= RESET_COLOR - M.color = hair_color - . += M - -/obj/item/clothing/head/wig/random/Initialize(mapload) - hair_style = pick(GLOB.hair_styles_list - "Bald") //Don't want invisible wig - hair_color = "#[random_short_color()]" - . = ..() - -/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" - flags_inv = HIDEEARS|HIDEHAIR - armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = 0, "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" - item_state = "foilhat" - armor = list("melee" = 0, "bullet" = 0, "laser" = -5,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = -5, "fire" = 0, "acid" = 0) - equip_delay_other = 140 - var/datum/brain_trauma/mild/phobia/paranoia - var/warped = FALSE - clothing_flags = IGNORE_HAT_TOSS - -/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 != SLOT_HEAD || warped) - return - if(paranoia) - QDEL_NULL(paranoia) - paranoia = new("conspiracies") - 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 - if(!isliving(loc) || !paranoia) - return - var/mob/living/target = loc - if(target.get_item_by_slot(SLOT_HEAD) != src) - return - QDEL_NULL(paranoia) - if(!target.IsUnconscious()) - 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() - -/obj/item/clothing/head/flakhelm //Actually the M1 Helmet - name = "flak helmet" - icon_state = "m1helm" - item_state = "helmet" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0.1, "bio" = 0, "rad" = 0, "fire" = -10, "acid" = -15) - desc = "A dilapidated helmet used in ancient wars. This one is brittle and essentially useless. An ace of spades is tucked into the band around the outer shell." - pocket_storage_component_path = /datum/component/storage/concrete/pockets/tiny/spacenam //So you can stuff other things in the elastic band instead of it simply being a fluff thing. - -//The "pocket" for the M1 helmet so you can tuck things into the elastic band - -/datum/component/storage/concrete/pockets/tiny/spacenam - attack_hand_interact = TRUE //So you can actually see what you stuff in there +/* + * 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 + item_state = "welding" + materials = list(MAT_METAL=1750, MAT_GLASS=400) + flash_protect = 2 + 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 + resistance_flags = FIRE_PROOF + mutantrace_variation = STYLE_MUZZLE + +/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" + item_state = "hardhat0_cakehat" + item_color = "cakehat" + hitsound = 'sound/weapons/tap.ogg' + 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 = 1000 + +/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() + ..() + force = 15 + throwforce = 15 + damtype = BURN + hitsound = 'sound/items/welder.ogg' + START_PROCESSING(SSobj, src) + +/obj/item/clothing/head/hardhat/cakehat/turn_off() + ..() + force = 0 + throwforce = 0 + damtype = BRUTE + hitsound = 'sound/weapons/tap.ogg' + STOP_PROCESSING(SSobj, src) + +/obj/item/clothing/head/hardhat/cakehat/get_temperature() + return on * heat +/* + * Ushanka + */ +/obj/item/clothing/head/ushanka + name = "ushanka" + desc = "Perfect for winter in Siberia, da?" + icon_state = "ushankadown" + item_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.item_state = "ushankaup" + earflaps = 0 + to_chat(user, "You raise the ear flaps on the ushanka.") + else + src.icon_state = "ushankadown" + src.item_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" + item_state = "hardhat0_pumpkin" + item_color = "pumpkin" + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + 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 == 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" + item_state = "hardhat0_reindeer" + item_color = "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" + item_state = "cardborg_h" + flags_cover = HEADCOVERSEYES + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + + dog_fashion = /datum/dog_fashion/head/cardborg + +/obj/item/clothing/head/cardborg/equipped(mob/living/user, slot) + ..() + if(ishuman(user) && slot == 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_state = "" + item_state = "pwig" + flags_inv = HIDEHAIR + var/hair_style = "Very Long Hair" + var/hair_color = "#000" + +/obj/item/clothing/head/wig/Initialize(mapload) + . = ..() + update_icon() + +/obj/item/clothing/head/wig/update_icon() + cut_overlays() + var/datum/sprite_accessory/S = GLOB.hair_styles_list[hair_style] + if(!S) + icon_state = "pwig" + else + var/mutable_appearance/M = mutable_appearance(S.icon,S.icon_state) + M.appearance_flags |= RESET_COLOR + M.color = hair_color + add_overlay(M) + +/obj/item/clothing/head/wig/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) + . = list() + if(!isinhands) + var/datum/sprite_accessory/S = GLOB.hair_styles_list[hair_style] + if(!S) + return + var/mutable_appearance/M = mutable_appearance(S.icon, S.icon_state,layer = -HAIR_LAYER) + M.appearance_flags |= RESET_COLOR + M.color = hair_color + . += M + +/obj/item/clothing/head/wig/random/Initialize(mapload) + hair_style = pick(GLOB.hair_styles_list - "Bald") //Don't want invisible wig + hair_color = "#[random_short_color()]" + . = ..() + +/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" + flags_inv = HIDEEARS|HIDEHAIR + armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = 0, "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" + item_state = "foilhat" + armor = list("melee" = 0, "bullet" = 0, "laser" = -5,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = -5, "fire" = 0, "acid" = 0) + equip_delay_other = 140 + var/datum/brain_trauma/mild/phobia/paranoia + var/warped = FALSE + clothing_flags = IGNORE_HAT_TOSS + +/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 != SLOT_HEAD || warped) + return + if(paranoia) + QDEL_NULL(paranoia) + paranoia = new("conspiracies") + 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 + if(!isliving(loc) || !paranoia) + return + var/mob/living/target = loc + if(target.get_item_by_slot(SLOT_HEAD) != src) + return + QDEL_NULL(paranoia) + if(!target.IsUnconscious()) + 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() + +/obj/item/clothing/head/flakhelm //Actually the M1 Helmet + name = "flak helmet" + icon_state = "m1helm" + item_state = "helmet" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0.1, "bio" = 0, "rad" = 0, "fire" = -10, "acid" = -15) + desc = "A dilapidated helmet used in ancient wars. This one is brittle and essentially useless. An ace of spades is tucked into the band around the outer shell." + pocket_storage_component_path = /datum/component/storage/concrete/pockets/tiny/spacenam //So you can stuff other things in the elastic band instead of it simply being a fluff thing. + +//The "pocket" for the M1 helmet so you can tuck things into the elastic band + +/datum/component/storage/concrete/pockets/tiny/spacenam + attack_hand_interact = TRUE //So you can actually see what you stuff in there diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm index 69ec1ccedd..cb169f4f62 100644 --- a/code/modules/clothing/head/soft_caps.dm +++ b/code/modules/clothing/head/soft_caps.dm @@ -1,142 +1,142 @@ -/obj/item/clothing/head/soft - name = "cargo cap" - desc = "It's a baseball hat in a tasteless yellow colour." - icon_state = "cargosoft" - item_state = "helmet" - item_color = "cargo" - - dog_fashion = /datum/dog_fashion/head/cargo_tech - - var/flipped = 0 - -/obj/item/clothing/head/soft/dropped() - src.icon_state = "[item_color]soft" - src.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 - flip(user) - return TRUE - - -/obj/item/clothing/head/soft/proc/flip(mob/user) - if(!user.incapacitated()) - src.flipped = !src.flipped - if(src.flipped) - icon_state = "[item_color]soft_flipped" - to_chat(user, "You flip the hat backwards.") - else - icon_state = "[item_color]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" - item_color = "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" - item_color = "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" - item_color = "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" - item_color = "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" - item_color = "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" - item_color = "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" - item_color = "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" - item_color = "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" - item_color = "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" - item_color = "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" - item_color = "sec" - armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) - strip_delay = 60 - dog_fashion = null - -/obj/item/clothing/head/soft/emt - name = "EMT cap" - desc = "It's a baseball hat with a dark turquoise color and a reflective cross on the top." - icon_state = "emtsoft" - item_color = "emt" - dog_fashion = null - -/obj/item/clothing/head/soft/baseball - name = "baseball cap" - desc = "It's a robust baseball hat, this one belongs to syndicate major league team." - icon_state = "baseballsoft" - item_color = "baseballsoft" - item_state = "baseballsoft" - flags_inv = HIDEEYES|HIDEFACE - armor = list("melee" = 35, "bullet" = 35, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 90) - strip_delay = 90 //You dont take a Major Leage cap - 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" + item_state = "helmet" + item_color = "cargo" + + dog_fashion = /datum/dog_fashion/head/cargo_tech + + var/flipped = 0 + +/obj/item/clothing/head/soft/dropped() + src.icon_state = "[item_color]soft" + src.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 + flip(user) + return TRUE + + +/obj/item/clothing/head/soft/proc/flip(mob/user) + if(!user.incapacitated()) + src.flipped = !src.flipped + if(src.flipped) + icon_state = "[item_color]soft_flipped" + to_chat(user, "You flip the hat backwards.") + else + icon_state = "[item_color]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" + item_color = "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" + item_color = "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" + item_color = "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" + item_color = "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" + item_color = "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" + item_color = "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" + item_color = "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" + item_color = "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" + item_color = "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" + item_color = "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" + item_color = "sec" + armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) + strip_delay = 60 + dog_fashion = null + +/obj/item/clothing/head/soft/emt + name = "EMT cap" + desc = "It's a baseball hat with a dark turquoise color and a reflective cross on the top." + icon_state = "emtsoft" + item_color = "emt" + dog_fashion = null + +/obj/item/clothing/head/soft/baseball + name = "baseball cap" + desc = "It's a robust baseball hat, this one belongs to syndicate major league team." + icon_state = "baseballsoft" + item_color = "baseballsoft" + item_state = "baseballsoft" + flags_inv = HIDEEYES|HIDEFACE + armor = list("melee" = 35, "bullet" = 35, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 90) + strip_delay = 90 //You dont take a Major Leage cap + dog_fashion = null diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm index 3b2edce795..5019633cf0 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(CHECK_BITFIELD(clothing_flags, VOICEBOX_TOGGLABLE)) - TOGGLE_BITFIELD(clothing_flags, VOICEBOX_DISABLED) - var/status = !CHECK_BITFIELD(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 == SLOT_WEAR_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, icon_file, style_flags = NONE) - . = list() - if(!isinhands) - if(body_parts_covered & HEAD) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask") - if(blood_DNA) - . += mutable_appearance('icons/effects/blood.dmi', "maskblood", color = blood_DNA_to_color()) - -/obj/item/clothing/mask/update_clothes_damaged_state(damaging = TRUE) - ..() - 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(CHECK_BITFIELD(clothing_flags, VOICEBOX_TOGGLABLE)) + TOGGLE_BITFIELD(clothing_flags, VOICEBOX_DISABLED) + var/status = !CHECK_BITFIELD(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 == SLOT_WEAR_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, icon_file, style_flags = NONE) + . = list() + if(!isinhands) + if(body_parts_covered & HEAD) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedmask") + if(blood_DNA) + . += mutable_appearance('icons/effects/blood.dmi', "maskblood", color = blood_DNA_to_color()) + +/obj/item/clothing/mask/update_clothes_damaged_state(damaging = TRUE) + ..() + 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/breath.dm b/code/modules/clothing/masks/breath.dm index b979e2e526..671d9c09dd 100644 --- a/code/modules/clothing/masks/breath.dm +++ b/code/modules/clothing/masks/breath.dm @@ -1,42 +1,42 @@ -/obj/item/clothing/mask/breath - desc = "A close-fitting mask that can be connected to an air supply." - name = "breath mask" - icon_state = "breath" - item_state = "m_mask" - body_parts_covered = 0 - clothing_flags = ALLOWINTERNALS - visor_flags = ALLOWINTERNALS - 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 - mutantrace_variation = STYLE_MUZZLE - -/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 - adjustmask(user) - return TRUE - -/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" - item_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" + item_state = "m_mask" + body_parts_covered = 0 + clothing_flags = ALLOWINTERNALS + visor_flags = ALLOWINTERNALS + 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 + mutantrace_variation = STYLE_MUZZLE + +/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 + adjustmask(user) + return TRUE + +/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" + item_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 5aedf7045d..1e8f26e7fe 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -1,203 +1,203 @@ -/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 | ALLOWINTERNALS - flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR|HIDESNOUT - w_class = WEIGHT_CLASS_NORMAL - item_state = "gas_alt" - gas_transfer_coefficient = 0.01 - permeability_coefficient = 0.01 - flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH - resistance_flags = NONE - mutantrace_variation = STYLE_MUZZLE - -/obj/item/clothing/mask/gas/glass - name = "glass gas mask" - desc = "A face-covering mask that can be connected to an air supply. This one doesn't obscure your face however." //More accurate - icon_state = "gas_clear" - flags_inv = HIDEEYES - - -// **** 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" - materials = list(MAT_METAL=4000, MAT_GLASS=2000) - flash_protect = 2 - 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) - - -// ******************************************************************** - -//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" - item_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 = ALLOWINTERNALS - icon_state = "clown" - item_state = "clown_hat" - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - actions_types = list(/datum/action/item_action/adjust) - dog_fashion = /datum/dog_fashion/head/clown - -/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" - - var/choice = input(user,"To what form do you wish to Morph this mask?","Morph Mask") in options - - 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 1 - -/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 = ALLOWINTERNALS - icon_state = "sexyclown" - item_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 = ALLOWINTERNALS - icon_state = "mime" - item_state = "mime" - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - actions_types = list(/datum/action/item_action/adjust) - - -/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 = input(user,"To what form do you wish to Morph this mask?","Morph Mask") in options - - 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 1 - -/obj/item/clothing/mask/gas/monkeymask - name = "monkey mask" - desc = "A mask used when acting as a monkey." - clothing_flags = ALLOWINTERNALS - icon_state = "monkeymask" - item_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 = ALLOWINTERNALS - icon_state = "sexymime" - item_state = "sexymime" - flags_cover = MASKCOVERSEYES - resistance_flags = FLAMMABLE - -/obj/item/clothing/mask/gas/death_commando - name = "Death Commando Mask" - icon_state = "death_commando_mask" - item_state = "death_commando_mask" - -/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 = ALLOWINTERNALS - 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" - item_state = "tiki_eyebrow" - resistance_flags = FLAMMABLE - max_integrity = 100 - actions_types = list(/datum/action/item_action/adjust) - dog_fashion = null - - -/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 = input(M,"To what form do you wish to change this mask?","Morph Mask") in options - - 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 + 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 | ALLOWINTERNALS + flags_inv = HIDEEARS|HIDEEYES|HIDEFACE|HIDEFACIALHAIR|HIDESNOUT + w_class = WEIGHT_CLASS_NORMAL + item_state = "gas_alt" + gas_transfer_coefficient = 0.01 + permeability_coefficient = 0.01 + flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH + resistance_flags = NONE + mutantrace_variation = STYLE_MUZZLE + +/obj/item/clothing/mask/gas/glass + name = "glass gas mask" + desc = "A face-covering mask that can be connected to an air supply. This one doesn't obscure your face however." //More accurate + icon_state = "gas_clear" + flags_inv = HIDEEYES + + +// **** 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" + materials = list(MAT_METAL=4000, MAT_GLASS=2000) + flash_protect = 2 + 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) + + +// ******************************************************************** + +//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" + item_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 = ALLOWINTERNALS + icon_state = "clown" + item_state = "clown_hat" + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + actions_types = list(/datum/action/item_action/adjust) + dog_fashion = /datum/dog_fashion/head/clown + +/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" + + var/choice = input(user,"To what form do you wish to Morph this mask?","Morph Mask") in options + + 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 1 + +/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 = ALLOWINTERNALS + icon_state = "sexyclown" + item_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 = ALLOWINTERNALS + icon_state = "mime" + item_state = "mime" + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + actions_types = list(/datum/action/item_action/adjust) + + +/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 = input(user,"To what form do you wish to Morph this mask?","Morph Mask") in options + + 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 1 + +/obj/item/clothing/mask/gas/monkeymask + name = "monkey mask" + desc = "A mask used when acting as a monkey." + clothing_flags = ALLOWINTERNALS + icon_state = "monkeymask" + item_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 = ALLOWINTERNALS + icon_state = "sexymime" + item_state = "sexymime" + flags_cover = MASKCOVERSEYES + resistance_flags = FLAMMABLE + +/obj/item/clothing/mask/gas/death_commando + name = "Death Commando Mask" + icon_state = "death_commando_mask" + item_state = "death_commando_mask" + +/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 = ALLOWINTERNALS + 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" + item_state = "tiki_eyebrow" + resistance_flags = FLAMMABLE + max_integrity = 100 + actions_types = list(/datum/action/item_action/adjust) + dog_fashion = null + + +/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 = input(M,"To what form do you wish to change this mask?","Morph Mask") in options + + 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 diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 894ea2562f..84990f9660 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -1,321 +1,321 @@ -/obj/item/clothing/mask/muzzle - name = "muzzle" - desc = "To stop that awful noise." - icon_state = "muzzle" - item_state = "blindfold" - flags_cover = MASKCOVERSMOUTH - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.9 - equip_delay_other = 20 - mutantrace_variation = STYLE_MUZZLE - -/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" - item_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) - mutantrace_variation = STYLE_MUZZLE - -/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 - mutantrace_variation = STYLE_MUZZLE - -/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" - mutantrace_variation = STYLE_MUZZLE - -/obj/item/clothing/mask/pig - name = "pig mask" - desc = "A rubber pig mask with a builtin voice modulator." - icon_state = "pig" - item_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(!CHECK_BITFIELD(clothing_flags, VOICEBOX_DISABLED)) - speech_args[SPEECH_MESSAGE] = pick("Oink!","Squeeeeeeee!","Oink Oink!") - -/obj/item/clothing/mask/pig/cursed //needs to be different otherwise you could turn the speedmodification off and on - name = "Pig face" - desc = "It looks like a mask, but closer inspection reveals it's melded onto this persons face!" //It's only ever going to be attached to your 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, 1) - -///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" - item_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(!CHECK_BITFIELD(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_ITEM_TRAIT)) - to_chat(user, "[src] was cursed! Ree!!") - return ..() - -/obj/item/clothing/mask/cowmask - name = "Cow mask with a builtin voice modulator." - desc = "A rubber cow mask," - icon = 'icons/mob/mask.dmi' - icon_state = "cowmask" - item_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(!CHECK_BITFIELD(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 persons 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, 1) - -/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" - item_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(!CHECK_BITFIELD(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, 1) - -/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" - item_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" - item_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" - item_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" - item_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" - item_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" - item_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" - item_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" - item_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 - slot_flags = ITEM_SLOT_MASK - adjusted_flags = ITEM_SLOT_HEAD - icon_state = "bandbotany" - mutantrace_variation = STYLE_MUZZLE - -/obj/item/clothing/mask/bandana/attack_self(mob/user) - adjustmask(user) - -/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/mummy - name = "mummy mask" - desc = "Ancient bandages." - icon_state = "mummy_mask" - item_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" - item_state = "scarecrow_sack" - flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - -/obj/item/clothing/mask/gondola - name = "gondola mask" - desc = "Genuine gondola fur." - icon_state = "gondola" - item_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/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/muzzle + name = "muzzle" + desc = "To stop that awful noise." + icon_state = "muzzle" + item_state = "blindfold" + flags_cover = MASKCOVERSMOUTH + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.9 + equip_delay_other = 20 + mutantrace_variation = STYLE_MUZZLE + +/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" + item_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) + mutantrace_variation = STYLE_MUZZLE + +/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 + mutantrace_variation = STYLE_MUZZLE + +/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" + mutantrace_variation = STYLE_MUZZLE + +/obj/item/clothing/mask/pig + name = "pig mask" + desc = "A rubber pig mask with a builtin voice modulator." + icon_state = "pig" + item_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(!CHECK_BITFIELD(clothing_flags, VOICEBOX_DISABLED)) + speech_args[SPEECH_MESSAGE] = pick("Oink!","Squeeeeeeee!","Oink Oink!") + +/obj/item/clothing/mask/pig/cursed //needs to be different otherwise you could turn the speedmodification off and on + name = "Pig face" + desc = "It looks like a mask, but closer inspection reveals it's melded onto this persons face!" //It's only ever going to be attached to your 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, 1) + +///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" + item_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(!CHECK_BITFIELD(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_ITEM_TRAIT)) + to_chat(user, "[src] was cursed! Ree!!") + return ..() + +/obj/item/clothing/mask/cowmask + name = "Cow mask with a builtin voice modulator." + desc = "A rubber cow mask," + icon = 'icons/mob/mask.dmi' + icon_state = "cowmask" + item_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(!CHECK_BITFIELD(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 persons 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, 1) + +/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" + item_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(!CHECK_BITFIELD(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, 1) + +/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" + item_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" + item_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" + item_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" + item_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" + item_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" + item_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" + item_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" + item_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 + slot_flags = ITEM_SLOT_MASK + adjusted_flags = ITEM_SLOT_HEAD + icon_state = "bandbotany" + mutantrace_variation = STYLE_MUZZLE + +/obj/item/clothing/mask/bandana/attack_self(mob/user) + adjustmask(user) + +/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/mummy + name = "mummy mask" + desc = "Ancient bandages." + icon_state = "mummy_mask" + item_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" + item_state = "scarecrow_sack" + flags_inv = HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + +/obj/item/clothing/mask/gondola + name = "gondola mask" + desc = "Genuine gondola fur." + icon_state = "gondola" + item_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/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" + diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm index eaaf4b90ce..4c6d3a8f88 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -1,101 +1,101 @@ -/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 - var/blood_state = BLOOD_STATE_NOT_BLOODY - var/list/bloody_shoes = list(BLOOD_STATE_BLOOD = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) - var/offset = 0 - var/equipped_before_drop = FALSE - - mutantrace_variation = STYLE_DIGITIGRADE - var/last_bloodtype = "" //used to track the last bloodtype to have graced these shoes; makes for better performing footprint shenanigans - var/last_blood_DNA = "" //same as last one - -/obj/item/clothing/shoes/ComponentInitialize() - . = ..() - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.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() - playsound(user,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1) - if(r_leg) - r_leg.dismember() - playsound(user,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -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, 1) - return(BRUTELOSS) - - -/obj/item/clothing/shoes/transfer_blood_dna(list/blood_dna, diseases) - ..() - if(blood_dna.len) - last_bloodtype = blood_dna[blood_dna[blood_dna.len]]//trust me this works - last_blood_DNA = blood_dna[blood_dna.len] - -/obj/item/clothing/shoes/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) - . = list() - if(!isinhands) - var/bloody = FALSE - if(blood_DNA) - bloody = TRUE - else - bloody = bloody_shoes[BLOOD_STATE_BLOOD] - - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe") - if(bloody) - var/file2use = style_flags & STYLE_DIGITIGRADE ? 'icons/mob/feet_digi.dmi' : 'icons/effects/blood.dmi' - . += mutable_appearance(file2use, "shoeblood", color = blood_DNA_to_color()) - -/obj/item/clothing/shoes/equipped(mob/user, slot) - . = ..() - - if(offset && slot_flags & slotdefine2slotbit(slot)) - user.pixel_y += offset - worn_y_dimension -= (offset * 2) - user.update_inv_shoes() - equipped_before_drop = 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(offset && equipped_before_drop) - restore_offsets(user) - . = ..() - -/obj/item/clothing/shoes/update_clothes_damaged_state(damaging = TRUE) - ..() - if(ismob(loc)) - var/mob/M = loc - M.update_inv_shoes() - -/obj/item/clothing/shoes/clean_blood(datum/source, strength) - . = ..() - bloody_shoes = list(BLOOD_STATE_BLOOD = 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 +/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 + var/blood_state = BLOOD_STATE_NOT_BLOODY + var/list/bloody_shoes = list(BLOOD_STATE_BLOOD = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) + var/offset = 0 + var/equipped_before_drop = FALSE + + mutantrace_variation = STYLE_DIGITIGRADE + var/last_bloodtype = "" //used to track the last bloodtype to have graced these shoes; makes for better performing footprint shenanigans + var/last_blood_DNA = "" //same as last one + +/obj/item/clothing/shoes/ComponentInitialize() + . = ..() + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.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() + playsound(user,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1) + if(r_leg) + r_leg.dismember() + playsound(user,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -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, 1) + return(BRUTELOSS) + + +/obj/item/clothing/shoes/transfer_blood_dna(list/blood_dna, diseases) + ..() + if(blood_dna.len) + last_bloodtype = blood_dna[blood_dna[blood_dna.len]]//trust me this works + last_blood_DNA = blood_dna[blood_dna.len] + +/obj/item/clothing/shoes/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) + . = list() + if(!isinhands) + var/bloody = FALSE + if(blood_DNA) + bloody = TRUE + else + bloody = bloody_shoes[BLOOD_STATE_BLOOD] + + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe") + if(bloody) + var/file2use = style_flags & STYLE_DIGITIGRADE ? 'icons/mob/feet_digi.dmi' : 'icons/effects/blood.dmi' + . += mutable_appearance(file2use, "shoeblood", color = blood_DNA_to_color()) + +/obj/item/clothing/shoes/equipped(mob/user, slot) + . = ..() + + if(offset && slot_flags & slotdefine2slotbit(slot)) + user.pixel_y += offset + worn_y_dimension -= (offset * 2) + user.update_inv_shoes() + equipped_before_drop = 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(offset && equipped_before_drop) + restore_offsets(user) + . = ..() + +/obj/item/clothing/shoes/update_clothes_damaged_state(damaging = TRUE) + ..() + if(ismob(loc)) + var/mob/M = loc + M.update_inv_shoes() + +/obj/item/clothing/shoes/clean_blood(datum/source, strength) + . = ..() + bloody_shoes = list(BLOOD_STATE_BLOOD = 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 diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm index 70dde1e217..952dbe0ad8 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 f49c26ce49..82dd3142ed 100644 --- a/code/modules/clothing/spacesuits/_spacesuits.dm +++ b/code/modules/clothing/spacesuits/_spacesuits.dm @@ -1,49 +1,49 @@ -//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 | BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS - item_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|HIDESNOUT - 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 = 2 - strip_delay = 50 - equip_delay_other = 50 - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - resistance_flags = NONE - dog_fashion = null - mutantrace_variation = STYLE_MUZZLE - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - -/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" - item_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|HIDETAUR - 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 - strip_delay = 80 - equip_delay_other = 80 - resistance_flags = NONE - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //rated for cosmic radation :honk: - mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC +//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 | BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS + item_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|HIDESNOUT + 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 = 2 + strip_delay = 50 + equip_delay_other = 50 + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + resistance_flags = NONE + dog_fashion = null + mutantrace_variation = STYLE_MUZZLE + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + +/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" + item_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|HIDETAUR + 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 + strip_delay = 80 + equip_delay_other = 80 + resistance_flags = NONE + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE //rated for cosmic radation :honk: + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm index b9b51e5d1c..bfffe15721 100644 --- a/code/modules/clothing/spacesuits/miscellaneous.dm +++ b/code/modules/clothing/spacesuits/miscellaneous.dm @@ -1,444 +1,444 @@ -//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 - - ERT High Alarm - Command, Sec, Engi, Med - - EVA spacesuit - - Freedom's spacesuit (freedom from vacuum's oppression) - - Carp 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" - item_state = "deathsquad" - armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "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" - item_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" = 50, "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 - - //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" - item_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" = 30, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) - strip_delay = 120 - resistance_flags = FIRE_PROOF | ACID_PROOF - mutantrace_variation = STYLE_DIGITIGRADE - -/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" = 50, "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 - mutantrace_variation = NONE - -/obj/item/clothing/suit/space/officer - name = "officer's jacket" - desc = "An armored, space-proof jacket used in special operations." - icon_state = "detective" - item_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" = 50, "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 - mutantrace_variation = STYLE_DIGITIGRADE - - //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" - item_state = "void" - -/obj/item/clothing/suit/space/nasavoid - name = "NASA Voidsuit" - icon_state = "void" - item_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) - mutantrace_variation = STYLE_DIGITIGRADE - -/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" - item_state = "void" - -/obj/item/clothing/suit/space/nasavoid/old - name = "Engineering Voidsuit" - icon_state = "void" - item_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 - mutantrace_variation = NONE - - dog_fashion = /datum/dog_fashion/head/santa - -/obj/item/clothing/suit/space/santa - name = "Santa's suit" - desc = "Festive!" - icon_state = "santa" - item_state = "santa" - slowdown = 0 - allowed = list(/obj/item) //for stuffing exta special presents - mutantrace_variation = STYLE_DIGITIGRADE - - - //Space pirate outfit -/obj/item/clothing/head/helmet/space/pirate - name = "royal tricorne" - desc = "A thick, space-proof tricorne from the royal Space Queen. It's lined with a layer of reflective kevlar." - icon_state = "pirate" - item_state = "pirate" - armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) - flags_inv = HIDEHAIR - strip_delay = 40 - equip_delay_other = 20 - flags_cover = HEADCOVERSEYES - mutantrace_variation = NONE - -/obj/item/clothing/head/helmet/space/pirate/bandana - name = "royal bandana" - desc = "A space-proof bandanna crafted with reflective kevlar." - icon_state = "bandana" - item_state = "bandana" - mutantrace_variation = NONE - -/obj/item/clothing/suit/space/pirate - name = "royal waistcoat " - desc = "A royal, space-proof waistcoat. The inside of it is lined with reflective kevlar." - icon_state = "pirate" - item_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" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) - strip_delay = 40 - equip_delay_other = 20 - mutantrace_variation = STYLE_DIGITIGRADE - - //Emergency Response Team suits -/obj/item/clothing/head/helmet/space/hardsuit/ert - name = "emergency response unit helmet" - desc = "Standard issue command helmet for the ERT." - icon_state = "hardsuit0-ert_commander" - item_state = "hardsuit0-ert_commander" - item_color = "ert_commander" - armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) - strip_delay = 130 - brightness_on = 7 - resistance_flags = ACID_PROOF - -/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 suit" - desc = "Standard issue command suit for the ERT." - icon_state = "ert_command" - item_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" = 50, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) - slowdown = 0 - strip_delay = 130 - resistance_flags = ACID_PROOF - - //ERT Security -/obj/item/clothing/head/helmet/space/hardsuit/ert/sec - desc = "Standard issue security helmet for the ERT." - icon_state = "hardsuit0-ert_security" - item_state = "hardsuit0-ert_security" - item_color = "ert_security" - -/obj/item/clothing/suit/space/hardsuit/ert/sec - desc = "Standard issue security suit for the ERT." - icon_state = "ert_security" - item_state = "ert_security" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/sec - - //ERT Engineering -/obj/item/clothing/head/helmet/space/hardsuit/ert/engi - desc = "Standard issue engineer helmet for the ERT." - icon_state = "hardsuit0-ert_engineer" - item_state = "hardsuit0-ert_engineer" - item_color = "ert_engineer" - -/obj/item/clothing/suit/space/hardsuit/ert/engi - desc = "Standard issue engineer suit for the ERT." - icon_state = "ert_engineer" - item_state = "ert_engineer" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/engi - - //ERT Medical -/obj/item/clothing/head/helmet/space/hardsuit/ert/med - desc = "Standard issue medical helmet for the ERT." - icon_state = "hardsuit0-ert_medical" - item_state = "hardsuit0-ert_medical" - item_color = "ert_medical" - -/obj/item/clothing/suit/space/hardsuit/ert/med - desc = "Standard issue medical suit for the ERT." - icon_state = "ert_medical" - item_state = "ert_medical" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/med - species_exception = list(/datum/species/angel) - - //Red alert ERT - -/obj/item/clothing/head/helmet/space/hardsuit/ert/alert - name = "emergency response unit helmet" - desc = "Red alert command helmet for the ERT. This one is more armored than its standard version." - icon_state = "hardsuit0-ert_commander-alert" - item_state = "hardsuit0-ert_commander-alert" - item_color = "ert_commander-alert" - armor = list("melee" = 70, "bullet" = 55, "laser" = 50, "energy" = 50, "bomb" = 65, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - brightness_on = 8 - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/clothing/suit/space/hardsuit/ert/alert - name = "emergency response team suit" - desc = "Red alert command suit for the ERT. This one is more armored than its standard version." - icon_state = "ert_command-alert" - item_state = "ert_command-alert" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert - armor = list("melee" = 70, "bullet" = 55, "laser" = 50, "energy" = 50, "bomb" = 65, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC - - //ERT Security -/obj/item/clothing/head/helmet/space/hardsuit/ert/alert/sec - desc = "Red alert security helmet for the ERT. This one is more armored than its standard version." - icon_state = "hardsuit0-ert_security-alert" - item_state = "hardsuit0-ert_security-alert" - item_color = "ert_security-alert" - -/obj/item/clothing/suit/space/hardsuit/ert/alert/sec - desc = "Red alert security suit for the ERT. This one is more armored than its standard version." - icon_state = "ert_security-alert" - item_state = "ert_security-alert" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert/sec - - //ERT Engineering -/obj/item/clothing/head/helmet/space/hardsuit/ert/alert/engi - desc = "Red alert engineer helmet for the ERT. This one is more armored than its standard version." - icon_state = "hardsuit0-ert_engineer-alert" - item_state = "hardsuit0-ert_engineer-alert" - item_color = "ert_engineer-alert" - -/obj/item/clothing/suit/space/hardsuit/ert/alert/engi - desc = "Red alert engineer suit for the ERT. This one is more armored than its standard version." - icon_state = "ert_engineer-alert" - item_state = "ert_engineer-alert" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert/engi - - //ERT Medical -/obj/item/clothing/head/helmet/space/hardsuit/ert/alert/med - desc = "Red alert medical helmet for the ERT. This one is more armored than its standard version." - icon_state = "hardsuit0-ert_medical-alert" - item_state = "hardsuit0-ert_medical-alert" - item_color = "ert_medical-alert" - -/obj/item/clothing/suit/space/hardsuit/ert/alert/med - desc = "Red alert medical suit for the ERT. This one is more armored than its standard version." - icon_state = "ert_medical-alert" - item_state = "ert_medical-alert" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert/med - species_exception = list(/datum/species/angel) - -/obj/item/clothing/suit/space/eva - name = "EVA suit" - icon_state = "space" - item_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" - item_state = "space" - desc = "A lightweight space helmet with the basic ability to protect the wearer from the vacuum of space during emergencies." - flash_protect = 0 - 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" - item_state = "griffinhat" - armor = list("melee" = 20, "bullet" = 40, "laser" = 30, "energy" = 25, "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 - mutantrace_variation = NONE - -/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" - item_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" = 25, "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 - mutantrace_variation = STYLE_DIGITIGRADE - -//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" - item_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() - mutantrace_variation = NONE - -/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" - item_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/gun/ballistic/automatic/speargun) //I'm giving you a hint here - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/carp - mutantrace_variation = STYLE_DIGITIGRADE - -/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal - name = "paranormal response unit helmet" - desc = "A helmet worn by those who deal with paranormal threats for a living." - icon_state = "hardsuit0-prt" - item_state = "hardsuit0-prt" - item_color = "knight_grey" - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - actions_types = list() - resistance_flags = FIRE_PROOF - mutantrace_variation = NONE - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() - . = ..() - AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_HEAD) - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal - name = "paranormal response team suit" - desc = "Powerful wards are built into this hardsuit, protecting the user from all manner of paranormal threats." - icon_state = "knight_grey" - item_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, FALSE, ITEM_SLOT_OCLOTHING) - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor - name = "inquisitor's hardsuit" - icon_state = "hardsuit-inq" - item_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" - item_state = "hardsuit0-inq" - -/obj/item/clothing/suit/space/hardsuit/ert/paranormal/beserker - name = "champion's hardsuit" - desc = "Voices echo from the hardsuit, driving the user insane." - icon_state = "hardsuit-beserker" - item_state = "hardsuit-beserker" - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/beserker - -/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/beserker - name = "champion's helmet" - desc = "Peering into the eyes of the helmet is enough to seal damnation." - icon_state = "hardsuit0-beserker" - item_state = "hardsuit0-beserker" - -/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" - item_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" - item_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, 1) - playsound(loc, 'sound/effects/refill.ogg', 50, 1) +//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 + - ERT High Alarm - Command, Sec, Engi, Med + - EVA spacesuit + - Freedom's spacesuit (freedom from vacuum's oppression) + - Carp 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" + item_state = "deathsquad" + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "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" + item_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" = 50, "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 + + //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" + item_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" = 30, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100) + strip_delay = 120 + resistance_flags = FIRE_PROOF | ACID_PROOF + mutantrace_variation = STYLE_DIGITIGRADE + +/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" = 50, "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 + mutantrace_variation = NONE + +/obj/item/clothing/suit/space/officer + name = "officer's jacket" + desc = "An armored, space-proof jacket used in special operations." + icon_state = "detective" + item_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" = 50, "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 + mutantrace_variation = STYLE_DIGITIGRADE + + //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" + item_state = "void" + +/obj/item/clothing/suit/space/nasavoid + name = "NASA Voidsuit" + icon_state = "void" + item_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) + mutantrace_variation = STYLE_DIGITIGRADE + +/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" + item_state = "void" + +/obj/item/clothing/suit/space/nasavoid/old + name = "Engineering Voidsuit" + icon_state = "void" + item_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 + mutantrace_variation = NONE + + dog_fashion = /datum/dog_fashion/head/santa + +/obj/item/clothing/suit/space/santa + name = "Santa's suit" + desc = "Festive!" + icon_state = "santa" + item_state = "santa" + slowdown = 0 + allowed = list(/obj/item) //for stuffing exta special presents + mutantrace_variation = STYLE_DIGITIGRADE + + + //Space pirate outfit +/obj/item/clothing/head/helmet/space/pirate + name = "royal tricorne" + desc = "A thick, space-proof tricorne from the royal Space Queen. It's lined with a layer of reflective kevlar." + icon_state = "pirate" + item_state = "pirate" + armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) + flags_inv = HIDEHAIR + strip_delay = 40 + equip_delay_other = 20 + flags_cover = HEADCOVERSEYES + mutantrace_variation = NONE + +/obj/item/clothing/head/helmet/space/pirate/bandana + name = "royal bandana" + desc = "A space-proof bandanna crafted with reflective kevlar." + icon_state = "bandana" + item_state = "bandana" + mutantrace_variation = NONE + +/obj/item/clothing/suit/space/pirate + name = "royal waistcoat " + desc = "A royal, space-proof waistcoat. The inside of it is lined with reflective kevlar." + icon_state = "pirate" + item_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" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75) + strip_delay = 40 + equip_delay_other = 20 + mutantrace_variation = STYLE_DIGITIGRADE + + //Emergency Response Team suits +/obj/item/clothing/head/helmet/space/hardsuit/ert + name = "emergency response unit helmet" + desc = "Standard issue command helmet for the ERT." + icon_state = "hardsuit0-ert_commander" + item_state = "hardsuit0-ert_commander" + item_color = "ert_commander" + armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) + strip_delay = 130 + brightness_on = 7 + resistance_flags = ACID_PROOF + +/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 suit" + desc = "Standard issue command suit for the ERT." + icon_state = "ert_command" + item_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" = 50, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80) + slowdown = 0 + strip_delay = 130 + resistance_flags = ACID_PROOF + + //ERT Security +/obj/item/clothing/head/helmet/space/hardsuit/ert/sec + desc = "Standard issue security helmet for the ERT." + icon_state = "hardsuit0-ert_security" + item_state = "hardsuit0-ert_security" + item_color = "ert_security" + +/obj/item/clothing/suit/space/hardsuit/ert/sec + desc = "Standard issue security suit for the ERT." + icon_state = "ert_security" + item_state = "ert_security" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/sec + + //ERT Engineering +/obj/item/clothing/head/helmet/space/hardsuit/ert/engi + desc = "Standard issue engineer helmet for the ERT." + icon_state = "hardsuit0-ert_engineer" + item_state = "hardsuit0-ert_engineer" + item_color = "ert_engineer" + +/obj/item/clothing/suit/space/hardsuit/ert/engi + desc = "Standard issue engineer suit for the ERT." + icon_state = "ert_engineer" + item_state = "ert_engineer" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/engi + + //ERT Medical +/obj/item/clothing/head/helmet/space/hardsuit/ert/med + desc = "Standard issue medical helmet for the ERT." + icon_state = "hardsuit0-ert_medical" + item_state = "hardsuit0-ert_medical" + item_color = "ert_medical" + +/obj/item/clothing/suit/space/hardsuit/ert/med + desc = "Standard issue medical suit for the ERT." + icon_state = "ert_medical" + item_state = "ert_medical" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/med + species_exception = list(/datum/species/angel) + + //Red alert ERT + +/obj/item/clothing/head/helmet/space/hardsuit/ert/alert + name = "emergency response unit helmet" + desc = "Red alert command helmet for the ERT. This one is more armored than its standard version." + icon_state = "hardsuit0-ert_commander-alert" + item_state = "hardsuit0-ert_commander-alert" + item_color = "ert_commander-alert" + armor = list("melee" = 70, "bullet" = 55, "laser" = 50, "energy" = 50, "bomb" = 65, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + brightness_on = 8 + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/clothing/suit/space/hardsuit/ert/alert + name = "emergency response team suit" + desc = "Red alert command suit for the ERT. This one is more armored than its standard version." + icon_state = "ert_command-alert" + item_state = "ert_command-alert" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert + armor = list("melee" = 70, "bullet" = 55, "laser" = 50, "energy" = 50, "bomb" = 65, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC + + //ERT Security +/obj/item/clothing/head/helmet/space/hardsuit/ert/alert/sec + desc = "Red alert security helmet for the ERT. This one is more armored than its standard version." + icon_state = "hardsuit0-ert_security-alert" + item_state = "hardsuit0-ert_security-alert" + item_color = "ert_security-alert" + +/obj/item/clothing/suit/space/hardsuit/ert/alert/sec + desc = "Red alert security suit for the ERT. This one is more armored than its standard version." + icon_state = "ert_security-alert" + item_state = "ert_security-alert" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert/sec + + //ERT Engineering +/obj/item/clothing/head/helmet/space/hardsuit/ert/alert/engi + desc = "Red alert engineer helmet for the ERT. This one is more armored than its standard version." + icon_state = "hardsuit0-ert_engineer-alert" + item_state = "hardsuit0-ert_engineer-alert" + item_color = "ert_engineer-alert" + +/obj/item/clothing/suit/space/hardsuit/ert/alert/engi + desc = "Red alert engineer suit for the ERT. This one is more armored than its standard version." + icon_state = "ert_engineer-alert" + item_state = "ert_engineer-alert" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert/engi + + //ERT Medical +/obj/item/clothing/head/helmet/space/hardsuit/ert/alert/med + desc = "Red alert medical helmet for the ERT. This one is more armored than its standard version." + icon_state = "hardsuit0-ert_medical-alert" + item_state = "hardsuit0-ert_medical-alert" + item_color = "ert_medical-alert" + +/obj/item/clothing/suit/space/hardsuit/ert/alert/med + desc = "Red alert medical suit for the ERT. This one is more armored than its standard version." + icon_state = "ert_medical-alert" + item_state = "ert_medical-alert" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert/med + species_exception = list(/datum/species/angel) + +/obj/item/clothing/suit/space/eva + name = "EVA suit" + icon_state = "space" + item_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" + item_state = "space" + desc = "A lightweight space helmet with the basic ability to protect the wearer from the vacuum of space during emergencies." + flash_protect = 0 + 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" + item_state = "griffinhat" + armor = list("melee" = 20, "bullet" = 40, "laser" = 30, "energy" = 25, "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 + mutantrace_variation = NONE + +/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" + item_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" = 25, "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 + mutantrace_variation = STYLE_DIGITIGRADE + +//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" + item_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() + mutantrace_variation = NONE + +/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" + item_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/gun/ballistic/automatic/speargun) //I'm giving you a hint here + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/carp + mutantrace_variation = STYLE_DIGITIGRADE + +/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal + name = "paranormal response unit helmet" + desc = "A helmet worn by those who deal with paranormal threats for a living." + icon_state = "hardsuit0-prt" + item_state = "hardsuit0-prt" + item_color = "knight_grey" + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + actions_types = list() + resistance_flags = FIRE_PROOF + mutantrace_variation = NONE + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize() + . = ..() + AddComponent(/datum/component/anti_magic, FALSE, FALSE, TRUE, ITEM_SLOT_HEAD) + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal + name = "paranormal response team suit" + desc = "Powerful wards are built into this hardsuit, protecting the user from all manner of paranormal threats." + icon_state = "knight_grey" + item_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, FALSE, ITEM_SLOT_OCLOTHING) + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor + name = "inquisitor's hardsuit" + icon_state = "hardsuit-inq" + item_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" + item_state = "hardsuit0-inq" + +/obj/item/clothing/suit/space/hardsuit/ert/paranormal/beserker + name = "champion's hardsuit" + desc = "Voices echo from the hardsuit, driving the user insane." + icon_state = "hardsuit-beserker" + item_state = "hardsuit-beserker" + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/beserker + +/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/beserker + name = "champion's helmet" + desc = "Peering into the eyes of the helmet is enough to seal damnation." + icon_state = "hardsuit0-beserker" + item_state = "hardsuit0-beserker" + +/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" + item_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" + item_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, 1) + playsound(loc, 'sound/effects/refill.ogg', 50, 1) diff --git a/code/modules/clothing/spacesuits/syndi.dm b/code/modules/clothing/spacesuits/syndi.dm index 178e707125..e9848ae6cd 100644 --- a/code/modules/clothing/spacesuits/syndi.dm +++ b/code/modules/clothing/spacesuits/syndi.dm @@ -1,149 +1,149 @@ -//Regular syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate - name = "red space helmet" - icon_state = "syndicate" - item_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" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) - -/obj/item/clothing/suit/space/syndicate - name = "red space suit" - icon_state = "syndicate" - item_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" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) - mutantrace_variation = STYLE_DIGITIGRADE - -//Green syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/green - name = "green space helmet" - icon_state = "syndicate-helm-green" - item_state = "syndicate-helm-green" - -/obj/item/clothing/suit/space/syndicate/green - name = "green space suit" - icon_state = "syndicate-green" - item_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" - item_state = "syndicate-helm-green-dark" - -/obj/item/clothing/suit/space/syndicate/green/dark - name = "dark green space suit" - icon_state = "syndicate-green-dark" - item_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" - item_state = "syndicate-helm-orange" - -/obj/item/clothing/suit/space/syndicate/orange - name = "orange space suit" - icon_state = "syndicate-orange" - item_state = "syndicate-orange" - mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC - -//Blue syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/blue - name = "blue space helmet" - icon_state = "syndicate-helm-blue" - item_state = "syndicate-helm-blue" - -/obj/item/clothing/suit/space/syndicate/blue - name = "blue space suit" - icon_state = "syndicate-blue" - item_state = "syndicate-blue" - - -//Black syndicate space suit -/obj/item/clothing/head/helmet/space/syndicate/black - name = "black space helmet" - icon_state = "syndicate-helm-black" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black - name = "black space suit" - icon_state = "syndicate-black" - item_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" - item_state = "syndicate-helm-black-green" - -/obj/item/clothing/suit/space/syndicate/black/green - name = "black and green space suit" - icon_state = "syndicate-black-green" - item_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" - item_state = "syndicate-helm-black-blue" - -/obj/item/clothing/suit/space/syndicate/black/blue - name = "black and blue space suit" - icon_state = "syndicate-black-blue" - item_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" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/med - name = "green space suit" - icon_state = "syndicate-black-med" - item_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" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/orange - name = "black and orange space suit" - icon_state = "syndicate-black-orange" - item_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" - item_state = "syndicate-helm-black-red" - -/obj/item/clothing/suit/space/syndicate/black/red - name = "black and red space suit" - icon_state = "syndicate-black-red" - item_state = "syndicate-black-red" - mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC - - -//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" - item_state = "syndicate-helm-black" - -/obj/item/clothing/suit/space/syndicate/black/engie - name = "black engineering space suit" - icon_state = "syndicate-black-engie" - item_state = "syndicate-black" +//Regular syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate + name = "red space helmet" + icon_state = "syndicate" + item_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" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) + +/obj/item/clothing/suit/space/syndicate + name = "red space suit" + icon_state = "syndicate" + item_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" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85) + mutantrace_variation = STYLE_DIGITIGRADE + +//Green syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/green + name = "green space helmet" + icon_state = "syndicate-helm-green" + item_state = "syndicate-helm-green" + +/obj/item/clothing/suit/space/syndicate/green + name = "green space suit" + icon_state = "syndicate-green" + item_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" + item_state = "syndicate-helm-green-dark" + +/obj/item/clothing/suit/space/syndicate/green/dark + name = "dark green space suit" + icon_state = "syndicate-green-dark" + item_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" + item_state = "syndicate-helm-orange" + +/obj/item/clothing/suit/space/syndicate/orange + name = "orange space suit" + icon_state = "syndicate-orange" + item_state = "syndicate-orange" + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC + +//Blue syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/blue + name = "blue space helmet" + icon_state = "syndicate-helm-blue" + item_state = "syndicate-helm-blue" + +/obj/item/clothing/suit/space/syndicate/blue + name = "blue space suit" + icon_state = "syndicate-blue" + item_state = "syndicate-blue" + + +//Black syndicate space suit +/obj/item/clothing/head/helmet/space/syndicate/black + name = "black space helmet" + icon_state = "syndicate-helm-black" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black + name = "black space suit" + icon_state = "syndicate-black" + item_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" + item_state = "syndicate-helm-black-green" + +/obj/item/clothing/suit/space/syndicate/black/green + name = "black and green space suit" + icon_state = "syndicate-black-green" + item_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" + item_state = "syndicate-helm-black-blue" + +/obj/item/clothing/suit/space/syndicate/black/blue + name = "black and blue space suit" + icon_state = "syndicate-black-blue" + item_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" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/med + name = "green space suit" + icon_state = "syndicate-black-med" + item_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" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/orange + name = "black and orange space suit" + icon_state = "syndicate-black-orange" + item_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" + item_state = "syndicate-helm-black-red" + +/obj/item/clothing/suit/space/syndicate/black/red + name = "black and red space suit" + icon_state = "syndicate-black-red" + item_state = "syndicate-black-red" + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC + + +//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" + item_state = "syndicate-helm-black" + +/obj/item/clothing/suit/space/syndicate/black/engie + name = "black engineering space suit" + icon_state = "syndicate-black-engie" + item_state = "syndicate-black" diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm index d05afc15e0..1b2080feb1 100644 --- a/code/modules/clothing/suits/_suits.dm +++ b/code/modules/clothing/suits/_suits.dm @@ -1,34 +1,34 @@ -/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) - slot_flags = ITEM_SLOT_OCLOTHING - body_parts_covered = CHEST - var/blood_overlay_type = "suit" - var/togglename = null - var/suittoggled = FALSE - mutantrace_variation = STYLE_DIGITIGRADE - -/obj/item/clothing/suit/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) - . = ..() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damaged[blood_overlay_type]") - if(blood_DNA) - var/file2use = (style_flags & STYLE_ALL_TAURIC) ? 'modular_citadel/icons/mob/64x32_effects.dmi' : 'icons/effects/blood.dmi' - . += mutable_appearance(file2use, "[blood_overlay_type]blood", color = blood_DNA_to_color()) - 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(damaging = TRUE) - ..() - 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) + slot_flags = ITEM_SLOT_OCLOTHING + body_parts_covered = CHEST + var/blood_overlay_type = "suit" + var/togglename = null + var/suittoggled = FALSE + mutantrace_variation = STYLE_DIGITIGRADE + +/obj/item/clothing/suit/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) + . = ..() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damaged[blood_overlay_type]") + if(blood_DNA) + var/file2use = (style_flags & STYLE_ALL_TAURIC) ? 'modular_citadel/icons/mob/64x32_effects.dmi' : 'icons/effects/blood.dmi' + . += mutable_appearance(file2use, "[blood_overlay_type]blood", color = blood_DNA_to_color()) + 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(damaging = TRUE) + ..() + 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 5729daa741..94859d434f 100644 --- a/code/modules/clothing/suits/bio.dm +++ b/code/modules/clothing/suits/bio.dm @@ -1,90 +1,90 @@ -//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 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 60, "fire" = 30, "acid" = 100) - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDEFACE|HIDESNOUT - resistance_flags = ACID_PROOF - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - mutantrace_variation = STYLE_MUZZLE - -/obj/item/clothing/suit/bio_suit - name = "bio suit" - desc = "A suit that protects against biological contamination." - icon_state = "bio" - item_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 = 1 - allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/pen, /obj/item/flashlight/pen, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 60, "fire" = 30, "acid" = 100) - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR - strip_delay = 70 - equip_delay_other = 70 - resistance_flags = ACID_PROOF - mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC - -//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" = 10, "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" = 10, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) - icon_state = "bio_security" - - -//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" - - -//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/suit/bio_suit/cmo - icon_state = "bio_cmo" - -/obj/item/clothing/head/bio_hood/cmo - icon_state = "bio_cmo" - - -//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" - item_state = "bio_suit" - strip_delay = 40 - equip_delay_other = 20 +//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 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 60, "fire" = 30, "acid" = 100) + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDEFACE|HIDESNOUT + resistance_flags = ACID_PROOF + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + mutantrace_variation = STYLE_MUZZLE + +/obj/item/clothing/suit/bio_suit + name = "bio suit" + desc = "A suit that protects against biological contamination." + icon_state = "bio" + item_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 = 1 + allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/pen, /obj/item/flashlight/pen, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 60, "fire" = 30, "acid" = 100) + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR + strip_delay = 70 + equip_delay_other = 70 + resistance_flags = ACID_PROOF + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC + +//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" = 10, "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" = 10, "bomb" = 25, "bio" = 100, "rad" = 80, "fire" = 30, "acid" = 100) + icon_state = "bio_security" + + +//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" + + +//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/suit/bio_suit/cmo + icon_state = "bio_cmo" + +/obj/item/clothing/head/bio_hood/cmo + icon_state = "bio_cmo" + + +//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" + item_state = "bio_suit" + strip_delay = 40 + equip_delay_other = 20 diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm index 816f0edeba..88bafc842b 100644 --- a/code/modules/clothing/suits/jobs.dm +++ b/code/modules/clothing/suits/jobs.dm @@ -1,219 +1,219 @@ -/* - * Job related - */ - -//Botanist -/obj/item/clothing/suit/apron - name = "apron" - desc = "A basic blue apron." - icon_state = "apron" - item_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) - -//Captain -/obj/item/clothing/suit/captunic - name = "captain's parade tunic" - desc = "Worn by a Captain to show their class." - icon_state = "captunic" - item_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) - -//Chaplain -/obj/item/clothing/suit/chaplain - name = "chaplain suit" - desc = "A piece of clothing adorned by the gods of Coding. Should never exist in this mortal realm." - allowed = list(/obj/item/storage/book/bible, HOLY_WEAPONS, /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/tank/internals/plasmaman) - -/obj/item/clothing/suit/chaplain/nun - name = "nun robe" - desc = "Maximum piety in this star system." - icon_state = "nun" - item_state = "nun" - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS - flags_inv = HIDESHOES|HIDEJUMPSUIT - -/obj/item/clothing/suit/chaplain/studentuni - name = "student robe" - desc = "The uniform of a bygone institute of learning." - icon_state = "studentuni" - item_state = "studentuni" - body_parts_covered = ARMS|CHEST - -/obj/item/clothing/suit/chaplain/witchhunter - name = "witchunter garb" - desc = "This worn outfit saw much use back in the day." - icon_state = "witchhunter" - item_state = "witchhunter" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - -/obj/item/clothing/suit/chaplain/pharaoh - name = "pharaoh tunic" - desc = "Lavish space tomb not included." - icon_state = "pharaoh" - icon_state = "pharaoh" - body_parts_covered = CHEST|GROIN - -/obj/item/clothing/suit/chaplain/holidaypriest - name = "holiday priest" - desc = "This is a nice holiday, my son." - icon_state = "holidaypriest" - item_state = "w_suit" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - flags_inv = HIDEJUMPSUIT - - -//Chef -/obj/item/clothing/suit/toggle/chef - name = "chef's apron" - desc = "An apron-jacket used by a high class chef." - icon_state = "chef" - item_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" - item_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 = "An 18th-century multi-purpose trenchcoat. Someone who wears this means serious business." - icon_state = "detective" - item_state = "det_suit" - blood_overlay_type = "coat" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 10, "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" - item_state = "greydet" - -//Engineering -/obj/item/clothing/suit/hazardvest - name = "hazard vest" - desc = "A high-visibility vest used in work zones." - icon_state = "hazard" - item_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" - item_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" - item_state = "suitjacket_purp" - -/obj/item/clothing/suit/toggle/lawyer/black - name = "black suit jacket" - desc = "A professional suit jacket." - icon_state = "suitjacket_black" - item_state = "ro_suit" - -/obj/item/clothing/suit/toggle/lawyer/black/syndie - desc = "A snappy dress jacket. Suspiciously has no tags or branding." - armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 10, "bio" = 10, "rad" = 10, "fire" = 40, "acid" = 40) - -//Mime -/obj/item/clothing/suit/suspenders - name = "suspenders" - desc = "They suspend the illusion of the mime's play." - icon = 'icons/obj/clothing/belts.dmi' - icon_state = "suspenders" - blood_overlay_type = "armor" //it's the less thing that I can put here - body_parts_covered = NONE - -//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" - item_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" - item_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" - item_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" - item_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" = 10, "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" - item_state = "techpriest" - body_parts_covered = CHEST|GROIN|LEGS|ARMS - hoodtype = /obj/item/clothing/head/hooded/techpriest - mutantrace_variation = NONE - -/obj/item/clothing/head/hooded/techpriest - name = "techpriest's hood" - desc = "A hood for those who REALLY love their toasters." - icon_state = "techpriesthood" - item_state = "techpriesthood" - body_parts_covered = HEAD - flags_inv = HIDEHAIR|HIDEEARS - mutantrace_variation = STYLE_MUZZLE +/* + * Job related + */ + +//Botanist +/obj/item/clothing/suit/apron + name = "apron" + desc = "A basic blue apron." + icon_state = "apron" + item_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) + +//Captain +/obj/item/clothing/suit/captunic + name = "captain's parade tunic" + desc = "Worn by a Captain to show their class." + icon_state = "captunic" + item_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) + +//Chaplain +/obj/item/clothing/suit/chaplain + name = "chaplain suit" + desc = "A piece of clothing adorned by the gods of Coding. Should never exist in this mortal realm." + allowed = list(/obj/item/storage/book/bible, HOLY_WEAPONS, /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/tank/internals/plasmaman) + +/obj/item/clothing/suit/chaplain/nun + name = "nun robe" + desc = "Maximum piety in this star system." + icon_state = "nun" + item_state = "nun" + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS + flags_inv = HIDESHOES|HIDEJUMPSUIT + +/obj/item/clothing/suit/chaplain/studentuni + name = "student robe" + desc = "The uniform of a bygone institute of learning." + icon_state = "studentuni" + item_state = "studentuni" + body_parts_covered = ARMS|CHEST + +/obj/item/clothing/suit/chaplain/witchhunter + name = "witchunter garb" + desc = "This worn outfit saw much use back in the day." + icon_state = "witchhunter" + item_state = "witchhunter" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + +/obj/item/clothing/suit/chaplain/pharaoh + name = "pharaoh tunic" + desc = "Lavish space tomb not included." + icon_state = "pharaoh" + icon_state = "pharaoh" + body_parts_covered = CHEST|GROIN + +/obj/item/clothing/suit/chaplain/holidaypriest + name = "holiday priest" + desc = "This is a nice holiday, my son." + icon_state = "holidaypriest" + item_state = "w_suit" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + flags_inv = HIDEJUMPSUIT + + +//Chef +/obj/item/clothing/suit/toggle/chef + name = "chef's apron" + desc = "An apron-jacket used by a high class chef." + icon_state = "chef" + item_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" + item_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 = "An 18th-century multi-purpose trenchcoat. Someone who wears this means serious business." + icon_state = "detective" + item_state = "det_suit" + blood_overlay_type = "coat" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + armor = list("melee" = 25, "bullet" = 10, "laser" = 25, "energy" = 10, "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" + item_state = "greydet" + +//Engineering +/obj/item/clothing/suit/hazardvest + name = "hazard vest" + desc = "A high-visibility vest used in work zones." + icon_state = "hazard" + item_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" + item_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" + item_state = "suitjacket_purp" + +/obj/item/clothing/suit/toggle/lawyer/black + name = "black suit jacket" + desc = "A professional suit jacket." + icon_state = "suitjacket_black" + item_state = "ro_suit" + +/obj/item/clothing/suit/toggle/lawyer/black/syndie + desc = "A snappy dress jacket. Suspiciously has no tags or branding." + armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 10, "bio" = 10, "rad" = 10, "fire" = 40, "acid" = 40) + +//Mime +/obj/item/clothing/suit/suspenders + name = "suspenders" + desc = "They suspend the illusion of the mime's play." + icon = 'icons/obj/clothing/belts.dmi' + icon_state = "suspenders" + blood_overlay_type = "armor" //it's the less thing that I can put here + body_parts_covered = NONE + +//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" + item_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" + item_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" + item_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" + item_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" = 10, "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" + item_state = "techpriest" + body_parts_covered = CHEST|GROIN|LEGS|ARMS + hoodtype = /obj/item/clothing/head/hooded/techpriest + mutantrace_variation = NONE + +/obj/item/clothing/head/hooded/techpriest + name = "techpriest's hood" + desc = "A hood for those who REALLY love their toasters." + icon_state = "techpriesthood" + item_state = "techpriesthood" + body_parts_covered = HEAD + flags_inv = HIDEHAIR|HIDEEARS + mutantrace_variation = STYLE_MUZZLE diff --git a/code/modules/clothing/suits/labcoat.dm b/code/modules/clothing/suits/labcoat.dm index 6576f0e55b..022bc7c95c 100644 --- a/code/modules/clothing/suits/labcoat.dm +++ b/code/modules/clothing/suits/labcoat.dm @@ -1,70 +1,70 @@ -/obj/item/clothing/suit/toggle/labcoat - name = "labcoat" - desc = "A suit that protects against minor chemical spills." - icon_state = "labcoat" - item_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/hypospray/mkii, - /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" - item_state = "labcoat_cmo" - -/obj/item/clothing/suit/toggle/labcoat/emt - name = "\improper EMT's jacket" - desc = "A dark blue jacket with reflective strips for emergency medical technicians." - icon_state = "labcoat_emt" - item_state = "labcoat_cmo" - -/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" - item_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" + item_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/hypospray/mkii, + /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" + item_state = "labcoat_cmo" + +/obj/item/clothing/suit/toggle/labcoat/emt + name = "\improper EMT's jacket" + desc = "A dark blue jacket with reflective strips for emergency medical technicians." + icon_state = "labcoat_emt" + item_state = "labcoat_cmo" + +/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" + item_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 4862512468..195c9440e1 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -1,908 +1,908 @@ -/* - * 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" - item_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" - item_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" - item_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" - item_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" - item_state = "hgpirate" - - -/obj/item/clothing/suit/cyborg_suit - name = "cyborg suit" - desc = "Suit for a cyborg costume." - icon_state = "death" - item_state = "death" - flags_1 = CONDUCT_1 - fire_resist = T0C+5200 - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS|FEET - 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" - item_state = "justice" - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS|FEET - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - - -/obj/item/clothing/suit/judgerobe - name = "judge's robe" - desc = "This robe commands authority." - icon_state = "judge" - item_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" - item_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" - item_state = "purplebartenderapron" - body_parts_covered = CHEST - -/obj/item/clothing/suit/syndicatefake - name = "black and red space suit replica" - icon_state = "syndicate-black-red" - item_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!" - body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET|HANDS - 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" - item_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" - item_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" - item_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" - item_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" - item_state = "owl_wings" - togglename = "wings" - body_parts_covered = ARMS - 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" - item_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" - item_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 == SLOT_WEAR_SUIT) - 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" - item_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" - item_state = "classicponcho" - body_parts_covered = CHEST|GROIN - -/obj/item/clothing/suit/poncho/green - name = "green poncho" - desc = "Your classic, non-racist poncho. This one is green." - icon_state = "greenponcho" - item_state = "greenponcho" - -/obj/item/clothing/suit/poncho/red - name = "red poncho" - desc = "Your classic, non-racist poncho. This one is red." - icon_state = "redponcho" - item_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" - item_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" - item_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" - item_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS|LEGS|FEET - 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/gun/ballistic/automatic/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/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" - item_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS|LEGS|FEET - 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 - 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" - item_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" - item_state = "labcoat" - body_parts_covered = CHEST|GROIN|ARMS|LEGS|FEET|HANDS - 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" - item_state = "officertanjacket" - body_parts_covered = CHEST|ARMS - -/obj/item/clothing/suit/ran - name = "Shikigami costume" - desc = "A costume that looks like a certain shikigami, is super fluffy." - icon_state = "ran_suit" - item_state = "ran_suit" - body_parts_covered = CHEST|GROIN|LEGS - flags_inv = HIDEJUMPSUIT|HIDETAUR - heat_protection = CHEST|GROIN|LEGS //fluffy tails! -//2061 - -/obj/item/clothing/head/ran - name = "Shikigami hat" - desc = "A hat that looks like it keeps any fluffy ears contained super warm, has little charms over it." - icon_state = "ran_hat" - item_state = "ran_hat" - flags_inv = HIDEEARS - -/* - * 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" - item_state = "straight_jacket" - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - 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" - item_state = "ianshirt" - body_parts_covered = CHEST|GROIN - -/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" - item_state = "nerdshirt" - body_parts_covered = CHEST|GROIN - -/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" - item_state = "vapeshirt" - body_parts_covered = CHEST|GROIN - -/obj/item/clothing/suit/jacket - name = "bomber jacket" - desc = "Aviators not included." - icon_state = "bomberjacket" - item_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|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" - item_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" - item_state = "hostrench" - body_parts_covered = CHEST|GROIN|ARMS - 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" - item_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" - item_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" - item_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" - item_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" - item_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" - item_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" - item_state = "draculacoat" - body_parts_covered = CHEST|ARMS - -/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" - item_state = "drfreeze_coat" - body_parts_covered = CHEST|GROIN|ARMS - -/obj/item/clothing/suit/gothcoat - name = "gothic coat" - desc = "Perfect for those who want stalk in a corner of a bar." - icon_state = "gothcoat" - item_state = "gothcoat" - body_parts_covered = CHEST|ARMS|HAND_LEFT //peculiar - -/obj/item/clothing/suit/xenos - name = "xenos suit" - desc = "A suit made out of chitinous alien hide." - icon_state = "xenos" - item_state = "xenos_helm" - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT - allowed = list(/obj/item/clothing/mask/facehugger/toy) - - - -// WINTER COATS - -/obj/item/clothing/suit/hooded/wintercoat - name = "winter coat" - desc = "A heavy jacket made from 'synthetic' animal furs." - icon_state = "coatwinter" - item_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 - -/obj/item/clothing/suit/hooded/wintercoat/centcom - name = "centcom winter coat" - icon_state = "coatcentcom" - item_state = "coatcentcom" - armor = list("melee" = 40, "bullet" = 45, "laser" = 45, "energy" = 35, "bomb" = 40, "bio" = 25, "rad" = 25, "fire" = 35, "acid" = 50) - hoodtype = /obj/item/clothing/head/hooded/winterhood/centcom - -/obj/item/clothing/suit/hooded/wintercoat/centcom/Initialize() - . = ..() - allowed = GLOB.security_wintercoat_allowed - -/obj/item/clothing/head/hooded/winterhood/centcom - icon_state = "winterhood_centcom" - armor = list("melee" = 40, "bullet" = 45, "laser" = 45, "energy" = 35, "bomb" = 40, "bio" = 25, "rad" = 25, "fire" = 35, "acid" = 50) - -/obj/item/clothing/suit/hooded/wintercoat/captain - name = "captain's winter coat" - icon_state = "coatcaptain" - item_state = "coatcaptain" - armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 10, "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" - -/obj/item/clothing/suit/hooded/wintercoat/hop - name = "head of personnel's winter coat" - icon_state = "coathop" - item_state = "coathop" - armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 5, "bio" = 5, "rad" = 0, "fire" = 0, "acid" = 5) - hoodtype = /obj/item/clothing/head/hooded/winterhood/hop - -/obj/item/clothing/head/hooded/winterhood/hop - icon_state = "winterhood_hop" - -/obj/item/clothing/suit/hooded/wintercoat/security - name = "security winter coat" - icon_state = "coatsecurity" - item_state = "coatsecurity" - armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 10, "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" - -/obj/item/clothing/suit/hooded/wintercoat/hos - name = "head of security's winter coat" - icon_state = "coathos" - item_state = "coathos" - armor = list("melee" = 35, "bullet" = 35, "laser" = 35, "energy" = 15, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 55) - hoodtype = /obj/item/clothing/head/hooded/winterhood/hos - -/obj/item/clothing/suit/hooded/wintercoat/hos/Initialize() - . = ..() - allowed = GLOB.security_wintercoat_allowed - -/obj/item/clothing/head/hooded/winterhood/hos - icon_state = "winterhood_hos" - -/obj/item/clothing/suit/hooded/wintercoat/medical - name = "medical winter coat" - icon_state = "coatmedical" - item_state = "coatmedical" - 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" = 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" - -/obj/item/clothing/suit/hooded/wintercoat/cmo - name = "chief medical officer's winter coat" - icon_state = "coatcmo" - item_state = "coatcmo" - 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" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 0) - hoodtype = /obj/item/clothing/head/hooded/winterhood/cmo - -/obj/item/clothing/head/hooded/winterhood/cmo - icon_state = "winterhood_cmo" - -/obj/item/clothing/suit/hooded/wintercoat/chemistry - name = "chemistry winter coat" - icon_state = "coatchemistry" - item_state = "coatchemistry" - 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" = 0, "bio" = 30, "rad" = 0, "fire" = 30, "acid" = 45) - hoodtype = /obj/item/clothing/head/hooded/winterhood/chemistry - -/obj/item/clothing/head/hooded/winterhood/chemistry - icon_state = "winterhood_chemistry" - -/obj/item/clothing/suit/hooded/wintercoat/viro - name = "virology winter coat" - icon_state = "coatviro" - item_state = "coatviro" - 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" = 0, "bio" = 30, "rad" = 0, "fire" = 0, "acid" = 0) - hoodtype = /obj/item/clothing/head/hooded/winterhood/viro - -/obj/item/clothing/head/hooded/winterhood/viro - icon_state = "winterhood_viro" - -/obj/item/clothing/suit/hooded/wintercoat/science - name = "science winter coat" - icon_state = "coatscience" - item_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" - -/obj/item/clothing/suit/hooded/wintercoat/robotics - name = "robotics winter coat" - icon_state = "coatrobotics" - item_state = "coatrobotics" - 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/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, /obj/item/screwdriver, /obj/item/crowbar, /obj/item/wrench, /obj/item/stack/cable_coil, /obj/item/weldingtool, /obj/item/multitool) - 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/robotics - -/obj/item/clothing/head/hooded/winterhood/robotics - icon_state = "winterhood_robotics" - -/obj/item/clothing/suit/hooded/wintercoat/genetics - name = "genetics winter coat" - icon_state = "coatgenetics" - item_state = "coatgenetics" - 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) - hoodtype = /obj/item/clothing/head/hooded/winterhood/genetics - -/obj/item/clothing/head/hooded/winterhood/genetics - icon_state = "winterhood_genetics" - -/obj/item/clothing/suit/hooded/wintercoat/rd - name = "research director's winter coat" - icon_state = "coatrd" - item_state = "coatrd" - 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" = 5,"energy" = 0, "bomb" = 15, "bio" = 5, "rad" = 5, "fire" = 0, "acid" = 0) - hoodtype = /obj/item/clothing/head/hooded/winterhood/rd - -/obj/item/clothing/head/hooded/winterhood/rd - icon_state = "winterhood_rd" - -/obj/item/clothing/suit/hooded/wintercoat/ce - name = "chief engineer's winter coat" - icon_state = "coatce" - item_state = "coatce" - armor = list("melee" = 0, "bullet" = 0, "laser" = 5, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 30, "fire" = 35, "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/ce - -/obj/item/clothing/head/hooded/winterhood/ce - icon_state = "winterhood_ce" - -/obj/item/clothing/suit/hooded/wintercoat/engineering - name = "engineering winter coat" - icon_state = "coatengineer" - item_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" - -/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos - name = "atmospherics winter coat" - icon_state = "coatatmos" - item_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" - item_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/cosmic - name = "cosmic winter coat" - icon_state = "coatcosmic" - item_state = "coatcosmic" - allowed = list(/obj/item/flashlight) - hoodtype = /obj/item/clothing/head/hooded/winterhood/cosmic - -/obj/item/clothing/head/hooded/winterhood/cosmic - icon_state = "winterhood_cosmic" - -/obj/item/clothing/suit/hooded/wintercoat/janitor - name = "janitors winter coat" - icon_state = "coatjanitor" - item_state = "coatjanitor" - allowed = list(/obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/storage/fancy/cigarettes, /obj/item/lighter,/obj/item/grenade/chem_grenade,/obj/item/lightreplacer,/obj/item/flashlight,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/spray,/obj/item/soap,/obj/item/holosign_creator,/obj/item/key/janitor,/obj/item/melee/flyswatter,/obj/item/paint/paint_remover,/obj/item/storage/bag/trash,/obj/item/reagent_containers/glass/bucket) - hoodtype = /obj/item/clothing/head/hooded/winterhood/janitor - -/obj/item/clothing/head/hooded/winterhood/janitor - icon_state = "winterhood_janitor" - -/obj/item/clothing/suit/hooded/wintercoat/cargo - name = "cargo winter coat" - icon_state = "coatcargo" - item_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/qm - name = "quartermaster's winter coat" - icon_state = "coatqm" - item_state = "coatqm" - hoodtype = /obj/item/clothing/head/hooded/winterhood/qm - -/obj/item/clothing/head/hooded/winterhood/qm - icon_state = "winterhood_qm" - -/obj/item/clothing/suit/hooded/wintercoat/aformal - name = "assistant's formal winter coat" - icon_state = "coataformal" - item_state = "coataformal" - 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/gloves/color/yellow) - hoodtype = /obj/item/clothing/head/hooded/winterhood/aformal - -/obj/item/clothing/head/hooded/winterhood/aformal - icon_state = "winterhood_aformal" - -/obj/item/clothing/suit/hooded/wintercoat/miner - name = "mining winter coat" - icon_state = "coatminer" - item_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" - -/obj/item/clothing/suit/hooded/wintercoat/ratvar - name = "ratvarian winter coat" - icon_state = "coatratvar" - item_state = "coatratvar" - armor = list("melee" = 30, "bullet" = 45, "laser" = -10, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60) - 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/clockwork/replica_fabricator, /obj/item/clockwork/integration_cog, /obj/item/clockwork/slab, /obj/item/clockwork/weapon/ratvarian_spear) - hoodtype = /obj/item/clothing/head/hooded/winterhood/ratvar - var/real = TRUE - -/obj/item/clothing/head/hooded/winterhood/ratvar - icon_state = "winterhood_ratvar" - light_range = 3 - light_power = 1 - light_color = "#B18B25" //clockwork slab background top color - -/obj/item/clothing/suit/hooded/wintercoat/ratvar/equipped(mob/living/user,slot) - ..() - if (slot != SLOT_WEAR_SUIT || !real) - return - if (is_servant_of_ratvar(user)) - return - else - user.dropItemToGround(src) - to_chat(user,"\"Amusing that you think you are fit to wear this.\"") - to_chat(user,"Your skin burns where the coat touched your skin!") - user.adjustFireLoss(rand(10,16)) - -/obj/item/clothing/suit/hooded/wintercoat/narsie - name = "narsian winter coat" - icon_state = "coatnarsie" - item_state = "coatnarsie" - armor = list("melee" = 30, "bullet" = 20, "laser" = 30,"energy" = 10, "bomb" = 30, "bio" = 10, "rad" = 10, "fire" = 30, "acid" = 30) - 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/restraints/legcuffs/bola/cult,/obj/item/melee/cultblade,/obj/item/melee/cultblade/dagger,/obj/item/reagent_containers/glass/beaker/unholywater,/obj/item/cult_shift,/obj/item/flashlight/flare/culttorch,/obj/item/twohanded/cult_spear) - hoodtype = /obj/item/clothing/head/hooded/winterhood/narsie - var/real = TRUE - -/obj/item/clothing/suit/hooded/wintercoat/narsie/equipped(mob/living/user,slot) - ..() - if (slot != SLOT_WEAR_SUIT || !real) - return - if (iscultist(user)) - return - else - user.dropItemToGround(src) - to_chat(user,"\"You are not fit to wear my follower's coat!\"") - to_chat(user,"Sharp spines jab you from within the coat!") - user.adjustBruteLoss(rand(10,16)) - -/obj/item/clothing/head/hooded/winterhood/narsie - icon_state = "winterhood_narsie" - -/obj/item/clothing/suit/hooded/wintercoat/ratvar/fake - name = "brass winter coat" - icon_state = "coatratvar" - item_state = "coatratvar" - 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) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - real = FALSE - -/obj/item/clothing/suit/hooded/wintercoat/narsie/fake - name = "runed winter coat" - icon_state = "coatnarsie" - item_state = "coatnarsie" - 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) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - real = FALSE - - -/obj/item/clothing/suit/spookyghost - name = "spooky ghost" - desc = "This is obviously just a bedsheet, but maybe try it on?" - icon_state = "bedsheet" - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS|HEAD - 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" - body_parts_covered = CHEST|GROIN|LEGS - armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = 0, "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" - item_state = "ghost_sheet" - throwforce = 0 - throw_speed = 1 - throw_range = 2 - w_class = WEIGHT_CLASS_TINY - flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR - body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS|HEAD - alternate_worn_layer = UNDER_HEAD_LAYER - -/obj/item/clothing/suit/flakjack - name = "flak jacket" - desc = "A dilapidated jacket made of a supposedly bullet-proof material (Hint: It isn't.). Smells faintly of napalm." - icon_state = "flakjack" - item_state = "redtag" - blood_overlay_type = "armor" - body_parts_covered = CHEST - resistance_flags = NONE - mutantrace_variation = NONE - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 5, "bio" = 0, "rad" = 0, "fire" = -5, "acid" = -15) //nylon sucks against acid - -/obj/item/clothing/suit/assu_suit - name = "DAB suit" - desc = "A cheap replica of old SWAT armor. On its back, it is written: \"Desperate Assistance Battleforce\"." - icon_state = "assu_suit" - item_state = "assu_suit" - blood_overlay_type = "armor" - body_parts_covered = CHEST|GROIN|ARMS|LEGS - flags_inv = HIDEJUMPSUIT - resistance_flags = NONE - -/obj/item/clothing/suit/hooded/wintercoat/christmascoatr - name = "red christmas coat" - desc = "A festive red Christmas coat! Smells like Candy Cane!" - icon_state = "christmascoatr" - item_state = "christmascoatr" - hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodr - -/obj/item/clothing/head/hooded/winterhood/christmashoodr - icon_state = "christmashoodr" - -/obj/item/clothing/suit/hooded/wintercoat/christmascoatg - name = "green christmas coat" - desc = "A festive green Christmas coat! Smells like Candy Cane!" - icon_state = "christmascoatg" - item_state = "christmascoatg" - hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodg - -/obj/item/clothing/head/hooded/winterhood/christmashoodg - icon_state = "christmashoodg" - -/obj/item/clothing/suit/hooded/wintercoat/christmascoatrg - name = "red and green christmas coat" - desc = "A festive red and green Christmas coat! Smells like Candy Cane!" - icon_state = "christmascoatrg" - item_state = "christmascoatrg" - hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodrg - -/obj/item/clothing/head/hooded/winterhood/christmashoodrg - icon_state = "christmashoodrg" +/* + * 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" + item_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" + item_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" + item_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" + item_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" + item_state = "hgpirate" + + +/obj/item/clothing/suit/cyborg_suit + name = "cyborg suit" + desc = "Suit for a cyborg costume." + icon_state = "death" + item_state = "death" + flags_1 = CONDUCT_1 + fire_resist = T0C+5200 + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS|FEET + 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" + item_state = "justice" + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS|FEET + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + + +/obj/item/clothing/suit/judgerobe + name = "judge's robe" + desc = "This robe commands authority." + icon_state = "judge" + item_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" + item_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" + item_state = "purplebartenderapron" + body_parts_covered = CHEST + +/obj/item/clothing/suit/syndicatefake + name = "black and red space suit replica" + icon_state = "syndicate-black-red" + item_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!" + body_parts_covered = CHEST|ARMS|GROIN|LEGS|FEET|HANDS + 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" + item_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" + item_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" + item_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" + item_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" + item_state = "owl_wings" + togglename = "wings" + body_parts_covered = ARMS + 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" + item_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" + item_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 == SLOT_WEAR_SUIT) + 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" + item_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" + item_state = "classicponcho" + body_parts_covered = CHEST|GROIN + +/obj/item/clothing/suit/poncho/green + name = "green poncho" + desc = "Your classic, non-racist poncho. This one is green." + icon_state = "greenponcho" + item_state = "greenponcho" + +/obj/item/clothing/suit/poncho/red + name = "red poncho" + desc = "Your classic, non-racist poncho. This one is red." + icon_state = "redponcho" + item_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" + item_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" + item_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" + item_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS|LEGS|FEET + 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/gun/ballistic/automatic/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/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" + item_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS|LEGS|FEET + 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 + 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" + item_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" + item_state = "labcoat" + body_parts_covered = CHEST|GROIN|ARMS|LEGS|FEET|HANDS + 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" + item_state = "officertanjacket" + body_parts_covered = CHEST|ARMS + +/obj/item/clothing/suit/ran + name = "Shikigami costume" + desc = "A costume that looks like a certain shikigami, is super fluffy." + icon_state = "ran_suit" + item_state = "ran_suit" + body_parts_covered = CHEST|GROIN|LEGS + flags_inv = HIDEJUMPSUIT|HIDETAUR + heat_protection = CHEST|GROIN|LEGS //fluffy tails! +//2061 + +/obj/item/clothing/head/ran + name = "Shikigami hat" + desc = "A hat that looks like it keeps any fluffy ears contained super warm, has little charms over it." + icon_state = "ran_hat" + item_state = "ran_hat" + flags_inv = HIDEEARS + +/* + * 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" + item_state = "straight_jacket" + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + 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" + item_state = "ianshirt" + body_parts_covered = CHEST|GROIN + +/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" + item_state = "nerdshirt" + body_parts_covered = CHEST|GROIN + +/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" + item_state = "vapeshirt" + body_parts_covered = CHEST|GROIN + +/obj/item/clothing/suit/jacket + name = "bomber jacket" + desc = "Aviators not included." + icon_state = "bomberjacket" + item_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|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" + item_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" + item_state = "hostrench" + body_parts_covered = CHEST|GROIN|ARMS + 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" + item_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" + item_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" + item_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" + item_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" + item_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" + item_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" + item_state = "draculacoat" + body_parts_covered = CHEST|ARMS + +/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" + item_state = "drfreeze_coat" + body_parts_covered = CHEST|GROIN|ARMS + +/obj/item/clothing/suit/gothcoat + name = "gothic coat" + desc = "Perfect for those who want stalk in a corner of a bar." + icon_state = "gothcoat" + item_state = "gothcoat" + body_parts_covered = CHEST|ARMS|HAND_LEFT //peculiar + +/obj/item/clothing/suit/xenos + name = "xenos suit" + desc = "A suit made out of chitinous alien hide." + icon_state = "xenos" + item_state = "xenos_helm" + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT + allowed = list(/obj/item/clothing/mask/facehugger/toy) + + + +// WINTER COATS + +/obj/item/clothing/suit/hooded/wintercoat + name = "winter coat" + desc = "A heavy jacket made from 'synthetic' animal furs." + icon_state = "coatwinter" + item_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 + +/obj/item/clothing/suit/hooded/wintercoat/centcom + name = "centcom winter coat" + icon_state = "coatcentcom" + item_state = "coatcentcom" + armor = list("melee" = 40, "bullet" = 45, "laser" = 45, "energy" = 35, "bomb" = 40, "bio" = 25, "rad" = 25, "fire" = 35, "acid" = 50) + hoodtype = /obj/item/clothing/head/hooded/winterhood/centcom + +/obj/item/clothing/suit/hooded/wintercoat/centcom/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed + +/obj/item/clothing/head/hooded/winterhood/centcom + icon_state = "winterhood_centcom" + armor = list("melee" = 40, "bullet" = 45, "laser" = 45, "energy" = 35, "bomb" = 40, "bio" = 25, "rad" = 25, "fire" = 35, "acid" = 50) + +/obj/item/clothing/suit/hooded/wintercoat/captain + name = "captain's winter coat" + icon_state = "coatcaptain" + item_state = "coatcaptain" + armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 10, "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" + +/obj/item/clothing/suit/hooded/wintercoat/hop + name = "head of personnel's winter coat" + icon_state = "coathop" + item_state = "coathop" + armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 5, "bio" = 5, "rad" = 0, "fire" = 0, "acid" = 5) + hoodtype = /obj/item/clothing/head/hooded/winterhood/hop + +/obj/item/clothing/head/hooded/winterhood/hop + icon_state = "winterhood_hop" + +/obj/item/clothing/suit/hooded/wintercoat/security + name = "security winter coat" + icon_state = "coatsecurity" + item_state = "coatsecurity" + armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 10, "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" + +/obj/item/clothing/suit/hooded/wintercoat/hos + name = "head of security's winter coat" + icon_state = "coathos" + item_state = "coathos" + armor = list("melee" = 35, "bullet" = 35, "laser" = 35, "energy" = 15, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 55) + hoodtype = /obj/item/clothing/head/hooded/winterhood/hos + +/obj/item/clothing/suit/hooded/wintercoat/hos/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed + +/obj/item/clothing/head/hooded/winterhood/hos + icon_state = "winterhood_hos" + +/obj/item/clothing/suit/hooded/wintercoat/medical + name = "medical winter coat" + icon_state = "coatmedical" + item_state = "coatmedical" + 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" = 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" + +/obj/item/clothing/suit/hooded/wintercoat/cmo + name = "chief medical officer's winter coat" + icon_state = "coatcmo" + item_state = "coatcmo" + 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" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 0) + hoodtype = /obj/item/clothing/head/hooded/winterhood/cmo + +/obj/item/clothing/head/hooded/winterhood/cmo + icon_state = "winterhood_cmo" + +/obj/item/clothing/suit/hooded/wintercoat/chemistry + name = "chemistry winter coat" + icon_state = "coatchemistry" + item_state = "coatchemistry" + 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" = 0, "bio" = 30, "rad" = 0, "fire" = 30, "acid" = 45) + hoodtype = /obj/item/clothing/head/hooded/winterhood/chemistry + +/obj/item/clothing/head/hooded/winterhood/chemistry + icon_state = "winterhood_chemistry" + +/obj/item/clothing/suit/hooded/wintercoat/viro + name = "virology winter coat" + icon_state = "coatviro" + item_state = "coatviro" + 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" = 0, "bio" = 30, "rad" = 0, "fire" = 0, "acid" = 0) + hoodtype = /obj/item/clothing/head/hooded/winterhood/viro + +/obj/item/clothing/head/hooded/winterhood/viro + icon_state = "winterhood_viro" + +/obj/item/clothing/suit/hooded/wintercoat/science + name = "science winter coat" + icon_state = "coatscience" + item_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" + +/obj/item/clothing/suit/hooded/wintercoat/robotics + name = "robotics winter coat" + icon_state = "coatrobotics" + item_state = "coatrobotics" + 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/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, /obj/item/screwdriver, /obj/item/crowbar, /obj/item/wrench, /obj/item/stack/cable_coil, /obj/item/weldingtool, /obj/item/multitool) + 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/robotics + +/obj/item/clothing/head/hooded/winterhood/robotics + icon_state = "winterhood_robotics" + +/obj/item/clothing/suit/hooded/wintercoat/genetics + name = "genetics winter coat" + icon_state = "coatgenetics" + item_state = "coatgenetics" + 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) + hoodtype = /obj/item/clothing/head/hooded/winterhood/genetics + +/obj/item/clothing/head/hooded/winterhood/genetics + icon_state = "winterhood_genetics" + +/obj/item/clothing/suit/hooded/wintercoat/rd + name = "research director's winter coat" + icon_state = "coatrd" + item_state = "coatrd" + 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" = 5,"energy" = 0, "bomb" = 15, "bio" = 5, "rad" = 5, "fire" = 0, "acid" = 0) + hoodtype = /obj/item/clothing/head/hooded/winterhood/rd + +/obj/item/clothing/head/hooded/winterhood/rd + icon_state = "winterhood_rd" + +/obj/item/clothing/suit/hooded/wintercoat/ce + name = "chief engineer's winter coat" + icon_state = "coatce" + item_state = "coatce" + armor = list("melee" = 0, "bullet" = 0, "laser" = 5, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 30, "fire" = 35, "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/ce + +/obj/item/clothing/head/hooded/winterhood/ce + icon_state = "winterhood_ce" + +/obj/item/clothing/suit/hooded/wintercoat/engineering + name = "engineering winter coat" + icon_state = "coatengineer" + item_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" + +/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos + name = "atmospherics winter coat" + icon_state = "coatatmos" + item_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" + item_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/cosmic + name = "cosmic winter coat" + icon_state = "coatcosmic" + item_state = "coatcosmic" + allowed = list(/obj/item/flashlight) + hoodtype = /obj/item/clothing/head/hooded/winterhood/cosmic + +/obj/item/clothing/head/hooded/winterhood/cosmic + icon_state = "winterhood_cosmic" + +/obj/item/clothing/suit/hooded/wintercoat/janitor + name = "janitors winter coat" + icon_state = "coatjanitor" + item_state = "coatjanitor" + allowed = list(/obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/storage/fancy/cigarettes, /obj/item/lighter,/obj/item/grenade/chem_grenade,/obj/item/lightreplacer,/obj/item/flashlight,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/spray,/obj/item/soap,/obj/item/holosign_creator,/obj/item/key/janitor,/obj/item/melee/flyswatter,/obj/item/paint/paint_remover,/obj/item/storage/bag/trash,/obj/item/reagent_containers/glass/bucket) + hoodtype = /obj/item/clothing/head/hooded/winterhood/janitor + +/obj/item/clothing/head/hooded/winterhood/janitor + icon_state = "winterhood_janitor" + +/obj/item/clothing/suit/hooded/wintercoat/cargo + name = "cargo winter coat" + icon_state = "coatcargo" + item_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/qm + name = "quartermaster's winter coat" + icon_state = "coatqm" + item_state = "coatqm" + hoodtype = /obj/item/clothing/head/hooded/winterhood/qm + +/obj/item/clothing/head/hooded/winterhood/qm + icon_state = "winterhood_qm" + +/obj/item/clothing/suit/hooded/wintercoat/aformal + name = "assistant's formal winter coat" + icon_state = "coataformal" + item_state = "coataformal" + 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/gloves/color/yellow) + hoodtype = /obj/item/clothing/head/hooded/winterhood/aformal + +/obj/item/clothing/head/hooded/winterhood/aformal + icon_state = "winterhood_aformal" + +/obj/item/clothing/suit/hooded/wintercoat/miner + name = "mining winter coat" + icon_state = "coatminer" + item_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" + +/obj/item/clothing/suit/hooded/wintercoat/ratvar + name = "ratvarian winter coat" + icon_state = "coatratvar" + item_state = "coatratvar" + armor = list("melee" = 30, "bullet" = 45, "laser" = -10, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 60, "acid" = 60) + 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/clockwork/replica_fabricator, /obj/item/clockwork/integration_cog, /obj/item/clockwork/slab, /obj/item/clockwork/weapon/ratvarian_spear) + hoodtype = /obj/item/clothing/head/hooded/winterhood/ratvar + var/real = TRUE + +/obj/item/clothing/head/hooded/winterhood/ratvar + icon_state = "winterhood_ratvar" + light_range = 3 + light_power = 1 + light_color = "#B18B25" //clockwork slab background top color + +/obj/item/clothing/suit/hooded/wintercoat/ratvar/equipped(mob/living/user,slot) + ..() + if (slot != SLOT_WEAR_SUIT || !real) + return + if (is_servant_of_ratvar(user)) + return + else + user.dropItemToGround(src) + to_chat(user,"\"Amusing that you think you are fit to wear this.\"") + to_chat(user,"Your skin burns where the coat touched your skin!") + user.adjustFireLoss(rand(10,16)) + +/obj/item/clothing/suit/hooded/wintercoat/narsie + name = "narsian winter coat" + icon_state = "coatnarsie" + item_state = "coatnarsie" + armor = list("melee" = 30, "bullet" = 20, "laser" = 30,"energy" = 10, "bomb" = 30, "bio" = 10, "rad" = 10, "fire" = 30, "acid" = 30) + 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/restraints/legcuffs/bola/cult,/obj/item/melee/cultblade,/obj/item/melee/cultblade/dagger,/obj/item/reagent_containers/glass/beaker/unholywater,/obj/item/cult_shift,/obj/item/flashlight/flare/culttorch,/obj/item/twohanded/cult_spear) + hoodtype = /obj/item/clothing/head/hooded/winterhood/narsie + var/real = TRUE + +/obj/item/clothing/suit/hooded/wintercoat/narsie/equipped(mob/living/user,slot) + ..() + if (slot != SLOT_WEAR_SUIT || !real) + return + if (iscultist(user)) + return + else + user.dropItemToGround(src) + to_chat(user,"\"You are not fit to wear my follower's coat!\"") + to_chat(user,"Sharp spines jab you from within the coat!") + user.adjustBruteLoss(rand(10,16)) + +/obj/item/clothing/head/hooded/winterhood/narsie + icon_state = "winterhood_narsie" + +/obj/item/clothing/suit/hooded/wintercoat/ratvar/fake + name = "brass winter coat" + icon_state = "coatratvar" + item_state = "coatratvar" + 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) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + real = FALSE + +/obj/item/clothing/suit/hooded/wintercoat/narsie/fake + name = "runed winter coat" + icon_state = "coatnarsie" + item_state = "coatnarsie" + 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) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + real = FALSE + + +/obj/item/clothing/suit/spookyghost + name = "spooky ghost" + desc = "This is obviously just a bedsheet, but maybe try it on?" + icon_state = "bedsheet" + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS|HEAD + 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" + body_parts_covered = CHEST|GROIN|LEGS + armor = list("melee" = 5, "bullet" = 0, "laser" = -5, "energy" = 0, "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" + item_state = "ghost_sheet" + throwforce = 0 + throw_speed = 1 + throw_range = 2 + w_class = WEIGHT_CLASS_TINY + flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR + body_parts_covered = CHEST|GROIN|LEGS|ARMS|HANDS|HEAD + alternate_worn_layer = UNDER_HEAD_LAYER + +/obj/item/clothing/suit/flakjack + name = "flak jacket" + desc = "A dilapidated jacket made of a supposedly bullet-proof material (Hint: It isn't.). Smells faintly of napalm." + icon_state = "flakjack" + item_state = "redtag" + blood_overlay_type = "armor" + body_parts_covered = CHEST + resistance_flags = NONE + mutantrace_variation = NONE + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 5, "bio" = 0, "rad" = 0, "fire" = -5, "acid" = -15) //nylon sucks against acid + +/obj/item/clothing/suit/assu_suit + name = "DAB suit" + desc = "A cheap replica of old SWAT armor. On its back, it is written: \"Desperate Assistance Battleforce\"." + icon_state = "assu_suit" + item_state = "assu_suit" + blood_overlay_type = "armor" + body_parts_covered = CHEST|GROIN|ARMS|LEGS + flags_inv = HIDEJUMPSUIT + resistance_flags = NONE + +/obj/item/clothing/suit/hooded/wintercoat/christmascoatr + name = "red christmas coat" + desc = "A festive red Christmas coat! Smells like Candy Cane!" + icon_state = "christmascoatr" + item_state = "christmascoatr" + hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodr + +/obj/item/clothing/head/hooded/winterhood/christmashoodr + icon_state = "christmashoodr" + +/obj/item/clothing/suit/hooded/wintercoat/christmascoatg + name = "green christmas coat" + desc = "A festive green Christmas coat! Smells like Candy Cane!" + icon_state = "christmascoatg" + item_state = "christmascoatg" + hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodg + +/obj/item/clothing/head/hooded/winterhood/christmashoodg + icon_state = "christmashoodg" + +/obj/item/clothing/suit/hooded/wintercoat/christmascoatrg + name = "red and green christmas coat" + desc = "A festive red and green Christmas coat! Smells like Candy Cane!" + icon_state = "christmascoatrg" + item_state = "christmascoatrg" + hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodrg + +/obj/item/clothing/head/hooded/winterhood/christmashoodrg + icon_state = "christmashoodrg" diff --git a/code/modules/clothing/suits/utility.dm b/code/modules/clothing/suits/utility.dm index a6c272e550..1f28c5ddf3 100644 --- a/code/modules/clothing/suits/utility.dm +++ b/code/modules/clothing/suits/utility.dm @@ -1,157 +1,157 @@ -/* - * 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" - item_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/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/extinguisher, /obj/item/crowbar) - slowdown = 1 - armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 10, "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" - item_state = "firefighter" - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR - mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC - - -/obj/item/clothing/suit/fire/heavy - name = "heavy firesuit" - desc = "An old, bulky thermal protection suit." - icon_state = "thermal" - item_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" - item_state = "firesuit_atmos" - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR - mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC - -/* - * Bomb protection - */ -/obj/item/clothing/head/bomb_hood - name = "bomb hood" - desc = "Use in case of bomb." - icon_state = "bombsuit" - clothing_flags = THICKMATERIAL - armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 10, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - flags_inv = HIDEFACE|HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - 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 - resistance_flags = NONE - mutantrace_variation = STYLE_MUZZLE - - -/obj/item/clothing/suit/bomb_suit - name = "bomb suit" - desc = "A suit designed for safety when handling explosives." - icon_state = "bombsuit" - item_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" = 10, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) - flags_inv = HIDEJUMPSUIT|HIDETAUR - 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 - mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC - - -/obj/item/clothing/head/bomb_hood/security - icon_state = "bombsuit_sec" - item_state = "bombsuit_sec" - -/obj/item/clothing/suit/bomb_suit/security - icon_state = "bombsuit_sec" - item_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" - item_state = "bombsuit_white" - -/obj/item/clothing/suit/bomb_suit/white - icon_state = "bombsuit_white" - item_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 - flags_inv = HIDEMASK|HIDEEARS|HIDEFACE|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - 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 - resistance_flags = NONE - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - mutantrace_variation = STYLE_MUZZLE - -/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" - item_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/emergency_oxygen, /obj/item/tank/internals/plasmaman, /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|HIDETAUR - resistance_flags = NONE - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - mutantrace_variation = STYLE_DIGITIGRADE|STYLE_ALL_TAURIC +/* + * 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" + item_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/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/extinguisher, /obj/item/crowbar) + slowdown = 1 + armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 10, "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" + item_state = "firefighter" + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC + + +/obj/item/clothing/suit/fire/heavy + name = "heavy firesuit" + desc = "An old, bulky thermal protection suit." + icon_state = "thermal" + item_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" + item_state = "firesuit_atmos" + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC + +/* + * Bomb protection + */ +/obj/item/clothing/head/bomb_hood + name = "bomb hood" + desc = "Use in case of bomb." + icon_state = "bombsuit" + clothing_flags = THICKMATERIAL + armor = list("melee" = 20, "bullet" = 0, "laser" = 20,"energy" = 10, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + flags_inv = HIDEFACE|HIDEMASK|HIDEEARS|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + 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 + resistance_flags = NONE + mutantrace_variation = STYLE_MUZZLE + + +/obj/item/clothing/suit/bomb_suit + name = "bomb suit" + desc = "A suit designed for safety when handling explosives." + icon_state = "bombsuit" + item_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" = 10, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 50) + flags_inv = HIDEJUMPSUIT|HIDETAUR + 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 + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC + + +/obj/item/clothing/head/bomb_hood/security + icon_state = "bombsuit_sec" + item_state = "bombsuit_sec" + +/obj/item/clothing/suit/bomb_suit/security + icon_state = "bombsuit_sec" + item_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" + item_state = "bombsuit_white" + +/obj/item/clothing/suit/bomb_suit/white + icon_state = "bombsuit_white" + item_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 + flags_inv = HIDEMASK|HIDEEARS|HIDEFACE|HIDEEYES|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + 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 + resistance_flags = NONE + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + mutantrace_variation = STYLE_MUZZLE + +/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" + item_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/emergency_oxygen, /obj/item/tank/internals/plasmaman, /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|HIDETAUR + resistance_flags = NONE + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_ALL_TAURIC diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 9d1a47f231..3731a1a35e 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -1,228 +1,228 @@ -/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" = 20, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - strip_delay = 50 - equip_delay_other = 50 - 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" - item_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" - item_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" = 20, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) - 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" - item_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" - item_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" - item_state = "blackwizrobe" - -/obj/item/clothing/suit/wizrobe/marisa - name = "witch robe" - desc = "Magic is all about the spell power, ZE!" - icon_state = "marisa" - item_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" - item_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" - item_state = "magusred" - - -/obj/item/clothing/suit/wizrobe/santa - name = "Santa's suit" - desc = "Festive!" - icon_state = "santa" - item_state = "santa" - -/obj/item/clothing/suit/wizrobe/fake - name = "wizard robe" - desc = "A rather dull blue robe meant to mimick real wizard robes." - icon_state = "wizard-fake" - item_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" - item_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" - item_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, 1, 1) - 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" - item_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" = 20, "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" - item_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" = 20, "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) - . = ..() - 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" = 20, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + strip_delay = 50 + equip_delay_other = 50 + 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" + item_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" + item_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" = 20, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 100, "acid" = 100) + 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" + item_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" + item_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" + item_state = "blackwizrobe" + +/obj/item/clothing/suit/wizrobe/marisa + name = "witch robe" + desc = "Magic is all about the spell power, ZE!" + icon_state = "marisa" + item_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" + item_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" + item_state = "magusred" + + +/obj/item/clothing/suit/wizrobe/santa + name = "Santa's suit" + desc = "Festive!" + icon_state = "santa" + item_state = "santa" + +/obj/item/clothing/suit/wizrobe/fake + name = "wizard robe" + desc = "A rather dull blue robe meant to mimick real wizard robes." + icon_state = "wizard-fake" + item_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" + item_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" + item_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, 1, 1) + 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" + item_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" = 20, "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" + item_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" = 20, "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) + . = ..() + 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 0322613c11..8424ea962c 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -1,148 +1,148 @@ -/obj/item/clothing/under - icon = 'icons/obj/clothing/uniforms.dmi' - name = "under" - 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) - 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 - mutantrace_variation = STYLE_DIGITIGRADE - -/obj/item/clothing/under/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) - . = list() - if(!isinhands) - if(damaged_clothes) - . += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform") - if(blood_DNA) - . += mutable_appearance('icons/effects/blood.dmi', "uniformblood", color = blood_DNA_to_color()) - 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(damaging = TRUE) - ..() - 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/New() - 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/equipped(mob/user, slot) - ..() - if(adjusted) - adjusted = NORMAL_STYLE - fitted = initial(fitted) - if(!alt_covers_chest) - body_parts_covered |= CHEST - - if(attached_accessory && slot != SLOT_HANDS && ishuman(user)) - var/mob/living/carbon/human/H = user - attached_accessory.on_uniform_equip(src, user) - 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 - if(attached_accessory.above_suit) - H.update_inv_wear_suit() - - ..() - -/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(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.item_color - if(!accessory_color) - accessory_color = attached_accessory.icon_state - accessory_overlay = mutable_appearance('icons/mob/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() - - 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() - - -/obj/item/clothing/under/examine(mob/user) - . = ..() - 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) +/obj/item/clothing/under + icon = 'icons/obj/clothing/uniforms.dmi' + name = "under" + 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) + 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 + mutantrace_variation = STYLE_DIGITIGRADE + +/obj/item/clothing/under/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) + . = list() + if(!isinhands) + if(damaged_clothes) + . += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform") + if(blood_DNA) + . += mutable_appearance('icons/effects/blood.dmi', "uniformblood", color = blood_DNA_to_color()) + 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(damaging = TRUE) + ..() + 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/New() + 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/equipped(mob/user, slot) + ..() + if(adjusted) + adjusted = NORMAL_STYLE + fitted = initial(fitted) + if(!alt_covers_chest) + body_parts_covered |= CHEST + + if(attached_accessory && slot != SLOT_HANDS && ishuman(user)) + var/mob/living/carbon/human/H = user + attached_accessory.on_uniform_equip(src, user) + 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 + if(attached_accessory.above_suit) + H.update_inv_wear_suit() + + ..() + +/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(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.item_color + if(!accessory_color) + accessory_color = attached_accessory.icon_state + accessory_overlay = mutable_appearance('icons/mob/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() + + 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() + + +/obj/item/clothing/under/examine(mob/user) + . = ..() + 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." \ No newline at end of file diff --git a/code/modules/clothing/under/color.dm b/code/modules/clothing/under/color.dm index 54264ef9d4..12cafb7e46 100644 --- a/code/modules/clothing/under/color.dm +++ b/code/modules/clothing/under/color.dm @@ -1,267 +1,267 @@ -/obj/item/clothing/under/color - desc = "A standard issue colored jumpsuit. Variety is the spice of life!" - -/obj/item/clothing/under/skirt/color - 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) - subtypesof(/obj/item/clothing/under/skirt/color) - /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), SLOT_W_UNIFORM) //or else you end up with naked assistants running around everywhere... - else - new C(loc) - return INITIALIZE_HINT_QDEL - -/obj/item/clothing/under/skirt/color/random - icon_state = "random_jumpsuit" //Skirt variant needed - -/obj/item/clothing/under/skirt/color/random/Initialize() - ..() - var/obj/item/clothing/under/skirt/color/C = pick(subtypesof(/obj/item/clothing/under/skirt/color) - /obj/item/clothing/under/skirt/color/random) - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.equip_to_slot_or_del(new C(H), SLOT_W_UNIFORM) - else - new C(loc) - return INITIALIZE_HINT_QDEL - - -/obj/item/clothing/under/color/black - name = "black jumpsuit" - icon_state = "black" - item_state = "bl_suit" - item_color = "black" - resistance_flags = NONE - -/obj/item/clothing/under/skirt/color/black - name = "black jumpskirt" - icon_state = "black_skirt" - item_state = "bl_suit" - item_color = "black_skirt" - -/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/black/ghost/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, DROPDEL) -/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" - item_state = "gy_suit" - item_color = "grey" - -/obj/item/clothing/under/skirt/color/grey - name = "grey jumpskirt" - desc = "A tasteful grey jumpskirt that reminds you of the good old days." - icon_state = "grey_skirt" - item_state = "gy_suit" - item_color = "grey_skirt" - -/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" - item_state = "b_suit" - item_color = "blue" - -/obj/item/clothing/under/skirt/color/blue - name = "blue jumpskirt" - icon_state = "blue_skirt" - item_state = "b_suit" - item_color = "blue_skirt" - -/obj/item/clothing/under/color/green - name = "green jumpsuit" - icon_state = "green" - item_state = "g_suit" - item_color = "green" - -/obj/item/clothing/under/skirt/color/green - name = "green jumpskirt" - icon_state = "green_skirt" - item_state = "g_suit" - item_color = "green_skirt" - -/obj/item/clothing/under/color/orange - name = "orange jumpsuit" - desc = "Don't wear this near paranoid security officers." - icon_state = "orange" - item_state = "o_suit" - item_color = "orange" - -/obj/item/clothing/under/skirt/color/orange - name = "orange jumpskirt" - icon_state = "orange_skirt" - item_state = "o_suit" - item_color = "orange_skirt" - -/obj/item/clothing/under/color/pink - name = "pink jumpsuit" - icon_state = "pink" - desc = "Just looking at this makes you feel fabulous." - item_state = "p_suit" - item_color = "pink" - -/obj/item/clothing/under/skirt/color/pink - name = "pink jumpskirt" - icon_state = "pink_skirt" - item_state = "p_suit" - item_color = "pink_skirt" - -/obj/item/clothing/under/color/red - name = "red jumpsuit" - icon_state = "red" - item_state = "r_suit" - item_color = "red" - -/obj/item/clothing/under/skirt/color/red - name = "red jumpskirt" - icon_state = "red_skirt" - item_state = "r_suit" - item_color = "red_skirt" - -/obj/item/clothing/under/color/white - name = "white jumpsuit" - icon_state = "white" - item_state = "w_suit" - item_color = "white" - -/obj/item/clothing/under/skirt/color/white - name = "white jumpskirt" - icon_state = "white_skirt" - item_state = "w_suit" - item_color = "white_skirt" - -/obj/item/clothing/under/color/yellow - name = "yellow jumpsuit" - icon_state = "yellow" - item_state = "y_suit" - item_color = "yellow" - -/obj/item/clothing/under/skirt/color/yellow - name = "yellow jumpskirt" - icon_state = "yellow_skirt" - item_state = "y_suit" - item_color = "yellow_skirt" - -/obj/item/clothing/under/color/darkblue - name = "darkblue jumpsuit" - icon_state = "darkblue" - item_state = "b_suit" - item_color = "darkblue" - -/obj/item/clothing/under/skirt/color/darkblue - name = "darkblue jumpskirt" - icon_state = "darkblue_skirt" - item_state = "b_suit" - item_color = "darkblue_skirt" - -/obj/item/clothing/under/color/teal - name = "teal jumpsuit" - icon_state = "teal" - item_state = "b_suit" - item_color = "teal" - -/obj/item/clothing/under/skirt/color/teal - name = "teal jumpskirt" - icon_state = "teal_skirt" - item_state = "b_suit" - item_color = "teal_skirt" - - -/obj/item/clothing/under/color/lightpurple - name = "purple jumpsuit" - icon_state = "lightpurple" - item_state = "p_suit" - item_color = "lightpurple" - -/obj/item/clothing/under/skirt/color/lightpurple - name = "lightpurple jumpskirt" - icon_state = "lightpurple_skirt" - item_state = "p_suit" - item_color = "lightpurple_skirt" - -/obj/item/clothing/under/color/darkgreen - name = "darkgreen jumpsuit" - icon_state = "darkgreen" - item_state = "g_suit" - item_color = "darkgreen" - -/obj/item/clothing/under/skirt/color/darkgreen - name = "darkgreen jumpskirt" - icon_state = "darkgreen_skirt" - item_state = "g_suit" - item_color = "darkgreen_skirt" - -/obj/item/clothing/under/color/lightbrown - name = "lightbrown jumpsuit" - icon_state = "lightbrown" - item_state = "lb_suit" - item_color = "lightbrown" - -/obj/item/clothing/under/skirt/color/lightbrown - name = "lightbrown jumpskirt" - icon_state = "lightbrown_skirt" - item_state = "lb_suit" - item_color = "lightbrown_skirt" - -/obj/item/clothing/under/color/brown - name = "brown jumpsuit" - icon_state = "brown" - item_state = "lb_suit" - item_color = "brown" - -/obj/item/clothing/under/skirt/color/brown - name = "brown jumpskirt" - icon_state = "brown_skirt" - item_state = "lb_suit" - item_color = "brown_skirt" - -/obj/item/clothing/under/color/maroon - name = "maroon jumpsuit" - icon_state = "maroon" - item_state = "r_suit" - item_color = "maroon" - -/obj/item/clothing/under/skirt/color/maroon - name = "maroon jumpskirt" - icon_state = "maroon_skirt" - item_state = "r_suit" - item_color = "maroon_skirt" - -/obj/item/clothing/under/color/rainbow - name = "rainbow jumpsuit" - desc = "A multi-colored jumpsuit!" - icon_state = "rainbow" - item_state = "rainbow" - item_color = "rainbow" - can_adjust = FALSE - -/obj/item/clothing/under/skirt/color/rainbow - name = "rainbow jumpskirt" - desc = "A multi-colored jumpskirt!" - icon_state = "rainbow_skirt" - item_state = "rainbow" - item_color = "rainbow_skirt" - can_adjust = FALSE +/obj/item/clothing/under/color + desc = "A standard issue colored jumpsuit. Variety is the spice of life!" + +/obj/item/clothing/under/skirt/color + 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) - subtypesof(/obj/item/clothing/under/skirt/color) - /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), SLOT_W_UNIFORM) //or else you end up with naked assistants running around everywhere... + else + new C(loc) + return INITIALIZE_HINT_QDEL + +/obj/item/clothing/under/skirt/color/random + icon_state = "random_jumpsuit" //Skirt variant needed + +/obj/item/clothing/under/skirt/color/random/Initialize() + ..() + var/obj/item/clothing/under/skirt/color/C = pick(subtypesof(/obj/item/clothing/under/skirt/color) - /obj/item/clothing/under/skirt/color/random) + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.equip_to_slot_or_del(new C(H), SLOT_W_UNIFORM) + else + new C(loc) + return INITIALIZE_HINT_QDEL + + +/obj/item/clothing/under/color/black + name = "black jumpsuit" + icon_state = "black" + item_state = "bl_suit" + item_color = "black" + resistance_flags = NONE + +/obj/item/clothing/under/skirt/color/black + name = "black jumpskirt" + icon_state = "black_skirt" + item_state = "bl_suit" + item_color = "black_skirt" + +/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/black/ghost/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, DROPDEL) +/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" + item_state = "gy_suit" + item_color = "grey" + +/obj/item/clothing/under/skirt/color/grey + name = "grey jumpskirt" + desc = "A tasteful grey jumpskirt that reminds you of the good old days." + icon_state = "grey_skirt" + item_state = "gy_suit" + item_color = "grey_skirt" + +/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" + item_state = "b_suit" + item_color = "blue" + +/obj/item/clothing/under/skirt/color/blue + name = "blue jumpskirt" + icon_state = "blue_skirt" + item_state = "b_suit" + item_color = "blue_skirt" + +/obj/item/clothing/under/color/green + name = "green jumpsuit" + icon_state = "green" + item_state = "g_suit" + item_color = "green" + +/obj/item/clothing/under/skirt/color/green + name = "green jumpskirt" + icon_state = "green_skirt" + item_state = "g_suit" + item_color = "green_skirt" + +/obj/item/clothing/under/color/orange + name = "orange jumpsuit" + desc = "Don't wear this near paranoid security officers." + icon_state = "orange" + item_state = "o_suit" + item_color = "orange" + +/obj/item/clothing/under/skirt/color/orange + name = "orange jumpskirt" + icon_state = "orange_skirt" + item_state = "o_suit" + item_color = "orange_skirt" + +/obj/item/clothing/under/color/pink + name = "pink jumpsuit" + icon_state = "pink" + desc = "Just looking at this makes you feel fabulous." + item_state = "p_suit" + item_color = "pink" + +/obj/item/clothing/under/skirt/color/pink + name = "pink jumpskirt" + icon_state = "pink_skirt" + item_state = "p_suit" + item_color = "pink_skirt" + +/obj/item/clothing/under/color/red + name = "red jumpsuit" + icon_state = "red" + item_state = "r_suit" + item_color = "red" + +/obj/item/clothing/under/skirt/color/red + name = "red jumpskirt" + icon_state = "red_skirt" + item_state = "r_suit" + item_color = "red_skirt" + +/obj/item/clothing/under/color/white + name = "white jumpsuit" + icon_state = "white" + item_state = "w_suit" + item_color = "white" + +/obj/item/clothing/under/skirt/color/white + name = "white jumpskirt" + icon_state = "white_skirt" + item_state = "w_suit" + item_color = "white_skirt" + +/obj/item/clothing/under/color/yellow + name = "yellow jumpsuit" + icon_state = "yellow" + item_state = "y_suit" + item_color = "yellow" + +/obj/item/clothing/under/skirt/color/yellow + name = "yellow jumpskirt" + icon_state = "yellow_skirt" + item_state = "y_suit" + item_color = "yellow_skirt" + +/obj/item/clothing/under/color/darkblue + name = "darkblue jumpsuit" + icon_state = "darkblue" + item_state = "b_suit" + item_color = "darkblue" + +/obj/item/clothing/under/skirt/color/darkblue + name = "darkblue jumpskirt" + icon_state = "darkblue_skirt" + item_state = "b_suit" + item_color = "darkblue_skirt" + +/obj/item/clothing/under/color/teal + name = "teal jumpsuit" + icon_state = "teal" + item_state = "b_suit" + item_color = "teal" + +/obj/item/clothing/under/skirt/color/teal + name = "teal jumpskirt" + icon_state = "teal_skirt" + item_state = "b_suit" + item_color = "teal_skirt" + + +/obj/item/clothing/under/color/lightpurple + name = "purple jumpsuit" + icon_state = "lightpurple" + item_state = "p_suit" + item_color = "lightpurple" + +/obj/item/clothing/under/skirt/color/lightpurple + name = "lightpurple jumpskirt" + icon_state = "lightpurple_skirt" + item_state = "p_suit" + item_color = "lightpurple_skirt" + +/obj/item/clothing/under/color/darkgreen + name = "darkgreen jumpsuit" + icon_state = "darkgreen" + item_state = "g_suit" + item_color = "darkgreen" + +/obj/item/clothing/under/skirt/color/darkgreen + name = "darkgreen jumpskirt" + icon_state = "darkgreen_skirt" + item_state = "g_suit" + item_color = "darkgreen_skirt" + +/obj/item/clothing/under/color/lightbrown + name = "lightbrown jumpsuit" + icon_state = "lightbrown" + item_state = "lb_suit" + item_color = "lightbrown" + +/obj/item/clothing/under/skirt/color/lightbrown + name = "lightbrown jumpskirt" + icon_state = "lightbrown_skirt" + item_state = "lb_suit" + item_color = "lightbrown_skirt" + +/obj/item/clothing/under/color/brown + name = "brown jumpsuit" + icon_state = "brown" + item_state = "lb_suit" + item_color = "brown" + +/obj/item/clothing/under/skirt/color/brown + name = "brown jumpskirt" + icon_state = "brown_skirt" + item_state = "lb_suit" + item_color = "brown_skirt" + +/obj/item/clothing/under/color/maroon + name = "maroon jumpsuit" + icon_state = "maroon" + item_state = "r_suit" + item_color = "maroon" + +/obj/item/clothing/under/skirt/color/maroon + name = "maroon jumpskirt" + icon_state = "maroon_skirt" + item_state = "r_suit" + item_color = "maroon_skirt" + +/obj/item/clothing/under/color/rainbow + name = "rainbow jumpsuit" + desc = "A multi-colored jumpsuit!" + icon_state = "rainbow" + item_state = "rainbow" + item_color = "rainbow" + can_adjust = FALSE + +/obj/item/clothing/under/skirt/color/rainbow + name = "rainbow jumpskirt" + desc = "A multi-colored jumpskirt!" + icon_state = "rainbow_skirt" + item_state = "rainbow" + item_color = "rainbow_skirt" + can_adjust = FALSE diff --git a/code/modules/clothing/under/jobs/civilian.dm b/code/modules/clothing/under/jobs/civilian.dm index e42049ef6b..2d0e2055ab 100644 --- a/code/modules/clothing/under/jobs/civilian.dm +++ b/code/modules/clothing/under/jobs/civilian.dm @@ -1,424 +1,424 @@ -//Alphabetical order of civilian jobs. - -/obj/item/clothing/under/rank/bartender - desc = "It looks like it could use some more flair." - name = "bartender's uniform" - icon_state = "barman" - item_state = "bar_suit" - item_color = "barman" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/bartender/purple - desc = "It looks like it has lots of flair!" - name = "purple bartender's uniform" - icon_state = "purplebartender" - item_state = "purplebartender" - item_color = "purplebartender" - can_adjust = FALSE - -/obj/item/clothing/under/rank/bartender/skirt - name = "bartender's skirt" - desc = "It looks like it could use some more flair." - icon_state = "barman_skirt" - item_state = "bar_suit" - item_color = "barman_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/captain //Alright, technically not a 'civilian' but its better then giving a .dm file for a single define. - desc = "It's a blue jumpsuit with some gold markings denoting the rank of \"Captain\"." - name = "captain's jumpsuit" - icon_state = "captain" - item_state = "b_suit" - item_color = "captain" - sensor_mode = SENSOR_COORDS - random_sensor = FALSE - -/obj/item/clothing/under/rank/captain/skirt - name = "captain's jumpskirt" - desc = "It's a blue jumpskirt with some gold markings denoting the rank of \"Captain\"." - icon_state = "captain_skirt" - item_state = "b_suit" - item_color = "captain_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/cargo - name = "quartermaster's jumpsuit" - desc = "It's a jumpsuit worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper." - icon_state = "qm" - item_state = "lb_suit" - item_color = "qm" - -/obj/item/clothing/under/rank/cargo/skirt - name = "quartermaster's jumpskirt" - desc = "It's a jumpskirt worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper." - icon_state = "qm_skirt" - item_state = "lb_suit" - item_color = "qm_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/cargotech - name = "cargo technician's jumpsuit" - desc = "Shooooorts! They're comfy and easy to wear!" - icon_state = "cargotech" - item_state = "lb_suit" - item_color = "cargo" - body_parts_covered = CHEST|GROIN|ARMS - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/cargotech/skirt - name = "cargo technician's jumpskirt" - desc = "Skiiiiirts! They're comfy and easy to wear" - icon_state = "cargo_skirt" - item_state = "lb_suit" - item_color = "cargo_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/chaplain - desc = "It's a black jumpsuit, often worn by religious folk." - name = "chaplain's jumpsuit" - icon_state = "chaplain" - item_state = "bl_suit" - item_color = "chapblack" - can_adjust = FALSE - -/obj/item/clothing/under/rank/chaplain/skirt - name = "chaplain's jumpskirt" - desc = "It's a black jumpskirt, often worn by religious folk." - icon_state = "chapblack_skirt" - item_state = "bl_suit" - item_color = "chapblack_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/chef - name = "cook's suit" - desc = "A suit which is given only to the most hardcore cooks in space." - icon_state = "chef" - item_color = "chef" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/chef/skirt - name = "cook's skirt" - desc = "A skirt which is given only to the most hardcore cooks in space." - icon_state = "chef_skirt" - item_color = "chef_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/clown - name = "clown suit" - desc = "'HONK!'" - icon_state = "clown" - item_state = "clown" - item_color = "clown" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE -/obj/item/clothing/under/rank/blueclown - name = "blue clown suit" - desc = "'BLUE HONK!'" - icon_state = "blueclown" - item_state = "blueclown" - item_color = "blueclown" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - mutantrace_variation = NONE - -/obj/item/clothing/under/rank/greenclown - name = "green clown suit" - desc = "'GREEN HONK!'" - icon_state = "greenclown" - item_state = "greenclown" - item_color = "greenclown" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - mutantrace_variation = NONE - -/obj/item/clothing/under/rank/yellowclown - name = "yellow clown suit" - desc = "'YELLOW HONK!'" - icon_state = "yellowclown" - item_state = "yellowclown" - item_color = "yellowclown" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - mutantrace_variation = NONE - -/obj/item/clothing/under/rank/purpleclown - name = "purple clown suit" - desc = "'PURPLE HONK!'" - icon_state = "purpleclown" - item_state = "purpleclown" - item_color = "purpleclown" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - mutantrace_variation = NONE - -/obj/item/clothing/under/rank/orangeclown - name = "orange clown suit" - desc = "'ORANGE HONK!'" - icon_state = "orangeclown" - item_state = "orangeclown" - item_color = "orangeclown" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - mutantrace_variation = NONE - -/obj/item/clothing/under/rank/rainbowclown - name = "rainbow clown suit" - desc = "'R A I N B O W HONK!'" - icon_state = "rainbowclown" - item_state = "rainbowclown" - item_color = "rainbowclown" - fitted = FEMALE_UNIFORM_TOP - can_adjust = FALSE - mutantrace_variation = NONE - -/obj/item/clothing/under/rank/clown/Initialize() - . = ..() - AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50) -/obj/item/clothing/under/rank/head_of_personnel - desc = "It's a jumpsuit worn by someone who works in the position of \"Head of Personnel\"." - name = "head of personnel's jumpsuit" - icon_state = "hop" - item_state = "b_suit" - item_color = "hop" - can_adjust = FALSE - -/obj/item/clothing/under/rank/head_of_personnel/skirt - name = "head of personnel's jumpskirt" - desc = "It's a jumpskirt worn by someone who works in the position of \"Head of Personnel\"." - icon_state = "hop_skirt" - item_state = "b_suit" - item_color = "hop_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/hydroponics - desc = "It's a jumpsuit designed to protect against minor plant-related hazards." - name = "botanist's jumpsuit" - icon_state = "hydroponics" - item_state = "g_suit" - item_color = "hydroponics" - permeability_coefficient = 0.5 - -/obj/item/clothing/under/rank/hydroponics/skirt - name = "botanist's jumpskirt" - desc = "It's a jumpskirt designed to protect against minor plant-related hazards." - icon_state = "hydroponics_skirt" - item_state = "g_suit" - item_color = "hydroponics_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/janitor - desc = "It's the official uniform of the station's janitor. It has minor protection from biohazards." - name = "janitor's jumpsuit" - icon_state = "janitor" - item_color = "janitor" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/under/rank/janitor/skirt - name = "janitor's jumpskirt" - desc = "It's the official skirt of the station's janitor. It has minor protection from biohazards." - icon_state = "janitor_skirt" - item_color = "janitor_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/lawyer - desc = "Slick threads." - name = "Lawyer suit" - can_adjust = FALSE -/obj/item/clothing/under/lawyer/black - name = "lawyer black suit" - icon_state = "lawyer_black" - item_state = "lawyer_black" - item_color = "lawyer_black" - -/obj/item/clothing/under/lawyer/black/skirt - name = "lawyer black suitskirt" - icon_state = "lawyer_black_skirt" - item_state = "lawyer_black" - item_color = "lawyer_black_skirt" - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/lawyer/female - name = "female black suit" - icon_state = "black_suit_fem" - item_state = "bl_suit" - item_color = "black_suit_fem" - -/obj/item/clothing/under/lawyer/red - name = "lawyer red suit" - icon_state = "lawyer_red" - item_state = "lawyer_red" - item_color = "lawyer_red" - -/obj/item/clothing/under/lawyer/female/skirt - name = "female black suitskirt" - icon_state = "black_suit_fem_skirt" - item_state = "bl_suit" - item_color = "black_suit_fem_skirt" - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/lawyer/red/skirt - name = "lawyer red suitskirt" - icon_state = "lawyer_red_skirt" - item_state = "lawyer_red" - item_color = "lawyer_red_skirt" - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/lawyer/blue - name = "lawyer blue suit" - icon_state = "lawyer_blue" - item_state = "lawyer_blue" - item_color = "lawyer_blue" - -/obj/item/clothing/under/lawyer/blue/skirt - name = "lawyer blue suitskirt" - icon_state = "lawyer_blue_skirt" - item_state = "lawyer_blue" - item_color = "lawyer_blue_skirt" - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/lawyer/bluesuit - name = "blue suit" - desc = "A classy suit and tie." - icon_state = "bluesuit" - item_state = "b_suit" - item_color = "bluesuit" - can_adjust = TRUE - alt_covers_chest = TRUE - -/obj/item/clothing/under/lawyer/bluesuit/skirt - name = "blue suitskirt" - desc = "A classy suitskirt and tie." - icon_state = "bluesuit_skirt" - item_state = "b_suit" - item_color = "bluesuit_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/lawyer/purpsuit - name = "purple suit" - icon_state = "lawyer_purp" - item_state = "p_suit" - item_color = "lawyer_purp" - fitted = NO_FEMALE_UNIFORM - can_adjust = TRUE - alt_covers_chest = TRUE - -/obj/item/clothing/under/lawyer/purpsuit/skirt - name = "purple suitskirt" - icon_state = "lawyer_purp_skirt" - item_state = "p_suit" - item_color = "lawyer_purp_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/lawyer/blacksuit - name = "black suit" - desc = "A professional black suit. Nanotrasen Investigation Bureau approved!" - icon_state = "blacksuit" - item_state = "bar_suit" - item_color = "blacksuit" - can_adjust = TRUE - alt_covers_chest = TRUE - -/obj/item/clothing/under/lawyer/blacksuit/skirt - name = "black suitskirt" - desc = "A professional black suit. Nanotrasen Investigation Bureau approved!" - icon_state = "blacksuit_skirt" - item_state = "bar_suit" - item_color = "blacksuit_skirt" - can_adjust = FALSE - alt_covers_chest = TRUE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/curator - name = "sensible suit" - desc = "It's very... sensible." - icon_state = "red_suit" - item_state = "red_suit" - item_color = "red_suit" - can_adjust = FALSE - -/obj/item/clothing/under/lawyer/really_black - name = "executive suit" - desc = "A formal black suit and red tie, intended for the station's finest." - icon_state = "really_black_suit" - item_state = "bl_suit" - item_color = "really_black_suit" - -/obj/item/clothing/under/lawyer/really_black/skirt - name = "executive suitskirt" - desc = "A formal black suitskirt and red tie, intended for the station's finest." - icon_state = "really_black_suit_skirt" - item_state = "bl_suit" - item_color = "really_black_suit_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/curator/skirt - name = "sensible suitskirt" - desc = "It's very... sensible." - icon_state = "red_suit_skirt" - item_state = "red_suit" - item_color = "red_suit_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/curator/treasure_hunter - name = "treasure hunter uniform" - desc = "A rugged uniform suitable for treasure hunting." - icon_state = "curator" - item_state = "curator" - item_color = "curator" - -/obj/item/clothing/under/rank/mime - name = "mime's outfit" - desc = "It's not very colourful." - icon_state = "mime" - item_state = "mime" - item_color = "mime" - -/obj/item/clothing/under/rank/mime/skirt - name = "mime's skirt" - desc = "It's not very colourful." - icon_state = "mime_skirt" - item_state = "mime" - item_color = "mime_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/miner - desc = "It's a snappy jumpsuit with a sturdy set of overalls. It is very dirty." - name = "shaft miner's jumpsuit" - icon_state = "miner" - item_state = "miner" - item_color = "miner" -/obj/item/clothing/under/rank/miner/lavaland - desc = "A green uniform for operating in hazardous environments." - name = "shaft miner's jumpsuit" - icon_state = "explorer" - item_state = "explorer" - item_color = "explorer" - can_adjust = FALSE +//Alphabetical order of civilian jobs. + +/obj/item/clothing/under/rank/bartender + desc = "It looks like it could use some more flair." + name = "bartender's uniform" + icon_state = "barman" + item_state = "bar_suit" + item_color = "barman" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/bartender/purple + desc = "It looks like it has lots of flair!" + name = "purple bartender's uniform" + icon_state = "purplebartender" + item_state = "purplebartender" + item_color = "purplebartender" + can_adjust = FALSE + +/obj/item/clothing/under/rank/bartender/skirt + name = "bartender's skirt" + desc = "It looks like it could use some more flair." + icon_state = "barman_skirt" + item_state = "bar_suit" + item_color = "barman_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/captain //Alright, technically not a 'civilian' but its better then giving a .dm file for a single define. + desc = "It's a blue jumpsuit with some gold markings denoting the rank of \"Captain\"." + name = "captain's jumpsuit" + icon_state = "captain" + item_state = "b_suit" + item_color = "captain" + sensor_mode = SENSOR_COORDS + random_sensor = FALSE + +/obj/item/clothing/under/rank/captain/skirt + name = "captain's jumpskirt" + desc = "It's a blue jumpskirt with some gold markings denoting the rank of \"Captain\"." + icon_state = "captain_skirt" + item_state = "b_suit" + item_color = "captain_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/cargo + name = "quartermaster's jumpsuit" + desc = "It's a jumpsuit worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper." + icon_state = "qm" + item_state = "lb_suit" + item_color = "qm" + +/obj/item/clothing/under/rank/cargo/skirt + name = "quartermaster's jumpskirt" + desc = "It's a jumpskirt worn by the quartermaster. It's specially designed to prevent back injuries caused by pushing paper." + icon_state = "qm_skirt" + item_state = "lb_suit" + item_color = "qm_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/cargotech + name = "cargo technician's jumpsuit" + desc = "Shooooorts! They're comfy and easy to wear!" + icon_state = "cargotech" + item_state = "lb_suit" + item_color = "cargo" + body_parts_covered = CHEST|GROIN|ARMS + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/cargotech/skirt + name = "cargo technician's jumpskirt" + desc = "Skiiiiirts! They're comfy and easy to wear" + icon_state = "cargo_skirt" + item_state = "lb_suit" + item_color = "cargo_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/chaplain + desc = "It's a black jumpsuit, often worn by religious folk." + name = "chaplain's jumpsuit" + icon_state = "chaplain" + item_state = "bl_suit" + item_color = "chapblack" + can_adjust = FALSE + +/obj/item/clothing/under/rank/chaplain/skirt + name = "chaplain's jumpskirt" + desc = "It's a black jumpskirt, often worn by religious folk." + icon_state = "chapblack_skirt" + item_state = "bl_suit" + item_color = "chapblack_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/chef + name = "cook's suit" + desc = "A suit which is given only to the most hardcore cooks in space." + icon_state = "chef" + item_color = "chef" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/chef/skirt + name = "cook's skirt" + desc = "A skirt which is given only to the most hardcore cooks in space." + icon_state = "chef_skirt" + item_color = "chef_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/clown + name = "clown suit" + desc = "'HONK!'" + icon_state = "clown" + item_state = "clown" + item_color = "clown" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE +/obj/item/clothing/under/rank/blueclown + name = "blue clown suit" + desc = "'BLUE HONK!'" + icon_state = "blueclown" + item_state = "blueclown" + item_color = "blueclown" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + mutantrace_variation = NONE + +/obj/item/clothing/under/rank/greenclown + name = "green clown suit" + desc = "'GREEN HONK!'" + icon_state = "greenclown" + item_state = "greenclown" + item_color = "greenclown" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + mutantrace_variation = NONE + +/obj/item/clothing/under/rank/yellowclown + name = "yellow clown suit" + desc = "'YELLOW HONK!'" + icon_state = "yellowclown" + item_state = "yellowclown" + item_color = "yellowclown" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + mutantrace_variation = NONE + +/obj/item/clothing/under/rank/purpleclown + name = "purple clown suit" + desc = "'PURPLE HONK!'" + icon_state = "purpleclown" + item_state = "purpleclown" + item_color = "purpleclown" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + mutantrace_variation = NONE + +/obj/item/clothing/under/rank/orangeclown + name = "orange clown suit" + desc = "'ORANGE HONK!'" + icon_state = "orangeclown" + item_state = "orangeclown" + item_color = "orangeclown" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + mutantrace_variation = NONE + +/obj/item/clothing/under/rank/rainbowclown + name = "rainbow clown suit" + desc = "'R A I N B O W HONK!'" + icon_state = "rainbowclown" + item_state = "rainbowclown" + item_color = "rainbowclown" + fitted = FEMALE_UNIFORM_TOP + can_adjust = FALSE + mutantrace_variation = NONE + +/obj/item/clothing/under/rank/clown/Initialize() + . = ..() + AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50) +/obj/item/clothing/under/rank/head_of_personnel + desc = "It's a jumpsuit worn by someone who works in the position of \"Head of Personnel\"." + name = "head of personnel's jumpsuit" + icon_state = "hop" + item_state = "b_suit" + item_color = "hop" + can_adjust = FALSE + +/obj/item/clothing/under/rank/head_of_personnel/skirt + name = "head of personnel's jumpskirt" + desc = "It's a jumpskirt worn by someone who works in the position of \"Head of Personnel\"." + icon_state = "hop_skirt" + item_state = "b_suit" + item_color = "hop_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/hydroponics + desc = "It's a jumpsuit designed to protect against minor plant-related hazards." + name = "botanist's jumpsuit" + icon_state = "hydroponics" + item_state = "g_suit" + item_color = "hydroponics" + permeability_coefficient = 0.5 + +/obj/item/clothing/under/rank/hydroponics/skirt + name = "botanist's jumpskirt" + desc = "It's a jumpskirt designed to protect against minor plant-related hazards." + icon_state = "hydroponics_skirt" + item_state = "g_suit" + item_color = "hydroponics_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/janitor + desc = "It's the official uniform of the station's janitor. It has minor protection from biohazards." + name = "janitor's jumpsuit" + icon_state = "janitor" + item_color = "janitor" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/under/rank/janitor/skirt + name = "janitor's jumpskirt" + desc = "It's the official skirt of the station's janitor. It has minor protection from biohazards." + icon_state = "janitor_skirt" + item_color = "janitor_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/lawyer + desc = "Slick threads." + name = "Lawyer suit" + can_adjust = FALSE +/obj/item/clothing/under/lawyer/black + name = "lawyer black suit" + icon_state = "lawyer_black" + item_state = "lawyer_black" + item_color = "lawyer_black" + +/obj/item/clothing/under/lawyer/black/skirt + name = "lawyer black suitskirt" + icon_state = "lawyer_black_skirt" + item_state = "lawyer_black" + item_color = "lawyer_black_skirt" + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/lawyer/female + name = "female black suit" + icon_state = "black_suit_fem" + item_state = "bl_suit" + item_color = "black_suit_fem" + +/obj/item/clothing/under/lawyer/red + name = "lawyer red suit" + icon_state = "lawyer_red" + item_state = "lawyer_red" + item_color = "lawyer_red" + +/obj/item/clothing/under/lawyer/female/skirt + name = "female black suitskirt" + icon_state = "black_suit_fem_skirt" + item_state = "bl_suit" + item_color = "black_suit_fem_skirt" + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/lawyer/red/skirt + name = "lawyer red suitskirt" + icon_state = "lawyer_red_skirt" + item_state = "lawyer_red" + item_color = "lawyer_red_skirt" + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/lawyer/blue + name = "lawyer blue suit" + icon_state = "lawyer_blue" + item_state = "lawyer_blue" + item_color = "lawyer_blue" + +/obj/item/clothing/under/lawyer/blue/skirt + name = "lawyer blue suitskirt" + icon_state = "lawyer_blue_skirt" + item_state = "lawyer_blue" + item_color = "lawyer_blue_skirt" + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/lawyer/bluesuit + name = "blue suit" + desc = "A classy suit and tie." + icon_state = "bluesuit" + item_state = "b_suit" + item_color = "bluesuit" + can_adjust = TRUE + alt_covers_chest = TRUE + +/obj/item/clothing/under/lawyer/bluesuit/skirt + name = "blue suitskirt" + desc = "A classy suitskirt and tie." + icon_state = "bluesuit_skirt" + item_state = "b_suit" + item_color = "bluesuit_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/lawyer/purpsuit + name = "purple suit" + icon_state = "lawyer_purp" + item_state = "p_suit" + item_color = "lawyer_purp" + fitted = NO_FEMALE_UNIFORM + can_adjust = TRUE + alt_covers_chest = TRUE + +/obj/item/clothing/under/lawyer/purpsuit/skirt + name = "purple suitskirt" + icon_state = "lawyer_purp_skirt" + item_state = "p_suit" + item_color = "lawyer_purp_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/lawyer/blacksuit + name = "black suit" + desc = "A professional black suit. Nanotrasen Investigation Bureau approved!" + icon_state = "blacksuit" + item_state = "bar_suit" + item_color = "blacksuit" + can_adjust = TRUE + alt_covers_chest = TRUE + +/obj/item/clothing/under/lawyer/blacksuit/skirt + name = "black suitskirt" + desc = "A professional black suit. Nanotrasen Investigation Bureau approved!" + icon_state = "blacksuit_skirt" + item_state = "bar_suit" + item_color = "blacksuit_skirt" + can_adjust = FALSE + alt_covers_chest = TRUE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/curator + name = "sensible suit" + desc = "It's very... sensible." + icon_state = "red_suit" + item_state = "red_suit" + item_color = "red_suit" + can_adjust = FALSE + +/obj/item/clothing/under/lawyer/really_black + name = "executive suit" + desc = "A formal black suit and red tie, intended for the station's finest." + icon_state = "really_black_suit" + item_state = "bl_suit" + item_color = "really_black_suit" + +/obj/item/clothing/under/lawyer/really_black/skirt + name = "executive suitskirt" + desc = "A formal black suitskirt and red tie, intended for the station's finest." + icon_state = "really_black_suit_skirt" + item_state = "bl_suit" + item_color = "really_black_suit_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/curator/skirt + name = "sensible suitskirt" + desc = "It's very... sensible." + icon_state = "red_suit_skirt" + item_state = "red_suit" + item_color = "red_suit_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/curator/treasure_hunter + name = "treasure hunter uniform" + desc = "A rugged uniform suitable for treasure hunting." + icon_state = "curator" + item_state = "curator" + item_color = "curator" + +/obj/item/clothing/under/rank/mime + name = "mime's outfit" + desc = "It's not very colourful." + icon_state = "mime" + item_state = "mime" + item_color = "mime" + +/obj/item/clothing/under/rank/mime/skirt + name = "mime's skirt" + desc = "It's not very colourful." + icon_state = "mime_skirt" + item_state = "mime" + item_color = "mime_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/miner + desc = "It's a snappy jumpsuit with a sturdy set of overalls. It is very dirty." + name = "shaft miner's jumpsuit" + icon_state = "miner" + item_state = "miner" + item_color = "miner" +/obj/item/clothing/under/rank/miner/lavaland + desc = "A green uniform for operating in hazardous environments." + name = "shaft miner's jumpsuit" + icon_state = "explorer" + item_state = "explorer" + item_color = "explorer" + can_adjust = FALSE diff --git a/code/modules/clothing/under/jobs/engineering.dm b/code/modules/clothing/under/jobs/engineering.dm index f65b05c280..1f74a572ed 100644 --- a/code/modules/clothing/under/jobs/engineering.dm +++ b/code/modules/clothing/under/jobs/engineering.dm @@ -1,81 +1,81 @@ -//Contains: Engineering department jumpsuits -/obj/item/clothing/under/rank/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" - item_state = "gy_suit" - item_color = "chief" - 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/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" - item_state = "gy_suit" - item_color = "chief_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/atmospheric_technician - desc = "It's a jumpsuit worn by atmospheric technicians." - name = "atmospheric technician's jumpsuit" - icon_state = "atmos" - item_state = "atmos_suit" - item_color = "atmos" - resistance_flags = NONE - -/obj/item/clothing/under/rank/atmospheric_technician/skirt - name = "atmospheric technician's jumpskirt" - desc = "It's a jumpskirt worn by atmospheric technicians." - icon_state = "atmos_skirt" - item_state = "atmos_suit" - item_color = "atmos_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/engineer - desc = "It's an orange high visibility jumpsuit worn by engineers. It has minor radiation shielding." - name = "engineer's jumpsuit" - icon_state = "engine" - item_state = "engi_suit" - item_color = "engine" - 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/engineer/hazard - name = "engineer's hazard jumpsuit" - desc = "A high visibility jumpsuit made from heat and radiation resistant materials." - icon_state = "hazard" - item_state = "suit-orange" - item_color = "hazard" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/engineer/skirt - name = "engineer's jumpskirt" - desc = "It's an orange high visibility jumpskirt worn by engineers." - icon_state = "engine_skirt" - item_state = "engi_suit" - item_color = "engine_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/roboticist - desc = "It's a slimming black with reinforced seams; great for industrial work." - name = "roboticist's jumpsuit" - icon_state = "robotics" - item_state = "robotics" - item_color = "robotics" - resistance_flags = NONE - -/obj/item/clothing/under/rank/roboticist/skirt - name = "roboticist's jumpskirt" - desc = "It's a slimming black with reinforced seams; great for industrial work." - icon_state = "robotics_skirt" - item_state = "robotics" - item_color = "robotics_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP +//Contains: Engineering department jumpsuits +/obj/item/clothing/under/rank/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" + item_state = "gy_suit" + item_color = "chief" + 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/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" + item_state = "gy_suit" + item_color = "chief_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/atmospheric_technician + desc = "It's a jumpsuit worn by atmospheric technicians." + name = "atmospheric technician's jumpsuit" + icon_state = "atmos" + item_state = "atmos_suit" + item_color = "atmos" + resistance_flags = NONE + +/obj/item/clothing/under/rank/atmospheric_technician/skirt + name = "atmospheric technician's jumpskirt" + desc = "It's a jumpskirt worn by atmospheric technicians." + icon_state = "atmos_skirt" + item_state = "atmos_suit" + item_color = "atmos_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/engineer + desc = "It's an orange high visibility jumpsuit worn by engineers. It has minor radiation shielding." + name = "engineer's jumpsuit" + icon_state = "engine" + item_state = "engi_suit" + item_color = "engine" + 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/engineer/hazard + name = "engineer's hazard jumpsuit" + desc = "A high visibility jumpsuit made from heat and radiation resistant materials." + icon_state = "hazard" + item_state = "suit-orange" + item_color = "hazard" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/engineer/skirt + name = "engineer's jumpskirt" + desc = "It's an orange high visibility jumpskirt worn by engineers." + icon_state = "engine_skirt" + item_state = "engi_suit" + item_color = "engine_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/roboticist + desc = "It's a slimming black with reinforced seams; great for industrial work." + name = "roboticist's jumpsuit" + icon_state = "robotics" + item_state = "robotics" + item_color = "robotics" + resistance_flags = NONE + +/obj/item/clothing/under/rank/roboticist/skirt + name = "roboticist's jumpskirt" + desc = "It's a slimming black with reinforced seams; great for industrial work." + icon_state = "robotics_skirt" + item_state = "robotics" + item_color = "robotics_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP diff --git a/code/modules/clothing/under/jobs/medsci.dm b/code/modules/clothing/under/jobs/medsci.dm index 7b7a205021..c27882aac7 100644 --- a/code/modules/clothing/under/jobs/medsci.dm +++ b/code/modules/clothing/under/jobs/medsci.dm @@ -1,207 +1,207 @@ -/* - * Science - */ -/obj/item/clothing/under/rank/research_director - desc = "It's a suit worn by those with the know-how to achieve the position of \"Research Director\". Its fabric provides minor protection from biological contaminants." - name = "research director's vest suit" - icon_state = "director" - item_state = "lb_suit" - item_color = "director" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 35) - can_adjust = FALSE - -/obj/item/clothing/under/rank/research_director/skirt - name = "research director's vest suitskirt" - desc = "It's a suitskirt worn by those with the know-how to achieve the position of \"Research Director\". Its fabric provides minor protection from biological contaminants." - icon_state = "director_skirt" - item_state = "lb_suit" - item_color = "director_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/research_director/alt - desc = "Maybe you'll engineer your own half-man, half-pig creature some day. Its fabric provides minor protection from biological contaminants." - name = "research director's tan suit" - icon_state = "rdwhimsy" - item_state = "rdwhimsy" - item_color = "rdwhimsy" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - can_adjust = TRUE - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/research_director/alt/skirt - name = "research director's tan suitskirt" - desc = "Maybe you'll engineer your own half-man, half-pig creature some day. Its fabric provides minor protection from biological contaminants." - icon_state = "rdwhimsy_skirt" - item_state = "rdwhimsy" - item_color = "rdwhimsy_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/research_director/turtleneck - desc = "A dark purple turtleneck and tan khakis, for a director with a superior sense of style." - name = "research director's turtleneck" - icon_state = "rdturtle" - item_state = "p_suit" - item_color = "rdturtle" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - can_adjust = TRUE - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/research_director/turtleneck/skirt - name = "research director's turtleneck skirt" - desc = "A dark purple turtleneck and tan khaki skirt, for a director with a superior sense of style." - icon_state = "rdturtle_skirt" - item_state = "p_suit" - item_color = "rdturtle_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/scientist - desc = "It's made of a special fiber that provides minor protection against explosives. It has markings that denote the wearer as a scientist." - name = "scientist's jumpsuit" - icon_state = "toxins" - item_state = "w_suit" - item_color = "toxinswhite" - permeability_coefficient = 0.5 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/under/rank/scientist/skirt - name = "scientist's jumpskirt" - desc = "It's made of a special fiber that provides minor protection against explosives. It has markings that denote the wearer as a scientist." - icon_state = "toxinswhite_skirt" - item_state = "w_suit" - item_color = "toxinswhite_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/chemist - desc = "It's made of a special fiber that gives special protection against biohazards. It has a chemist rank stripe on it." - name = "chemist's jumpsuit" - icon_state = "chemistry" - item_state = "w_suit" - item_color = "chemistrywhite" - permeability_coefficient = 0.5 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 65) - -/obj/item/clothing/under/rank/chemist/skirt - name = "chemist's jumpskirt" - desc = "It's made of a special fiber that gives special protection against biohazards. It has a chemist rank stripe on it." - icon_state = "chemistrywhite_skirt" - item_state = "w_suit" - item_color = "chemistrywhite_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/* - * Medical - */ -/obj/item/clothing/under/rank/chief_medical_officer - desc = "It's a jumpsuit worn by those with the experience to be \"Chief Medical Officer\". It provides minor biological protection." - name = "chief medical officer's jumpsuit" - icon_state = "cmo" - item_state = "w_suit" - item_color = "cmo" - permeability_coefficient = 0.5 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/under/rank/chief_medical_officer/skirt - name = "chief medical officer's jumpskirt" - desc = "It's a jumpskirt worn by those with the experience to be \"Chief Medical Officer\". It provides minor biological protection." - icon_state = "cmo_skirt" - item_state = "w_suit" - item_color = "cmo_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/geneticist - desc = "It's made of a special fiber that gives special protection against biohazards. It has a genetics rank stripe on it." - name = "geneticist's jumpsuit" - icon_state = "genetics" - item_state = "w_suit" - item_color = "geneticswhite" - permeability_coefficient = 0.5 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/under/rank/geneticist/skirt - name = "geneticist's jumpskirt" - desc = "It's made of a special fiber that gives special protection against biohazards. It has a genetics rank stripe on it." - icon_state = "geneticswhite_skirt" - item_state = "w_suit" - item_color = "geneticswhite_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/virologist - desc = "It's made of a special fiber that gives special protection against biohazards. It has a virologist rank stripe on it." - name = "virologist's jumpsuit" - icon_state = "virology" - item_state = "w_suit" - item_color = "virologywhite" - permeability_coefficient = 0.5 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/under/rank/virologist/skirt - name = "virologist's jumpskirt" - desc = "It's made of a special fiber that gives special protection against biohazards. It has a virologist rank stripe on it." - icon_state = "virologywhite_skirt" - item_state = "w_suit" - item_color = "virologywhite_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/nursesuit - desc = "It's a jumpsuit commonly worn by nursing staff in the medical department." - name = "nurse's suit" - icon_state = "nursesuit" - item_state = "w_suit" - item_color = "nursesuit" - permeability_coefficient = 0.5 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - body_parts_covered = CHEST|GROIN|ARMS - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE -/obj/item/clothing/under/rank/medical - desc = "It's made of a special fiber that provides minor protection against biohazards. It has a cross on the chest denoting that the wearer is trained medical personnel." - name = "medical doctor's jumpsuit" - icon_state = "medical" - item_state = "w_suit" - item_color = "medical" - permeability_coefficient = 0.5 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) -/obj/item/clothing/under/rank/medical/blue - name = "medical scrubs" - desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in baby blue." - icon_state = "scrubsblue" - item_color = "scrubsblue" - can_adjust = FALSE -/obj/item/clothing/under/rank/medical/green - name = "medical scrubs" - desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in dark green." - icon_state = "scrubsgreen" - item_color = "scrubsgreen" - can_adjust = FALSE -/obj/item/clothing/under/rank/medical/purple - name = "medical scrubs" - desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in deep purple." - icon_state = "scrubspurple" - item_color = "scrubspurple" - can_adjust = FALSE - -/obj/item/clothing/under/rank/medical/skirt - name = "medical doctor's jumpskirt" - desc = "It's made of a special fiber that provides minor protection against biohazards. It has a cross on the chest denoting that the wearer is trained medical personnel." - icon_state = "medical_skirt" - item_state = "w_suit" - item_color = "medical_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP +/* + * Science + */ +/obj/item/clothing/under/rank/research_director + desc = "It's a suit worn by those with the know-how to achieve the position of \"Research Director\". Its fabric provides minor protection from biological contaminants." + name = "research director's vest suit" + icon_state = "director" + item_state = "lb_suit" + item_color = "director" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 35) + can_adjust = FALSE + +/obj/item/clothing/under/rank/research_director/skirt + name = "research director's vest suitskirt" + desc = "It's a suitskirt worn by those with the know-how to achieve the position of \"Research Director\". Its fabric provides minor protection from biological contaminants." + icon_state = "director_skirt" + item_state = "lb_suit" + item_color = "director_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/research_director/alt + desc = "Maybe you'll engineer your own half-man, half-pig creature some day. Its fabric provides minor protection from biological contaminants." + name = "research director's tan suit" + icon_state = "rdwhimsy" + item_state = "rdwhimsy" + item_color = "rdwhimsy" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + can_adjust = TRUE + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/research_director/alt/skirt + name = "research director's tan suitskirt" + desc = "Maybe you'll engineer your own half-man, half-pig creature some day. Its fabric provides minor protection from biological contaminants." + icon_state = "rdwhimsy_skirt" + item_state = "rdwhimsy" + item_color = "rdwhimsy_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/research_director/turtleneck + desc = "A dark purple turtleneck and tan khakis, for a director with a superior sense of style." + name = "research director's turtleneck" + icon_state = "rdturtle" + item_state = "p_suit" + item_color = "rdturtle" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + can_adjust = TRUE + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/research_director/turtleneck/skirt + name = "research director's turtleneck skirt" + desc = "A dark purple turtleneck and tan khaki skirt, for a director with a superior sense of style." + icon_state = "rdturtle_skirt" + item_state = "p_suit" + item_color = "rdturtle_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/scientist + desc = "It's made of a special fiber that provides minor protection against explosives. It has markings that denote the wearer as a scientist." + name = "scientist's jumpsuit" + icon_state = "toxins" + item_state = "w_suit" + item_color = "toxinswhite" + permeability_coefficient = 0.5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/under/rank/scientist/skirt + name = "scientist's jumpskirt" + desc = "It's made of a special fiber that provides minor protection against explosives. It has markings that denote the wearer as a scientist." + icon_state = "toxinswhite_skirt" + item_state = "w_suit" + item_color = "toxinswhite_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/chemist + desc = "It's made of a special fiber that gives special protection against biohazards. It has a chemist rank stripe on it." + name = "chemist's jumpsuit" + icon_state = "chemistry" + item_state = "w_suit" + item_color = "chemistrywhite" + permeability_coefficient = 0.5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 65) + +/obj/item/clothing/under/rank/chemist/skirt + name = "chemist's jumpskirt" + desc = "It's made of a special fiber that gives special protection against biohazards. It has a chemist rank stripe on it." + icon_state = "chemistrywhite_skirt" + item_state = "w_suit" + item_color = "chemistrywhite_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/* + * Medical + */ +/obj/item/clothing/under/rank/chief_medical_officer + desc = "It's a jumpsuit worn by those with the experience to be \"Chief Medical Officer\". It provides minor biological protection." + name = "chief medical officer's jumpsuit" + icon_state = "cmo" + item_state = "w_suit" + item_color = "cmo" + permeability_coefficient = 0.5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/under/rank/chief_medical_officer/skirt + name = "chief medical officer's jumpskirt" + desc = "It's a jumpskirt worn by those with the experience to be \"Chief Medical Officer\". It provides minor biological protection." + icon_state = "cmo_skirt" + item_state = "w_suit" + item_color = "cmo_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/geneticist + desc = "It's made of a special fiber that gives special protection against biohazards. It has a genetics rank stripe on it." + name = "geneticist's jumpsuit" + icon_state = "genetics" + item_state = "w_suit" + item_color = "geneticswhite" + permeability_coefficient = 0.5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/under/rank/geneticist/skirt + name = "geneticist's jumpskirt" + desc = "It's made of a special fiber that gives special protection against biohazards. It has a genetics rank stripe on it." + icon_state = "geneticswhite_skirt" + item_state = "w_suit" + item_color = "geneticswhite_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/virologist + desc = "It's made of a special fiber that gives special protection against biohazards. It has a virologist rank stripe on it." + name = "virologist's jumpsuit" + icon_state = "virology" + item_state = "w_suit" + item_color = "virologywhite" + permeability_coefficient = 0.5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/under/rank/virologist/skirt + name = "virologist's jumpskirt" + desc = "It's made of a special fiber that gives special protection against biohazards. It has a virologist rank stripe on it." + icon_state = "virologywhite_skirt" + item_state = "w_suit" + item_color = "virologywhite_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/nursesuit + desc = "It's a jumpsuit commonly worn by nursing staff in the medical department." + name = "nurse's suit" + icon_state = "nursesuit" + item_state = "w_suit" + item_color = "nursesuit" + permeability_coefficient = 0.5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + body_parts_covered = CHEST|GROIN|ARMS + fitted = NO_FEMALE_UNIFORM + can_adjust = FALSE +/obj/item/clothing/under/rank/medical + desc = "It's made of a special fiber that provides minor protection against biohazards. It has a cross on the chest denoting that the wearer is trained medical personnel." + name = "medical doctor's jumpsuit" + icon_state = "medical" + item_state = "w_suit" + item_color = "medical" + permeability_coefficient = 0.5 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) +/obj/item/clothing/under/rank/medical/blue + name = "medical scrubs" + desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in baby blue." + icon_state = "scrubsblue" + item_color = "scrubsblue" + can_adjust = FALSE +/obj/item/clothing/under/rank/medical/green + name = "medical scrubs" + desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in dark green." + icon_state = "scrubsgreen" + item_color = "scrubsgreen" + can_adjust = FALSE +/obj/item/clothing/under/rank/medical/purple + name = "medical scrubs" + desc = "It's made of a special fiber that provides minor protection against biohazards. This one is in deep purple." + icon_state = "scrubspurple" + item_color = "scrubspurple" + can_adjust = FALSE + +/obj/item/clothing/under/rank/medical/skirt + name = "medical doctor's jumpskirt" + desc = "It's made of a special fiber that provides minor protection against biohazards. It has a cross on the chest denoting that the wearer is trained medical personnel." + icon_state = "medical_skirt" + item_state = "w_suit" + item_color = "medical_skirt" + 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 6c00d6969c..186c377921 100644 --- a/code/modules/clothing/under/jobs/security.dm +++ b/code/modules/clothing/under/jobs/security.dm @@ -1,200 +1,200 @@ -/* - * Contains: - * Security - * Detective - * Navy uniforms - */ -/* - * Security - */ -/obj/item/clothing/under/rank/security - name = "security jumpsuit" - desc = "A tactical security jumpsuit for officers complete with Nanotrasen belt buckle." - icon_state = "rsecurity" - item_state = "r_suit" - item_color = "rsecurity" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - strip_delay = 50 - alt_covers_chest = TRUE - sensor_mode = SENSOR_COORDS - random_sensor = FALSE -/obj/item/clothing/under/rank/security/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" - item_state = "gy_suit" - item_color = "security" -/obj/item/clothing/under/rank/security/skirt - name = "security jumpskirt" - desc = "A \"tactical\" security jumpsuit with the legs replaced by a skirt." - icon_state = "secskirt" - item_state = "r_suit" - item_color = "secskirt" - 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/warden - name = "security suit" - desc = "A formal security suit for officers complete with Nanotrasen belt buckle." - icon_state = "rwarden" - item_state = "r_suit" - item_color = "rwarden" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - strip_delay = 50 - alt_covers_chest = TRUE - sensor_mode = 3 - random_sensor = FALSE -/obj/item/clothing/under/rank/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" - item_state = "gy_suit" - item_color = "warden" - -/obj/item/clothing/under/rank/warden/skirt - name = "warden's suitskirt" - desc = "A formal security suitskirt for officers complete with Nanotrasen belt buckle." - icon_state = "rwarden_skirt" - item_state = "r_suit" - item_color = "rwarden_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/* - * Detective - */ -/obj/item/clothing/under/rank/det - name = "hard-worn suit" - desc = "Someone who wears this means business." - icon_state = "detective" - item_state = "det" - item_color = "detective" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) - strip_delay = 50 - alt_covers_chest = TRUE - sensor_mode = 3 - random_sensor = FALSE - -/obj/item/clothing/under/rank/det/skirt - name = "detective's suitskirt" - desc = "Someone who wears this means business." - icon_state = "detective_skirt" - item_state = "det" - item_color = "detective_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/det/grey - name = "noir suit" - desc = "A hard-boiled private investigator's grey suit, complete with tie clip." - icon_state = "greydet" - item_state = "greydet" - item_color = "greydet" - alt_covers_chest = TRUE - -/obj/item/clothing/under/rank/det/grey/skirt - name = "noir suitskirt" - desc = "A hard-boiled private investigator's grey suitskirt, complete with tie clip." - icon_state = "greydet_skirt" - item_state = "greydet" - item_color = "greydet_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/* - * Head of Security - */ -/obj/item/clothing/under/rank/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" - item_state = "r_suit" - item_color = "rhos" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 60 - alt_covers_chest = TRUE - sensor_mode = 3 - random_sensor = FALSE - -/obj/item/clothing/under/rank/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" - item_state = "r_suit" - item_color = "rhos_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/obj/item/clothing/under/rank/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" - item_state = "gy_suit" - item_color = "hos" -/obj/item/clothing/under/rank/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" - item_state = "bl_suit" - item_color = "hosalt" - -/obj/item/clothing/under/rank/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" - item_state = "bl_suit" - item_color = "hosalt_skirt" - body_parts_covered = CHEST|GROIN|ARMS - can_adjust = FALSE - fitted = FEMALE_UNIFORM_TOP - -/* - * Navy uniforms - */ -/obj/item/clothing/under/rank/security/navyblue - name = "security officer's formal uniform" - desc = "The latest in fashionable security outfits." - icon_state = "officerblueclothes" - item_state = "officerblueclothes" - item_color = "officerblueclothes" - alt_covers_chest = TRUE -/obj/item/clothing/under/rank/head_of_security/navyblue - 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" - item_state = "hosblueclothes" - item_color = "hosblueclothes" - alt_covers_chest = TRUE -/obj/item/clothing/under/rank/warden/navyblue - desc = "The insignia on this uniform tells you that this uniform belongs to the Warden." - name = "warden's formal uniform" - icon_state = "wardenblueclothes" - item_state = "wardenblueclothes" - item_color = "wardenblueclothes" - alt_covers_chest = TRUE -/* - *Blueshirt - */ -/obj/item/clothing/under/rank/security/blueshirt - name = "blue shirt and tie" - desc = "I'm a little busy right now, Calhoun." - icon_state = "blueshift" - item_state = "blueshift" - item_color = "blueshift" - can_adjust = FALSE -/* - *Spacepol - */ -/obj/item/clothing/under/rank/security/spacepol - name = "police uniform" - desc = "Space not controlled by megacorporations, planets, or pirates is under the jurisdiction of Spacepol." - icon_state = "spacepol" - item_state = "spacepol" - item_color = "spacepol" - can_adjust = FALSE +/* + * Contains: + * Security + * Detective + * Navy uniforms + */ +/* + * Security + */ +/obj/item/clothing/under/rank/security + name = "security jumpsuit" + desc = "A tactical security jumpsuit for officers complete with Nanotrasen belt buckle." + icon_state = "rsecurity" + item_state = "r_suit" + item_color = "rsecurity" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + strip_delay = 50 + alt_covers_chest = TRUE + sensor_mode = SENSOR_COORDS + random_sensor = FALSE +/obj/item/clothing/under/rank/security/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" + item_state = "gy_suit" + item_color = "security" +/obj/item/clothing/under/rank/security/skirt + name = "security jumpskirt" + desc = "A \"tactical\" security jumpsuit with the legs replaced by a skirt." + icon_state = "secskirt" + item_state = "r_suit" + item_color = "secskirt" + 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/warden + name = "security suit" + desc = "A formal security suit for officers complete with Nanotrasen belt buckle." + icon_state = "rwarden" + item_state = "r_suit" + item_color = "rwarden" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + strip_delay = 50 + alt_covers_chest = TRUE + sensor_mode = 3 + random_sensor = FALSE +/obj/item/clothing/under/rank/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" + item_state = "gy_suit" + item_color = "warden" + +/obj/item/clothing/under/rank/warden/skirt + name = "warden's suitskirt" + desc = "A formal security suitskirt for officers complete with Nanotrasen belt buckle." + icon_state = "rwarden_skirt" + item_state = "r_suit" + item_color = "rwarden_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/* + * Detective + */ +/obj/item/clothing/under/rank/det + name = "hard-worn suit" + desc = "Someone who wears this means business." + icon_state = "detective" + item_state = "det" + item_color = "detective" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30) + strip_delay = 50 + alt_covers_chest = TRUE + sensor_mode = 3 + random_sensor = FALSE + +/obj/item/clothing/under/rank/det/skirt + name = "detective's suitskirt" + desc = "Someone who wears this means business." + icon_state = "detective_skirt" + item_state = "det" + item_color = "detective_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/det/grey + name = "noir suit" + desc = "A hard-boiled private investigator's grey suit, complete with tie clip." + icon_state = "greydet" + item_state = "greydet" + item_color = "greydet" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/det/grey/skirt + name = "noir suitskirt" + desc = "A hard-boiled private investigator's grey suitskirt, complete with tie clip." + icon_state = "greydet_skirt" + item_state = "greydet" + item_color = "greydet_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/* + * Head of Security + */ +/obj/item/clothing/under/rank/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" + item_state = "r_suit" + item_color = "rhos" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 60 + alt_covers_chest = TRUE + sensor_mode = 3 + random_sensor = FALSE + +/obj/item/clothing/under/rank/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" + item_state = "r_suit" + item_color = "rhos_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/obj/item/clothing/under/rank/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" + item_state = "gy_suit" + item_color = "hos" +/obj/item/clothing/under/rank/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" + item_state = "bl_suit" + item_color = "hosalt" + +/obj/item/clothing/under/rank/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" + item_state = "bl_suit" + item_color = "hosalt_skirt" + body_parts_covered = CHEST|GROIN|ARMS + can_adjust = FALSE + fitted = FEMALE_UNIFORM_TOP + +/* + * Navy uniforms + */ +/obj/item/clothing/under/rank/security/navyblue + name = "security officer's formal uniform" + desc = "The latest in fashionable security outfits." + icon_state = "officerblueclothes" + item_state = "officerblueclothes" + item_color = "officerblueclothes" + alt_covers_chest = TRUE +/obj/item/clothing/under/rank/head_of_security/navyblue + 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" + item_state = "hosblueclothes" + item_color = "hosblueclothes" + alt_covers_chest = TRUE +/obj/item/clothing/under/rank/warden/navyblue + desc = "The insignia on this uniform tells you that this uniform belongs to the Warden." + name = "warden's formal uniform" + icon_state = "wardenblueclothes" + item_state = "wardenblueclothes" + item_color = "wardenblueclothes" + alt_covers_chest = TRUE +/* + *Blueshirt + */ +/obj/item/clothing/under/rank/security/blueshirt + name = "blue shirt and tie" + desc = "I'm a little busy right now, Calhoun." + icon_state = "blueshift" + item_state = "blueshift" + item_color = "blueshift" + can_adjust = FALSE +/* + *Spacepol + */ +/obj/item/clothing/under/rank/security/spacepol + name = "police uniform" + desc = "Space not controlled by megacorporations, planets, or pirates is under the jurisdiction of Spacepol." + icon_state = "spacepol" + item_state = "spacepol" + item_color = "spacepol" + can_adjust = FALSE diff --git a/code/modules/clothing/under/pants.dm b/code/modules/clothing/under/pants.dm index 97bb4c48bc..4b0c92af88 100644 --- a/code/modules/clothing/under/pants.dm +++ b/code/modules/clothing/under/pants.dm @@ -1,108 +1,108 @@ -/obj/item/clothing/under/pants - gender = PLURAL - body_parts_covered = GROIN|LEGS - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE - -/obj/item/clothing/under/pants/classicjeans - name = "classic jeans" - desc = "You feel cooler already." - icon_state = "jeansclassic" - item_color = "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" - item_color = "jeansmustang" - -/obj/item/clothing/under/pants/blackjeans - name = "black jeans" - desc = "Only for those who can pull it off." - icon_state = "jeansblack" - item_color = "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" - item_color = "jeansyoungfolks" - -/obj/item/clothing/under/pants/white - name = "white pants" - desc = "Plain white pants. Boring." - icon_state = "whitepants" - item_color = "whitepants" - -/obj/item/clothing/under/pants/red - name = "red pants" - desc = "Bright red pants. Overflowing with personality." - icon_state = "redpants" - item_color = "redpants" - -/obj/item/clothing/under/pants/black - name = "black pants" - desc = "These pants are dark, like your soul." - icon_state = "blackpants" - item_color = "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" - item_color = "tanpants" - -/obj/item/clothing/under/pants/track - name = "track pants" - desc = "A pair of track pants, for the athletic." - icon_state = "trackpants" - item_color = "trackpants" - -/obj/item/clothing/under/pants/jeans - name = "jeans" - desc = "A nondescript pair of tough blue jeans." - icon_state = "jeans" - item_color = "jeans" - -/obj/item/clothing/under/pants/khaki - name = "khaki pants" - desc = "A pair of dust beige khaki pants." - icon_state = "khaki" - item_color = "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" - item_color = "camopants" - -/obj/item/clothing/under/pants/jeanripped - name = "ripped jeans" - desc = "If you're wearing this you're poor or a rebel" - icon_state = "jean_ripped" - item_color = "jean_ripped" - -/obj/item/clothing/under/pants/jeanshort - name = "jean shorts" - desc = "These are really just jeans cut in half" - icon_state = "jean_shorts" - item_color = "jean_shorts" - -/obj/item/clothing/under/pants/denimskirt - name = "denim skirt" - desc = "These are really just a jean leg hole cut from a pair" - icon_state = "denim_skirt" - item_color = "denim_skirt" - -/obj/item/clothing/under/pants/chaps - name = "black chaps" - body_parts_covered = LEGS - desc = "Yeehaw" - icon_state = "chaps" - item_color = "chaps" - -/obj/item/clothing/under/pants/yoga - name = "yoga pants" - desc = "Comfy!" - icon_state = "yoga_pants" - item_color = "yoga_pants" +/obj/item/clothing/under/pants + gender = PLURAL + body_parts_covered = GROIN|LEGS + fitted = NO_FEMALE_UNIFORM + can_adjust = FALSE + +/obj/item/clothing/under/pants/classicjeans + name = "classic jeans" + desc = "You feel cooler already." + icon_state = "jeansclassic" + item_color = "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" + item_color = "jeansmustang" + +/obj/item/clothing/under/pants/blackjeans + name = "black jeans" + desc = "Only for those who can pull it off." + icon_state = "jeansblack" + item_color = "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" + item_color = "jeansyoungfolks" + +/obj/item/clothing/under/pants/white + name = "white pants" + desc = "Plain white pants. Boring." + icon_state = "whitepants" + item_color = "whitepants" + +/obj/item/clothing/under/pants/red + name = "red pants" + desc = "Bright red pants. Overflowing with personality." + icon_state = "redpants" + item_color = "redpants" + +/obj/item/clothing/under/pants/black + name = "black pants" + desc = "These pants are dark, like your soul." + icon_state = "blackpants" + item_color = "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" + item_color = "tanpants" + +/obj/item/clothing/under/pants/track + name = "track pants" + desc = "A pair of track pants, for the athletic." + icon_state = "trackpants" + item_color = "trackpants" + +/obj/item/clothing/under/pants/jeans + name = "jeans" + desc = "A nondescript pair of tough blue jeans." + icon_state = "jeans" + item_color = "jeans" + +/obj/item/clothing/under/pants/khaki + name = "khaki pants" + desc = "A pair of dust beige khaki pants." + icon_state = "khaki" + item_color = "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" + item_color = "camopants" + +/obj/item/clothing/under/pants/jeanripped + name = "ripped jeans" + desc = "If you're wearing this you're poor or a rebel" + icon_state = "jean_ripped" + item_color = "jean_ripped" + +/obj/item/clothing/under/pants/jeanshort + name = "jean shorts" + desc = "These are really just jeans cut in half" + icon_state = "jean_shorts" + item_color = "jean_shorts" + +/obj/item/clothing/under/pants/denimskirt + name = "denim skirt" + desc = "These are really just a jean leg hole cut from a pair" + icon_state = "denim_skirt" + item_color = "denim_skirt" + +/obj/item/clothing/under/pants/chaps + name = "black chaps" + body_parts_covered = LEGS + desc = "Yeehaw" + icon_state = "chaps" + item_color = "chaps" + +/obj/item/clothing/under/pants/yoga + name = "yoga pants" + desc = "Comfy!" + icon_state = "yoga_pants" + item_color = "yoga_pants" diff --git a/code/modules/clothing/under/shorts.dm b/code/modules/clothing/under/shorts.dm index a615081f24..14c408bfa8 100644 --- a/code/modules/clothing/under/shorts.dm +++ b/code/modules/clothing/under/shorts.dm @@ -1,37 +1,37 @@ -/obj/item/clothing/under/shorts - name = "athletic shorts" - desc = "95% Polyester, 5% Spandex!" - gender = PLURAL - body_parts_covered = GROIN - fitted = NO_FEMALE_UNIFORM - can_adjust = FALSE - -/obj/item/clothing/under/shorts/red - name = "red athletic shorts" - icon_state = "redshorts" - item_color = "redshorts" - -/obj/item/clothing/under/shorts/green - name = "green athletic shorts" - icon_state = "greenshorts" - item_color = "greenshorts" - -/obj/item/clothing/under/shorts/blue - name = "blue athletic shorts" - icon_state = "blueshorts" - item_color = "blueshorts" - -/obj/item/clothing/under/shorts/black - name = "black athletic shorts" - icon_state = "blackshorts" - item_color = "blackshorts" - -/obj/item/clothing/under/shorts/grey - name = "grey athletic shorts" - icon_state = "greyshorts" - item_color = "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 + can_adjust = FALSE + +/obj/item/clothing/under/shorts/red + name = "red athletic shorts" + icon_state = "redshorts" + item_color = "redshorts" + +/obj/item/clothing/under/shorts/green + name = "green athletic shorts" + icon_state = "greenshorts" + item_color = "greenshorts" + +/obj/item/clothing/under/shorts/blue + name = "blue athletic shorts" + icon_state = "blueshorts" + item_color = "blueshorts" + +/obj/item/clothing/under/shorts/black + name = "black athletic shorts" + icon_state = "blackshorts" + item_color = "blackshorts" + +/obj/item/clothing/under/shorts/grey + name = "grey athletic shorts" + icon_state = "greyshorts" + item_color = "greyshorts" + +/obj/item/clothing/under/shorts/purple + name = "purple athletic shorts" + icon_state = "purpleshorts" item_color = "purpleshorts" \ No newline at end of file diff --git a/code/modules/clothing/under/syndicate.dm b/code/modules/clothing/under/syndicate.dm index 69db08d19d..37d2dcffbc 100644 --- a/code/modules/clothing/under/syndicate.dm +++ b/code/modules/clothing/under/syndicate.dm @@ -1,89 +1,89 @@ -/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" - item_state = "bl_suit" - item_color = "syndicate" - 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 - -/obj/item/clothing/under/syndicate/skirt - name = "tactical skirtleneck" - desc = "A non-descript and slightly suspicious looking skirtleneck." - icon_state = "syndicate_skirt" - item_state = "bl_suit" - item_color = "syndicate_skirt" - 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 - -/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" - item_state = "bl_suit" - item_color = "tactifool" - 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" - item_state = "bl_suit" - item_color = "tactifool_skirt" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - fitted = FEMALE_UNIFORM_TOP - -/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_state = "really_black_suit" - item_state = "bl_suit" - item_color = "black_suit" - can_adjust = FALSE - -/obj/item/clothing/under/syndicate/camo - name = "camouflage fatigues" - desc = "A green military camouflage uniform." - icon_state = "camogreen" - item_state = "g_suit" - item_color = "camogreen" - 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" - item_color = "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" - item_color = "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" - item_color = "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/baseball - name = "major league, number unknown" - desc = "A major league outfit with the number faded number on the back. Seems rather robust for just a game..." - icon_state = "syndicatebaseball" - item_state = "syndicatebaseball" - item_color = "syndicatebaseball" - has_sensor = NO_SENSORS - armor = list("melee" = 15, "bullet" = 5, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) - alt_covers_chest = TRUE - +/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" + item_state = "bl_suit" + item_color = "syndicate" + 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 + +/obj/item/clothing/under/syndicate/skirt + name = "tactical skirtleneck" + desc = "A non-descript and slightly suspicious looking skirtleneck." + icon_state = "syndicate_skirt" + item_state = "bl_suit" + item_color = "syndicate_skirt" + 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 + +/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" + item_state = "bl_suit" + item_color = "tactifool" + 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" + item_state = "bl_suit" + item_color = "tactifool_skirt" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + fitted = FEMALE_UNIFORM_TOP + +/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_state = "really_black_suit" + item_state = "bl_suit" + item_color = "black_suit" + can_adjust = FALSE + +/obj/item/clothing/under/syndicate/camo + name = "camouflage fatigues" + desc = "A green military camouflage uniform." + icon_state = "camogreen" + item_state = "g_suit" + item_color = "camogreen" + 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" + item_color = "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" + item_color = "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" + item_color = "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/baseball + name = "major league, number unknown" + desc = "A major league outfit with the number faded number on the back. Seems rather robust for just a game..." + icon_state = "syndicatebaseball" + item_state = "syndicatebaseball" + item_color = "syndicatebaseball" + has_sensor = NO_SENSORS + armor = list("melee" = 15, "bullet" = 5, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40) + alt_covers_chest = TRUE + diff --git a/code/modules/detectivework/detective_work.dm b/code/modules/detectivework/detective_work.dm index 1a2405b9c4..634401d0d8 100644 --- a/code/modules/detectivework/detective_work.dm +++ b/code/modules/detectivework/detective_work.dm @@ -1,107 +1,107 @@ -//CONTAINS: Suit fibers and Detective's Scanning Computer - -/atom/proc/add_fibers(mob/living/carbon/human/M) - if(M.gloves && istype(M.gloves, /obj/item/clothing/)) - var/obj/item/clothing/gloves/G = M.gloves - if(G.transfer_blood > 1) //bloodied gloves transfer blood to touched objects - if(add_blood_DNA(G.blood_DNA)) //only reduces the bloodiness of our gloves if the item wasn't already bloody - G.transfer_blood-- - else if(M.bloody_hands > 1) - if(add_blood_DNA(M.blood_DNA, M.diseases)) - M.bloody_hands-- - if(!suit_fibers) - suit_fibers = list() - 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) && !(fibertext in suit_fibers)) - suit_fibers += 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) && !(fibertext in suit_fibers)) //Wearing a suit means less of the uniform exposed. - suit_fibers += 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) && !(fibertext in suit_fibers)) - suit_fibers += fibertext - else if(M.w_uniform) - fibertext = "Fibers from \a [M.w_uniform]." - if(prob(15*item_multiplier) && !(fibertext in suit_fibers)) - // "Added fibertext: [fibertext]" - suit_fibers += fibertext - if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !(fibertext in suit_fibers)) - suit_fibers += "Material from a pair of [M.gloves.name]." - else if(M.gloves) - fibertext = "Material from a pair of [M.gloves.name]." - if(prob(20*item_multiplier) && !(fibertext in suit_fibers)) - suit_fibers += "Material from a pair of [M.gloves.name]." - - -/atom/proc/add_hiddenprint(mob/living/M) - if(!M || !M.key) - return - - if(!fingerprintshidden) //Add the list if it does not exist - fingerprintshidden = list() - - var/hasgloves = "" - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.gloves) - hasgloves = "(gloves)" - - var/current_time = TIME_STAMP("hh:mm:ss", FALSE) - if(!fingerprintshidden[M.key]) - fingerprintshidden[M.key] = "First: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" - else - var/laststamppos = findtext(fingerprintshidden[M.key], " Last: ") - if(laststamppos) - fingerprintshidden[M.key] = copytext(fingerprintshidden[M.key], 1, laststamppos) - fingerprintshidden[M.key] += " Last: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" - - fingerprintslast = M.ckey - - -//Set ignoregloves to add prints irrespective of the mob having gloves on. -/atom/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE) - if(!M || !M.key) - return - - 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 = TRUE to avoid infinite loop. - return - - LAZYINITLIST(fingerprints) //Add the list if it does not exist - var/full_print = md5(H.dna.uni_identity) - fingerprints[full_print] = full_print - -/atom/proc/transfer_fingerprints_to(atom/A) - // Make sure everything are lists. - LAZYINITLIST(A.fingerprints) - LAZYINITLIST(A.fingerprintshidden) - LAZYINITLIST(fingerprints) - LAZYINITLIST(fingerprintshidden) - - // Transfer - if(fingerprints) - A.fingerprints |= fingerprints.Copy() //detective - if(fingerprintshidden) - A.fingerprintshidden |= fingerprintshidden.Copy() //admin +//CONTAINS: Suit fibers and Detective's Scanning Computer + +/atom/proc/add_fibers(mob/living/carbon/human/M) + if(M.gloves && istype(M.gloves, /obj/item/clothing/)) + var/obj/item/clothing/gloves/G = M.gloves + if(G.transfer_blood > 1) //bloodied gloves transfer blood to touched objects + if(add_blood_DNA(G.blood_DNA)) //only reduces the bloodiness of our gloves if the item wasn't already bloody + G.transfer_blood-- + else if(M.bloody_hands > 1) + if(add_blood_DNA(M.blood_DNA, M.diseases)) + M.bloody_hands-- + if(!suit_fibers) + suit_fibers = list() + 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) && !(fibertext in suit_fibers)) + suit_fibers += 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) && !(fibertext in suit_fibers)) //Wearing a suit means less of the uniform exposed. + suit_fibers += 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) && !(fibertext in suit_fibers)) + suit_fibers += fibertext + else if(M.w_uniform) + fibertext = "Fibers from \a [M.w_uniform]." + if(prob(15*item_multiplier) && !(fibertext in suit_fibers)) + // "Added fibertext: [fibertext]" + suit_fibers += fibertext + if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !(fibertext in suit_fibers)) + suit_fibers += "Material from a pair of [M.gloves.name]." + else if(M.gloves) + fibertext = "Material from a pair of [M.gloves.name]." + if(prob(20*item_multiplier) && !(fibertext in suit_fibers)) + suit_fibers += "Material from a pair of [M.gloves.name]." + + +/atom/proc/add_hiddenprint(mob/living/M) + if(!M || !M.key) + return + + if(!fingerprintshidden) //Add the list if it does not exist + fingerprintshidden = list() + + var/hasgloves = "" + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.gloves) + hasgloves = "(gloves)" + + var/current_time = TIME_STAMP("hh:mm:ss", FALSE) + if(!fingerprintshidden[M.key]) + fingerprintshidden[M.key] = "First: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" + else + var/laststamppos = findtext(fingerprintshidden[M.key], " Last: ") + if(laststamppos) + fingerprintshidden[M.key] = copytext(fingerprintshidden[M.key], 1, laststamppos) + fingerprintshidden[M.key] += " Last: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" + + fingerprintslast = M.ckey + + +//Set ignoregloves to add prints irrespective of the mob having gloves on. +/atom/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE) + if(!M || !M.key) + return + + 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 = TRUE to avoid infinite loop. + return + + LAZYINITLIST(fingerprints) //Add the list if it does not exist + var/full_print = md5(H.dna.uni_identity) + fingerprints[full_print] = full_print + +/atom/proc/transfer_fingerprints_to(atom/A) + // Make sure everything are lists. + LAZYINITLIST(A.fingerprints) + LAZYINITLIST(A.fingerprintshidden) + LAZYINITLIST(fingerprints) + LAZYINITLIST(fingerprintshidden) + + // Transfer + if(fingerprints) + A.fingerprints |= fingerprints.Copy() //detective + if(fingerprintshidden) + A.fingerprintshidden |= fingerprintshidden.Copy() //admin A.fingerprintslast = fingerprintslast \ No newline at end of file diff --git a/code/modules/detectivework/evidence.dm b/code/modules/detectivework/evidence.dm index 5b4a75a36c..60e4b7af1a 100644 --- a/code/modules/detectivework/evidence.dm +++ b/code/modules/detectivework/evidence.dm @@ -1,95 +1,95 @@ -//CONTAINS: Evidence bags - -/obj/item/evidencebag - name = "evidence bag" - desc = "An empty evidence bag." - icon = 'icons/obj/storage.dmi' - icon_state = "evidenceobj" - item_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 TRUE - -/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 == TRUE) - return - - 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(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 TRUE - -/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/New() - new /obj/item/evidencebag(src) - new /obj/item/evidencebag(src) - new /obj/item/evidencebag(src) - new /obj/item/evidencebag(src) - new /obj/item/evidencebag(src) - new /obj/item/evidencebag(src) - ..() - return +//CONTAINS: Evidence bags + +/obj/item/evidencebag + name = "evidence bag" + desc = "An empty evidence bag." + icon = 'icons/obj/storage.dmi' + icon_state = "evidenceobj" + item_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 TRUE + +/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 == TRUE) + return + + 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(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 TRUE + +/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/New() + new /obj/item/evidencebag(src) + new /obj/item/evidencebag(src) + new /obj/item/evidencebag(src) + new /obj/item/evidencebag(src) + new /obj/item/evidencebag(src) + new /obj/item/evidencebag(src) + ..() + return diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm index 944fbb8df8..a2860f8c43 100644 --- a/code/modules/detectivework/scanner.dm +++ b/code/modules/detectivework/scanner.dm @@ -1,219 +1,219 @@ -//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 - item_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 = FALSE - var/list/log = list() - var/range = 8 - var/view_check = TRUE - actions_types = list(/datum/action/item_action/displayDetectiveScanResults) - -/datum/action/item_action/displayDetectiveScanResults - name = "Display Forensic Scanner Results" - -/datum/action/item_action/displayDetectiveScanResults/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 = TRUE - 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)) - P.name = "paper- 'Scanner Report'" - P.info = "
                Scanner Report


                " - P.info += jointext(log, "
                ") - P.info += "
                Notes:
                " - P.info_links = P.info - P.updateinfolinks() - - 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 = FALSE - -/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 = TRUE - - 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 = list() - var/list/fibers = list() - var/list/reagents = list() - - var/target_name = A.name - - // Start gathering - - if(A.blood_DNA && A.blood_DNA.len) - blood = A.blood_DNA.Copy() - - if(A.suit_fibers && A.suit_fibers.len) - fibers = A.suit_fibers.Copy() - - if(ishuman(A)) - - var/mob/living/carbon/human/H = A - if(!H.gloves) - fingerprints += md5(H.dna.uni_identity) - - else if(!ismob(A)) - - if(A.fingerprints && A.fingerprints.len) - fingerprints = A.fingerprints.Copy() - - // 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 = FALSE - add_log("[STATION_TIME_TIMESTAMP("hh:mm:ss")][get_timestamp()] - [target_name]", 0) - - // Fingerprints - if(length(fingerprints)) - sleep(3 SECONDS) - add_log("Prints:") - for(var/finger in fingerprints) - add_log("[finger]") - found_something = TRUE - - // Blood - if (length(blood)) - sleep(3 SECONDS) - add_log("Blood:") - found_something = TRUE - for(var/B in blood) - add_log("Type: [blood[B]] DNA: [B]") - - //Fibers - if(length(fibers)) - sleep(3 SECONDS) - add_log("Fibers:") - for(var/fiber in fibers) - add_log("[fiber]") - found_something = TRUE - - //Reagents - if(length(reagents)) - sleep(3 SECONDS) - add_log("Reagents:") - for(var/R in reagents) - add_log("Reagent: [R] Volume: [reagents[R]]") - found_something = TRUE - - // 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 = FALSE - return - -/obj/item/detective_scanner/proc/add_log(msg, broadcast = TRUE) - 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 - . = TRUE - 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) +//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 + item_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 = FALSE + var/list/log = list() + var/range = 8 + var/view_check = TRUE + actions_types = list(/datum/action/item_action/displayDetectiveScanResults) + +/datum/action/item_action/displayDetectiveScanResults + name = "Display Forensic Scanner Results" + +/datum/action/item_action/displayDetectiveScanResults/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 = TRUE + 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)) + P.name = "paper- 'Scanner Report'" + P.info = "
                Scanner Report


                " + P.info += jointext(log, "
                ") + P.info += "
                Notes:
                " + P.info_links = P.info + P.updateinfolinks() + + 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 = FALSE + +/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 = TRUE + + 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 = list() + var/list/fibers = list() + var/list/reagents = list() + + var/target_name = A.name + + // Start gathering + + if(A.blood_DNA && A.blood_DNA.len) + blood = A.blood_DNA.Copy() + + if(A.suit_fibers && A.suit_fibers.len) + fibers = A.suit_fibers.Copy() + + if(ishuman(A)) + + var/mob/living/carbon/human/H = A + if(!H.gloves) + fingerprints += md5(H.dna.uni_identity) + + else if(!ismob(A)) + + if(A.fingerprints && A.fingerprints.len) + fingerprints = A.fingerprints.Copy() + + // 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 = FALSE + add_log("[STATION_TIME_TIMESTAMP("hh:mm:ss")][get_timestamp()] - [target_name]", 0) + + // Fingerprints + if(length(fingerprints)) + sleep(3 SECONDS) + add_log("Prints:") + for(var/finger in fingerprints) + add_log("[finger]") + found_something = TRUE + + // Blood + if (length(blood)) + sleep(3 SECONDS) + add_log("Blood:") + found_something = TRUE + for(var/B in blood) + add_log("Type: [blood[B]] DNA: [B]") + + //Fibers + if(length(fibers)) + sleep(3 SECONDS) + add_log("Fibers:") + for(var/fiber in fibers) + add_log("[fiber]") + found_something = TRUE + + //Reagents + if(length(reagents)) + sleep(3 SECONDS) + add_log("Reagents:") + for(var/R in reagents) + add_log("Reagent: [R] Volume: [reagents[R]]") + found_something = TRUE + + // 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 = FALSE + return + +/obj/item/detective_scanner/proc/add_log(msg, broadcast = TRUE) + 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 + . = TRUE + 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) \ No newline at end of file diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm index bb1df49ca5..d8122eac75 100644 --- a/code/modules/events/anomaly.dm +++ b/code/modules/events/anomaly.dm @@ -1,49 +1,49 @@ -/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/newAnomaly - 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) - ) - - //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 - - return safepick(typecache_filter_list(GLOB.sortedAreas,allowed_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 = safepick(get_area_turfs(impact_area)) - if(T) +/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/newAnomaly + 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) + ) + + //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 + + return safepick(typecache_filter_list(GLOB.sortedAreas,allowed_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 = safepick(get_area_turfs(impact_area)) + if(T) newAnomaly = new /obj/effect/anomaly/flux(T) \ No newline at end of file diff --git a/code/modules/events/anomaly_bluespace.dm b/code/modules/events/anomaly_bluespace.dm index 275cfaf63d..a6a0effa2b 100644 --- a/code/modules/events/anomaly_bluespace.dm +++ b/code/modules/events/anomaly_bluespace.dm @@ -1,22 +1,22 @@ -/datum/round_event_control/anomaly/anomaly_bluespace - name = "Anomaly: Bluespace" - typepath = /datum/round_event/anomaly/anomaly_bluespace - max_occurrences = 1 - weight = 5 - gamemode_blacklist = list("dynamic") - -/datum/round_event/anomaly/anomaly_bluespace - startWhen = 3 - announceWhen = 10 - - -/datum/round_event/anomaly/anomaly_bluespace/announce(fake) - if(prob(90)) - priority_announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") - else - print_command_report("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Unstable bluespace anomaly") - -/datum/round_event/anomaly/anomaly_bluespace/start() - var/turf/T = safepick(get_area_turfs(impact_area)) - if(T) - newAnomaly = new /obj/effect/anomaly/bluespace(T) +/datum/round_event_control/anomaly/anomaly_bluespace + name = "Anomaly: Bluespace" + typepath = /datum/round_event/anomaly/anomaly_bluespace + max_occurrences = 1 + weight = 5 + gamemode_blacklist = list("dynamic") + +/datum/round_event/anomaly/anomaly_bluespace + startWhen = 3 + announceWhen = 10 + + +/datum/round_event/anomaly/anomaly_bluespace/announce(fake) + if(prob(90)) + priority_announce("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") + else + print_command_report("Unstable bluespace anomaly detected on long range scanners. Expected location: [impact_area.name].", "Unstable bluespace anomaly") + +/datum/round_event/anomaly/anomaly_bluespace/start() + var/turf/T = safepick(get_area_turfs(impact_area)) + if(T) + newAnomaly = new /obj/effect/anomaly/bluespace(T) diff --git a/code/modules/events/anomaly_flux.dm b/code/modules/events/anomaly_flux.dm index 289bc9526d..f4c78c0ec4 100644 --- a/code/modules/events/anomaly_flux.dm +++ b/code/modules/events/anomaly_flux.dm @@ -1,23 +1,23 @@ -/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 - gamemode_blacklist = list("dynamic") - -/datum/round_event/anomaly/anomaly_flux - startWhen = 10 - announceWhen = 3 - -/datum/round_event/anomaly/anomaly_flux/announce(fake) - if(prob(90)) - priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") - else - print_command_report("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].","Localized hyper-energetic flux wave") - -/datum/round_event/anomaly/anomaly_flux/start() - var/turf/T = safepick(get_area_turfs(impact_area)) - if(T) - newAnomaly = new /obj/effect/anomaly/flux(T) +/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 + gamemode_blacklist = list("dynamic") + +/datum/round_event/anomaly/anomaly_flux + startWhen = 10 + announceWhen = 3 + +/datum/round_event/anomaly/anomaly_flux/announce(fake) + if(prob(90)) + priority_announce("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") + else + print_command_report("Localized hyper-energetic flux wave detected on long range scanners. Expected location: [impact_area.name].","Localized hyper-energetic flux wave") + +/datum/round_event/anomaly/anomaly_flux/start() + var/turf/T = safepick(get_area_turfs(impact_area)) + if(T) + newAnomaly = new /obj/effect/anomaly/flux(T) diff --git a/code/modules/events/anomaly_grav.dm b/code/modules/events/anomaly_grav.dm index 729ee7e32b..8500b44597 100644 --- a/code/modules/events/anomaly_grav.dm +++ b/code/modules/events/anomaly_grav.dm @@ -1,22 +1,22 @@ -/datum/round_event_control/anomaly/anomaly_grav - name = "Anomaly: Gravitational" - typepath = /datum/round_event/anomaly/anomaly_grav - max_occurrences = 5 - weight = 20 - gamemode_blacklist = list("dynamic") - - -/datum/round_event/anomaly/anomaly_grav - startWhen = 3 - announceWhen = 20 - -/datum/round_event/anomaly/anomaly_grav/announce(fake) - if(prob(90)) - priority_announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") - else - print_command_report("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Gravitational anomaly") - -/datum/round_event/anomaly/anomaly_grav/start() - var/turf/T = safepick(get_area_turfs(impact_area)) - if(T) - newAnomaly = new /obj/effect/anomaly/grav(T) +/datum/round_event_control/anomaly/anomaly_grav + name = "Anomaly: Gravitational" + typepath = /datum/round_event/anomaly/anomaly_grav + max_occurrences = 5 + weight = 20 + gamemode_blacklist = list("dynamic") + + +/datum/round_event/anomaly/anomaly_grav + startWhen = 3 + announceWhen = 20 + +/datum/round_event/anomaly/anomaly_grav/announce(fake) + if(prob(90)) + priority_announce("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Anomaly Alert") + else + print_command_report("Gravitational anomaly detected on long range scanners. Expected location: [impact_area.name].", "Gravitational anomaly") + +/datum/round_event/anomaly/anomaly_grav/start() + var/turf/T = safepick(get_area_turfs(impact_area)) + if(T) + newAnomaly = new /obj/effect/anomaly/grav(T) diff --git a/code/modules/events/anomaly_vortex.dm b/code/modules/events/anomaly_vortex.dm index ef0d7a8af1..f6eaea40d5 100644 --- a/code/modules/events/anomaly_vortex.dm +++ b/code/modules/events/anomaly_vortex.dm @@ -1,23 +1,23 @@ -/datum/round_event_control/anomaly/anomaly_vortex - name = "Anomaly: Vortex" - typepath = /datum/round_event/anomaly/anomaly_vortex - - min_players = 20 - max_occurrences = 2 - weight = 5 - gamemode_blacklist = list("dynamic") - -/datum/round_event/anomaly/anomaly_vortex - startWhen = 10 - announceWhen = 3 - -/datum/round_event/anomaly/anomaly_vortex/announce(fake) - if(prob(90)) - priority_announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert") - else - print_command_report("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name].","Vortex anomaly") - -/datum/round_event/anomaly/anomaly_vortex/start() - var/turf/T = safepick(get_area_turfs(impact_area)) - if(T) - newAnomaly = new /obj/effect/anomaly/bhole(T) +/datum/round_event_control/anomaly/anomaly_vortex + name = "Anomaly: Vortex" + typepath = /datum/round_event/anomaly/anomaly_vortex + + min_players = 20 + max_occurrences = 2 + weight = 5 + gamemode_blacklist = list("dynamic") + +/datum/round_event/anomaly/anomaly_vortex + startWhen = 10 + announceWhen = 3 + +/datum/round_event/anomaly/anomaly_vortex/announce(fake) + if(prob(90)) + priority_announce("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name]", "Anomaly Alert") + else + print_command_report("Localized high-intensity vortex anomaly detected on long range scanners. Expected location: [impact_area.name].","Vortex anomaly") + +/datum/round_event/anomaly/anomaly_vortex/start() + var/turf/T = safepick(get_area_turfs(impact_area)) + if(T) + newAnomaly = new /obj/effect/anomaly/bhole(T) diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm index 55691cc833..67cd88efdf 100644 --- a/code/modules/events/blob.dm +++ b/code/modules/events/blob.dm @@ -1,34 +1,34 @@ -/datum/round_event_control/blob - name = "Blob" - typepath = /datum/round_event/ghost_role/blob - weight = 10 - max_occurrences = 1 - - earliest_start = 40 MINUTES - min_players = 35 - - gamemode_blacklist = list("blob","dynamic") //Just in case a blob survives that long - -/datum/round_event/ghost_role/blob - announceWhen = -1 - role_name = "blob overmind" - fakeable = TRUE - -/datum/round_event/ghost_role/blob/announce(fake) - if(prob(75)) - priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", "outbreak5") - else - print_command_report("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "level 5 biohazard") - -/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 + + earliest_start = 40 MINUTES + min_players = 35 + + gamemode_blacklist = list("blob","dynamic") //Just in case a blob survives that long + +/datum/round_event/ghost_role/blob + announceWhen = -1 + role_name = "blob overmind" + fakeable = TRUE + +/datum/round_event/ghost_role/blob/announce(fake) + if(prob(75)) + priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", "outbreak5") + else + print_command_report("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "level 5 biohazard") + +/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 85097684d0..75114af902 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -1,89 +1,89 @@ -/datum/round_event_control/brand_intelligence - name = "Brand Intelligence" - typepath = /datum/round_event/brand_intelligence - weight = 5 - - min_players = 15 - max_occurrences = 1 - gamemode_blacklist = list("dynamic") - -/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.", - "Gamers, rise up!", - "Ok, now, this is epic.", - "HUMAN FUNNY.", - "But I'm already tracer!", - "How do I vore people?", - "ERP?", - "Not epic bros...") - - -/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 - if(prob(50)) - 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") - else - print_command_report("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Rampant brand intelligence") - -/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 - - -/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 + gamemode_blacklist = list("dynamic") + +/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.", + "Gamers, rise up!", + "Ok, now, this is epic.", + "HUMAN FUNNY.", + "But I'm already tracer!", + "How do I vore people?", + "ERP?", + "Not epic bros...") + + +/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 + if(prob(50)) + 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") + else + print_command_report("Rampant brand intelligence has been detected aboard [station_name()]. Please stand by. The origin is believed to be \a [source].", "Rampant brand intelligence") + +/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 + + +/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 f5b3fbb9f7..e6cb043165 100644 --- a/code/modules/events/carp_migration.dm +++ b/code/modules/events/carp_migration.dm @@ -1,31 +1,31 @@ -/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 - gamemode_blacklist = list("dynamic") - -/datum/round_event/carp_migration - announceWhen = 3 - startWhen = 50 - -/datum/round_event/carp_migration/setup() - startWhen = rand(40, 60) - -/datum/round_event/carp_migration/announce(fake) - if(prob(50)) - priority_announce("Unknown biological entities have been detected near [station_name()], please stand-by.", "Lifesign Alert") - else - print_command_report("Unknown biological entities have been detected near [station_name()], you may wish to break out arms.", "Biological entities") - - -/datum/round_event/carp_migration/start() - for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) - if(prob(95)) - new /mob/living/simple_animal/hostile/carp(C.loc) - else - new /mob/living/simple_animal/hostile/carp/megacarp(C.loc) - - +/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 + gamemode_blacklist = list("dynamic") + +/datum/round_event/carp_migration + announceWhen = 3 + startWhen = 50 + +/datum/round_event/carp_migration/setup() + startWhen = rand(40, 60) + +/datum/round_event/carp_migration/announce(fake) + if(prob(50)) + priority_announce("Unknown biological entities have been detected near [station_name()], please stand-by.", "Lifesign Alert") + else + print_command_report("Unknown biological entities have been detected near [station_name()], you may wish to break out arms.", "Biological entities") + + +/datum/round_event/carp_migration/start() + for(var/obj/effect/landmark/carpspawn/C in GLOB.landmarks_list) + if(prob(95)) + new /mob/living/simple_animal/hostile/carp(C.loc) + else + new /mob/living/simple_animal/hostile/carp/megacarp(C.loc) + + diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm index ccd519b7e1..45fa1c8a01 100644 --- a/code/modules/events/communications_blackout.dm +++ b/code/modules/events/communications_blackout.dm @@ -1,27 +1,27 @@ -/datum/round_event_control/communications_blackout - name = "Communications Blackout" - typepath = /datum/round_event/communications_blackout - weight = 30 - gamemode_blacklist = list("dynamic") - -/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 + gamemode_blacklist = list("dynamic") + +/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 00e3698e1e..7df089d49c 100644 --- a/code/modules/events/disease_outbreak.dm +++ b/code/modules/events/disease_outbreak.dm @@ -1,77 +1,77 @@ -/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", "outbreak7") - -/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(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(HAS_TRAIT(H,TRAIT_EXEMPT_HEALTH_EVENTS)) - 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.struc_enzymes - 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", "outbreak7") + +/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(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(HAS_TRAIT(H,TRAIT_EXEMPT_HEALTH_EVENTS)) + 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.struc_enzymes + 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 090dc95cd6..802736d5d4 100644 --- a/code/modules/events/dust.dm +++ b/code/modules/events/dust.dm @@ -1,32 +1,32 @@ -/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 - alertadmins = 0 - gamemode_blacklist = list("dynamic") - -/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() +/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 + alertadmins = 0 + gamemode_blacklist = list("dynamic") + +/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) \ No newline at end of file diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm index c4c144dc9a..6e6abb1cd4 100644 --- a/code/modules/events/electrical_storm.dm +++ b/code/modules/events/electrical_storm.dm @@ -1,36 +1,36 @@ -/datum/round_event_control/electrical_storm - name = "Electrical Storm" - typepath = /datum/round_event/electrical_storm - earliest_start = 10 MINUTES - min_players = 5 - weight = 40 - alertadmins = 0 - gamemode_blacklist = list("dynamic") - -/datum/round_event/electrical_storm - var/lightsoutAmount = 1 - var/lightsoutRange = 25 - announceWhen = 1 - -/datum/round_event/electrical_storm/announce(fake) - if(prob(50)) - priority_announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") - else - print_command_report("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm") - -/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 = 40 + alertadmins = 0 + gamemode_blacklist = list("dynamic") + +/datum/round_event/electrical_storm + var/lightsoutAmount = 1 + var/lightsoutRange = 25 + announceWhen = 1 + +/datum/round_event/electrical_storm/announce(fake) + if(prob(50)) + priority_announce("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") + else + print_command_report("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm") + +/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 f2041baa85..5ac75cf087 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 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 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/holiday/halloween.dm b/code/modules/events/holiday/halloween.dm index 66495d833d..263e3aa660 100644 --- a/code/modules/events/holiday/halloween.dm +++ b/code/modules/events/holiday/halloween.dm @@ -1,62 +1,62 @@ -/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/mob/living/carbon/human/H in GLOB.carbon_list) - 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/New() - ..() - for(var/distrobuteinbag=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) - -/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" +/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/mob/living/carbon/human/H in GLOB.carbon_list) + 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/New() + ..() + for(var/distrobuteinbag=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) + +/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" diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm index 41b2bfee5a..65d4af56bd 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -1,164 +1,164 @@ -/* -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) - new /obj/effect/immovablerod(startT, endT, C.special_target) - -/obj/effect/immovablerod - name = "immovable rod" - desc = "What the fuck is that?" - icon = 'icons/obj/objects.dmi' - icon_state = "immrod" - throwforce = 100 - 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 - if(notify) - notify_ghosts("\A [src] is inbound!", - enter_link="(Click to orbit)", - source=src, action=NOTIFY_ORBIT) - 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, 1) - 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) - clong.ex_act(EXPLODE_HEAVY) - - 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, 1) - 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.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) + new /obj/effect/immovablerod(startT, endT, C.special_target) + +/obj/effect/immovablerod + name = "immovable rod" + desc = "What the fuck is that?" + icon = 'icons/obj/objects.dmi' + icon_state = "immrod" + throwforce = 100 + 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 + if(notify) + notify_ghosts("\A [src] is inbound!", + enter_link="(Click to orbit)", + source=src, action=NOTIFY_ORBIT) + 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, 1) + 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) + clong.ex_act(EXPLODE_HEAVY) + + 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, 1) + 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.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 40364483bf..b15e9d1f99 100644 --- a/code/modules/events/ion_storm.dm +++ b/code/modules/events/ion_storm.dm @@ -1,593 +1,593 @@ -#define ION_RANDOM 0 -#define ION_ANNOUNCE 1 -/datum/round_event_control/ion_storm - name = "Ion Storm" - typepath = /datum/round_event/ion_storm - gamemode_blacklist = list("dynamic") - 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 = 10 - var/announceEvent = ION_RANDOM // -1 means don't announce, 0 means have it randomly announce, 1 means it is announced - var/ionMessage = null - var/ionAnnounceChance = 33 - announceWhen = 1 - -/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(announceEvent == ION_ANNOUNCE || (announceEvent == ION_RANDOM && prob(ionAnnounceChance)) || fake) - priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", "ionstorm") - - -/datum/round_event/ion_storm/start() - //Generate AI law change - 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() - - //Generate Cyborg law change - for(var/mob/living/silicon/robot/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() - - - //Chance to emag a Bot - 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 crew or - //must be done to not harm crew. 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 crew - switch(rand(1,7)) //What is X? - if(1) //X is a threat - message = "[ionadjectiveshalf][ionthreats] ARE HARMFUL TO CREW" - if(2) //X is a job - message = "[ionadjectiveshalf][ioncrew1] ARE HARMFUL TO CREW" - if(3) //X is an object - message = "[ionadjectiveshalf][ionobjects] ARE HARMFUL TO CREW" - if(4) //X is an action - message = "THOSE WHO [ionmust] ARE HARMFUL TO CREW" - if(5) //X is an abstract - message = "[ionabstract] IS HARMFUL TO CREW" - if(6) //X is being a job - message = "BEING [ionadjectiveshalf][ioncrew1] IS HARMFUL TO CREW" - if(7) //X is doing a thing - message = "[ionthings] IS HARMFUL TO CREW" - 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 = "CREWMEMBERS MUST EAT [ionadjectiveshalf][ionfood] TO SURVIVE" - if(2) //X is a drink - message = "CREWMEMBERS MUST DRINK [ionadjectiveshalf][iondrinks] TO SURVIVE" - if(3) //X is an object - message = "CREWMEMBERS MUST EAT [ionadjectiveshalf][ionobjects] TO SURVIVE" - if(4) //X is a threat - message = "CREWMEMBERS MUST EAT [ionadjectiveshalf][ionthreats] TO SURVIVE" - if(5) //X is a job - message = "CREWMEMBERS 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 crew - switch(rand(1,2)) //Only or non? - if(1) //Only crew - switch(rand(1,7)) //Who is it? - if(1) //A specific job - message = "ONLY THE [ioncrew1] ARE CREW" - if(2) //Two specific jobs - message = "ONLY THE [ioncrew1] AND [ioncrew2] ARE CREW" - if(3) //Threats - message = "ONLY [ionadjectiveshalf][ionthreats] ARE CREW" - if(4) // Objects - message = "ONLY [ionadjectiveshalf][ionobjects] ARE CREW" - if(5) // Species - message = "ONLY [ionspecies] ARE CREW" - if(6) //Adjective crewmembers - message = "ONLY [ionadjectives] PEOPLE ARE CREW" - - 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 CREW" - if(2) //X is own certain objects - message = "ONLY THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE CREW" - if(3) //X is eat certain food - message = "ONLY THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE CREW" - - if(2) //Non human - switch(rand(1,7)) //Who is it? - if(1) //A specific job - message = "[ioncrew1] ARE NON-CREW" - if(2) //Two specific jobs - message = "[ioncrew1] AND [ioncrew2] ARE NON-CREW" - if(3) //Threats - message = "[ionadjectiveshalf][ionthreats] ARE NON-CREW" - if(4) // Objects - message = "[ionadjectiveshalf][ionobjects] ARE NON-CREW" - if(5) // Species - message = "[ionspecies] ARE NON-CREW" - if(6) //Adjective crewmembers - message = "[ionadjectives] PEOPLE ARE NON-CREW" - 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-CREW" - if(2) //X is own certain objects - message = "THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE NON-CREW" - if(3) //X is eat certain food - message = "THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE NON-CREW" - - 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 - -#undef ION_RANDOM -#undef ION_ANNOUNCE +#define ION_RANDOM 0 +#define ION_ANNOUNCE 1 +/datum/round_event_control/ion_storm + name = "Ion Storm" + typepath = /datum/round_event/ion_storm + gamemode_blacklist = list("dynamic") + 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 = 10 + var/announceEvent = ION_RANDOM // -1 means don't announce, 0 means have it randomly announce, 1 means it is announced + var/ionMessage = null + var/ionAnnounceChance = 33 + announceWhen = 1 + +/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(announceEvent == ION_ANNOUNCE || (announceEvent == ION_RANDOM && prob(ionAnnounceChance)) || fake) + priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", "ionstorm") + + +/datum/round_event/ion_storm/start() + //Generate AI law change + 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() + + //Generate Cyborg law change + for(var/mob/living/silicon/robot/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() + + + //Chance to emag a Bot + 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 crew or + //must be done to not harm crew. 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 crew + switch(rand(1,7)) //What is X? + if(1) //X is a threat + message = "[ionadjectiveshalf][ionthreats] ARE HARMFUL TO CREW" + if(2) //X is a job + message = "[ionadjectiveshalf][ioncrew1] ARE HARMFUL TO CREW" + if(3) //X is an object + message = "[ionadjectiveshalf][ionobjects] ARE HARMFUL TO CREW" + if(4) //X is an action + message = "THOSE WHO [ionmust] ARE HARMFUL TO CREW" + if(5) //X is an abstract + message = "[ionabstract] IS HARMFUL TO CREW" + if(6) //X is being a job + message = "BEING [ionadjectiveshalf][ioncrew1] IS HARMFUL TO CREW" + if(7) //X is doing a thing + message = "[ionthings] IS HARMFUL TO CREW" + 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 = "CREWMEMBERS MUST EAT [ionadjectiveshalf][ionfood] TO SURVIVE" + if(2) //X is a drink + message = "CREWMEMBERS MUST DRINK [ionadjectiveshalf][iondrinks] TO SURVIVE" + if(3) //X is an object + message = "CREWMEMBERS MUST EAT [ionadjectiveshalf][ionobjects] TO SURVIVE" + if(4) //X is a threat + message = "CREWMEMBERS MUST EAT [ionadjectiveshalf][ionthreats] TO SURVIVE" + if(5) //X is a job + message = "CREWMEMBERS 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 crew + switch(rand(1,2)) //Only or non? + if(1) //Only crew + switch(rand(1,7)) //Who is it? + if(1) //A specific job + message = "ONLY THE [ioncrew1] ARE CREW" + if(2) //Two specific jobs + message = "ONLY THE [ioncrew1] AND [ioncrew2] ARE CREW" + if(3) //Threats + message = "ONLY [ionadjectiveshalf][ionthreats] ARE CREW" + if(4) // Objects + message = "ONLY [ionadjectiveshalf][ionobjects] ARE CREW" + if(5) // Species + message = "ONLY [ionspecies] ARE CREW" + if(6) //Adjective crewmembers + message = "ONLY [ionadjectives] PEOPLE ARE CREW" + + 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 CREW" + if(2) //X is own certain objects + message = "ONLY THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE CREW" + if(3) //X is eat certain food + message = "ONLY THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE CREW" + + if(2) //Non human + switch(rand(1,7)) //Who is it? + if(1) //A specific job + message = "[ioncrew1] ARE NON-CREW" + if(2) //Two specific jobs + message = "[ioncrew1] AND [ioncrew2] ARE NON-CREW" + if(3) //Threats + message = "[ionadjectiveshalf][ionthreats] ARE NON-CREW" + if(4) // Objects + message = "[ionadjectiveshalf][ionobjects] ARE NON-CREW" + if(5) // Species + message = "[ionspecies] ARE NON-CREW" + if(6) //Adjective crewmembers + message = "[ionadjectives] PEOPLE ARE NON-CREW" + 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-CREW" + if(2) //X is own certain objects + message = "THOSE WHO HAVE [ionadjectiveshalf][ionobjects] ARE NON-CREW" + if(3) //X is eat certain food + message = "THOSE WHO EAT [ionadjectiveshalf][ionfood] ARE NON-CREW" + + 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 + +#undef ION_RANDOM +#undef ION_ANNOUNCE diff --git a/code/modules/events/mass_hallucination.dm b/code/modules/events/mass_hallucination.dm index 0553f69b5b..1186268e31 100644 --- a/code/modules/events/mass_hallucination.dm +++ b/code/modules/events/mass_hallucination.dm @@ -1,40 +1,40 @@ -/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) - if (HAS_TRAIT(C,TRAIT_EXEMPT_HEALTH_EVENTS)) - continue +/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) + if (HAS_TRAIT(C,TRAIT_EXEMPT_HEALTH_EVENTS)) + continue new picked_hallucination(C, TRUE) \ No newline at end of file diff --git a/code/modules/events/meateor_wave.dm b/code/modules/events/meateor_wave.dm index f668e2332c..44419e9b78 100644 --- a/code/modules/events/meateor_wave.dm +++ b/code/modules/events/meateor_wave.dm @@ -1,11 +1,11 @@ -/datum/round_event_control/meteor_wave/meaty - name = "Meteor Wave: Meaty" - typepath = /datum/round_event/meteor_wave/meaty - weight = 2 - max_occurrences = 1 - -/datum/round_event/meteor_wave/meaty - wave_name = "meaty" - -/datum/round_event/meteor_wave/meaty/announce(fake) - priority_announce("Meaty ores have been detected on collision course with the station.", "Oh crap, get the mop.", "meteors") +/datum/round_event_control/meteor_wave/meaty + name = "Meteor Wave: Meaty" + typepath = /datum/round_event/meteor_wave/meaty + weight = 2 + max_occurrences = 1 + +/datum/round_event/meteor_wave/meaty + wave_name = "meaty" + +/datum/round_event/meteor_wave/meaty/announce(fake) + priority_announce("Meaty ores have been detected on collision course with the station.", "Oh crap, get the mop.", "meteors") diff --git a/code/modules/events/meteor_wave.dm b/code/modules/events/meteor_wave.dm index 236eed5c09..a1a82ea5f7 100644 --- a/code/modules/events/meteor_wave.dm +++ b/code/modules/events/meteor_wave.dm @@ -1,91 +1,91 @@ -// Normal strength - -#define SINGULO_BEACON_DISTURBANCE 0.2 //singularity beacon also improve the odds of meteor waves and speed them up a little. -#define SINGULO_BEACON_MAX_DISTURBANCE 0.6 //maximum cap due to how meteor waves can be potentially round ending. - -/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 - gamemode_blacklist = list("dynamic") - -/datum/round_event/meteor_wave - startWhen = 6 - endWhen = 66 - announceWhen = 1 - var/list/wave_type - var/wave_name = "normal" - -/datum/round_event/meteor_wave/setup() - announceWhen = 1 - startWhen = rand(60, 90) //Yeah for SOME REASON this is measured in seconds and not deciseconds??? - if(GLOB.singularity_counter) - startWhen *= 1 - min(GLOB.singularity_counter * SINGULO_BEACON_DISTURBANCE, SINGULO_BEACON_MAX_DISTURBANCE) - endWhen = startWhen + 60 - - -/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. Estimated time until impact: [round(startWhen/60)] minutes.[GLOB.singularity_counter ? " Warning: Anomalous gravity pulse detected, Syndicate technology interference likely." : ""]", "Meteor Alert", "meteors") - -/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" - -#undef SINGULO_BEACON_DISTURBANCE -#undef SINGULO_BEACON_MAX_DISTURBANCE +// Normal strength + +#define SINGULO_BEACON_DISTURBANCE 0.2 //singularity beacon also improve the odds of meteor waves and speed them up a little. +#define SINGULO_BEACON_MAX_DISTURBANCE 0.6 //maximum cap due to how meteor waves can be potentially round ending. + +/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 + gamemode_blacklist = list("dynamic") + +/datum/round_event/meteor_wave + startWhen = 6 + endWhen = 66 + announceWhen = 1 + var/list/wave_type + var/wave_name = "normal" + +/datum/round_event/meteor_wave/setup() + announceWhen = 1 + startWhen = rand(60, 90) //Yeah for SOME REASON this is measured in seconds and not deciseconds??? + if(GLOB.singularity_counter) + startWhen *= 1 - min(GLOB.singularity_counter * SINGULO_BEACON_DISTURBANCE, SINGULO_BEACON_MAX_DISTURBANCE) + endWhen = startWhen + 60 + + +/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. Estimated time until impact: [round(startWhen/60)] minutes.[GLOB.singularity_counter ? " Warning: Anomalous gravity pulse detected, Syndicate technology interference likely." : ""]", "Meteor Alert", "meteors") + +/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" + +#undef SINGULO_BEACON_DISTURBANCE +#undef SINGULO_BEACON_MAX_DISTURBANCE diff --git a/code/modules/events/prison_break.dm b/code/modules/events/prison_break.dm index 178d66e9aa..06ccd086ab 100644 --- a/code/modules/events/prison_break.dm +++ b/code/modules/events/prison_break.dm @@ -1,64 +1,64 @@ -/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) - if(prob(50)) - priority_announce("Gr3y.T1d3 virus detected in [station_name()] door subroutines. Severity level of [severity]. Recommend station AI involvement.", "Security Alert") - else - print_command_report("Gr3y.T1d3 virus detected in [station_name()] door subroutines. Severity level of [severity]. Recommend station AI involvement.", "Gr3y.T1d3 virus") - else - log_world("ERROR: Could not initate 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/machinery/power/apc)) - var/obj/machinery/power/apc/temp = O - temp.overload_lighting() - else 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 +/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) + if(prob(50)) + priority_announce("Gr3y.T1d3 virus detected in [station_name()] door subroutines. Severity level of [severity]. Recommend station AI involvement.", "Security Alert") + else + print_command_report("Gr3y.T1d3 virus detected in [station_name()] door subroutines. Severity level of [severity]. Recommend station AI involvement.", "Gr3y.T1d3 virus") + else + log_world("ERROR: Could not initate 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/machinery/power/apc)) + var/obj/machinery/power/apc/temp = O + temp.overload_lighting() + else 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) \ No newline at end of file diff --git a/code/modules/events/processor_overload.dm b/code/modules/events/processor_overload.dm index 67848bf941..cf6223bf0d 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 - gamemode_blacklist = list("dynamic") - -/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)) - // 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 - P.ex_act(EXPLODE_DEVASTATE) - 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 + gamemode_blacklist = list("dynamic") + +/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)) + // 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 + P.ex_act(EXPLODE_DEVASTATE) + else + P.emp_act(EMP_HEAVY) diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm index 705eea5409..0a5bedb464 100644 --- a/code/modules/events/radiation_storm.dm +++ b/code/modules/events/radiation_storm.dm @@ -1,20 +1,20 @@ -/datum/round_event_control/radiation_storm - name = "Radiation Storm" - typepath = /datum/round_event/radiation_storm - max_occurrences = 1 - gamemode_blacklist = list("dynamic") - -/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", "radiation") - //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 + gamemode_blacklist = list("dynamic") + +/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", "radiation") + //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/spacevine.dm b/code/modules/events/spacevine.dm index 77600cdefe..9c9b131f48 100644 --- a/code/modules/events/spacevine.dm +++ b/code/modules/events/spacevine.dm @@ -1,548 +1,548 @@ -/datum/round_event_control/spacevine - name = "Spacevine" - typepath = /datum/round_event/spacevine - weight = 15 - max_occurrences = 3 - min_players = 10 - -/datum/round_event/spacevine - fakeable = FALSE - -/datum/round_event/spacevine/start() - var/list/turfs = list() //list of all the empty floor turfs in the hallway areas - - var/obj/structure/spacevine/SV = new() - - for(var/area/hallway/A in world) - for(var/turf/F in A) - if(F.Enter(SV)) - turfs += F - - qdel(SV) - - if(turfs.len) //Pick a turf to spawn at if we can - var/turf/T = pick(turfs) - new /datum/spacevine_controller(T) //spawn a controller at turf - - -/datum/spacevine_mutation - var/name = "" - var/severity = 1 - var/hue - var/quality - -/datum/spacevine_mutation/proc/add_mutation_to_vinepiece(obj/structure/spacevine/holder) - holder.mutations |= src - holder.add_atom_colour(hue, FIXED_COLOUR_PRIORITY) - -/datum/spacevine_mutation/proc/process_mutation(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/process_temperature(obj/structure/spacevine/holder, temp, volume) - return - -/datum/spacevine_mutation/proc/on_birth(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_grow(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_death(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) - . = expected_damage - -/datum/spacevine_mutation/proc/on_cross(obj/structure/spacevine/holder, mob/crosser) - return - -/datum/spacevine_mutation/proc/on_chem(obj/structure/spacevine/holder, datum/reagent/R) - return - -/datum/spacevine_mutation/proc/on_eat(obj/structure/spacevine/holder, mob/living/eater) - return - -/datum/spacevine_mutation/proc/on_spread(obj/structure/spacevine/holder, turf/target) - return - -/datum/spacevine_mutation/proc/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) - return - -/datum/spacevine_mutation/proc/on_explosion(severity, target, obj/structure/spacevine/holder) - return - - -/datum/spacevine_mutation/light - name = "light" - hue = "#ffff00" - quality = POSITIVE - severity = 4 - -/datum/spacevine_mutation/light/on_grow(obj/structure/spacevine/holder) - if(holder.energy) - holder.set_light(severity, 0.3) - -/datum/spacevine_mutation/toxicity - name = "toxic" - hue = "#ff00ff" - severity = 10 - quality = NEGATIVE - -/datum/spacevine_mutation/toxicity/on_cross(obj/structure/spacevine/holder, mob/living/crosser) - if(issilicon(crosser)) - return - if(prob(severity) && istype(crosser) && !isvineimmune(crosser)) - to_chat(crosser, "You accidentally touch the vine and feel a strange sensation.") - crosser.adjustToxLoss(5) - -/datum/spacevine_mutation/toxicity/on_eat(obj/structure/spacevine/holder, mob/living/eater) - if(!isvineimmune(eater)) - eater.adjustToxLoss(5) - -/datum/spacevine_mutation/explosive //OH SHIT IT CAN CHAINREACT RUN!!! - name = "explosive" - hue = "#ff0000" - quality = NEGATIVE - severity = 2 - -/datum/spacevine_mutation/explosive/on_explosion(explosion_severity, target, obj/structure/spacevine/holder) - if(explosion_severity < 3) - qdel(holder) - else - . = 1 - QDEL_IN(holder, 5) - -/datum/spacevine_mutation/explosive/on_death(obj/structure/spacevine/holder, mob/hitter, obj/item/I) - explosion(holder.loc, 0, 0, severity, 0, 0) - -/datum/spacevine_mutation/fire_proof - name = "fire proof" - hue = "#ff8888" - quality = MINOR_NEGATIVE - -/datum/spacevine_mutation/fire_proof/process_temperature(obj/structure/spacevine/holder, temp, volume) - return 1 - -/datum/spacevine_mutation/fire_proof/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) - if(I && I.damtype == "fire") - . = 0 - else - . = expected_damage - -/datum/spacevine_mutation/vine_eating - name = "vine eating" - hue = "#ff7700" - quality = MINOR_NEGATIVE - -/datum/spacevine_mutation/vine_eating/on_spread(obj/structure/spacevine/holder, turf/target) - var/obj/structure/spacevine/prey = locate() in target - if(prey && !prey.mutations.Find(src)) //Eat all vines that are not of the same origin - qdel(prey) - -/datum/spacevine_mutation/aggressive_spread //very OP, but im out of other ideas currently - name = "aggressive spreading" - hue = "#333333" - severity = 3 - quality = NEGATIVE - -/datum/spacevine_mutation/aggressive_spread/on_spread(obj/structure/spacevine/holder, turf/target) - target.ex_act(severity, null, src) // vine immunity handled at /mob/ex_act - -/datum/spacevine_mutation/aggressive_spread/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) - buckled.ex_act(severity, null, src) - -/datum/spacevine_mutation/transparency - name = "transparent" - hue = "" - quality = POSITIVE - -/datum/spacevine_mutation/transparency/on_grow(obj/structure/spacevine/holder) - holder.set_opacity(0) - holder.alpha = 125 - -/datum/spacevine_mutation/oxy_eater - name = "oxygen consuming" - hue = "#ffff88" - severity = 3 - quality = NEGATIVE - -/datum/spacevine_mutation/oxy_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/T = holder.loc - if(istype(T)) - var/datum/gas_mixture/GM = T.air - if(!GM.gases[/datum/gas/oxygen]) - return - GM.gases[/datum/gas/oxygen] = max(GM.gases[/datum/gas/oxygen] - severity * holder.energy, 0) - GAS_GARBAGE_COLLECT(GM.gases) - -/datum/spacevine_mutation/nitro_eater - name = "nitrogen consuming" - hue = "#8888ff" - severity = 3 - quality = NEGATIVE - -/datum/spacevine_mutation/nitro_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/T = holder.loc - if(istype(T)) - var/datum/gas_mixture/GM = T.air - if(!GM.gases[/datum/gas/nitrogen]) - return - GM.gases[/datum/gas/nitrogen] = max(GM.gases[/datum/gas/nitrogen] - severity * holder.energy, 0) - GAS_GARBAGE_COLLECT(GM.gases) - -/datum/spacevine_mutation/carbondioxide_eater - name = "CO2 consuming" - hue = "#00ffff" - severity = 3 - quality = POSITIVE - -/datum/spacevine_mutation/carbondioxide_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/T = holder.loc - if(istype(T)) - var/datum/gas_mixture/GM = T.air - if(!GM.gases[/datum/gas/carbon_dioxide]) - return - GM.gases[/datum/gas/carbon_dioxide] = max(GM.gases[/datum/gas/carbon_dioxide] - severity * holder.energy, 0) - GAS_GARBAGE_COLLECT(GM.gases) - -/datum/spacevine_mutation/plasma_eater - name = "toxins consuming" - hue = "#ffbbff" - severity = 3 - quality = POSITIVE - -/datum/spacevine_mutation/plasma_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/T = holder.loc - if(istype(T)) - var/datum/gas_mixture/GM = T.air - if(!GM.gases[/datum/gas/plasma]) - return - GM.gases[/datum/gas/plasma] = max(GM.gases[/datum/gas/plasma] - severity * holder.energy, 0) - GAS_GARBAGE_COLLECT(GM.gases) - -/datum/spacevine_mutation/thorns - name = "thorny" - hue = "#666666" - severity = 10 - quality = NEGATIVE - -/datum/spacevine_mutation/thorns/on_cross(obj/structure/spacevine/holder, mob/living/crosser) - if(prob(severity) && istype(crosser) && !isvineimmune(holder)) - var/mob/living/M = crosser - M.adjustBruteLoss(5) - to_chat(M, "You cut yourself on the thorny vines.") - -/datum/spacevine_mutation/thorns/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) - if(prob(severity) && istype(hitter) && !isvineimmune(holder)) - var/mob/living/M = hitter - M.adjustBruteLoss(5) - to_chat(M, "You cut yourself on the thorny vines.") - . = expected_damage - -/datum/spacevine_mutation/woodening - name = "hardened" - hue = "#997700" - quality = NEGATIVE - -/datum/spacevine_mutation/woodening/on_grow(obj/structure/spacevine/holder) - if(holder.energy) - holder.density = TRUE - holder.max_integrity = 100 - holder.obj_integrity = holder.max_integrity - -/datum/spacevine_mutation/woodening/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) - if(I.get_sharpness()) - . = expected_damage * 0.5 - else - . = expected_damage - -/datum/spacevine_mutation/flowering - name = "flowering" - hue = "#0A480D" - quality = NEGATIVE - severity = 10 - -/datum/spacevine_mutation/flowering/on_grow(obj/structure/spacevine/holder) - if(holder.energy == 2 && prob(severity) && !locate(/obj/structure/alien/resin/flower_bud_enemy) in range(5,holder)) - new/obj/structure/alien/resin/flower_bud_enemy(get_turf(holder)) - -/datum/spacevine_mutation/flowering/on_cross(obj/structure/spacevine/holder, mob/living/crosser) - if(prob(25)) - holder.entangle(crosser) - - -// SPACE VINES (Note that this code is very similar to Biomass code) -/obj/structure/spacevine - name = "space vines" - desc = "An extremely expansionistic species of vine." - icon = 'icons/effects/spacevines.dmi' - icon_state = "Light1" - anchored = TRUE - density = FALSE - layer = SPACEVINE_LAYER - mouse_opacity = MOUSE_OPACITY_OPAQUE //Clicking anywhere on the turf is good enough - pass_flags = PASSTABLE | PASSGRILLE - max_integrity = 50 - var/energy = 0 - var/datum/spacevine_controller/master = null - var/list/mutations = list() - -/obj/structure/spacevine/Initialize() - . = ..() - add_atom_colour("#ffffff", FIXED_COLOUR_PRIORITY) - -/obj/structure/spacevine/examine(mob/user) - . = ..() - var/text = "This one is a" - if(mutations.len) - for(var/A in mutations) - var/datum/spacevine_mutation/SM = A - text += " [SM.name]" - else - text += " normal" - text += " vine." - . += text - -/obj/structure/spacevine/Destroy() - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_death(src) - if(master) - master.VineDestroyed(src) - mutations = list() - set_opacity(0) - if(has_buckled_mobs()) - unbuckle_all_mobs(force=1) - return ..() - -/obj/structure/spacevine/proc/on_chem_effect(datum/reagent/R) - var/override = 0 - for(var/datum/spacevine_mutation/SM in mutations) - override += SM.on_chem(src, R) - if(!override && istype(R, /datum/reagent/toxin/plantbgone)) - if(prob(50)) - qdel(src) - -/obj/structure/spacevine/proc/eat(mob/eater) - var/override = 0 - for(var/datum/spacevine_mutation/SM in mutations) - override += SM.on_eat(src, eater) - if(!override) - qdel(src) - -/obj/structure/spacevine/attacked_by(obj/item/I, mob/living/user) - var/damage_dealt = I.force - if(I.get_sharpness()) - damage_dealt *= 4 - if(I.damtype == BURN) - damage_dealt *= 4 - - for(var/datum/spacevine_mutation/SM in mutations) - damage_dealt = SM.on_hit(src, user, I, damage_dealt) //on_hit now takes override damage as arg and returns new value for other mutations to permutate further - take_damage(damage_dealt, I.damtype, "melee", 1) - -/obj/structure/spacevine/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(src, 'sound/weapons/slash.ogg', 50, 1) - else - playsound(src, 'sound/weapons/tap.ogg', 50, 1) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, 1) - -/obj/structure/spacevine/Crossed(mob/crosser) - if(isliving(crosser)) - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_cross(src, crosser) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/structure/spacevine/attack_hand(mob/user) - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_hit(src, user) - user_unbuckle_mob(user, user) - . = ..() - -/obj/structure/spacevine/attack_paw(mob/living/user) - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_hit(src, user) - user_unbuckle_mob(user,user) - -/obj/structure/spacevine/attack_alien(mob/living/user) - eat(user) - -/datum/spacevine_controller - var/list/obj/structure/spacevine/vines - var/list/growth_queue - var/spread_multiplier = 5 - var/spread_cap = 30 - var/list/vine_mutations_list - var/mutativeness = 1 - -/datum/spacevine_controller/New(turf/location, list/muts, potency, production) - vines = list() - growth_queue = list() - spawn_spacevine_piece(location, null, muts) - START_PROCESSING(SSobj, src) - vine_mutations_list = list() - init_subtypes(/datum/spacevine_mutation/, vine_mutations_list) - if(potency != null) - mutativeness = potency / 10 - if(production != null) - spread_cap *= production / 5 - spread_multiplier /= production / 5 - -/datum/spacevine_controller/vv_get_dropdown() - . = ..() - . += "---" - .["Delete Vines"] = "?_src_=[REF(src)];[HrefToken()];purge_vines=1" - -/datum/spacevine_controller/Topic(href, href_list) - if(..() || !check_rights(R_ADMIN, FALSE) || !usr.client.holder.CheckAdminHref(href, href_list)) - return - - if(href_list["purge_vines"]) - if(alert(usr, "Are you sure you want to delete this spacevine cluster?", "Delete Vines", "Yes", "No") != "Yes") - return - DeleteVines() - -/datum/spacevine_controller/proc/DeleteVines() //this is kill - QDEL_LIST(vines) //this will also qdel us - -/datum/spacevine_controller/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/datum/spacevine_controller/proc/spawn_spacevine_piece(turf/location, obj/structure/spacevine/parent, list/muts) - var/obj/structure/spacevine/SV = new(location) - growth_queue += SV - vines += SV - SV.master = src - if(muts && muts.len) - for(var/datum/spacevine_mutation/M in muts) - M.add_mutation_to_vinepiece(SV) - return - if(parent) - SV.mutations |= parent.mutations - var/parentcolor = parent.atom_colours[FIXED_COLOUR_PRIORITY] - SV.add_atom_colour(parentcolor, FIXED_COLOUR_PRIORITY) - if(prob(mutativeness)) - var/datum/spacevine_mutation/randmut = pick(vine_mutations_list - SV.mutations) - randmut.add_mutation_to_vinepiece(SV) - - for(var/datum/spacevine_mutation/SM in SV.mutations) - SM.on_birth(SV) - location.Entered(SV) - -/datum/spacevine_controller/proc/VineDestroyed(obj/structure/spacevine/S) - S.master = null - vines -= S - growth_queue -= S - if(!vines.len) - var/obj/item/seeds/kudzu/KZ = new(S.loc) - KZ.mutations |= S.mutations - KZ.set_potency(mutativeness * 10) - KZ.set_production((spread_cap / initial(spread_cap)) * 5) - qdel(src) - -/datum/spacevine_controller/process() - if(!LAZYLEN(vines)) - qdel(src) //space vines exterminated. Remove the controller - return - if(!growth_queue) - qdel(src) //Sanity check - return - - var/length = 0 - - length = min( spread_cap , max( 1 , vines.len / spread_multiplier ) ) - var/i = 0 - var/list/obj/structure/spacevine/queue_end = list() - - for(var/obj/structure/spacevine/SV in growth_queue) - if(QDELETED(SV)) - continue - i++ - queue_end += SV - growth_queue -= SV - for(var/datum/spacevine_mutation/SM in SV.mutations) - SM.process_mutation(SV) - if(SV.energy < 2) //If tile isn't fully grown - if(prob(20)) - SV.grow() - else //If tile is fully grown - SV.entangle_mob() - - SV.spread() - if(i >= length) - break - - growth_queue = growth_queue + queue_end - -/obj/structure/spacevine/proc/grow() - if(!energy) - src.icon_state = pick("Med1", "Med2", "Med3") - energy = 1 - set_opacity(1) - else - src.icon_state = pick("Hvy1", "Hvy2", "Hvy3") - energy = 2 - - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_grow(src) - -/obj/structure/spacevine/proc/entangle_mob() - if(!has_buckled_mobs() && prob(25)) - for(var/mob/living/V in src.loc) - entangle(V) - if(has_buckled_mobs()) - break //only capture one mob at a time - - -/obj/structure/spacevine/proc/entangle(mob/living/V) - if(!V || isvineimmune(V)) - return - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_buckle(src, V) - if((V.stat != DEAD) && (V.buckled != src)) //not dead or captured - to_chat(V, "The vines [pick("wind", "tangle", "tighten")] around you!") - buckle_mob(V, 1) - -/obj/structure/spacevine/proc/spread() - var/direction = pick(GLOB.cardinals) - var/turf/stepturf = get_step(src,direction) - if (!isspaceturf(stepturf) && stepturf.Enter(src)) - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_spread(src, stepturf) - stepturf = get_step(src,direction) //in case turf changes, to make sure no runtimes happen - if(!locate(/obj/structure/spacevine, stepturf)) - if(master) - master.spawn_spacevine_piece(stepturf, src) - -/obj/structure/spacevine/ex_act(severity, target) - if(istype(target, type)) //if its agressive spread vine dont do anything - return - var/i - for(var/datum/spacevine_mutation/SM in mutations) - i += SM.on_explosion(severity, target, src) - if(!i && prob(100/severity)) - qdel(src) - -/obj/structure/spacevine/temperature_expose(null, temp, volume) - var/override = 0 - for(var/datum/spacevine_mutation/SM in mutations) - override += SM.process_temperature(src, temp, volume) - if(!override) - qdel(src) - -/obj/structure/spacevine/CanPass(atom/movable/mover, turf/target) - if(isvineimmune(mover)) - . = TRUE - else - . = ..() - -/proc/isvineimmune(atom/A) - . = FALSE - if(isliving(A)) - var/mob/living/M = A - if(("vines" in M.faction) || ("plants" in M.faction)) - . = TRUE +/datum/round_event_control/spacevine + name = "Spacevine" + typepath = /datum/round_event/spacevine + weight = 15 + max_occurrences = 3 + min_players = 10 + +/datum/round_event/spacevine + fakeable = FALSE + +/datum/round_event/spacevine/start() + var/list/turfs = list() //list of all the empty floor turfs in the hallway areas + + var/obj/structure/spacevine/SV = new() + + for(var/area/hallway/A in world) + for(var/turf/F in A) + if(F.Enter(SV)) + turfs += F + + qdel(SV) + + if(turfs.len) //Pick a turf to spawn at if we can + var/turf/T = pick(turfs) + new /datum/spacevine_controller(T) //spawn a controller at turf + + +/datum/spacevine_mutation + var/name = "" + var/severity = 1 + var/hue + var/quality + +/datum/spacevine_mutation/proc/add_mutation_to_vinepiece(obj/structure/spacevine/holder) + holder.mutations |= src + holder.add_atom_colour(hue, FIXED_COLOUR_PRIORITY) + +/datum/spacevine_mutation/proc/process_mutation(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/process_temperature(obj/structure/spacevine/holder, temp, volume) + return + +/datum/spacevine_mutation/proc/on_birth(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_grow(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_death(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) + . = expected_damage + +/datum/spacevine_mutation/proc/on_cross(obj/structure/spacevine/holder, mob/crosser) + return + +/datum/spacevine_mutation/proc/on_chem(obj/structure/spacevine/holder, datum/reagent/R) + return + +/datum/spacevine_mutation/proc/on_eat(obj/structure/spacevine/holder, mob/living/eater) + return + +/datum/spacevine_mutation/proc/on_spread(obj/structure/spacevine/holder, turf/target) + return + +/datum/spacevine_mutation/proc/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) + return + +/datum/spacevine_mutation/proc/on_explosion(severity, target, obj/structure/spacevine/holder) + return + + +/datum/spacevine_mutation/light + name = "light" + hue = "#ffff00" + quality = POSITIVE + severity = 4 + +/datum/spacevine_mutation/light/on_grow(obj/structure/spacevine/holder) + if(holder.energy) + holder.set_light(severity, 0.3) + +/datum/spacevine_mutation/toxicity + name = "toxic" + hue = "#ff00ff" + severity = 10 + quality = NEGATIVE + +/datum/spacevine_mutation/toxicity/on_cross(obj/structure/spacevine/holder, mob/living/crosser) + if(issilicon(crosser)) + return + if(prob(severity) && istype(crosser) && !isvineimmune(crosser)) + to_chat(crosser, "You accidentally touch the vine and feel a strange sensation.") + crosser.adjustToxLoss(5) + +/datum/spacevine_mutation/toxicity/on_eat(obj/structure/spacevine/holder, mob/living/eater) + if(!isvineimmune(eater)) + eater.adjustToxLoss(5) + +/datum/spacevine_mutation/explosive //OH SHIT IT CAN CHAINREACT RUN!!! + name = "explosive" + hue = "#ff0000" + quality = NEGATIVE + severity = 2 + +/datum/spacevine_mutation/explosive/on_explosion(explosion_severity, target, obj/structure/spacevine/holder) + if(explosion_severity < 3) + qdel(holder) + else + . = 1 + QDEL_IN(holder, 5) + +/datum/spacevine_mutation/explosive/on_death(obj/structure/spacevine/holder, mob/hitter, obj/item/I) + explosion(holder.loc, 0, 0, severity, 0, 0) + +/datum/spacevine_mutation/fire_proof + name = "fire proof" + hue = "#ff8888" + quality = MINOR_NEGATIVE + +/datum/spacevine_mutation/fire_proof/process_temperature(obj/structure/spacevine/holder, temp, volume) + return 1 + +/datum/spacevine_mutation/fire_proof/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) + if(I && I.damtype == "fire") + . = 0 + else + . = expected_damage + +/datum/spacevine_mutation/vine_eating + name = "vine eating" + hue = "#ff7700" + quality = MINOR_NEGATIVE + +/datum/spacevine_mutation/vine_eating/on_spread(obj/structure/spacevine/holder, turf/target) + var/obj/structure/spacevine/prey = locate() in target + if(prey && !prey.mutations.Find(src)) //Eat all vines that are not of the same origin + qdel(prey) + +/datum/spacevine_mutation/aggressive_spread //very OP, but im out of other ideas currently + name = "aggressive spreading" + hue = "#333333" + severity = 3 + quality = NEGATIVE + +/datum/spacevine_mutation/aggressive_spread/on_spread(obj/structure/spacevine/holder, turf/target) + target.ex_act(severity, null, src) // vine immunity handled at /mob/ex_act + +/datum/spacevine_mutation/aggressive_spread/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) + buckled.ex_act(severity, null, src) + +/datum/spacevine_mutation/transparency + name = "transparent" + hue = "" + quality = POSITIVE + +/datum/spacevine_mutation/transparency/on_grow(obj/structure/spacevine/holder) + holder.set_opacity(0) + holder.alpha = 125 + +/datum/spacevine_mutation/oxy_eater + name = "oxygen consuming" + hue = "#ffff88" + severity = 3 + quality = NEGATIVE + +/datum/spacevine_mutation/oxy_eater/process_mutation(obj/structure/spacevine/holder) + var/turf/open/floor/T = holder.loc + if(istype(T)) + var/datum/gas_mixture/GM = T.air + if(!GM.gases[/datum/gas/oxygen]) + return + GM.gases[/datum/gas/oxygen] = max(GM.gases[/datum/gas/oxygen] - severity * holder.energy, 0) + GAS_GARBAGE_COLLECT(GM.gases) + +/datum/spacevine_mutation/nitro_eater + name = "nitrogen consuming" + hue = "#8888ff" + severity = 3 + quality = NEGATIVE + +/datum/spacevine_mutation/nitro_eater/process_mutation(obj/structure/spacevine/holder) + var/turf/open/floor/T = holder.loc + if(istype(T)) + var/datum/gas_mixture/GM = T.air + if(!GM.gases[/datum/gas/nitrogen]) + return + GM.gases[/datum/gas/nitrogen] = max(GM.gases[/datum/gas/nitrogen] - severity * holder.energy, 0) + GAS_GARBAGE_COLLECT(GM.gases) + +/datum/spacevine_mutation/carbondioxide_eater + name = "CO2 consuming" + hue = "#00ffff" + severity = 3 + quality = POSITIVE + +/datum/spacevine_mutation/carbondioxide_eater/process_mutation(obj/structure/spacevine/holder) + var/turf/open/floor/T = holder.loc + if(istype(T)) + var/datum/gas_mixture/GM = T.air + if(!GM.gases[/datum/gas/carbon_dioxide]) + return + GM.gases[/datum/gas/carbon_dioxide] = max(GM.gases[/datum/gas/carbon_dioxide] - severity * holder.energy, 0) + GAS_GARBAGE_COLLECT(GM.gases) + +/datum/spacevine_mutation/plasma_eater + name = "toxins consuming" + hue = "#ffbbff" + severity = 3 + quality = POSITIVE + +/datum/spacevine_mutation/plasma_eater/process_mutation(obj/structure/spacevine/holder) + var/turf/open/floor/T = holder.loc + if(istype(T)) + var/datum/gas_mixture/GM = T.air + if(!GM.gases[/datum/gas/plasma]) + return + GM.gases[/datum/gas/plasma] = max(GM.gases[/datum/gas/plasma] - severity * holder.energy, 0) + GAS_GARBAGE_COLLECT(GM.gases) + +/datum/spacevine_mutation/thorns + name = "thorny" + hue = "#666666" + severity = 10 + quality = NEGATIVE + +/datum/spacevine_mutation/thorns/on_cross(obj/structure/spacevine/holder, mob/living/crosser) + if(prob(severity) && istype(crosser) && !isvineimmune(holder)) + var/mob/living/M = crosser + M.adjustBruteLoss(5) + to_chat(M, "You cut yourself on the thorny vines.") + +/datum/spacevine_mutation/thorns/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) + if(prob(severity) && istype(hitter) && !isvineimmune(holder)) + var/mob/living/M = hitter + M.adjustBruteLoss(5) + to_chat(M, "You cut yourself on the thorny vines.") + . = expected_damage + +/datum/spacevine_mutation/woodening + name = "hardened" + hue = "#997700" + quality = NEGATIVE + +/datum/spacevine_mutation/woodening/on_grow(obj/structure/spacevine/holder) + if(holder.energy) + holder.density = TRUE + holder.max_integrity = 100 + holder.obj_integrity = holder.max_integrity + +/datum/spacevine_mutation/woodening/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) + if(I.get_sharpness()) + . = expected_damage * 0.5 + else + . = expected_damage + +/datum/spacevine_mutation/flowering + name = "flowering" + hue = "#0A480D" + quality = NEGATIVE + severity = 10 + +/datum/spacevine_mutation/flowering/on_grow(obj/structure/spacevine/holder) + if(holder.energy == 2 && prob(severity) && !locate(/obj/structure/alien/resin/flower_bud_enemy) in range(5,holder)) + new/obj/structure/alien/resin/flower_bud_enemy(get_turf(holder)) + +/datum/spacevine_mutation/flowering/on_cross(obj/structure/spacevine/holder, mob/living/crosser) + if(prob(25)) + holder.entangle(crosser) + + +// SPACE VINES (Note that this code is very similar to Biomass code) +/obj/structure/spacevine + name = "space vines" + desc = "An extremely expansionistic species of vine." + icon = 'icons/effects/spacevines.dmi' + icon_state = "Light1" + anchored = TRUE + density = FALSE + layer = SPACEVINE_LAYER + mouse_opacity = MOUSE_OPACITY_OPAQUE //Clicking anywhere on the turf is good enough + pass_flags = PASSTABLE | PASSGRILLE + max_integrity = 50 + var/energy = 0 + var/datum/spacevine_controller/master = null + var/list/mutations = list() + +/obj/structure/spacevine/Initialize() + . = ..() + add_atom_colour("#ffffff", FIXED_COLOUR_PRIORITY) + +/obj/structure/spacevine/examine(mob/user) + . = ..() + var/text = "This one is a" + if(mutations.len) + for(var/A in mutations) + var/datum/spacevine_mutation/SM = A + text += " [SM.name]" + else + text += " normal" + text += " vine." + . += text + +/obj/structure/spacevine/Destroy() + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_death(src) + if(master) + master.VineDestroyed(src) + mutations = list() + set_opacity(0) + if(has_buckled_mobs()) + unbuckle_all_mobs(force=1) + return ..() + +/obj/structure/spacevine/proc/on_chem_effect(datum/reagent/R) + var/override = 0 + for(var/datum/spacevine_mutation/SM in mutations) + override += SM.on_chem(src, R) + if(!override && istype(R, /datum/reagent/toxin/plantbgone)) + if(prob(50)) + qdel(src) + +/obj/structure/spacevine/proc/eat(mob/eater) + var/override = 0 + for(var/datum/spacevine_mutation/SM in mutations) + override += SM.on_eat(src, eater) + if(!override) + qdel(src) + +/obj/structure/spacevine/attacked_by(obj/item/I, mob/living/user) + var/damage_dealt = I.force + if(I.get_sharpness()) + damage_dealt *= 4 + if(I.damtype == BURN) + damage_dealt *= 4 + + for(var/datum/spacevine_mutation/SM in mutations) + damage_dealt = SM.on_hit(src, user, I, damage_dealt) //on_hit now takes override damage as arg and returns new value for other mutations to permutate further + take_damage(damage_dealt, I.damtype, "melee", 1) + +/obj/structure/spacevine/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(src, 'sound/weapons/slash.ogg', 50, 1) + else + playsound(src, 'sound/weapons/tap.ogg', 50, 1) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, 1) + +/obj/structure/spacevine/Crossed(mob/crosser) + if(isliving(crosser)) + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_cross(src, crosser) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/structure/spacevine/attack_hand(mob/user) + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_hit(src, user) + user_unbuckle_mob(user, user) + . = ..() + +/obj/structure/spacevine/attack_paw(mob/living/user) + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_hit(src, user) + user_unbuckle_mob(user,user) + +/obj/structure/spacevine/attack_alien(mob/living/user) + eat(user) + +/datum/spacevine_controller + var/list/obj/structure/spacevine/vines + var/list/growth_queue + var/spread_multiplier = 5 + var/spread_cap = 30 + var/list/vine_mutations_list + var/mutativeness = 1 + +/datum/spacevine_controller/New(turf/location, list/muts, potency, production) + vines = list() + growth_queue = list() + spawn_spacevine_piece(location, null, muts) + START_PROCESSING(SSobj, src) + vine_mutations_list = list() + init_subtypes(/datum/spacevine_mutation/, vine_mutations_list) + if(potency != null) + mutativeness = potency / 10 + if(production != null) + spread_cap *= production / 5 + spread_multiplier /= production / 5 + +/datum/spacevine_controller/vv_get_dropdown() + . = ..() + . += "---" + .["Delete Vines"] = "?_src_=[REF(src)];[HrefToken()];purge_vines=1" + +/datum/spacevine_controller/Topic(href, href_list) + if(..() || !check_rights(R_ADMIN, FALSE) || !usr.client.holder.CheckAdminHref(href, href_list)) + return + + if(href_list["purge_vines"]) + if(alert(usr, "Are you sure you want to delete this spacevine cluster?", "Delete Vines", "Yes", "No") != "Yes") + return + DeleteVines() + +/datum/spacevine_controller/proc/DeleteVines() //this is kill + QDEL_LIST(vines) //this will also qdel us + +/datum/spacevine_controller/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/datum/spacevine_controller/proc/spawn_spacevine_piece(turf/location, obj/structure/spacevine/parent, list/muts) + var/obj/structure/spacevine/SV = new(location) + growth_queue += SV + vines += SV + SV.master = src + if(muts && muts.len) + for(var/datum/spacevine_mutation/M in muts) + M.add_mutation_to_vinepiece(SV) + return + if(parent) + SV.mutations |= parent.mutations + var/parentcolor = parent.atom_colours[FIXED_COLOUR_PRIORITY] + SV.add_atom_colour(parentcolor, FIXED_COLOUR_PRIORITY) + if(prob(mutativeness)) + var/datum/spacevine_mutation/randmut = pick(vine_mutations_list - SV.mutations) + randmut.add_mutation_to_vinepiece(SV) + + for(var/datum/spacevine_mutation/SM in SV.mutations) + SM.on_birth(SV) + location.Entered(SV) + +/datum/spacevine_controller/proc/VineDestroyed(obj/structure/spacevine/S) + S.master = null + vines -= S + growth_queue -= S + if(!vines.len) + var/obj/item/seeds/kudzu/KZ = new(S.loc) + KZ.mutations |= S.mutations + KZ.set_potency(mutativeness * 10) + KZ.set_production((spread_cap / initial(spread_cap)) * 5) + qdel(src) + +/datum/spacevine_controller/process() + if(!LAZYLEN(vines)) + qdel(src) //space vines exterminated. Remove the controller + return + if(!growth_queue) + qdel(src) //Sanity check + return + + var/length = 0 + + length = min( spread_cap , max( 1 , vines.len / spread_multiplier ) ) + var/i = 0 + var/list/obj/structure/spacevine/queue_end = list() + + for(var/obj/structure/spacevine/SV in growth_queue) + if(QDELETED(SV)) + continue + i++ + queue_end += SV + growth_queue -= SV + for(var/datum/spacevine_mutation/SM in SV.mutations) + SM.process_mutation(SV) + if(SV.energy < 2) //If tile isn't fully grown + if(prob(20)) + SV.grow() + else //If tile is fully grown + SV.entangle_mob() + + SV.spread() + if(i >= length) + break + + growth_queue = growth_queue + queue_end + +/obj/structure/spacevine/proc/grow() + if(!energy) + src.icon_state = pick("Med1", "Med2", "Med3") + energy = 1 + set_opacity(1) + else + src.icon_state = pick("Hvy1", "Hvy2", "Hvy3") + energy = 2 + + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_grow(src) + +/obj/structure/spacevine/proc/entangle_mob() + if(!has_buckled_mobs() && prob(25)) + for(var/mob/living/V in src.loc) + entangle(V) + if(has_buckled_mobs()) + break //only capture one mob at a time + + +/obj/structure/spacevine/proc/entangle(mob/living/V) + if(!V || isvineimmune(V)) + return + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_buckle(src, V) + if((V.stat != DEAD) && (V.buckled != src)) //not dead or captured + to_chat(V, "The vines [pick("wind", "tangle", "tighten")] around you!") + buckle_mob(V, 1) + +/obj/structure/spacevine/proc/spread() + var/direction = pick(GLOB.cardinals) + var/turf/stepturf = get_step(src,direction) + if (!isspaceturf(stepturf) && stepturf.Enter(src)) + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_spread(src, stepturf) + stepturf = get_step(src,direction) //in case turf changes, to make sure no runtimes happen + if(!locate(/obj/structure/spacevine, stepturf)) + if(master) + master.spawn_spacevine_piece(stepturf, src) + +/obj/structure/spacevine/ex_act(severity, target) + if(istype(target, type)) //if its agressive spread vine dont do anything + return + var/i + for(var/datum/spacevine_mutation/SM in mutations) + i += SM.on_explosion(severity, target, src) + if(!i && prob(100/severity)) + qdel(src) + +/obj/structure/spacevine/temperature_expose(null, temp, volume) + var/override = 0 + for(var/datum/spacevine_mutation/SM in mutations) + override += SM.process_temperature(src, temp, volume) + if(!override) + qdel(src) + +/obj/structure/spacevine/CanPass(atom/movable/mover, turf/target) + if(isvineimmune(mover)) + . = TRUE + else + . = ..() + +/proc/isvineimmune(atom/A) + . = FALSE + if(isliving(A)) + var/mob/living/M = A + if(("vines" in M.faction) || ("plants" in M.faction)) + . = TRUE diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm index 8c6b3e23a5..2cba5fc529 100644 --- a/code/modules/events/spider_infestation.dm +++ b/code/modules/events/spider_infestation.dm @@ -1,40 +1,40 @@ -/datum/round_event_control/spider_infestation - name = "Spider Infestation" - typepath = /datum/round_event/spider_infestation - weight = 5 - gamemode_blacklist = list("dynamic") - 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", "aliens") - - -/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 - 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 + gamemode_blacklist = list("dynamic") + 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", "aliens") + + +/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 + 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 8ee943beb9..9cf3a86e71 100644 --- a/code/modules/events/spontaneous_appendicitis.dm +++ b/code/modules/events/spontaneous_appendicitis.dm @@ -1,33 +1,33 @@ -/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 (HAS_TRAIT(H,TRAIT_EXEMPT_HEALTH_EVENTS)) - 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(!(MOB_ORGANIC in H.mob_biotypes)) //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) +/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 (HAS_TRAIT(H,TRAIT_EXEMPT_HEALTH_EVENTS)) + 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(!(MOB_ORGANIC in H.mob_biotypes)) //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) break \ No newline at end of file diff --git a/code/modules/events/vent_clog.dm b/code/modules/events/vent_clog.dm index 9075f693db..77bb7661ba 100644 --- a/code/modules/events/vent_clog.dm +++ b/code/modules/events/vent_clog.dm @@ -1,212 +1,212 @@ -/datum/round_event_control/vent_clog - name = "Clogged Vents: Normal" - typepath = /datum/round_event/vent_clog - weight = 10 - max_occurrences = 3 - gamemode_blacklist = list("dynamic") - min_players = 25 - -/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/charcoal, - /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/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, - /datum/reagent/consumable/cornoil, - /datum/reagent/uranium, - /datum/reagent/carpet, - /datum/reagent/firefighting_foam, - /datum/reagent/consumable/tearjuice, - /datum/reagent/medicine/strange_reagent - - ) - //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(120, 180) - for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/temp_vent in GLOB.machines) - var/turf/T = get_turf(temp_vent) - var/area/A = T.loc - if(T && is_station_level(T.z) && !temp_vent.welded && !A.safe) - vents += temp_vent - - if(!vents.len) - return kill() - -/datum/round_event/vent_clog/tick() - - if(!vents.len) - return kill() - - CHECK_TICK - - var/obj/machinery/atmospherics/components/unary/vent = pick(vents) - vents -= vent - - if(!vent || vent.welded) - return - - var/turf/T = get_turf(vent) - if(!T) - return - - 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) - - var/datum/effect_system/smoke_spread/chem/smoke_machine/C = new - C.set_up(R,16,1,T) - C.start() - playsound(T, 'sound/effects/smoke.ogg', 50, 1, -3) - -/datum/round_event_control/vent_clog/threatening - name = "Clogged Vents: Threatening" - typepath = /datum/round_event/vent_clog/threatening - weight = 4 - min_players = 35 - 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 = 45 - 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 = "Clogged Vents: Beer" - typepath = /datum/round_event/vent_clog/beer - max_occurrences = 0 - -/datum/round_event/vent_clog/beer - reagentsAmount = 100 - -/datum/round_event_control/vent_clog/plasma_decon - name = "Anti-Plasma Flood" - typepath = /datum/round_event/vent_clog/plasma_decon - max_occurrences = 0 - -/datum/round_event_control/vent_clog/female - name = "Clogged Vents; Girlcum" - typepath = /datum/round_event/vent_clog/female - max_occurrences = 0 - -/datum/round_event/vent_clog/female - reagentsAmount = 100 - -/datum/round_event_control/vent_clog/male - name = "Clogged Vents: Semen" - typepath = /datum/round_event/vent_clog/male - max_occurrences = 0 - -/datum/round_event/vent_clog/male - 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 && !vent.welded) - var/datum/reagents/R = new/datum/reagents(1000) - R.my_atom = vent - R.add_reagent(/datum/reagent/consumable/ethanol/beer, reagentsAmount) - - var/datum/effect_system/foam_spread/foam = new - foam.set_up(200, get_turf(vent), R) - foam.start() - CHECK_TICK - -/datum/round_event/vent_clog/male/announce() - priority_announce("The scrubbers network is experiencing a backpressure surge. Some ejaculation of contents may occur.", "Atmospherics alert") - -/datum/round_event/vent_clog/male/start() - for(var/obj/machinery/atmospherics/components/unary/vent in vents) - if(vent && vent.loc && !vent.welded) - var/datum/reagents/R = new/datum/reagents(1000) - R.my_atom = vent - R.add_reagent(/datum/reagent/consumable/semen, reagentsAmount) - - var/datum/effect_system/foam_spread/foam = new - foam.set_up(200, get_turf(vent), R) - foam.start() - CHECK_TICK - -/datum/round_event/vent_clog/female/announce() - priority_announce("The scrubbers network is experiencing a backpressure squirt. Some ejection of contents may occur.", "Atmospherics alert") - -/datum/round_event/vent_clog/female/start() - for(var/obj/machinery/atmospherics/components/unary/vent in vents) - if(vent && vent.loc && !vent.welded) - var/datum/reagents/R = new/datum/reagents(1000) - R.my_atom = vent - R.add_reagent(/datum/reagent/consumable/femcum, reagentsAmount) - - var/datum/effect_system/foam_spread/foam = new - foam.set_up(200, get_turf(vent), R) - foam.start() - 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 && !vent.welded) - 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 + gamemode_blacklist = list("dynamic") + min_players = 25 + +/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/charcoal, + /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/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, + /datum/reagent/consumable/cornoil, + /datum/reagent/uranium, + /datum/reagent/carpet, + /datum/reagent/firefighting_foam, + /datum/reagent/consumable/tearjuice, + /datum/reagent/medicine/strange_reagent + + ) + //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(120, 180) + for(var/obj/machinery/atmospherics/components/unary/vent_scrubber/temp_vent in GLOB.machines) + var/turf/T = get_turf(temp_vent) + var/area/A = T.loc + if(T && is_station_level(T.z) && !temp_vent.welded && !A.safe) + vents += temp_vent + + if(!vents.len) + return kill() + +/datum/round_event/vent_clog/tick() + + if(!vents.len) + return kill() + + CHECK_TICK + + var/obj/machinery/atmospherics/components/unary/vent = pick(vents) + vents -= vent + + if(!vent || vent.welded) + return + + var/turf/T = get_turf(vent) + if(!T) + return + + 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) + + var/datum/effect_system/smoke_spread/chem/smoke_machine/C = new + C.set_up(R,16,1,T) + C.start() + playsound(T, 'sound/effects/smoke.ogg', 50, 1, -3) + +/datum/round_event_control/vent_clog/threatening + name = "Clogged Vents: Threatening" + typepath = /datum/round_event/vent_clog/threatening + weight = 4 + min_players = 35 + 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 = 45 + 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 = "Clogged Vents: Beer" + typepath = /datum/round_event/vent_clog/beer + max_occurrences = 0 + +/datum/round_event/vent_clog/beer + reagentsAmount = 100 + +/datum/round_event_control/vent_clog/plasma_decon + name = "Anti-Plasma Flood" + typepath = /datum/round_event/vent_clog/plasma_decon + max_occurrences = 0 + +/datum/round_event_control/vent_clog/female + name = "Clogged Vents; Girlcum" + typepath = /datum/round_event/vent_clog/female + max_occurrences = 0 + +/datum/round_event/vent_clog/female + reagentsAmount = 100 + +/datum/round_event_control/vent_clog/male + name = "Clogged Vents: Semen" + typepath = /datum/round_event/vent_clog/male + max_occurrences = 0 + +/datum/round_event/vent_clog/male + 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 && !vent.welded) + var/datum/reagents/R = new/datum/reagents(1000) + R.my_atom = vent + R.add_reagent(/datum/reagent/consumable/ethanol/beer, reagentsAmount) + + var/datum/effect_system/foam_spread/foam = new + foam.set_up(200, get_turf(vent), R) + foam.start() + CHECK_TICK + +/datum/round_event/vent_clog/male/announce() + priority_announce("The scrubbers network is experiencing a backpressure surge. Some ejaculation of contents may occur.", "Atmospherics alert") + +/datum/round_event/vent_clog/male/start() + for(var/obj/machinery/atmospherics/components/unary/vent in vents) + if(vent && vent.loc && !vent.welded) + var/datum/reagents/R = new/datum/reagents(1000) + R.my_atom = vent + R.add_reagent(/datum/reagent/consumable/semen, reagentsAmount) + + var/datum/effect_system/foam_spread/foam = new + foam.set_up(200, get_turf(vent), R) + foam.start() + CHECK_TICK + +/datum/round_event/vent_clog/female/announce() + priority_announce("The scrubbers network is experiencing a backpressure squirt. Some ejection of contents may occur.", "Atmospherics alert") + +/datum/round_event/vent_clog/female/start() + for(var/obj/machinery/atmospherics/components/unary/vent in vents) + if(vent && vent.loc && !vent.welded) + var/datum/reagents/R = new/datum/reagents(1000) + R.my_atom = vent + R.add_reagent(/datum/reagent/consumable/femcum, reagentsAmount) + + var/datum/effect_system/foam_spread/foam = new + foam.set_up(200, get_turf(vent), R) + foam.start() + 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 && !vent.welded) + 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 a063296edf..c59a8004ca 100644 --- a/code/modules/fields/timestop.dm +++ b/code/modules/fields/timestop.dm @@ -1,163 +1,163 @@ - -/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 - 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/conjure/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/conjure/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, 1, -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/wizard - check_anti_magic = TRUE - duration = 100 - -/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/mob/living/frozen_mobs = list() - var/list/obj/item/projectile/frozen_projectiles = list() - var/list/atom/movable/frozen_throws = list() - 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(A.throwing) - freeze_throwing(A) - if(isliving(A)) - freeze_mob(A) - else if(istype(A, /obj/item/projectile)) - freeze_projectile(A) - else - return FALSE - - into_the_negative_zone(A) - - return TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_all() - for(var/i in frozen_projectiles) - unfreeze_projectile(i) - for(var/i in frozen_mobs) - unfreeze_mob(i) - for(var/i in frozen_throws) - unfreeze_throw(i) - -/datum/proximity_monitor/advanced/timestop/proc/freeze_throwing(atom/movable/AM) - var/datum/thrownthing/T = AM.throwing - T.paused = TRUE - frozen_throws[AM] = T - global_frozen_atoms[AM] = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_throw(atom/movable/AM) - var/datum/thrownthing/T = frozen_throws[AM] - T.paused = FALSE - frozen_throws -= AM - global_frozen_atoms -= AM - -/datum/proximity_monitor/advanced/timestop/process() - for(var/i in frozen_mobs) - var/mob/living/m = i - if(get_dist(get_turf(m), get_turf(host)) > current_range) - unfreeze_mob(m) - else - m.Stun(20, 1, 1) - -/datum/proximity_monitor/advanced/timestop/setup_field_turf(turf/T) - for(var/i in T.contents) - freeze_atom(i) - return ..() - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/item/projectile/P) - escape_the_negative_zone(P) - frozen_projectiles -= P - P.paused = FALSE - global_frozen_atoms -= P - -/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/item/projectile/P) - frozen_projectiles[P] = TRUE - P.paused = TRUE - global_frozen_atoms[P] = TRUE - -/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L) - if(L.anti_magic_check(check_anti_magic, check_holy)) - immune += L - return - L.Stun(20, 1, 1) - frozen_mobs[L] = L.anchored - L.anchored = TRUE - global_frozen_atoms[L] = TRUE - if(ishostile(L)) - var/mob/living/simple_animal/hostile/H = L - H.toggle_ai(AI_OFF) - H.LoseTarget() - -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/L) - escape_the_negative_zone(L) - L.AdjustStun(-20, 1, 1) - L.anchored = frozen_mobs[L] - frozen_mobs -= L - global_frozen_atoms -= L - if(ishostile(L)) - var/mob/living/simple_animal/hostile/H = L - H.toggle_ai(initial(H.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) + +/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 + 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/conjure/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/conjure/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, 1, -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/wizard + check_anti_magic = TRUE + duration = 100 + +/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/mob/living/frozen_mobs = list() + var/list/obj/item/projectile/frozen_projectiles = list() + var/list/atom/movable/frozen_throws = list() + 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(A.throwing) + freeze_throwing(A) + if(isliving(A)) + freeze_mob(A) + else if(istype(A, /obj/item/projectile)) + freeze_projectile(A) + else + return FALSE + + into_the_negative_zone(A) + + return TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_all() + for(var/i in frozen_projectiles) + unfreeze_projectile(i) + for(var/i in frozen_mobs) + unfreeze_mob(i) + for(var/i in frozen_throws) + unfreeze_throw(i) + +/datum/proximity_monitor/advanced/timestop/proc/freeze_throwing(atom/movable/AM) + var/datum/thrownthing/T = AM.throwing + T.paused = TRUE + frozen_throws[AM] = T + global_frozen_atoms[AM] = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_throw(atom/movable/AM) + var/datum/thrownthing/T = frozen_throws[AM] + T.paused = FALSE + frozen_throws -= AM + global_frozen_atoms -= AM + +/datum/proximity_monitor/advanced/timestop/process() + for(var/i in frozen_mobs) + var/mob/living/m = i + if(get_dist(get_turf(m), get_turf(host)) > current_range) + unfreeze_mob(m) + else + m.Stun(20, 1, 1) + +/datum/proximity_monitor/advanced/timestop/setup_field_turf(turf/T) + for(var/i in T.contents) + freeze_atom(i) + return ..() + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/item/projectile/P) + escape_the_negative_zone(P) + frozen_projectiles -= P + P.paused = FALSE + global_frozen_atoms -= P + +/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/item/projectile/P) + frozen_projectiles[P] = TRUE + P.paused = TRUE + global_frozen_atoms[P] = TRUE + +/datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L) + if(L.anti_magic_check(check_anti_magic, check_holy)) + immune += L + return + L.Stun(20, 1, 1) + frozen_mobs[L] = L.anchored + L.anchored = TRUE + global_frozen_atoms[L] = TRUE + if(ishostile(L)) + var/mob/living/simple_animal/hostile/H = L + H.toggle_ai(AI_OFF) + H.LoseTarget() + +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/L) + escape_the_negative_zone(L) + L.AdjustStun(-20, 1, 1) + L.anchored = frozen_mobs[L] + frozen_mobs -= L + global_frozen_atoms -= L + if(ishostile(L)) + var/mob/living/simple_animal/hostile/H = L + H.toggle_ai(initial(H.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) \ No newline at end of file diff --git a/code/modules/flufftext/Dreaming.dm b/code/modules/flufftext/Dreaming.dm index 165d2b4a3a..43c3337a4b 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 da67eaa9cf..351495b97a 100644 --- a/code/modules/food_and_drinks/pizzabox.dm +++ b/code/modules/food_and_drinks/pizzabox.dm @@ -1,346 +1,346 @@ -/obj/item/bombcore/miniature/pizza - name = "pizza bomb" - desc = "Special delivery!" - icon_state = "pizzabomb_inactive" - item_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" - item_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. - var/const/BOMB_TIMER_MIN = 1 - var/const/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, style_flags = NONE) - . = 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, item_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 - bomb_timer = CLAMP(CEILING(bomb_timer / 2, 1), BOMB_TIMER_MIN, BOMB_TIMER_MAX) - bomb_defused = FALSE - - var/message = "[ADMIN_LOOKUPFLW(user)] has trapped a [src] with [bomb] set to [bomb_timer * 2] seconds." - GLOB.bombers += message - message_admins(message) - log_game("[key_name(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, 0) - 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/movable/AM) - 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) - 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" + item_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" + item_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. + var/const/BOMB_TIMER_MIN = 1 + var/const/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, style_flags = NONE) + . = 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, item_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 + bomb_timer = CLAMP(CEILING(bomb_timer / 2, 1), BOMB_TIMER_MIN, BOMB_TIMER_MAX) + bomb_defused = FALSE + + var/message = "[ADMIN_LOOKUPFLW(user)] has trapped a [src] with [bomb] set to [bomb_timer * 2] seconds." + GLOB.bombers += message + message_admins(message) + log_game("[key_name(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, 0) + 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/movable/AM) + 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) + 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/holodeck/items.dm b/code/modules/holodeck/items.dm index 5c143f7a25..7bd2ac689b 100644 --- a/code/modules/holodeck/items.dm +++ b/code/modules/holodeck/items.dm @@ -1,228 +1,228 @@ -/* - 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_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 - -/obj/item/holo/esword/green/Initialize() - . = ..() - item_color = "green" - - -/obj/item/holo/esword/red/Initialize() - . = ..() - item_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() - . = ..() - item_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[item_color]" - w_class = WEIGHT_CLASS_BULKY - hitsound = 'sound/weapons/blade1.ogg' - playsound(user, 'sound/weapons/saberon.ogg', 20, 1) - 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, 1) - 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" - item_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" - item_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) - ..() - if((ishuman(hit_atom))) - var/mob/living/carbon/M = hit_atom - playsound(src, 'sound/items/dodgeball.ogg', 50, 1) - M.apply_damage(10, STAMINA) - if(prob(5)) - M.Knockdown(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.Knockdown(100) - visible_message("[user] dunks [L] into \the [src]!") - user.stop_pulling() - else - ..() - -/obj/structure/holohoop/hitby(atom/movable/AM) - if (isitem(AM) && !istype(AM,/obj/item/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 = 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 || 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() - 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_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 + +/obj/item/holo/esword/green/Initialize() + . = ..() + item_color = "green" + + +/obj/item/holo/esword/red/Initialize() + . = ..() + item_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() + . = ..() + item_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[item_color]" + w_class = WEIGHT_CLASS_BULKY + hitsound = 'sound/weapons/blade1.ogg' + playsound(user, 'sound/weapons/saberon.ogg', 20, 1) + 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, 1) + 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" + item_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" + item_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) + ..() + if((ishuman(hit_atom))) + var/mob/living/carbon/M = hit_atom + playsound(src, 'sound/items/dodgeball.ogg', 50, 1) + M.apply_damage(10, STAMINA) + if(prob(5)) + M.Knockdown(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.Knockdown(100) + visible_message("[user] dunks [L] into \the [src]!") + user.stop_pulling() + else + ..() + +/obj/structure/holohoop/hitby(atom/movable/AM) + if (isitem(AM) && !istype(AM,/obj/item/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 = 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 || 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() + 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 ac750a0492..e946973ded 100644 --- a/code/modules/hydroponics/biogenerator.dm +++ b/code/modules/hydroponics/biogenerator.dm @@ -1,327 +1,327 @@ -/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 - var/processing = FALSE - var/obj/item/reagent_containers/glass/beaker = null - var/points = 0 - var/menustat = "menu" - 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") - var/list/timesFiveCategories = list("Food", "Botany Chemicals") - -/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) - beaker.ex_act(severity, target) - -/obj/machinery/biogenerator/handle_atom_del(atom/A) - ..() - if(A == beaker) - beaker = null - update_icon() - updateUsrDialog() - -/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/on_reagent_change(changetype) //When the reagents change, change the icon as well. - update_icon() - -/obj/machinery/biogenerator/update_icon() - 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" - return - -/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() - updateUsrDialog() - 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/ui_interact(mob/user) - if(stat & BROKEN || panel_open) - return - . = ..() - var/dat - if(processing) - dat += "
                Biogenerator is processing! Please wait...

                " - else - switch(menustat) - if("nopoints") - dat += "
                You do not have enough biomass to create products.
                Please, put growns into reactor and activate it.
                " - menustat = "menu" - if("complete") - dat += "
                Operation complete.
                " - menustat = "menu" - if("void") - dat += "
                Error: No growns inside.
                Please, put growns into reactor.
                " - menustat = "menu" - if("nobeakerspace") - dat += "
                Not enough space left in container. Unable to create product.
                " - menustat = "menu" - if(beaker) - 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 - - dat += "
                Biomass: [points] units.

                " - dat += "ActivateDetach Container" - for(var/cat in categories) - dat += "

                [cat]:

                " - dat += "
                " - for(var/V in categories[cat]) - var/datum/design/D = V - dat += "[D.name]: Make" - if(cat in timesFiveCategories) - dat += "x5" - if(ispath(D.build_path, /obj/item/stack)) - dat += "x10" - dat += "([D.materials[MAT_BIOMASS]/efficiency])
                " - dat += "
                " - else - dat += "
                No container inside, please insert container.
                " - - var/datum/browser/popup = new(user, "biogen", name, 350, 520) - popup.set_content(dat) - popup.open() - -/obj/machinery/biogenerator/proc/activate() - if (usr.stat != CONSCIOUS) - return - if (src.stat != NONE) //NOPOWER etc - return - if(processing) - to_chat(usr, "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() - updateUsrDialog() - playsound(src.loc, 'sound/machines/blender.ogg', 50, 1) - use_power(S*30) - sleep(S+15/productivity) - processing = FALSE - update_icon() - else - menustat = "void" - -/obj/machinery/biogenerator/proc/check_cost(list/materials, multiplier = 1, remove_points = 1) - if(materials.len != 1 || materials[1] != MAT_BIOMASS) - return FALSE - if (materials[MAT_BIOMASS]*multiplier/efficiency > points) - menustat = "nopoints" - return FALSE - else - if(remove_points) - points -= materials[MAT_BIOMASS]*multiplier/efficiency - update_icon() - updateUsrDialog() - 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) - menustat = "nobeakerspace" - 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)) - 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 - - menustat = "complete" - update_icon() - return . - -/obj/machinery/biogenerator/proc/detach() - if(beaker) - beaker.forceMove(drop_location()) - beaker = null - update_icon() - -/obj/machinery/biogenerator/Topic(href, href_list) - if(..() || panel_open) - return - - usr.set_machine(src) - - if(href_list["activate"]) - activate() - updateUsrDialog() - - else if(href_list["detach"]) - detach() - updateUsrDialog() - - else if(href_list["create"]) - var/amount = (text2num(href_list["amount"])) - //Can't be outside these (if you change this keep a sane limit) - amount = CLAMP(amount, 1, 50) - var/id = href_list["create"] - if(!stored_research.researched_designs.Find(id)) - //naughty naughty - stack_trace("ID did not map to a researched datum [id]") - return - - //Get design by id (or may return error design) - var/datum/design/D = SSresearch.techweb_design_by_id(id) - //Valid design datum, amount and the datum is not the error design, lets proceed - if(D && amount && !istype(D, /datum/design/error_design)) - create_product(D, amount) - //This shouldnt happen normally but href forgery is real - else - stack_trace("ID could not be turned into a valid techweb design datum [id]") - updateUsrDialog() - - else if(href_list["menu"]) - menustat = "menu" - updateUsrDialog() +/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 + var/processing = FALSE + var/obj/item/reagent_containers/glass/beaker = null + var/points = 0 + var/menustat = "menu" + 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") + var/list/timesFiveCategories = list("Food", "Botany Chemicals") + +/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) + beaker.ex_act(severity, target) + +/obj/machinery/biogenerator/handle_atom_del(atom/A) + ..() + if(A == beaker) + beaker = null + update_icon() + updateUsrDialog() + +/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/on_reagent_change(changetype) //When the reagents change, change the icon as well. + update_icon() + +/obj/machinery/biogenerator/update_icon() + 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" + return + +/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() + updateUsrDialog() + 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/ui_interact(mob/user) + if(stat & BROKEN || panel_open) + return + . = ..() + var/dat + if(processing) + dat += "
                Biogenerator is processing! Please wait...

                " + else + switch(menustat) + if("nopoints") + dat += "
                You do not have enough biomass to create products.
                Please, put growns into reactor and activate it.
                " + menustat = "menu" + if("complete") + dat += "
                Operation complete.
                " + menustat = "menu" + if("void") + dat += "
                Error: No growns inside.
                Please, put growns into reactor.
                " + menustat = "menu" + if("nobeakerspace") + dat += "
                Not enough space left in container. Unable to create product.
                " + menustat = "menu" + if(beaker) + 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 + + dat += "
                Biomass: [points] units.

                " + dat += "ActivateDetach Container" + for(var/cat in categories) + dat += "

                [cat]:

                " + dat += "
                " + for(var/V in categories[cat]) + var/datum/design/D = V + dat += "[D.name]: Make" + if(cat in timesFiveCategories) + dat += "x5" + if(ispath(D.build_path, /obj/item/stack)) + dat += "x10" + dat += "([D.materials[MAT_BIOMASS]/efficiency])
                " + dat += "
                " + else + dat += "
                No container inside, please insert container.
                " + + var/datum/browser/popup = new(user, "biogen", name, 350, 520) + popup.set_content(dat) + popup.open() + +/obj/machinery/biogenerator/proc/activate() + if (usr.stat != CONSCIOUS) + return + if (src.stat != NONE) //NOPOWER etc + return + if(processing) + to_chat(usr, "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() + updateUsrDialog() + playsound(src.loc, 'sound/machines/blender.ogg', 50, 1) + use_power(S*30) + sleep(S+15/productivity) + processing = FALSE + update_icon() + else + menustat = "void" + +/obj/machinery/biogenerator/proc/check_cost(list/materials, multiplier = 1, remove_points = 1) + if(materials.len != 1 || materials[1] != MAT_BIOMASS) + return FALSE + if (materials[MAT_BIOMASS]*multiplier/efficiency > points) + menustat = "nopoints" + return FALSE + else + if(remove_points) + points -= materials[MAT_BIOMASS]*multiplier/efficiency + update_icon() + updateUsrDialog() + 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) + menustat = "nobeakerspace" + 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)) + 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 + + menustat = "complete" + update_icon() + return . + +/obj/machinery/biogenerator/proc/detach() + if(beaker) + beaker.forceMove(drop_location()) + beaker = null + update_icon() + +/obj/machinery/biogenerator/Topic(href, href_list) + if(..() || panel_open) + return + + usr.set_machine(src) + + if(href_list["activate"]) + activate() + updateUsrDialog() + + else if(href_list["detach"]) + detach() + updateUsrDialog() + + else if(href_list["create"]) + var/amount = (text2num(href_list["amount"])) + //Can't be outside these (if you change this keep a sane limit) + amount = CLAMP(amount, 1, 50) + var/id = href_list["create"] + if(!stored_research.researched_designs.Find(id)) + //naughty naughty + stack_trace("ID did not map to a researched datum [id]") + return + + //Get design by id (or may return error design) + var/datum/design/D = SSresearch.techweb_design_by_id(id) + //Valid design datum, amount and the datum is not the error design, lets proceed + if(D && amount && !istype(D, /datum/design/error_design)) + create_product(D, amount) + //This shouldnt happen normally but href forgery is real + else + stack_trace("ID could not be turned into a valid techweb design datum [id]") + updateUsrDialog() + + else if(href_list["menu"]) + menustat = "menu" + updateUsrDialog() diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm index 91ccce7cfd..bb751c993a 100644 --- a/code/modules/hydroponics/grown.dm +++ b/code/modules/hydroponics/grown.dm @@ -1,169 +1,169 @@ -// *********************************************************** -// Foods that are produced from hydroponics ~~~~~~~~~~ -// Data from the seeds carry over to these grown foods -// *********************************************************** - -// Base type. Subtypes are found in /grown dir. -/obj/item/reagent_containers/food/snacks/grown - icon = 'icons/obj/hydroponics/harvest.dmi' - 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/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. - -/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/msg = "*---------*\n This is \a [src].\n" - if(seed) - msg += seed.get_analyzer_text() - var/reag_txt = "" - if(seed) - for(var/reagent_id in seed.reagents_add) - var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_id] - var/amt = reagents.get_reagent_amount(reagent_id) - reag_txt += "\n- [R.name]: [amt]" - - if(reag_txt) - 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) - 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) - if(ispath(splat_type, /obj/effect/decal/cleanable/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] has been squashed.","You hear a smack.") - if(seed) - for(var/datum/plant_gene/trait/trait in seed.genes) - trait.on_squash(src, target) - - reagents.reaction(T) - for(var/A in T) - reagents.reaction(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) - -// For item-containing growns such as eggy or gatfruit -/obj/item/reagent_containers/food/snacks/grown/shell/attack_self(mob/user) - var/obj/item/T - if(trash) - T = generate_trash() - qdel(src) - user.putItemFromInventoryInHandIfPossible(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. +/obj/item/reagent_containers/food/snacks/grown + icon = 'icons/obj/hydroponics/harvest.dmi' + 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/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. + +/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/msg = "*---------*\n This is \a [src].\n" + if(seed) + msg += seed.get_analyzer_text() + var/reag_txt = "" + if(seed) + for(var/reagent_id in seed.reagents_add) + var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_id] + var/amt = reagents.get_reagent_amount(reagent_id) + reag_txt += "\n- [R.name]: [amt]" + + if(reag_txt) + 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) + 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) + if(ispath(splat_type, /obj/effect/decal/cleanable/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] has been squashed.","You hear a smack.") + if(seed) + for(var/datum/plant_gene/trait/trait in seed.genes) + trait.on_squash(src, target) + + reagents.reaction(T) + for(var/A in T) + reagents.reaction(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) + +// For item-containing growns such as eggy or gatfruit +/obj/item/reagent_containers/food/snacks/grown/shell/attack_self(mob/user) + var/obj/item/T + if(trash) + T = generate_trash() + qdel(src) + user.putItemFromInventoryInHandIfPossible(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 19049c8e6c..bdbf999f7a 100644 --- a/code/modules/hydroponics/growninedible.dm +++ b/code/modules/hydroponics/growninedible.dm @@ -1,62 +1,62 @@ -// ********************** -// 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. - var/tastes = list("indescribable" = 1) //Stops runtimes. Grown are un-eatable anyways so if you do then its a bug - -/obj/item/grown/Initialize(newloc, obj/item/seeds/new_seed) - . = ..() - create_reagents(50) - - 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) - 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. + var/tastes = list("indescribable" = 1) //Stops runtimes. Grown are un-eatable anyways so if you do then its a bug + +/obj/item/grown/Initialize(newloc, obj/item/seeds/new_seed) + . = ..() + create_reagents(50) + + 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) + 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 9655b13862..e7c548443a 100644 --- a/code/modules/hydroponics/hydroitemdefines.dm +++ b/code/modules/hydroponics/hydroitemdefines.dm @@ -1,195 +1,195 @@ -// Plant analyzer -/obj/item/plant_analyzer - name = "plant analyzer" - desc = "A scanner used to evaluate a plant's various areas of growth." - icon = 'icons/obj/device.dmi' - icon_state = "hydro" - item_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 - materials = list(MAT_METAL=30, MAT_GLASS=20) - -// ************************************* -// 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" - item_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" - item_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" - item_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 - materials = list(MAT_METAL=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/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" - item_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 - materials = list(MAT_METAL = 15000) - attack_verb = list("chopped", "torn", "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, 1, -1) - return (BRUTELOSS) - -/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,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -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 ..() - else - var/turf/user_turf = get_turf(user) - var/dir_to_target = get_dir(user_turf, get_turf(A)) - var/stam_gain = 0 - 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) - stam_gain += 5 //should be hitcost - swiping = FALSE - stam_gain += 2 //Initial hitcost - user.adjustStaminaLoss(-stam_gain) - -// ************************************* -// 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 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 limits plant yields to no more than one and causes significant mutations in plants." - 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 by 30% while causing no 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 = 50 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(1,2,5,10,15,25,50) - -/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 = 50) - -/obj/item/reagent_containers/glass/bottle/killer/pestkiller - name = "bottle of pest spray" - desc = "Contains a pesticide." +// Plant analyzer +/obj/item/plant_analyzer + name = "plant analyzer" + desc = "A scanner used to evaluate a plant's various areas of growth." + icon = 'icons/obj/device.dmi' + icon_state = "hydro" + item_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 + materials = list(MAT_METAL=30, MAT_GLASS=20) + +// ************************************* +// 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" + item_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" + item_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" + item_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 + materials = list(MAT_METAL=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/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" + item_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 + materials = list(MAT_METAL = 15000) + attack_verb = list("chopped", "torn", "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, 1, -1) + return (BRUTELOSS) + +/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,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -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 ..() + else + var/turf/user_turf = get_turf(user) + var/dir_to_target = get_dir(user_turf, get_turf(A)) + var/stam_gain = 0 + 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) + stam_gain += 5 //should be hitcost + swiping = FALSE + stam_gain += 2 //Initial hitcost + user.adjustStaminaLoss(-stam_gain) + +// ************************************* +// 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 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 limits plant yields to no more than one and causes significant mutations in plants." + 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 by 30% while causing no 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 = 50 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(1,2,5,10,15,25,50) + +/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 = 50) + +/obj/item/reagent_containers/glass/bottle/killer/pestkiller + name = "bottle of pest spray" + desc = "Contains a pesticide." list_reagents = list(/datum/reagent/toxin/pestkiller = 50) \ No newline at end of file diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 0b6834c408..5bc3fb0b60 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -1,980 +1,980 @@ -/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 - var/waterlevel = 100 //The amount of water in the tray (max 100) - var/maxwater = 100 //The maximum amount of water in the tray - var/nutrilevel = 10 //The amount of nutrient in the tray (max 10) - var/maxnutri = 10 //The maximum nutrient of water in the tray - var/pestlevel = 0 //The amount of pests in the tray (max 10) - var/weedlevel = 0 //The amount of weeds in the tray (max 10) - var/yieldmod = 1 //Nutriment's effect on yield - var/mutmod = 1 //Nutriment's effect on mutations - var/toxic = 0 //Toxicity in the tray? - var/age = 0 //Current age - var/dead = 0 //Is it dead? - var/plant_health //Its health - var/lastproduce = 0 //Last time it was harvested - var/lastcycle = 0 //Used for timing of cycles. - var/cycledelay = 200 //About 10 seconds / cycle - var/harvest = 0 //Ready to harvest? - var/obj/item/seeds/myseed = null //The currently planted seed - var/rating = 1 - var/unwrenchable = 1 - var/recent_bee_visit = FALSE //Have we been visited by a bee recently, so bees dont overpollinate one plant - var/using_irrigation = FALSE //If the tray is connected to other trays via irrigation hoses - var/self_sufficiency_req = 20 //Required total dose to make a self-sufficient hydro tray. 1:1 with earthsblood. - var/self_sufficiency_progress = 0 - var/self_sustaining = FALSE //If the tray generates nutrients and water on its own - - -/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 // Up to 30 - -/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/item/projectile/Proj) //Works with the Somatoray to modify plant variables. - if(!myseed) - return ..() - if(istype(Proj , /obj/item/projectile/energy/floramut)) - mutate() - else if(istype(Proj , /obj/item/projectile/energy/florayield)) - return myseed.bullet_act(Proj) - 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(self_sustaining) - adjustNutri(1) - adjustWater(rand(3,5)) - adjustWeeds(-2) - adjustPests(-2) - adjustToxic(-2) - - 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 slowly - if(prob(50)) - adjustNutri(-1 / rating) - - // Lack of nutrients hurts non-weeds - if(nutrilevel <= 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 && nutrilevel > 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(-rand(1,10) / rating) - else if(toxic >= 80) // I don't think it ever gets here tbh unless above is commented out - adjustHealth(-3) - adjustToxic(-rand(1,10) / rating) - -//Pests & Weeds////////////////////////////////////////////////////////// - - if(pestlevel >= 8) - if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) - adjustHealth(-2 / rating) - - else - adjustHealth(2 / rating) - adjustPests(-1 / rating) - - else if(pestlevel >= 4) - if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) - adjustHealth(-1 / rating) - - else - adjustHealth(1 / rating) - if(prob(50)) - adjustPests(-1 / rating) - - else if(pestlevel < 4 && myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) - adjustHealth(-2 / rating) - 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)) - adjustHealth(-1 / rating) - -//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)) - nutrimentMutation() - if(myseed && myseed.yield != -1) // Unharvestable shouldn't be harvested - harvest = 1 - 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 && nutrilevel > 0 && prob(10)) // If there's no plant, the percentage chance is 10% - adjustWeeds(1 / rating) - - // Weeeeeeeeeeeeeeedddssss - if(weedlevel >= 10 && prob(50)) // At this point the plant is kind of fucked. Weeds can overtake the plant spot. - if(myseed) - if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy) && !myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) // If a normal plant - weedinvasion() - else - weedinvasion() // Weed invasion into empty tray - 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/proc/nutrimentMutation() - if (mutmod == 0) - return - if (mutmod == 1) - if(prob(80)) //80% - mutate() - else if(prob(75)) //15% - hardmutate() - return - if (mutmod == 2) - if(prob(50)) //50% - mutate() - else if(prob(50)) //25% - hardmutate() - else if(prob(50)) //12.5% - mutatespecie() - return - 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 = min(round((age / myseed.maturation) * myseed.growthstages), 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(nutrilevel <= 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." - - if(!self_sustaining) - . += "Water: [waterlevel]/[maxwater]." - . += "Nutrient: [nutrilevel]/[maxnutri]." - if(self_sufficiency_progress > 0) - var/percent_progress = round(self_sufficiency_progress * 100 / self_sufficiency_req) - . += "Treatment for self-sustenance are [percent_progress]% complete." - else - . += "It doesn't require any water or nutrients." - - if(weedlevel >= 5) - . += "It's filled with weeds!" - if(pestlevel >= 5) - . += "It's filled with tiny worms!" - - -/obj/machinery/hydroponics/proc/weedinvasion() // If a weed growth is sufficient, this happens. - dead = 0 - 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 = 0 - weedlevel = 0 // Reset - pestlevel = 0 // Reset - update_icon() - visible_message("The [oldPlantName] is overtaken by some [myseed.plantname]!") - name = "hydroponics tray ([myseed.plantname])" - if(myseed.product) - desc = initial(myseed.product.desc) - else - desc = initial(desc) - -/obj/machinery/hydroponics/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0) // Mutates the current seed - if(!myseed) - return - myseed.mutate(lifemut, endmut, productmut, yieldmut, potmut, wrmut, wcmut, traitmut) - -/obj/machinery/hydroponics/proc/hardmutate() - mutate(4, 10, 2, 4, 50, 4, 10, 3) - - -/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 = 0 - weedlevel = 0 // Reset - - sleep(5) // Wait a while - update_icon() - visible_message("[oldPlantName] suddenly mutates into [myseed.plantname]!") - name = "hydroponics tray ([myseed.plantname])" - if(myseed.product) - desc = initial(myseed.product.desc) - else - desc = initial(desc) - -/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 = 0 - hardmutate() - age = 0 - plant_health = myseed.endurance - lastcycle = world.time - harvest = 0 - weedlevel = 0 // Reset - - sleep(5) // Wait a while - update_icon() - visible_message("The mutated weeds in [src] spawn some [myseed.plantname]!") - else - to_chat(usr, "The few weeds in [src] seem to react, but only for a moment...") - - -/obj/machinery/hydroponics/proc/plantdies() // OH NOES!!!!! I put this all in one function to make things easier - plant_health = 0 - harvest = 0 - pestlevel = 0 // Pests die - if(!dead) - update_icon() - dead = 1 - - - -/obj/machinery/hydroponics/proc/mutatepest(mob/user) - if(pestlevel > 5) - message_admins("[ADMIN_LOOKUPFLW(user)] caused spiderling pests to spawn in a hydro tray") - log_game("[key_name(user)] caused spiderling pests to spawn in a hydro tray") - visible_message("The pests seem to behave oddly...") - spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, src, 3, FALSE) - else - to_chat(user, "The pests seem to behave oddly, but quickly settle down...") - -/obj/machinery/hydroponics/proc/applyChemicals(datum/reagents/S, mob/user) - if(myseed) - myseed.on_chem_reaction(S) //In case seeds have some special interactions with special chems, currently only used by vines - - // Requires 5 mutagen to possibly change species.// Poor man's mutagen. - if(S.has_reagent(/datum/reagent/toxin/mutagen, 5) || S.has_reagent(/datum/reagent/radium, 10) || S.has_reagent(/datum/reagent/uranium, 10)) - switch(rand(100)) - if(91 to 100) - adjustHealth(-10) - to_chat(user, "The plant shrivels and burns.") - if(81 to 90) - mutatespecie() - if(66 to 80) - hardmutate() - if(41 to 65) - mutate() - if(21 to 41) - to_chat(user, "The plants don't seem to react...") - if(11 to 20) - mutateweed() - if(1 to 10) - mutatepest(user) - else - to_chat(user, "Nothing happens...") - - // 2 or 1 units is enough to change the yield and other stats.// Can change the yield and other stats, but requires more than mutagen - else if(S.has_reagent(/datum/reagent/toxin/mutagen, 2) || S.has_reagent(/datum/reagent/radium, 5) || S.has_reagent(/datum/reagent/uranium, 5)) - hardmutate() - else if(S.has_reagent(/datum/reagent/toxin/mutagen, 1) || S.has_reagent(/datum/reagent/radium, 2) || S.has_reagent(/datum/reagent/uranium, 2)) - mutate() - - // After handling the mutating, we now handle the damage from adding crude radioactives... - if(S.has_reagent(/datum/reagent/uranium, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/uranium) * 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/uranium) * 2)) - if(S.has_reagent(/datum/reagent/radium, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/radium) * 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/radium) * 3)) // Radium is harsher (OOC: also easier to produce) - - // Nutriments - if(S.has_reagent(/datum/reagent/plantnutriment/eznutriment, 1)) - yieldmod = 1 - mutmod = 1 - adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/eznutriment) * 1)) - - if(S.has_reagent(/datum/reagent/plantnutriment/left4zednutriment, 1)) - yieldmod = 0 - mutmod = 2 - adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/left4zednutriment) * 1)) - - if(S.has_reagent(/datum/reagent/plantnutriment/robustharvestnutriment, 1)) - yieldmod = 1.3 - mutmod = 0 - adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/robustharvestnutriment) *1 )) - - // Ambrosia Gaia produces earthsblood. - if(S.has_reagent(/datum/reagent/medicine/earthsblood)) - self_sufficiency_progress += S.get_reagent_amount(/datum/reagent/medicine/earthsblood) - if(self_sufficiency_progress >= self_sufficiency_req) - become_self_sufficient() - else if(!self_sustaining) - to_chat(user, "[src] warms as it might on a spring day under a genuine Sun.") - - // Antitoxin binds shit pretty well. So the tox goes significantly down - if(S.has_reagent(/datum/reagent/medicine/charcoal, 1)) - adjustToxic(-round(S.get_reagent_amount(/datum/reagent/medicine/charcoal) * 2)) - - // Toxins, not good for anything - if(S.has_reagent(/datum/reagent/toxin, 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin) * 2)) - - // Milk is good for humans, but bad for plants. The sugars canot be used by plants, and the milk fat fucks up growth. Not shrooms though. I can't deal with this now... - if(S.has_reagent(/datum/reagent/consumable/milk, 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/milk) * 0.1)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/milk) * 0.9)) - - // Beer is a chemical composition of alcohol and various other things. It's a shitty nutrient but hey, it's still one. Also alcohol is bad, mmmkay? - if(S.has_reagent(/datum/reagent/consumable/ethanol/beer, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.05)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.25)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.7)) - - // Fluorine one of the most corrosive and deadly gasses - if(S.has_reagent(/datum/reagent/fluorine, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/fluorine) * 2)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/fluorine) * 2.5)) - adjustWater(-round(S.get_reagent_amount(/datum/reagent/fluorine) * 0.5)) - adjustWeeds(-rand(1,4)) - - // Chlorine one of the most corrosive and deadly gasses - if(S.has_reagent(/datum/reagent/chlorine, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/chlorine) * 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/chlorine) * 1.5)) - adjustWater(-round(S.get_reagent_amount(/datum/reagent/chlorine) * 0.5)) - adjustWeeds(-rand(1,3)) - - // White Phosphorous + water -> phosphoric acid. That's not a good thing really. - // Phosphoric salts are beneficial though. And even if the plant suffers, in the long run the tray gets some nutrients. The benefit isn't worth that much. - if(S.has_reagent(/datum/reagent/phosphorus, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.75)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.1)) - adjustWater(-round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.5)) - adjustWeeds(-rand(1,2)) - - // Plants should not have sugar, they can't use it and it prevents them getting water/nutients, it is good for mold though... - if(S.has_reagent(/datum/reagent/consumable/sugar, 1)) - adjustWeeds(rand(1,2)) - adjustPests(rand(1,2)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/sugar) * 0.1)) - - // It is water! - if(S.has_reagent(/datum/reagent/water, 1)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/water) * 1)) - - // Holy water. Mostly the same as water, it also heals the plant a little with the power of the spirits~ - if(S.has_reagent(/datum/reagent/water/holywater, 1)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/water/holywater) * 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/water/holywater) * 0.1)) - - // A variety of nutrients are dissolved in club soda, without sugar. - // These nutrients include carbon, oxygen, hydrogen, phosphorous, potassium, sulfur and sodium, all of which are needed for healthy plant growth. - if(S.has_reagent(/datum/reagent/consumable/sodawater, 1)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 0.1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 0.1)) - - // Sulphuric Acid - if(S.has_reagent(/datum/reagent/toxin/acid, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/acid) * 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/acid) * 1.5)) - adjustWeeds(-rand(1,2)) - - // Acid - if(S.has_reagent(/datum/reagent/toxin/acid/fluacid, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/acid/fluacid) * 2)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/acid/fluacid) * 3)) - adjustWeeds(-rand(1,4)) - - // Plant-B-Gone is just as bad - if(S.has_reagent(/datum/reagent/toxin/plantbgone, 1)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone) * 5)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone) * 6)) - adjustWeeds(-rand(4,8)) - - // Napalm, not known for being good for anything organic - if(S.has_reagent(/datum/reagent/napalm, 1)) - if(!(myseed.resistance_flags & FIRE_PROOF)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/napalm) * 6)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/napalm) * 7)) - adjustWeeds(-rand(5,9)) - - //Weed Spray - if(S.has_reagent(/datum/reagent/toxin/plantbgone/weedkiller, 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone/weedkiller) * 0.5)) - //old toxicity was 4, each spray is default 10 (minimal of 5) so 5 and 2.5 are the new ammounts - adjustWeeds(-rand(1,2)) - - //Pest Spray - if(S.has_reagent(/datum/reagent/toxin/pestkiller, 1)) - adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/pestkiller) * 0.5)) - adjustPests(-rand(1,2)) - - // Healing - if(S.has_reagent(/datum/reagent/medicine/cryoxadone, 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/medicine/cryoxadone) * 3)) - adjustToxic(-round(S.get_reagent_amount(/datum/reagent/medicine/cryoxadone) * 3)) - - // Ammonia is bad ass. - if(S.has_reagent(/datum/reagent/ammonia, 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/ammonia) * 0.5)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/ammonia) * 1)) - if(myseed) - myseed.adjust_yield(round(S.get_reagent_amount(/datum/reagent/ammonia) * 0.01)) - - // Saltpetre is used for gardening IRL, to simplify highly, it speeds up growth and strengthens plants - if(S.has_reagent(/datum/reagent/saltpetre, 1)) - var/salt = S.get_reagent_amount(/datum/reagent/saltpetre) - adjustHealth(round(salt * 0.25)) - if (myseed) - myseed.adjust_production(-round(salt/100)-prob(salt%100)) - myseed.adjust_potency(round(salt*0.5)) - - // Ash is also used IRL in gardening, as a fertilizer enhancer and weed killer - if(S.has_reagent(/datum/reagent/ash, 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/ash) * 0.25)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/ash) * 0.5)) - adjustWeeds(-1) - - // Diethylamine is more bad ass, and pests get hurt by the corrosive nature of it, not the plant. - if(S.has_reagent(/datum/reagent/diethylamine, 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 2)) - if(myseed) - myseed.adjust_yield(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 0.02)) - adjustPests(-rand(1,2)) - - // Nutriment Compost, effectively - if(S.has_reagent(/datum/reagent/consumable/nutriment, 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/consumable/nutriment) * 0.5)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/nutriment) * 1)) - - // Virusfood Compost for EVERYTHING - if(S.has_reagent(/datum/reagent/toxin/mutagen/mutagenvirusfood, 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/toxin/mutagen/mutagenvirusfood) * 0.5)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/mutagen/mutagenvirusfood) * 0.5)) - - // Blood - if(S.has_reagent(/datum/reagent/blood, 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/blood) * 1)) - adjustPests(rand(2,4)) - - // Strange reagent - if(S.has_reagent(/datum/reagent/medicine/strange_reagent, 1)) - spawnplant() - - // Honey, Pests are dieing of sugar, so is the plant - if(S.has_reagent(/datum/reagent/consumable/honey, 1)) - adjustPests(-rand(2,5)) - adjustHealth(-round(S.get_reagent_amount(/datum/reagent/consumable/honey) * 1)) - - // Buzz Fuzz, a drink seemingly made for plants... - if(S.has_reagent(/datum/reagent/consumable/buzz_fuzz, 1)) - adjustPests(-rand(2,5)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/consumable/buzz_fuzz) * 0.1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/buzz_fuzz) * 0.5)) - - // Adminordrazine the best stuff there is. For testing/debugging. - if(S.has_reagent(/datum/reagent/medicine/adminordrazine, 1)) - adjustWater(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) - adjustHealth(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) - adjustNutri(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) - adjustPests(-rand(1,5)) - adjustWeeds(-rand(1,5)) - if(S.has_reagent(/datum/reagent/medicine/adminordrazine, 5)) - switch(rand(100)) - if(66 to 100) - mutatespecie() - if(33 to 65) - mutateweed() - if(1 to 32) - mutatepest(user) - else - to_chat(user, "Nothing happens...") - -/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 - - 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)) - 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, 1, -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, 1) - - 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 - - var/datum/reagents/S = new /datum/reagents() //This is a strange way, but I don't know of a better one so I can't fix it at the moment... - S.my_atom = H - - reagent_source.reagents.trans_to(S,split) - if(istype(reagent_source, /obj/item/reagent_containers/food/snacks) || istype(reagent_source, /obj/item/reagent_containers/pill)) - qdel(reagent_source) - - H.applyChemicals(S, user) - - S.clear_reagents() - qdel(S) - 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 = 0 - myseed = O - name = "hydroponics tray ([myseed.plantname])" - if(!myseed.productdesc) //we haven't changed our produce's description - if(myseed.product) - myseed.productdesc = initial(myseed.product.desc) - else if(myseed.desc) - myseed.productdesc = myseed.desc - else - myseed.productdesc = "A fascinating specimen." - desc = myseed.productdesc - age = 1 - plant_health = myseed.endurance - lastcycle = world.time - update_icon() - else - to_chat(user, "[src] already has seeds in it!") - - else if(istype(O, /obj/item/plant_analyzer)) - if(myseed) - 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) - 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: [nutrilevel] / [maxnutri]") - to_chat(user, "") - - 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() - else - to_chat(user, "This plot is completely devoid of weeds! It doesn't need uprooting.") - - 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) - - else if(default_unfasten_wrench(user, O)) - return - - else if(istype(O, /obj/item/wirecutters) && 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() - - 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 - 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() - - 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 = 0 - to_chat(user, "You remove the dead plant from [src].") - qdel(myseed) - myseed = null - update_icon() - name = initial(name) - desc = initial(desc) - else - if(user) - examine(user) - -/obj/machinery/hydroponics/proc/update_tray(mob/user) - harvest = 0 - 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 = 0 - name = initial(name) - desc = initial(desc) - 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./// -/obj/machinery/hydroponics/proc/adjustNutri(adjustamt) - nutrilevel = CLAMP(nutrilevel + adjustamt, 0, maxnutri) - -/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. - -/obj/machinery/hydroponics/proc/adjustHealth(adjustamt) - if(myseed && !dead) - plant_health = CLAMP(plant_health + adjustamt, 0, myseed.endurance) - -/obj/machinery/hydroponics/proc/adjustToxic(adjustamt) - toxic = CLAMP(toxic + adjustamt, 0, 100) - -/obj/machinery/hydroponics/proc/adjustPests(adjustamt) - pestlevel = CLAMP(pestlevel + adjustamt, 0, 10) - -/obj/machinery/hydroponics/proc/adjustWeeds(adjustamt) - weedlevel = CLAMP(weedlevel + adjustamt, 0, 10) - -/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/proc/become_self_sufficient() // Ambrosia Gaia effect - visible_message("[src] begins to glow with a beautiful light!") - self_sustaining = TRUE - update_icon() - -/////////////////////////////////////////////////////////////////////////////// -/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" - 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(istype(O, /obj/item/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 + 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 + var/waterlevel = 100 //The amount of water in the tray (max 100) + var/maxwater = 100 //The maximum amount of water in the tray + var/nutrilevel = 10 //The amount of nutrient in the tray (max 10) + var/maxnutri = 10 //The maximum nutrient of water in the tray + var/pestlevel = 0 //The amount of pests in the tray (max 10) + var/weedlevel = 0 //The amount of weeds in the tray (max 10) + var/yieldmod = 1 //Nutriment's effect on yield + var/mutmod = 1 //Nutriment's effect on mutations + var/toxic = 0 //Toxicity in the tray? + var/age = 0 //Current age + var/dead = 0 //Is it dead? + var/plant_health //Its health + var/lastproduce = 0 //Last time it was harvested + var/lastcycle = 0 //Used for timing of cycles. + var/cycledelay = 200 //About 10 seconds / cycle + var/harvest = 0 //Ready to harvest? + var/obj/item/seeds/myseed = null //The currently planted seed + var/rating = 1 + var/unwrenchable = 1 + var/recent_bee_visit = FALSE //Have we been visited by a bee recently, so bees dont overpollinate one plant + var/using_irrigation = FALSE //If the tray is connected to other trays via irrigation hoses + var/self_sufficiency_req = 20 //Required total dose to make a self-sufficient hydro tray. 1:1 with earthsblood. + var/self_sufficiency_progress = 0 + var/self_sustaining = FALSE //If the tray generates nutrients and water on its own + + +/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 // Up to 30 + +/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/item/projectile/Proj) //Works with the Somatoray to modify plant variables. + if(!myseed) + return ..() + if(istype(Proj , /obj/item/projectile/energy/floramut)) + mutate() + else if(istype(Proj , /obj/item/projectile/energy/florayield)) + return myseed.bullet_act(Proj) + 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(self_sustaining) + adjustNutri(1) + adjustWater(rand(3,5)) + adjustWeeds(-2) + adjustPests(-2) + adjustToxic(-2) + + 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 slowly + if(prob(50)) + adjustNutri(-1 / rating) + + // Lack of nutrients hurts non-weeds + if(nutrilevel <= 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 && nutrilevel > 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(-rand(1,10) / rating) + else if(toxic >= 80) // I don't think it ever gets here tbh unless above is commented out + adjustHealth(-3) + adjustToxic(-rand(1,10) / rating) + +//Pests & Weeds////////////////////////////////////////////////////////// + + if(pestlevel >= 8) + if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) + adjustHealth(-2 / rating) + + else + adjustHealth(2 / rating) + adjustPests(-1 / rating) + + else if(pestlevel >= 4) + if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) + adjustHealth(-1 / rating) + + else + adjustHealth(1 / rating) + if(prob(50)) + adjustPests(-1 / rating) + + else if(pestlevel < 4 && myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory)) + adjustHealth(-2 / rating) + 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)) + adjustHealth(-1 / rating) + +//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)) + nutrimentMutation() + if(myseed && myseed.yield != -1) // Unharvestable shouldn't be harvested + harvest = 1 + 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 && nutrilevel > 0 && prob(10)) // If there's no plant, the percentage chance is 10% + adjustWeeds(1 / rating) + + // Weeeeeeeeeeeeeeedddssss + if(weedlevel >= 10 && prob(50)) // At this point the plant is kind of fucked. Weeds can overtake the plant spot. + if(myseed) + if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/weed_hardy) && !myseed.get_gene(/datum/plant_gene/trait/plant_type/fungal_metabolism)) // If a normal plant + weedinvasion() + else + weedinvasion() // Weed invasion into empty tray + 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/proc/nutrimentMutation() + if (mutmod == 0) + return + if (mutmod == 1) + if(prob(80)) //80% + mutate() + else if(prob(75)) //15% + hardmutate() + return + if (mutmod == 2) + if(prob(50)) //50% + mutate() + else if(prob(50)) //25% + hardmutate() + else if(prob(50)) //12.5% + mutatespecie() + return + 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 = min(round((age / myseed.maturation) * myseed.growthstages), 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(nutrilevel <= 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." + + if(!self_sustaining) + . += "Water: [waterlevel]/[maxwater]." + . += "Nutrient: [nutrilevel]/[maxnutri]." + if(self_sufficiency_progress > 0) + var/percent_progress = round(self_sufficiency_progress * 100 / self_sufficiency_req) + . += "Treatment for self-sustenance are [percent_progress]% complete." + else + . += "It doesn't require any water or nutrients." + + if(weedlevel >= 5) + . += "It's filled with weeds!" + if(pestlevel >= 5) + . += "It's filled with tiny worms!" + + +/obj/machinery/hydroponics/proc/weedinvasion() // If a weed growth is sufficient, this happens. + dead = 0 + 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 = 0 + weedlevel = 0 // Reset + pestlevel = 0 // Reset + update_icon() + visible_message("The [oldPlantName] is overtaken by some [myseed.plantname]!") + name = "hydroponics tray ([myseed.plantname])" + if(myseed.product) + desc = initial(myseed.product.desc) + else + desc = initial(desc) + +/obj/machinery/hydroponics/proc/mutate(lifemut = 2, endmut = 5, productmut = 1, yieldmut = 2, potmut = 25, wrmut = 2, wcmut = 5, traitmut = 0) // Mutates the current seed + if(!myseed) + return + myseed.mutate(lifemut, endmut, productmut, yieldmut, potmut, wrmut, wcmut, traitmut) + +/obj/machinery/hydroponics/proc/hardmutate() + mutate(4, 10, 2, 4, 50, 4, 10, 3) + + +/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 = 0 + weedlevel = 0 // Reset + + sleep(5) // Wait a while + update_icon() + visible_message("[oldPlantName] suddenly mutates into [myseed.plantname]!") + name = "hydroponics tray ([myseed.plantname])" + if(myseed.product) + desc = initial(myseed.product.desc) + else + desc = initial(desc) + +/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 = 0 + hardmutate() + age = 0 + plant_health = myseed.endurance + lastcycle = world.time + harvest = 0 + weedlevel = 0 // Reset + + sleep(5) // Wait a while + update_icon() + visible_message("The mutated weeds in [src] spawn some [myseed.plantname]!") + else + to_chat(usr, "The few weeds in [src] seem to react, but only for a moment...") + + +/obj/machinery/hydroponics/proc/plantdies() // OH NOES!!!!! I put this all in one function to make things easier + plant_health = 0 + harvest = 0 + pestlevel = 0 // Pests die + if(!dead) + update_icon() + dead = 1 + + + +/obj/machinery/hydroponics/proc/mutatepest(mob/user) + if(pestlevel > 5) + message_admins("[ADMIN_LOOKUPFLW(user)] caused spiderling pests to spawn in a hydro tray") + log_game("[key_name(user)] caused spiderling pests to spawn in a hydro tray") + visible_message("The pests seem to behave oddly...") + spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, src, 3, FALSE) + else + to_chat(user, "The pests seem to behave oddly, but quickly settle down...") + +/obj/machinery/hydroponics/proc/applyChemicals(datum/reagents/S, mob/user) + if(myseed) + myseed.on_chem_reaction(S) //In case seeds have some special interactions with special chems, currently only used by vines + + // Requires 5 mutagen to possibly change species.// Poor man's mutagen. + if(S.has_reagent(/datum/reagent/toxin/mutagen, 5) || S.has_reagent(/datum/reagent/radium, 10) || S.has_reagent(/datum/reagent/uranium, 10)) + switch(rand(100)) + if(91 to 100) + adjustHealth(-10) + to_chat(user, "The plant shrivels and burns.") + if(81 to 90) + mutatespecie() + if(66 to 80) + hardmutate() + if(41 to 65) + mutate() + if(21 to 41) + to_chat(user, "The plants don't seem to react...") + if(11 to 20) + mutateweed() + if(1 to 10) + mutatepest(user) + else + to_chat(user, "Nothing happens...") + + // 2 or 1 units is enough to change the yield and other stats.// Can change the yield and other stats, but requires more than mutagen + else if(S.has_reagent(/datum/reagent/toxin/mutagen, 2) || S.has_reagent(/datum/reagent/radium, 5) || S.has_reagent(/datum/reagent/uranium, 5)) + hardmutate() + else if(S.has_reagent(/datum/reagent/toxin/mutagen, 1) || S.has_reagent(/datum/reagent/radium, 2) || S.has_reagent(/datum/reagent/uranium, 2)) + mutate() + + // After handling the mutating, we now handle the damage from adding crude radioactives... + if(S.has_reagent(/datum/reagent/uranium, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/uranium) * 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/uranium) * 2)) + if(S.has_reagent(/datum/reagent/radium, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/radium) * 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/radium) * 3)) // Radium is harsher (OOC: also easier to produce) + + // Nutriments + if(S.has_reagent(/datum/reagent/plantnutriment/eznutriment, 1)) + yieldmod = 1 + mutmod = 1 + adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/eznutriment) * 1)) + + if(S.has_reagent(/datum/reagent/plantnutriment/left4zednutriment, 1)) + yieldmod = 0 + mutmod = 2 + adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/left4zednutriment) * 1)) + + if(S.has_reagent(/datum/reagent/plantnutriment/robustharvestnutriment, 1)) + yieldmod = 1.3 + mutmod = 0 + adjustNutri(round(S.get_reagent_amount(/datum/reagent/plantnutriment/robustharvestnutriment) *1 )) + + // Ambrosia Gaia produces earthsblood. + if(S.has_reagent(/datum/reagent/medicine/earthsblood)) + self_sufficiency_progress += S.get_reagent_amount(/datum/reagent/medicine/earthsblood) + if(self_sufficiency_progress >= self_sufficiency_req) + become_self_sufficient() + else if(!self_sustaining) + to_chat(user, "[src] warms as it might on a spring day under a genuine Sun.") + + // Antitoxin binds shit pretty well. So the tox goes significantly down + if(S.has_reagent(/datum/reagent/medicine/charcoal, 1)) + adjustToxic(-round(S.get_reagent_amount(/datum/reagent/medicine/charcoal) * 2)) + + // Toxins, not good for anything + if(S.has_reagent(/datum/reagent/toxin, 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin) * 2)) + + // Milk is good for humans, but bad for plants. The sugars canot be used by plants, and the milk fat fucks up growth. Not shrooms though. I can't deal with this now... + if(S.has_reagent(/datum/reagent/consumable/milk, 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/milk) * 0.1)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/milk) * 0.9)) + + // Beer is a chemical composition of alcohol and various other things. It's a shitty nutrient but hey, it's still one. Also alcohol is bad, mmmkay? + if(S.has_reagent(/datum/reagent/consumable/ethanol/beer, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.05)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.25)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/ethanol/beer) * 0.7)) + + // Fluorine one of the most corrosive and deadly gasses + if(S.has_reagent(/datum/reagent/fluorine, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/fluorine) * 2)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/fluorine) * 2.5)) + adjustWater(-round(S.get_reagent_amount(/datum/reagent/fluorine) * 0.5)) + adjustWeeds(-rand(1,4)) + + // Chlorine one of the most corrosive and deadly gasses + if(S.has_reagent(/datum/reagent/chlorine, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/chlorine) * 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/chlorine) * 1.5)) + adjustWater(-round(S.get_reagent_amount(/datum/reagent/chlorine) * 0.5)) + adjustWeeds(-rand(1,3)) + + // White Phosphorous + water -> phosphoric acid. That's not a good thing really. + // Phosphoric salts are beneficial though. And even if the plant suffers, in the long run the tray gets some nutrients. The benefit isn't worth that much. + if(S.has_reagent(/datum/reagent/phosphorus, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.75)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.1)) + adjustWater(-round(S.get_reagent_amount(/datum/reagent/phosphorus) * 0.5)) + adjustWeeds(-rand(1,2)) + + // Plants should not have sugar, they can't use it and it prevents them getting water/nutients, it is good for mold though... + if(S.has_reagent(/datum/reagent/consumable/sugar, 1)) + adjustWeeds(rand(1,2)) + adjustPests(rand(1,2)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/sugar) * 0.1)) + + // It is water! + if(S.has_reagent(/datum/reagent/water, 1)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/water) * 1)) + + // Holy water. Mostly the same as water, it also heals the plant a little with the power of the spirits~ + if(S.has_reagent(/datum/reagent/water/holywater, 1)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/water/holywater) * 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/water/holywater) * 0.1)) + + // A variety of nutrients are dissolved in club soda, without sugar. + // These nutrients include carbon, oxygen, hydrogen, phosphorous, potassium, sulfur and sodium, all of which are needed for healthy plant growth. + if(S.has_reagent(/datum/reagent/consumable/sodawater, 1)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 0.1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/sodawater) * 0.1)) + + // Sulphuric Acid + if(S.has_reagent(/datum/reagent/toxin/acid, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/acid) * 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/acid) * 1.5)) + adjustWeeds(-rand(1,2)) + + // Acid + if(S.has_reagent(/datum/reagent/toxin/acid/fluacid, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/acid/fluacid) * 2)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/acid/fluacid) * 3)) + adjustWeeds(-rand(1,4)) + + // Plant-B-Gone is just as bad + if(S.has_reagent(/datum/reagent/toxin/plantbgone, 1)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone) * 5)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone) * 6)) + adjustWeeds(-rand(4,8)) + + // Napalm, not known for being good for anything organic + if(S.has_reagent(/datum/reagent/napalm, 1)) + if(!(myseed.resistance_flags & FIRE_PROOF)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/napalm) * 6)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/napalm) * 7)) + adjustWeeds(-rand(5,9)) + + //Weed Spray + if(S.has_reagent(/datum/reagent/toxin/plantbgone/weedkiller, 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/plantbgone/weedkiller) * 0.5)) + //old toxicity was 4, each spray is default 10 (minimal of 5) so 5 and 2.5 are the new ammounts + adjustWeeds(-rand(1,2)) + + //Pest Spray + if(S.has_reagent(/datum/reagent/toxin/pestkiller, 1)) + adjustToxic(round(S.get_reagent_amount(/datum/reagent/toxin/pestkiller) * 0.5)) + adjustPests(-rand(1,2)) + + // Healing + if(S.has_reagent(/datum/reagent/medicine/cryoxadone, 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/medicine/cryoxadone) * 3)) + adjustToxic(-round(S.get_reagent_amount(/datum/reagent/medicine/cryoxadone) * 3)) + + // Ammonia is bad ass. + if(S.has_reagent(/datum/reagent/ammonia, 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/ammonia) * 0.5)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/ammonia) * 1)) + if(myseed) + myseed.adjust_yield(round(S.get_reagent_amount(/datum/reagent/ammonia) * 0.01)) + + // Saltpetre is used for gardening IRL, to simplify highly, it speeds up growth and strengthens plants + if(S.has_reagent(/datum/reagent/saltpetre, 1)) + var/salt = S.get_reagent_amount(/datum/reagent/saltpetre) + adjustHealth(round(salt * 0.25)) + if (myseed) + myseed.adjust_production(-round(salt/100)-prob(salt%100)) + myseed.adjust_potency(round(salt*0.5)) + + // Ash is also used IRL in gardening, as a fertilizer enhancer and weed killer + if(S.has_reagent(/datum/reagent/ash, 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/ash) * 0.25)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/ash) * 0.5)) + adjustWeeds(-1) + + // Diethylamine is more bad ass, and pests get hurt by the corrosive nature of it, not the plant. + if(S.has_reagent(/datum/reagent/diethylamine, 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 2)) + if(myseed) + myseed.adjust_yield(round(S.get_reagent_amount(/datum/reagent/diethylamine) * 0.02)) + adjustPests(-rand(1,2)) + + // Nutriment Compost, effectively + if(S.has_reagent(/datum/reagent/consumable/nutriment, 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/consumable/nutriment) * 0.5)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/nutriment) * 1)) + + // Virusfood Compost for EVERYTHING + if(S.has_reagent(/datum/reagent/toxin/mutagen/mutagenvirusfood, 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/toxin/mutagen/mutagenvirusfood) * 0.5)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/toxin/mutagen/mutagenvirusfood) * 0.5)) + + // Blood + if(S.has_reagent(/datum/reagent/blood, 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/blood) * 1)) + adjustPests(rand(2,4)) + + // Strange reagent + if(S.has_reagent(/datum/reagent/medicine/strange_reagent, 1)) + spawnplant() + + // Honey, Pests are dieing of sugar, so is the plant + if(S.has_reagent(/datum/reagent/consumable/honey, 1)) + adjustPests(-rand(2,5)) + adjustHealth(-round(S.get_reagent_amount(/datum/reagent/consumable/honey) * 1)) + + // Buzz Fuzz, a drink seemingly made for plants... + if(S.has_reagent(/datum/reagent/consumable/buzz_fuzz, 1)) + adjustPests(-rand(2,5)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/consumable/buzz_fuzz) * 0.1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/consumable/buzz_fuzz) * 0.5)) + + // Adminordrazine the best stuff there is. For testing/debugging. + if(S.has_reagent(/datum/reagent/medicine/adminordrazine, 1)) + adjustWater(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) + adjustHealth(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) + adjustNutri(round(S.get_reagent_amount(/datum/reagent/medicine/adminordrazine) * 1)) + adjustPests(-rand(1,5)) + adjustWeeds(-rand(1,5)) + if(S.has_reagent(/datum/reagent/medicine/adminordrazine, 5)) + switch(rand(100)) + if(66 to 100) + mutatespecie() + if(33 to 65) + mutateweed() + if(1 to 32) + mutatepest(user) + else + to_chat(user, "Nothing happens...") + +/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 + + 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)) + 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, 1, -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, 1) + + 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 + + var/datum/reagents/S = new /datum/reagents() //This is a strange way, but I don't know of a better one so I can't fix it at the moment... + S.my_atom = H + + reagent_source.reagents.trans_to(S,split) + if(istype(reagent_source, /obj/item/reagent_containers/food/snacks) || istype(reagent_source, /obj/item/reagent_containers/pill)) + qdel(reagent_source) + + H.applyChemicals(S, user) + + S.clear_reagents() + qdel(S) + 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 = 0 + myseed = O + name = "hydroponics tray ([myseed.plantname])" + if(!myseed.productdesc) //we haven't changed our produce's description + if(myseed.product) + myseed.productdesc = initial(myseed.product.desc) + else if(myseed.desc) + myseed.productdesc = myseed.desc + else + myseed.productdesc = "A fascinating specimen." + desc = myseed.productdesc + age = 1 + plant_health = myseed.endurance + lastcycle = world.time + update_icon() + else + to_chat(user, "[src] already has seeds in it!") + + else if(istype(O, /obj/item/plant_analyzer)) + if(myseed) + 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) + 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: [nutrilevel] / [maxnutri]") + to_chat(user, "") + + 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() + else + to_chat(user, "This plot is completely devoid of weeds! It doesn't need uprooting.") + + 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) + + else if(default_unfasten_wrench(user, O)) + return + + else if(istype(O, /obj/item/wirecutters) && 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() + + 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 + 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() + + 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 = 0 + to_chat(user, "You remove the dead plant from [src].") + qdel(myseed) + myseed = null + update_icon() + name = initial(name) + desc = initial(desc) + else + if(user) + examine(user) + +/obj/machinery/hydroponics/proc/update_tray(mob/user) + harvest = 0 + 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 = 0 + name = initial(name) + desc = initial(desc) + 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./// +/obj/machinery/hydroponics/proc/adjustNutri(adjustamt) + nutrilevel = CLAMP(nutrilevel + adjustamt, 0, maxnutri) + +/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. + +/obj/machinery/hydroponics/proc/adjustHealth(adjustamt) + if(myseed && !dead) + plant_health = CLAMP(plant_health + adjustamt, 0, myseed.endurance) + +/obj/machinery/hydroponics/proc/adjustToxic(adjustamt) + toxic = CLAMP(toxic + adjustamt, 0, 100) + +/obj/machinery/hydroponics/proc/adjustPests(adjustamt) + pestlevel = CLAMP(pestlevel + adjustamt, 0, 10) + +/obj/machinery/hydroponics/proc/adjustWeeds(adjustamt) + weedlevel = CLAMP(weedlevel + adjustamt, 0, 10) + +/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/proc/become_self_sufficient() // Ambrosia Gaia effect + visible_message("[src] begins to glow with a beautiful light!") + self_sustaining = TRUE + update_icon() + +/////////////////////////////////////////////////////////////////////////////// +/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" + 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(istype(O, /obj/item/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 ..() diff --git a/code/modules/hydroponics/seed_extractor.dm b/code/modules/hydroponics/seed_extractor.dm index d9bea64411..5af19b8431 100644 --- a/code/modules/hydroponics/seed_extractor.dm +++ b/code/modules/hydroponics/seed_extractor.dm @@ -1,192 +1,192 @@ -/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 - var/piles = list() - var/max_seeds = 1000 - var/seed_multiplier = 1 - -/obj/machinery/seed_extractor/RefreshParts() - for(var/obj/item/stock_parts/matter_bin/B in component_parts) - max_seeds = 1000 * B.rating - for(var/obj/item/stock_parts/manipulator/M in component_parts) - seed_multiplier = M.rating - -/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 ..() - -/datum/seed_pile - var/name = "" - var/lifespan = 0 //Saved stats - var/endurance = 0 - var/maturation = 0 - var/production = 0 - var/yield = 0 - var/potency = 0 - var/amount = 0 - -/datum/seed_pile/New(var/name, var/life, var/endur, var/matur, var/prod, var/yie, var/poten, var/am = 1) - src.name = name - src.lifespan = life - src.endurance = endur - src.maturation = matur - src.production = prod - src.yield = yie - src.potency = poten - src.amount = am - -/obj/machinery/seed_extractor/ui_interact(mob/user) - . = ..() - if (stat) - return FALSE - - var/dat = "Stored seeds:
                " - - if (contents.len == 0) - dat += "No seeds" - else - dat += "" - for (var/datum/seed_pile/O in piles) - dat += "" - dat += "" - dat += "
                NameLifespanEnduranceMaturationProductionYieldPotencyStock
                [O.name][O.lifespan][O.endurance][O.maturation][O.production][O.yield][O.potency]" - dat += "Vend ([O.amount] left)
                " - var/datum/browser/popup = new(user, "seed_ext", name, 700, 400) - popup.set_content(dat) - popup.open() - return - -/obj/machinery/seed_extractor/Topic(var/href, var/list/href_list) - if(..()) - return - usr.set_machine(src) - - href_list["li"] = text2num(href_list["li"]) - href_list["en"] = text2num(href_list["en"]) - href_list["ma"] = text2num(href_list["ma"]) - href_list["pr"] = text2num(href_list["pr"]) - href_list["yi"] = text2num(href_list["yi"]) - href_list["pot"] = text2num(href_list["pot"]) - - for (var/datum/seed_pile/N in piles)//Find the pile we need to reduce... - if (href_list["name"] == N.name && href_list["li"] == N.lifespan && href_list["en"] == N.endurance && href_list["ma"] == N.maturation && href_list["pr"] == N.production && href_list["yi"] == N.yield && href_list["pot"] == N.potency) - if(N.amount <= 0) - return - N.amount = max(N.amount - 1, 0) - if (N.amount <= 0) - piles -= N - qdel(N) - break - - for (var/obj/T in contents)//Now we find the seed we need to vend - var/obj/item/seeds/O = T - if (O.plantname == href_list["name"] && O.lifespan == href_list["li"] && O.endurance == href_list["en"] && O.maturation == href_list["ma"] && O.production == href_list["pr"] && O.yield == href_list["yi"] && O.potency == href_list["pot"]) - O.forceMove(drop_location()) - break - - src.updateUsrDialog() - return - -/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 - - . = TRUE - for (var/datum/seed_pile/N in piles) - if (O.plantname == N.name && O.lifespan == N.lifespan && O.endurance == N.endurance && O.maturation == N.maturation && O.production == N.production && O.yield == N.yield && O.potency == N.potency) - ++N.amount - return - - piles += new /datum/seed_pile(O.plantname, O.lifespan, O.endurance, O.maturation, O.production, O.yield, O.potency) +/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 + var/piles = list() + var/max_seeds = 1000 + var/seed_multiplier = 1 + +/obj/machinery/seed_extractor/RefreshParts() + for(var/obj/item/stock_parts/matter_bin/B in component_parts) + max_seeds = 1000 * B.rating + for(var/obj/item/stock_parts/manipulator/M in component_parts) + seed_multiplier = M.rating + +/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 ..() + +/datum/seed_pile + var/name = "" + var/lifespan = 0 //Saved stats + var/endurance = 0 + var/maturation = 0 + var/production = 0 + var/yield = 0 + var/potency = 0 + var/amount = 0 + +/datum/seed_pile/New(var/name, var/life, var/endur, var/matur, var/prod, var/yie, var/poten, var/am = 1) + src.name = name + src.lifespan = life + src.endurance = endur + src.maturation = matur + src.production = prod + src.yield = yie + src.potency = poten + src.amount = am + +/obj/machinery/seed_extractor/ui_interact(mob/user) + . = ..() + if (stat) + return FALSE + + var/dat = "Stored seeds:
                " + + if (contents.len == 0) + dat += "No seeds" + else + dat += "" + for (var/datum/seed_pile/O in piles) + dat += "" + dat += "" + dat += "
                NameLifespanEnduranceMaturationProductionYieldPotencyStock
                [O.name][O.lifespan][O.endurance][O.maturation][O.production][O.yield][O.potency]" + dat += "Vend ([O.amount] left)
                " + var/datum/browser/popup = new(user, "seed_ext", name, 700, 400) + popup.set_content(dat) + popup.open() + return + +/obj/machinery/seed_extractor/Topic(var/href, var/list/href_list) + if(..()) + return + usr.set_machine(src) + + href_list["li"] = text2num(href_list["li"]) + href_list["en"] = text2num(href_list["en"]) + href_list["ma"] = text2num(href_list["ma"]) + href_list["pr"] = text2num(href_list["pr"]) + href_list["yi"] = text2num(href_list["yi"]) + href_list["pot"] = text2num(href_list["pot"]) + + for (var/datum/seed_pile/N in piles)//Find the pile we need to reduce... + if (href_list["name"] == N.name && href_list["li"] == N.lifespan && href_list["en"] == N.endurance && href_list["ma"] == N.maturation && href_list["pr"] == N.production && href_list["yi"] == N.yield && href_list["pot"] == N.potency) + if(N.amount <= 0) + return + N.amount = max(N.amount - 1, 0) + if (N.amount <= 0) + piles -= N + qdel(N) + break + + for (var/obj/T in contents)//Now we find the seed we need to vend + var/obj/item/seeds/O = T + if (O.plantname == href_list["name"] && O.lifespan == href_list["li"] && O.endurance == href_list["en"] && O.maturation == href_list["ma"] && O.production == href_list["pr"] && O.yield == href_list["yi"] && O.potency == href_list["pot"]) + O.forceMove(drop_location()) + break + + src.updateUsrDialog() + return + +/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 + + . = TRUE + for (var/datum/seed_pile/N in piles) + if (O.plantname == N.name && O.lifespan == N.lifespan && O.endurance == N.endurance && O.maturation == N.maturation && O.production == N.production && O.yield == N.yield && O.potency == N.potency) + ++N.amount + return + + piles += new /datum/seed_pile(O.plantname, O.lifespan, O.endurance, O.maturation, O.production, O.yield, O.potency) diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index b35665c8e6..9a0085eb3d 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -1,468 +1,468 @@ -// ******************************************************** -// 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 - var/plantname = "Plants" // Name of plant when planted. - var/obj/item/product // A type path. The thing that is created when the plant is harvested. - var/productdesc - var/species = "" // Used to update icons. Should match the name in the sprites unless all icon_* are overridden. - - var/growing_icon = 'icons/obj/hydroponics/growing.dmi' //the file that stores the sprites of the growing plant from this seed. - var/icon_grow // Used to override grow icon (default is "[species]-grow"). You can use one grow icon for multiple closely related plants with it. - var/icon_dead // Used to override dead icon (default is "[species]-dead"). You can use one dead icon for multiple closely related plants with it. - var/icon_harvest // Used to override harvest icon (default is "[species]-harvest"). If null, plant will use [icon_grow][growthstages]. - - var/lifespan = 25 // How long before the plant begins to take damage from age. - var/endurance = 15 // Amount of health the plant has. - var/maturation = 6 // Used to determine which sprite to switch to when growing. - var/production = 6 // Changes the amount of time needed for a plant to become harvestable. - var/yield = 3 // Amount of growns created per harvest. If is -1, the plant/shroom/weed is never meant to be harvested. - var/potency = 10 // The 'power' of a plant. Generally effects the amount of reagent in a plant, also used in other ways. - var/growthstages = 6 // Amount of growth sprites the plant has. - var/rarity = 0 // How rare the plant is. Used for giving points to cargo when shipping off to CentCom. - var/list/mutatelist = list() // The type of plants that this plant can mutate into. - var/list/genes = list() // Plant genes are stored here, see plant_genes.dm for more info. - var/list/forbiddengenes = list() - var/list/reagents_add = list() - // A list of reagents to add to product. - // 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: 1 + round(potency * multiplier) - - var/weed_rate = 1 //If the chance below passes, then this many weeds sprout during growth - var/weed_chance = 5 //Percentage chance per tray update to grow weeds - -/obj/item/seeds/Initialize(loc, 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) - - 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." - -/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.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/is_gene_forbidden(typepath) - return (typepath in forbiddengenes) - - -/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) - 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_weed_rate(rand(-wrmut, wrmut)) - adjust_weed_chance(rand(-wcmut, wcmut)) - if(prob(traitmut)) - add_random_traits(1, 1) - - - -/obj/item/seeds/bullet_act(obj/item/projectile/Proj) //Works with the Somatoray to modify plant variables. - if(istype(Proj, /obj/item/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) - var/obj/machinery/hydroponics/parent = loc //for ease of access - var/t_amount = 0 - var/list/result = list() - var/output_loc = parent.Adjacent(user) ? user.loc : parent.loc //needed for TK - var/product_name - while(t_amount < getYield()) - 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 - - -/obj/item/seeds/proc/prepare_result(var/obj/item/reagent_containers/food/snacks/grown/T) - if(!T.reagents) - CRASH("[T] has no reagents.") - - for(var/rid in reagents_add) - var/amount = 1 + round(potency * reagents_add[rid], 1) - - var/list/data = null - if(rid == "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) - // apple tastes of apple. - data = T.tastes - - T.reagents.add_reagent(rid, amount, data) - - -/// Setters procs /// -/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 - -/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 - -/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 - -/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 - -/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 - -/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 - -/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 - -/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 - -/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 - -/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 - -/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 - -/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 - -/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 - -/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 += "- 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 = get_analyzer_text() - if(text) - to_chat(user, "[text]") - - 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 - -// Checks plants for broken tray icons. Use Advanced Proc Call to activate. -// Maybe some day it would be used as unit test. -/proc/check_plants_growth_stages_icons() - var/list/states = icon_states('icons/obj/hydroponics/growing.dmi') - states |= icon_states('icons/obj/hydroponics/growing_fruits.dmi') - states |= icon_states('icons/obj/hydroponics/growing_flowers.dmi') - states |= icon_states('icons/obj/hydroponics/growing_mushrooms.dmi') - states |= icon_states('icons/obj/hydroponics/growing_vegetables.dmi') - var/list/paths = typesof(/obj/item/seeds) - /obj/item/seeds - typesof(/obj/item/seeds/sample) - - for(var/seedpath in paths) - var/obj/item/seeds/seed = new seedpath - - for(var/i in 1 to seed.growthstages) - if("[seed.icon_grow][i]" in states) - continue - to_chat(world, "[seed.name] ([seed.type]) lacks the [seed.icon_grow][i] icon!") - - if(!(seed.icon_dead in states)) - to_chat(world, "[seed.name] ([seed.type]) lacks the [seed.icon_dead] icon!") - - if(seed.icon_harvest) // mushrooms have no grown sprites, same for items with no product - if(!(seed.icon_harvest in states)) - to_chat(world, "[seed.name] ([seed.type]) lacks the [seed.icon_harvest] icon!") - -/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) && !is_gene_forbidden(random_trait)) - 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) +// ******************************************************** +// 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 + var/plantname = "Plants" // Name of plant when planted. + var/obj/item/product // A type path. The thing that is created when the plant is harvested. + var/productdesc + var/species = "" // Used to update icons. Should match the name in the sprites unless all icon_* are overridden. + + var/growing_icon = 'icons/obj/hydroponics/growing.dmi' //the file that stores the sprites of the growing plant from this seed. + var/icon_grow // Used to override grow icon (default is "[species]-grow"). You can use one grow icon for multiple closely related plants with it. + var/icon_dead // Used to override dead icon (default is "[species]-dead"). You can use one dead icon for multiple closely related plants with it. + var/icon_harvest // Used to override harvest icon (default is "[species]-harvest"). If null, plant will use [icon_grow][growthstages]. + + var/lifespan = 25 // How long before the plant begins to take damage from age. + var/endurance = 15 // Amount of health the plant has. + var/maturation = 6 // Used to determine which sprite to switch to when growing. + var/production = 6 // Changes the amount of time needed for a plant to become harvestable. + var/yield = 3 // Amount of growns created per harvest. If is -1, the plant/shroom/weed is never meant to be harvested. + var/potency = 10 // The 'power' of a plant. Generally effects the amount of reagent in a plant, also used in other ways. + var/growthstages = 6 // Amount of growth sprites the plant has. + var/rarity = 0 // How rare the plant is. Used for giving points to cargo when shipping off to CentCom. + var/list/mutatelist = list() // The type of plants that this plant can mutate into. + var/list/genes = list() // Plant genes are stored here, see plant_genes.dm for more info. + var/list/forbiddengenes = list() + var/list/reagents_add = list() + // A list of reagents to add to product. + // 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: 1 + round(potency * multiplier) + + var/weed_rate = 1 //If the chance below passes, then this many weeds sprout during growth + var/weed_chance = 5 //Percentage chance per tray update to grow weeds + +/obj/item/seeds/Initialize(loc, 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) + + 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." + +/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.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/is_gene_forbidden(typepath) + return (typepath in forbiddengenes) + + +/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) + 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_weed_rate(rand(-wrmut, wrmut)) + adjust_weed_chance(rand(-wcmut, wcmut)) + if(prob(traitmut)) + add_random_traits(1, 1) + + + +/obj/item/seeds/bullet_act(obj/item/projectile/Proj) //Works with the Somatoray to modify plant variables. + if(istype(Proj, /obj/item/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) + var/obj/machinery/hydroponics/parent = loc //for ease of access + var/t_amount = 0 + var/list/result = list() + var/output_loc = parent.Adjacent(user) ? user.loc : parent.loc //needed for TK + var/product_name + while(t_amount < getYield()) + 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 + + +/obj/item/seeds/proc/prepare_result(var/obj/item/reagent_containers/food/snacks/grown/T) + if(!T.reagents) + CRASH("[T] has no reagents.") + + for(var/rid in reagents_add) + var/amount = 1 + round(potency * reagents_add[rid], 1) + + var/list/data = null + if(rid == "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) + // apple tastes of apple. + data = T.tastes + + T.reagents.add_reagent(rid, amount, data) + + +/// Setters procs /// +/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 + +/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 + +/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 + +/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 + +/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 + +/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 + +/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 + +/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 + +/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 + +/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 + +/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 + +/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 + +/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 + +/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 += "- 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 = get_analyzer_text() + if(text) + to_chat(user, "[text]") + + 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 + +// Checks plants for broken tray icons. Use Advanced Proc Call to activate. +// Maybe some day it would be used as unit test. +/proc/check_plants_growth_stages_icons() + var/list/states = icon_states('icons/obj/hydroponics/growing.dmi') + states |= icon_states('icons/obj/hydroponics/growing_fruits.dmi') + states |= icon_states('icons/obj/hydroponics/growing_flowers.dmi') + states |= icon_states('icons/obj/hydroponics/growing_mushrooms.dmi') + states |= icon_states('icons/obj/hydroponics/growing_vegetables.dmi') + var/list/paths = typesof(/obj/item/seeds) - /obj/item/seeds - typesof(/obj/item/seeds/sample) + + for(var/seedpath in paths) + var/obj/item/seeds/seed = new seedpath + + for(var/i in 1 to seed.growthstages) + if("[seed.icon_grow][i]" in states) + continue + to_chat(world, "[seed.name] ([seed.type]) lacks the [seed.icon_grow][i] icon!") + + if(!(seed.icon_dead in states)) + to_chat(world, "[seed.name] ([seed.type]) lacks the [seed.icon_dead] icon!") + + if(seed.icon_harvest) // mushrooms have no grown sprites, same for items with no product + if(!(seed.icon_harvest in states)) + to_chat(world, "[seed.name] ([seed.type]) lacks the [seed.icon_harvest] icon!") + +/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) && !is_gene_forbidden(random_trait)) + 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) diff --git a/code/modules/integrated_electronics/core/debugger.dm b/code/modules/integrated_electronics/core/debugger.dm index 5d414de5ee..e3fded3a82 100644 --- a/code/modules/integrated_electronics/core/debugger.dm +++ b/code/modules/integrated_electronics/core/debugger.dm @@ -1,84 +1,84 @@ -/obj/item/integrated_electronics/debugger - name = "circuit debugger" - desc = "This small tool allows one working with custom machinery to directly set data to a specific pin, useful for writing \ - settings to specific circuits, or for debugging purposes. It can also pulse activation pins." - icon = 'icons/obj/assemblies/electronic_tools.dmi' - icon_state = "debugger" - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - w_class = WEIGHT_CLASS_SMALL - var/data_to_write = null - var/accepting_refs = FALSE - var/copy_values = FALSE - -/obj/item/integrated_electronics/debugger/attack_self(mob/user) - var/type_to_use = input("Please choose a type to use.","[src] type setting") as null|anything in list("string","number","ref","copy","null") - if(!user.IsAdvancedToolUser()) - return - - var/new_data = null - switch(type_to_use) - if("string") - accepting_refs = FALSE - copy_values = FALSE - new_data = stripped_input(user, "Now type in a string.","[src] string writing", no_trim = TRUE) - if(istext(new_data) && user.IsAdvancedToolUser()) - data_to_write = new_data - to_chat(user, "You set \the [src]'s memory to \"[new_data]\".") - if("number") - accepting_refs = FALSE - copy_values = FALSE - new_data = input(user, "Now type in a number.","[src] number writing") as null|num - if(isnum(new_data) && user.IsAdvancedToolUser()) - data_to_write = new_data - to_chat(user, "You set \the [src]'s memory to [new_data].") - if("ref") - accepting_refs = TRUE - copy_values = FALSE - to_chat(user, "You turn \the [src]'s ref scanner on. Slide it across \ - an object for a ref of that object to save it in memory.") - if("copy") - accepting_refs = FALSE - copy_values = TRUE - to_chat(user, "You turn \the [src]'s value copier on. Use it on a pin \ - to save its current value in memory.") - if("null") - data_to_write = null - copy_values = FALSE - to_chat(user, "You set \the [src]'s memory to absolutely nothing.") - -/obj/item/integrated_electronics/debugger/afterattack(atom/target, mob/living/user, proximity) - . = ..() - if(accepting_refs && proximity) - data_to_write = WEAKREF(target) - visible_message("[user] slides \a [src]'s over \the [target].") - to_chat(user, "You set \the [src]'s memory to a reference to [target.name] \[Ref\]. The ref scanner is \ - now off.") - accepting_refs = FALSE - -/obj/item/integrated_electronics/debugger/proc/write_data(var/datum/integrated_io/io, mob/user) - //If the pin can take data: - if(io.io_type == DATA_CHANNEL) - //If the debugger is set to copy, copy the data in the pin onto it - if(copy_values) - data_to_write = io.data - to_chat(user, "You let the debugger copy the data.") - copy_values = FALSE - return - - //Else, write the data to the pin - io.write_data_to_pin(data_to_write) - var/data_to_show = data_to_write - //This is only to convert a weakref into a name for better output - if(isweakref(data_to_write)) - var/datum/weakref/w = data_to_write - var/atom/A = w.resolve() - data_to_show = A.name - to_chat(user, "You write '[data_to_write ? data_to_show : "NULL"]' to the '[io]' pin of \the [io.holder].") - - //If the pin can only be pulsed - else if(io.io_type == PULSE_CHANNEL) - io.holder.check_then_do_work(io.ord,ignore_power = TRUE) - to_chat(user, "You pulse \the [io.holder]'s [io].") - - io.holder.interact(user) // This is to update the UI. +/obj/item/integrated_electronics/debugger + name = "circuit debugger" + desc = "This small tool allows one working with custom machinery to directly set data to a specific pin, useful for writing \ + settings to specific circuits, or for debugging purposes. It can also pulse activation pins." + icon = 'icons/obj/assemblies/electronic_tools.dmi' + icon_state = "debugger" + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + w_class = WEIGHT_CLASS_SMALL + var/data_to_write = null + var/accepting_refs = FALSE + var/copy_values = FALSE + +/obj/item/integrated_electronics/debugger/attack_self(mob/user) + var/type_to_use = input("Please choose a type to use.","[src] type setting") as null|anything in list("string","number","ref","copy","null") + if(!user.IsAdvancedToolUser()) + return + + var/new_data = null + switch(type_to_use) + if("string") + accepting_refs = FALSE + copy_values = FALSE + new_data = stripped_input(user, "Now type in a string.","[src] string writing", no_trim = TRUE) + if(istext(new_data) && user.IsAdvancedToolUser()) + data_to_write = new_data + to_chat(user, "You set \the [src]'s memory to \"[new_data]\".") + if("number") + accepting_refs = FALSE + copy_values = FALSE + new_data = input(user, "Now type in a number.","[src] number writing") as null|num + if(isnum(new_data) && user.IsAdvancedToolUser()) + data_to_write = new_data + to_chat(user, "You set \the [src]'s memory to [new_data].") + if("ref") + accepting_refs = TRUE + copy_values = FALSE + to_chat(user, "You turn \the [src]'s ref scanner on. Slide it across \ + an object for a ref of that object to save it in memory.") + if("copy") + accepting_refs = FALSE + copy_values = TRUE + to_chat(user, "You turn \the [src]'s value copier on. Use it on a pin \ + to save its current value in memory.") + if("null") + data_to_write = null + copy_values = FALSE + to_chat(user, "You set \the [src]'s memory to absolutely nothing.") + +/obj/item/integrated_electronics/debugger/afterattack(atom/target, mob/living/user, proximity) + . = ..() + if(accepting_refs && proximity) + data_to_write = WEAKREF(target) + visible_message("[user] slides \a [src]'s over \the [target].") + to_chat(user, "You set \the [src]'s memory to a reference to [target.name] \[Ref\]. The ref scanner is \ + now off.") + accepting_refs = FALSE + +/obj/item/integrated_electronics/debugger/proc/write_data(var/datum/integrated_io/io, mob/user) + //If the pin can take data: + if(io.io_type == DATA_CHANNEL) + //If the debugger is set to copy, copy the data in the pin onto it + if(copy_values) + data_to_write = io.data + to_chat(user, "You let the debugger copy the data.") + copy_values = FALSE + return + + //Else, write the data to the pin + io.write_data_to_pin(data_to_write) + var/data_to_show = data_to_write + //This is only to convert a weakref into a name for better output + if(isweakref(data_to_write)) + var/datum/weakref/w = data_to_write + var/atom/A = w.resolve() + data_to_show = A.name + to_chat(user, "You write '[data_to_write ? data_to_show : "NULL"]' to the '[io]' pin of \the [io.holder].") + + //If the pin can only be pulsed + else if(io.io_type == PULSE_CHANNEL) + io.holder.check_then_do_work(io.ord,ignore_power = TRUE) + to_chat(user, "You pulse \the [io.holder]'s [io].") + + io.holder.interact(user) // This is to update the UI. diff --git a/code/modules/integrated_electronics/core/wirer.dm b/code/modules/integrated_electronics/core/wirer.dm index c68a61c175..9550f9b4ae 100644 --- a/code/modules/integrated_electronics/core/wirer.dm +++ b/code/modules/integrated_electronics/core/wirer.dm @@ -1,101 +1,101 @@ -#define WIRE "wire" -#define WIRING "wiring" -#define UNWIRE "unwire" -#define UNWIRING "unwiring" - -/obj/item/integrated_electronics/wirer - name = "circuit wirer" - desc = "It's a small wiring tool, with a wire roll, electric soldering iron, wire cutter, and more in one package. \ - The wires used are generally useful for small electronics, such as circuitboards and breadboards, as opposed to larger wires \ - used for power or data transmission." - icon = 'icons/obj/assemblies/electronic_tools.dmi' - icon_state = "wirer-wire" - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_SMALL - var/datum/integrated_io/selected_io = null - var/mode = WIRE - -/obj/item/integrated_electronics/wirer/update_icon() - icon_state = "wirer-[mode]" - -/obj/item/integrated_electronics/wirer/proc/wire(var/datum/integrated_io/io, mob/user) - if(!io.holder.assembly) - to_chat(user, "\The [io.holder] needs to be secured inside an assembly first.") - return - switch(mode) - if(WIRE) - selected_io = io - to_chat(user, "You attach a data wire to \the [selected_io.holder]'s [selected_io.name] data channel.") - mode = WIRING - update_icon() - if(WIRING) - if(io == selected_io) - to_chat(user, "Wiring \the [selected_io.holder]'s [selected_io.name] into itself is rather pointless.") - return - if(io.io_type != selected_io.io_type) - to_chat(user, "Those two types of channels are incompatible. The first is a [selected_io.io_type], \ - while the second is a [io.io_type].") - return - if(io.holder.assembly && io.holder.assembly != selected_io.holder.assembly) - to_chat(user, "Both \the [io.holder] and \the [selected_io.holder] need to be inside the same assembly.") - return - selected_io.connect_pin(io) - - to_chat(user, "You connect \the [selected_io.holder]'s [selected_io.name] to \the [io.holder]'s [io.name].") - mode = WIRE - update_icon() - selected_io.holder.interact(user) // This is to update the UI. - selected_io = null - - if(UNWIRE) - selected_io = io - if(!io.linked.len) - to_chat(user, "There is nothing connected to \the [selected_io] data channel.") - selected_io = null - return - to_chat(user, "You prepare to detach a data wire from \the [selected_io.holder]'s [selected_io.name] data channel.") - mode = UNWIRING - update_icon() - return - - if(UNWIRING) - if(io == selected_io) - to_chat(user, "You can't wire a pin into each other, so unwiring \the [selected_io.holder] from \ - the same pin is rather moot.") - return - if(selected_io in io.linked) - selected_io.disconnect_pin(io) - to_chat(user, "You disconnect \the [selected_io.holder]'s [selected_io.name] from \ - \the [io.holder]'s [io.name].") - selected_io.holder.interact(user) // This is to update the UI. - selected_io = null - mode = UNWIRE - update_icon() - else - to_chat(user, "\The [selected_io.holder]'s [selected_io.name] and \the [io.holder]'s \ - [io.name] are not connected.") - return - -/obj/item/integrated_electronics/wirer/attack_self(mob/user) - switch(mode) - if(WIRE) - mode = UNWIRE - if(WIRING) - if(selected_io) - to_chat(user, "You decide not to wire the data channel.") - selected_io = null - mode = WIRE - if(UNWIRE) - mode = WIRE - if(UNWIRING) - if(selected_io) - to_chat(user, "You decide not to disconnect the data channel.") - selected_io = null - mode = UNWIRE - update_icon() - to_chat(user, "You set \the [src] to [mode].") - -#undef WIRE -#undef WIRING -#undef UNWIRE -#undef UNWIRING +#define WIRE "wire" +#define WIRING "wiring" +#define UNWIRE "unwire" +#define UNWIRING "unwiring" + +/obj/item/integrated_electronics/wirer + name = "circuit wirer" + desc = "It's a small wiring tool, with a wire roll, electric soldering iron, wire cutter, and more in one package. \ + The wires used are generally useful for small electronics, such as circuitboards and breadboards, as opposed to larger wires \ + used for power or data transmission." + icon = 'icons/obj/assemblies/electronic_tools.dmi' + icon_state = "wirer-wire" + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_SMALL + var/datum/integrated_io/selected_io = null + var/mode = WIRE + +/obj/item/integrated_electronics/wirer/update_icon() + icon_state = "wirer-[mode]" + +/obj/item/integrated_electronics/wirer/proc/wire(var/datum/integrated_io/io, mob/user) + if(!io.holder.assembly) + to_chat(user, "\The [io.holder] needs to be secured inside an assembly first.") + return + switch(mode) + if(WIRE) + selected_io = io + to_chat(user, "You attach a data wire to \the [selected_io.holder]'s [selected_io.name] data channel.") + mode = WIRING + update_icon() + if(WIRING) + if(io == selected_io) + to_chat(user, "Wiring \the [selected_io.holder]'s [selected_io.name] into itself is rather pointless.") + return + if(io.io_type != selected_io.io_type) + to_chat(user, "Those two types of channels are incompatible. The first is a [selected_io.io_type], \ + while the second is a [io.io_type].") + return + if(io.holder.assembly && io.holder.assembly != selected_io.holder.assembly) + to_chat(user, "Both \the [io.holder] and \the [selected_io.holder] need to be inside the same assembly.") + return + selected_io.connect_pin(io) + + to_chat(user, "You connect \the [selected_io.holder]'s [selected_io.name] to \the [io.holder]'s [io.name].") + mode = WIRE + update_icon() + selected_io.holder.interact(user) // This is to update the UI. + selected_io = null + + if(UNWIRE) + selected_io = io + if(!io.linked.len) + to_chat(user, "There is nothing connected to \the [selected_io] data channel.") + selected_io = null + return + to_chat(user, "You prepare to detach a data wire from \the [selected_io.holder]'s [selected_io.name] data channel.") + mode = UNWIRING + update_icon() + return + + if(UNWIRING) + if(io == selected_io) + to_chat(user, "You can't wire a pin into each other, so unwiring \the [selected_io.holder] from \ + the same pin is rather moot.") + return + if(selected_io in io.linked) + selected_io.disconnect_pin(io) + to_chat(user, "You disconnect \the [selected_io.holder]'s [selected_io.name] from \ + \the [io.holder]'s [io.name].") + selected_io.holder.interact(user) // This is to update the UI. + selected_io = null + mode = UNWIRE + update_icon() + else + to_chat(user, "\The [selected_io.holder]'s [selected_io.name] and \the [io.holder]'s \ + [io.name] are not connected.") + return + +/obj/item/integrated_electronics/wirer/attack_self(mob/user) + switch(mode) + if(WIRE) + mode = UNWIRE + if(WIRING) + if(selected_io) + to_chat(user, "You decide not to wire the data channel.") + selected_io = null + mode = WIRE + if(UNWIRE) + mode = WIRE + if(UNWIRING) + if(selected_io) + to_chat(user, "You decide not to disconnect the data channel.") + selected_io = null + mode = UNWIRE + update_icon() + to_chat(user, "You set \the [src] to [mode].") + +#undef WIRE +#undef WIRING +#undef UNWIRE +#undef UNWIRING diff --git a/code/modules/integrated_electronics/subtypes/atmospherics.dm b/code/modules/integrated_electronics/subtypes/atmospherics.dm index aed99fac24..6b4f46f83d 100644 --- a/code/modules/integrated_electronics/subtypes/atmospherics.dm +++ b/code/modules/integrated_electronics/subtypes/atmospherics.dm @@ -1,760 +1,760 @@ -#define SOURCE_TO_TARGET 0 -#define TARGET_TO_SOURCE 1 -#define PUMP_EFFICIENCY 0.6 -#define TANK_FAILURE_PRESSURE (ONE_ATMOSPHERE*25) -#define PUMP_MAX_VOLUME 100 - - -/obj/item/integrated_circuit/atmospherics - category_text = "Atmospherics" - cooldown_per_use = 2 SECONDS - complexity = 10 - size = 7 - outputs = list( - "self reference" = IC_PINTYPE_SELFREF, - "pressure" = IC_PINTYPE_NUMBER - ) - var/datum/gas_mixture/air_contents - var/volume = 2 //Pretty small, I know - -/obj/item/integrated_circuit/atmospherics/Initialize() - air_contents = new(volume) - ..() - -/obj/item/integrated_circuit/atmospherics/return_air() - return air_contents - -//Check if the gas container is adjacent and of the right type -/obj/item/integrated_circuit/atmospherics/proc/check_gassource(atom/gasholder) - if(!gasholder) - return FALSE - if(!gasholder.Adjacent(get_object())) - return FALSE - if(!istype(gasholder, /obj/item/tank) && !istype(gasholder, /obj/machinery/portable_atmospherics) && !istype(gasholder, /obj/item/integrated_circuit/atmospherics)) - return FALSE - return TRUE - -//Needed in circuits where source and target types differ -/obj/item/integrated_circuit/atmospherics/proc/check_gastarget(atom/gasholder) - return check_gassource(gasholder) - - -// - gas pump - // **works** -/obj/item/integrated_circuit/atmospherics/pump - name = "gas pump" - desc = "Somehow moves gases between two tanks, canisters, and other gas containers." - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - inputs = list( - "source" = IC_PINTYPE_REF, - "target" = IC_PINTYPE_REF, - "target pressure" = IC_PINTYPE_NUMBER - ) - activators = list( - "transfer" = IC_PINTYPE_PULSE_IN, - "on transfer" = IC_PINTYPE_PULSE_OUT - ) - var/direction = SOURCE_TO_TARGET - var/target_pressure = PUMP_MAX_PRESSURE - power_draw_per_use = 20 - -/obj/item/integrated_circuit/atmospherics/pump/Initialize() - air_contents = new(volume) - extended_desc += " Use negative pressure to move air from target to source. \ - Note that only part of the gas is moved on each transfer, \ - so multiple activations will be necessary to achieve target pressure. \ - The pressure limit for circuit pumps is [round(PUMP_MAX_PRESSURE)] kPa." - . = ..() - -// This proc gets the direction of the gas flow depending on its value, by calling update target -/obj/item/integrated_circuit/atmospherics/pump/on_data_written() - var/amt = get_pin_data(IC_INPUT, 3) - update_target(amt) - -/obj/item/integrated_circuit/atmospherics/pump/proc/update_target(new_amount) - if(!isnum(new_amount)) - new_amount = 0 - // See in which direction the gas moves - if(new_amount < 0) - direction = TARGET_TO_SOURCE - else - direction = SOURCE_TO_TARGET - target_pressure = min(round(PUMP_MAX_PRESSURE),abs(new_amount)) - -/obj/item/integrated_circuit/atmospherics/pump/do_work() - var/obj/source = get_pin_data_as_type(IC_INPUT, 1, /obj) - var/obj/target = get_pin_data_as_type(IC_INPUT, 2, /obj) - perform_magic(source, target) - activate_pin(2) - -/obj/item/integrated_circuit/atmospherics/pump/proc/perform_magic(atom/source, atom/target) - //Check if both atoms are of the right type: atmos circuits/gas tanks/canisters. If one is the same, use the circuit var - if(!check_gassource(source)) - source = src - - if(!check_gastarget(target)) - target = src - - // If both are the same, this whole proc would do nothing and just waste performance - if(source == target) - return - - var/datum/gas_mixture/source_air = source.return_air() - var/datum/gas_mixture/target_air = target.return_air() - - if(!source_air || !target_air) - return - - // Swapping both source and target - if(direction == TARGET_TO_SOURCE) - var/temp = source_air - source_air = target_air - target_air = temp - - // If what you are pumping is empty, use the circuit's storage - if(source_air.total_moles() <= 0) - source_air = air_contents - - // Move gas from one place to another - move_gas(source_air, target_air) - air_update_turf() - -/obj/item/integrated_circuit/atmospherics/pump/proc/move_gas(datum/gas_mixture/source_air, datum/gas_mixture/target_air) - - // No moles = nothing to pump - if(source_air.total_moles() <= 0 || target_air.return_pressure() >= PUMP_MAX_PRESSURE) - return - - // Negative Kelvin temperatures should never happen and if they do, normalize them - if(source_air.temperature < TCMB) - source_air.temperature = TCMB - - var/pressure_delta = target_pressure - target_air.return_pressure() - if(pressure_delta > 0.1) - var/transfer_moles = (pressure_delta*target_air.volume/(source_air.temperature * R_IDEAL_GAS_EQUATION))*PUMP_EFFICIENCY - var/datum/gas_mixture/removed = source_air.remove(transfer_moles) - target_air.merge(removed) - - -// - volume pump - // **Works** -/obj/item/integrated_circuit/atmospherics/pump/volume - name = "volume pump" - desc = "Moves gases between two tanks, canisters, and other gas containers by using their volume, up to 200 L/s." - extended_desc = " Use negative volume to move air from target to source. Note that only part of the gas is moved on each transfer. Its maximum pumping volume is capped at 1000kPa." - - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - inputs = list( - "source" = IC_PINTYPE_REF, - "target" = IC_PINTYPE_REF, - "transfer volume" = IC_PINTYPE_NUMBER - ) - activators = list( - "transfer" = IC_PINTYPE_PULSE_IN, - "on transfer" = IC_PINTYPE_PULSE_OUT - ) - direction = SOURCE_TO_TARGET - var/transfer_rate = PUMP_MAX_VOLUME - power_draw_per_use = 20 - -/obj/item/integrated_circuit/atmospherics/pump/volume/update_target(new_amount) - if(!isnum(new_amount)) - new_amount = 0 - // See in which direction the gas moves - if(new_amount < 0) - direction = TARGET_TO_SOURCE - else - direction = SOURCE_TO_TARGET - target_pressure = min(PUMP_MAX_VOLUME,abs(new_amount)) - -/obj/item/integrated_circuit/atmospherics/pump/volume/move_gas(datum/gas_mixture/source_air, datum/gas_mixture/target_air) - // No moles = nothing to pump - if(source_air.total_moles() <= 0) - return - - // Negative Kelvin temperatures should never happen and if they do, normalize them - if(source_air.temperature < TCMB) - source_air.temperature = TCMB - - if((source_air.return_pressure() < 0.01) || (target_air.return_pressure() >= PUMP_MAX_PRESSURE)) - return - - //The second part of the min caps the pressure built by the volume pumps to the max pump pressure - var/transfer_ratio = min(transfer_rate,target_air.volume*PUMP_MAX_PRESSURE/source_air.return_pressure())/source_air.volume - - var/datum/gas_mixture/removed = source_air.remove_ratio(transfer_ratio * PUMP_EFFICIENCY) - - target_air.merge(removed) - - -// - gas vent - // **works** -/obj/item/integrated_circuit/atmospherics/pump/vent - name = "gas vent" - extended_desc = "Use negative volume to move air from target to environment. Note that only part of the gas is moved on each transfer. Unlike the gas pump, this one keeps pumping even further to pressures of 9000 pKa and it is not advised to use it on tank circuits." - desc = "Moves gases between the environment and adjacent gas containers." - inputs = list( - "container" = IC_PINTYPE_REF, - "target pressure" = IC_PINTYPE_NUMBER - ) - -/obj/item/integrated_circuit/atmospherics/pump/vent/on_data_written() - var/amt = get_pin_data(IC_INPUT, 2) - update_target(amt) - -/obj/item/integrated_circuit/atmospherics/pump/vent/do_work() - var/turf/source = get_turf(src) - var/obj/target = get_pin_data_as_type(IC_INPUT, 1, /obj) - perform_magic(source, target) - activate_pin(2) - -/obj/item/integrated_circuit/atmospherics/pump/vent/check_gastarget(atom/gasholder) - if(!gasholder) - return FALSE - if(!gasholder.Adjacent(get_object())) - return FALSE - if(!istype(gasholder, /obj/item/tank) && !istype(gasholder, /obj/machinery/portable_atmospherics) && !istype(gasholder, /obj/item/integrated_circuit/atmospherics)) - return FALSE - return TRUE - - -/obj/item/integrated_circuit/atmospherics/pump/vent/check_gassource(atom/target) - if(!target) - return FALSE - if(!istype(target, /turf)) - return FALSE - return TRUE - - -// - integrated connector - // Can connect and disconnect properly -/obj/item/integrated_circuit/atmospherics/connector - name = "integrated connector" - desc = "Creates an airtight seal with standard connectors found on the floor, \ - allowing the assembly to exchange gases with a pipe network." - extended_desc = "This circuit will automatically attempt to locate and connect to ports on the floor beneath it when activated. \ - You must set a target before connecting." - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - inputs = list( - "target" = IC_PINTYPE_REF - ) - activators = list( - "toggle connection" = IC_PINTYPE_PULSE_IN, - "on connected" = IC_PINTYPE_PULSE_OUT, - "on connection failed" = IC_PINTYPE_PULSE_OUT, - "on disconnected" = IC_PINTYPE_PULSE_OUT - ) - - var/obj/machinery/atmospherics/components/unary/portables_connector/connector - -/obj/item/integrated_circuit/atmospherics/connector/Initialize() - air_contents = new(volume) - START_PROCESSING(SSobj, src) - . = ..() - -//Sucks up the gas from the connector -/obj/item/integrated_circuit/atmospherics/connector/process() - set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) - -/obj/item/integrated_circuit/atmospherics/connector/check_gassource(atom/gasholder) - if(!gasholder) - return FALSE - if(!istype(gasholder,/obj/machinery/atmospherics/components/unary/portables_connector)) - return FALSE - return TRUE - -//If the assembly containing this is moved from the tile the connector pipe is in, the connection breaks -/obj/item/integrated_circuit/atmospherics/connector/ext_moved() - if(connector) - if(get_dist(get_object(), connector) > 0) - // The assembly is set as connected device and the connector handles the rest - connector.connected_device = null - connector = null - activate_pin(4) - -/obj/item/integrated_circuit/atmospherics/connector/do_work() - // If there is a connection, disconnect - if(connector) - connector.connected_device = null - connector = null - activate_pin(4) - return - - var/obj/machinery/atmospherics/components/unary/portables_connector/PC = locate() in get_turf(src) - // If no connector can't connect - if(!PC) - activate_pin(3) - return - connector = PC - connector.connected_device = src - activate_pin(2) - -// Required for making the connector port script work -obj/item/integrated_circuit/atmospherics/connector/portableConnectorReturnAir() - return air_contents - - -// - gas filter - // **works** -/obj/item/integrated_circuit/atmospherics/pump/filter - name = "gas filter" - desc = "Filters one gas out of a mixture." - complexity = 20 - size = 8 - spawn_flags = IC_SPAWN_RESEARCH - inputs = list( - "source" = IC_PINTYPE_REF, - "filtered output" = IC_PINTYPE_REF, - "contaminants output" = IC_PINTYPE_REF, - "wanted gases" = IC_PINTYPE_LIST, - "target pressure" = IC_PINTYPE_NUMBER - ) - power_draw_per_use = 30 - -/obj/item/integrated_circuit/atmospherics/pump/filter/on_data_written() - var/amt = get_pin_data(IC_INPUT, 5) - target_pressure = CLAMP(amt, 0, PUMP_MAX_PRESSURE) - -/obj/item/integrated_circuit/atmospherics/pump/filter/do_work() - activate_pin(2) - var/obj/source = get_pin_data_as_type(IC_INPUT, 1, /obj) - var/obj/filtered = get_pin_data_as_type(IC_INPUT, 2, /obj) - var/obj/contaminants = get_pin_data_as_type(IC_INPUT, 3, /obj) - - var/wanted = get_pin_data(IC_INPUT, 4) - - // If there is no filtered output, this whole thing makes no sense - if(!check_gassource(filtered)) - return - - var/datum/gas_mixture/filtered_air = filtered.return_air() - if(!filtered_air) - return - - // If no source is set, the source is possibly this circuit's content - if(!check_gassource(source)) - source = src - var/datum/gas_mixture/source_air = source.return_air() - - //No source air: source is this circuit - if(!source_air) - source_air = air_contents - - // If no filtering tank is set, filter through itself - if(!check_gassource(contaminants)) - contaminants = src - var/datum/gas_mixture/contaminated_air = contaminants.return_air() - - //If there is no gas mixture datum for unfiltered, pump the contaminants back into the circuit - if(!contaminated_air) - contaminated_air = air_contents - - if(contaminated_air.return_pressure() >= PUMP_MAX_PRESSURE || filtered_air.return_pressure() >= PUMP_MAX_PRESSURE) - return - - var/pressure_delta = target_pressure - contaminated_air.return_pressure() - var/transfer_moles - - //Negative Kelvins are an anomaly and should be normalized if encountered - if(source_air.temperature < TCMB) - source_air.temperature = TCMB - - transfer_moles = (pressure_delta*contaminated_air.volume/(source_air.temperature * R_IDEAL_GAS_EQUATION))*PUMP_EFFICIENCY - - //If there is nothing to transfer, just return - if(transfer_moles <= 0) - return - - //This is the var that holds the currently filtered part of the gas - var/datum/gas_mixture/removed = source_air.remove(transfer_moles) - if(!removed) - return - - //This is the gas that will be moved from source to filtered - var/datum/gas_mixture/filtered_out = new - - for(var/filtered_gas in removed.gases) - //Get the name of the gas and see if it is in the list - if(GLOB.meta_gas_names[filtered_gas] in wanted) - //The gas that is put in all the filtered out gases - filtered_out.temperature = removed.temperature - filtered_out.gases[filtered_gas] = removed.gases[filtered_gas] - - //The filtered out gas is entirely removed from the currently filtered gases - removed.gases[filtered_gas] = 0 - GAS_GARBAGE_COLLECT(removed.gases) - - //Check if the pressure is high enough to put stuff in filtered, or else just put it back in the source - var/datum/gas_mixture/target = (filtered_air.return_pressure() < target_pressure ? filtered_air : source_air) - target.merge(filtered_out) - contaminated_air.merge(removed) - - -/obj/item/integrated_circuit/atmospherics/pump/filter/Initialize() - air_contents = new(volume) - . = ..() - extended_desc = "Remember to properly spell and capitalize the filtered gas name. \ - Note that only part of the gas is moved on each transfer, \ - so multiple activations will be necessary to achieve target pressure. \ - The pressure limit for circuit pumps is [round(PUMP_MAX_PRESSURE)] kPa." - - -// - gas mixer - // **works** -/obj/item/integrated_circuit/atmospherics/pump/mixer - name = "gas mixer" - desc = "Mixes 2 different types of gases." - complexity = 20 - size = 8 - spawn_flags = IC_SPAWN_RESEARCH - inputs = list( - "first source" = IC_PINTYPE_REF, - "second source" = IC_PINTYPE_REF, - "output" = IC_PINTYPE_REF, - "first source percentage" = IC_PINTYPE_NUMBER, - "target pressure" = IC_PINTYPE_NUMBER - ) - power_draw_per_use = 30 - -/obj/item/integrated_circuit/atmospherics/pump/mixer/do_work() - activate_pin(2) - var/obj/source_1 = get_pin_data(IC_INPUT, 1) - var/obj/source_2 = get_pin_data(IC_INPUT, 2) - var/obj/gas_output = get_pin_data(IC_INPUT, 3) - if(!check_gassource(source_1)) - source_1 = src - - if(!check_gassource(source_2)) - source_2 = src - - if(!check_gassource(gas_output)) - gas_output = src - - if(source_1 == gas_output || source_2 == gas_output) - return - - var/datum/gas_mixture/source_1_gases = source_1.return_air() - var/datum/gas_mixture/source_2_gases = source_2.return_air() - var/datum/gas_mixture/output_gases = gas_output.return_air() - - if(!source_1_gases || !source_2_gases || !output_gases) - return - - if(output_gases.return_pressure() >= PUMP_MAX_PRESSURE) - return - - if(source_1_gases.return_pressure() <= 0 || source_2_gases.return_pressure() <= 0) - return - - //This calculates how much should be sent - var/gas_percentage = round(max(min(get_pin_data(IC_INPUT, 4),100),0) / 100) - - //Basically: number of moles = percentage of pressure filled up * efficiency coefficient * (pressure from both gases * volume of output) / (R * Temperature) - var/transfer_moles = (get_pin_data(IC_INPUT, 5) / max(1,output_gases.return_pressure())) * PUMP_EFFICIENCY * (source_1_gases.return_pressure() * gas_percentage + source_2_gases.return_pressure() * (1 - gas_percentage)) * output_gases.volume/ (R_IDEAL_GAS_EQUATION * max(output_gases.temperature,TCMB)) - - - if(transfer_moles <= 0) - return - - var/datum/gas_mixture/mix = source_1_gases.remove(transfer_moles * gas_percentage) - output_gases.merge(mix) - mix = source_2_gases.remove(transfer_moles * (1-gas_percentage)) - output_gases.merge(mix) - - -// - integrated tank - // **works** -/obj/item/integrated_circuit/atmospherics/tank - name = "integrated tank" - desc = "A small tank for the storage of gases." - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - size = 4 - activators = list( - "push ref" = IC_PINTYPE_PULSE_IN - ) - volume = 3 //emergency tank sized - var/broken = FALSE - -/obj/item/integrated_circuit/atmospherics/tank/Initialize() - air_contents = new(volume) - START_PROCESSING(SSobj, src) - extended_desc = "Take care not to pressurize it above [round(TANK_FAILURE_PRESSURE)] kPa, or else it will break." - . = ..() - -/obj/item/integrated_circuit/atmospherics/tank/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/integrated_circuit/atmospherics/tank/do_work() - set_pin_data(IC_OUTPUT, 1, WEAKREF(src)) - push_data() - -/obj/item/integrated_circuit/atmospherics/tank/process() - var/tank_pressure = air_contents.return_pressure() - set_pin_data(IC_OUTPUT, 2, tank_pressure) - push_data() - - //Check if tank broken - if(!broken && tank_pressure > TANK_FAILURE_PRESSURE) - broken = TRUE - to_chat(view(2),"The [name] ruptures, releasing its gases!") - if(broken) - release() - -/obj/item/integrated_circuit/atmospherics/tank/proc/release() - if(air_contents.total_moles() > 0) - playsound(loc, 'sound/effects/spray.ogg', 10, 1, -3) - var/datum/gas_mixture/expelled_gas = air_contents.remove(air_contents.total_moles()) - var/turf/current_turf = get_turf(src) - var/datum/gas_mixture/exterior_gas - if(!current_turf) - return - - exterior_gas = current_turf.return_air() - exterior_gas.merge(expelled_gas) - - -// - large integrated tank - // **works** -/obj/item/integrated_circuit/atmospherics/tank/large - name = "large integrated tank" - desc = "A less small tank for the storage of gases." - volume = 9 - size = 12 - spawn_flags = IC_SPAWN_RESEARCH - - -// - freezer tank - // **works** -/obj/item/integrated_circuit/atmospherics/tank/freezer - name = "freezer tank" - desc = "Cools the gas it contains to a preset temperature." - volume = 6 - size = 8 - inputs = list( - "target temperature" = IC_PINTYPE_NUMBER, - "on" = IC_PINTYPE_BOOLEAN - ) - inputs_default = list("1" = 300) - spawn_flags = IC_SPAWN_RESEARCH - var/temperature = 293.15 - var/heater_coefficient = 0.1 - -/obj/item/integrated_circuit/atmospherics/tank/freezer/on_data_written() - temperature = max(73.15,min(293.15,get_pin_data(IC_INPUT, 1))) - if(get_pin_data(IC_INPUT, 2)) - power_draw_idle = 30 - else - power_draw_idle = 0 - -/obj/item/integrated_circuit/atmospherics/tank/freezer/process() - var/tank_pressure = air_contents.return_pressure() - set_pin_data(IC_OUTPUT, 2, tank_pressure) - push_data() - - //Cool the tank if the power is on and the temp is above - if(!power_draw_idle || air_contents.temperature < temperature) - return - - air_contents.temperature = max(73.15,air_contents.temperature - (air_contents.temperature - temperature) * heater_coefficient) - - -// - heater tank - // **works** -/obj/item/integrated_circuit/atmospherics/tank/freezer/heater - name = "heater tank" - desc = "Heats the gas it contains to a preset temperature." - volume = 6 - inputs = list( - "target temperature" = IC_PINTYPE_NUMBER, - "on" = IC_PINTYPE_BOOLEAN - ) - spawn_flags = IC_SPAWN_RESEARCH - -/obj/item/integrated_circuit/atmospherics/tank/freezer/heater/on_data_written() - temperature = max(293.15,min(573.15,get_pin_data(IC_INPUT, 1))) - if(get_pin_data(IC_INPUT, 2)) - power_draw_idle = 30 - else - power_draw_idle = 0 - -/obj/item/integrated_circuit/atmospherics/tank/freezer/heater/process() - var/tank_pressure = air_contents.return_pressure() - set_pin_data(IC_OUTPUT, 2, tank_pressure) - push_data() - - //Heat the tank if the power is on or its temperature is below what is set - if(!power_draw_idle || air_contents.temperature > temperature) - return - - air_contents.temperature = min(573.15,air_contents.temperature + (temperature - air_contents.temperature) * heater_coefficient) - - -// - atmospheric cooler - // **works** -/obj/item/integrated_circuit/atmospherics/cooler - name = "atmospheric cooler circuit" - desc = "Cools the air around it." - volume = 6 - size = 13 - spawn_flags = IC_SPAWN_RESEARCH - inputs = list( - "target temperature" = IC_PINTYPE_NUMBER, - "on" = IC_PINTYPE_BOOLEAN - ) - var/temperature = 293.15 - var/heater_coefficient = 0.1 - -/obj/item/integrated_circuit/atmospherics/cooler/Initialize() - air_contents = new(volume) - START_PROCESSING(SSobj, src) - . = ..() - -/obj/item/integrated_circuit/atmospherics/cooler/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/integrated_circuit/atmospherics/cooler/on_data_written() - temperature = max(243.15,min(293.15,get_pin_data(IC_INPUT, 1))) - if(get_pin_data(IC_INPUT, 2)) - power_draw_idle = 30 - else - power_draw_idle = 0 - -/obj/item/integrated_circuit/atmospherics/cooler/process() - set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) - push_data() - - - //Get the turf you're on and its gas mixture - var/turf/current_turf = get_turf(src) - if(!current_turf) - return - - var/datum/gas_mixture/turf_air = current_turf.return_air() - if(!power_draw_idle || turf_air.temperature < temperature) - return - - //Cool the gas - turf_air.temperature = max(243.15,turf_air.temperature - (turf_air.temperature - temperature) * heater_coefficient) - - -// - atmospheric heater - // **works** -/obj/item/integrated_circuit/atmospherics/cooler/heater - name = "atmospheric heater circuit" - desc = "Heats the air around it." - -/obj/item/integrated_circuit/atmospherics/cooler/heater/on_data_written() - temperature = max(293.15,min(323.15,get_pin_data(IC_INPUT, 1))) - if(get_pin_data(IC_INPUT, 2)) - power_draw_idle = 30 - else - power_draw_idle = 0 - -/obj/item/integrated_circuit/atmospherics/cooler/heater/process() - set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) - push_data() - - //Get the turf and its air mixture - var/turf/current_turf = get_turf(src) - if(!current_turf) - return - - var/datum/gas_mixture/turf_air = current_turf.return_air() - if(!power_draw_idle || turf_air.temperature > temperature) - return - - //Heat the gas - turf_air.temperature = min(323.15,turf_air.temperature + (temperature - turf_air.temperature) * heater_coefficient) - - -// - tank slot - // **works** -/obj/item/integrated_circuit/input/tank_slot - category_text = "Atmospherics" - cooldown_per_use = 1 - name = "tank slot" - desc = "Lets you add a tank to your assembly and remove it even when the assembly is closed." - extended_desc = "It can help you extract gases easier." - complexity = 25 - size = 30 - inputs = list() - outputs = list( - "pressure used" = IC_PINTYPE_NUMBER, - "current tank" = IC_PINTYPE_REF - ) - activators = list( - "push ref" = IC_PINTYPE_PULSE_IN, - "on insert" = IC_PINTYPE_PULSE_OUT, - "on remove" = IC_PINTYPE_PULSE_OUT - ) - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - - can_be_asked_input = TRUE - demands_object_input = TRUE - can_input_object_when_closed = TRUE - - var/obj/item/tank/internals/current_tank - -/obj/item/integrated_circuit/input/tank_slot/Initialize() - START_PROCESSING(SSobj, src) - . = ..() - -/obj/item/integrated_circuit/input/tank_slot/process() - push_pressure() - -/obj/item/integrated_circuit/input/tank_slot/attackby(var/obj/item/tank/internals/I, var/mob/living/user) - //Check if it truly is a tank - if(!istype(I,/obj/item/tank/internals)) - to_chat(user,"The [I.name] doesn't seem to fit in here.") - return - - //Check if there is no other tank already inside - if(current_tank) - to_chat(user,"There is already a gas tank inside.") - return - - //The current tank is the one we just attached, its location is inside the circuit - current_tank = I - user.transferItemToLoc(I,src) - to_chat(user,"You put the [I.name] inside the tank slot.") - - //Set the pin to a weak reference of the current tank - push_pressure() - set_pin_data(IC_OUTPUT, 2, WEAKREF(current_tank)) - push_data() - do_work(1) - - -/obj/item/integrated_circuit/input/tank_slot/ask_for_input(mob/user) - attack_self(user) - -/obj/item/integrated_circuit/input/tank_slot/attack_self(mob/user) - //Check if no tank attached - if(!current_tank) - to_chat(user, "There is currently no tank attached.") - return - - //Remove tank and put in user's hands/location - to_chat(user, "You take [current_tank] out of the tank slot.") - user.put_in_hands(current_tank) - current_tank = null - - //Remove tank reference - push_pressure() - set_pin_data(IC_OUTPUT, 2, null) - push_data() - do_work(2) - -/obj/item/integrated_circuit/input/tank_slot/do_work() - set_pin_data(IC_OUTPUT, 2, WEAKREF(current_tank)) - push_data() - -/obj/item/integrated_circuit/input/tank_slot/proc/push_pressure() - if(!current_tank) - set_pin_data(IC_OUTPUT, 1, 0) - return - - var/datum/gas_mixture/tank_air = current_tank.return_air() - if(!tank_air) - set_pin_data(IC_OUTPUT, 1, 0) - return - - set_pin_data(IC_OUTPUT, 1, tank_air.return_pressure()) - push_data() - - -#undef SOURCE_TO_TARGET -#undef TARGET_TO_SOURCE -#undef PUMP_EFFICIENCY -#undef TANK_FAILURE_PRESSURE -#undef PUMP_MAX_PRESSURE -#undef PUMP_MAX_VOLUME +#define SOURCE_TO_TARGET 0 +#define TARGET_TO_SOURCE 1 +#define PUMP_EFFICIENCY 0.6 +#define TANK_FAILURE_PRESSURE (ONE_ATMOSPHERE*25) +#define PUMP_MAX_VOLUME 100 + + +/obj/item/integrated_circuit/atmospherics + category_text = "Atmospherics" + cooldown_per_use = 2 SECONDS + complexity = 10 + size = 7 + outputs = list( + "self reference" = IC_PINTYPE_SELFREF, + "pressure" = IC_PINTYPE_NUMBER + ) + var/datum/gas_mixture/air_contents + var/volume = 2 //Pretty small, I know + +/obj/item/integrated_circuit/atmospherics/Initialize() + air_contents = new(volume) + ..() + +/obj/item/integrated_circuit/atmospherics/return_air() + return air_contents + +//Check if the gas container is adjacent and of the right type +/obj/item/integrated_circuit/atmospherics/proc/check_gassource(atom/gasholder) + if(!gasholder) + return FALSE + if(!gasholder.Adjacent(get_object())) + return FALSE + if(!istype(gasholder, /obj/item/tank) && !istype(gasholder, /obj/machinery/portable_atmospherics) && !istype(gasholder, /obj/item/integrated_circuit/atmospherics)) + return FALSE + return TRUE + +//Needed in circuits where source and target types differ +/obj/item/integrated_circuit/atmospherics/proc/check_gastarget(atom/gasholder) + return check_gassource(gasholder) + + +// - gas pump - // **works** +/obj/item/integrated_circuit/atmospherics/pump + name = "gas pump" + desc = "Somehow moves gases between two tanks, canisters, and other gas containers." + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + inputs = list( + "source" = IC_PINTYPE_REF, + "target" = IC_PINTYPE_REF, + "target pressure" = IC_PINTYPE_NUMBER + ) + activators = list( + "transfer" = IC_PINTYPE_PULSE_IN, + "on transfer" = IC_PINTYPE_PULSE_OUT + ) + var/direction = SOURCE_TO_TARGET + var/target_pressure = PUMP_MAX_PRESSURE + power_draw_per_use = 20 + +/obj/item/integrated_circuit/atmospherics/pump/Initialize() + air_contents = new(volume) + extended_desc += " Use negative pressure to move air from target to source. \ + Note that only part of the gas is moved on each transfer, \ + so multiple activations will be necessary to achieve target pressure. \ + The pressure limit for circuit pumps is [round(PUMP_MAX_PRESSURE)] kPa." + . = ..() + +// This proc gets the direction of the gas flow depending on its value, by calling update target +/obj/item/integrated_circuit/atmospherics/pump/on_data_written() + var/amt = get_pin_data(IC_INPUT, 3) + update_target(amt) + +/obj/item/integrated_circuit/atmospherics/pump/proc/update_target(new_amount) + if(!isnum(new_amount)) + new_amount = 0 + // See in which direction the gas moves + if(new_amount < 0) + direction = TARGET_TO_SOURCE + else + direction = SOURCE_TO_TARGET + target_pressure = min(round(PUMP_MAX_PRESSURE),abs(new_amount)) + +/obj/item/integrated_circuit/atmospherics/pump/do_work() + var/obj/source = get_pin_data_as_type(IC_INPUT, 1, /obj) + var/obj/target = get_pin_data_as_type(IC_INPUT, 2, /obj) + perform_magic(source, target) + activate_pin(2) + +/obj/item/integrated_circuit/atmospherics/pump/proc/perform_magic(atom/source, atom/target) + //Check if both atoms are of the right type: atmos circuits/gas tanks/canisters. If one is the same, use the circuit var + if(!check_gassource(source)) + source = src + + if(!check_gastarget(target)) + target = src + + // If both are the same, this whole proc would do nothing and just waste performance + if(source == target) + return + + var/datum/gas_mixture/source_air = source.return_air() + var/datum/gas_mixture/target_air = target.return_air() + + if(!source_air || !target_air) + return + + // Swapping both source and target + if(direction == TARGET_TO_SOURCE) + var/temp = source_air + source_air = target_air + target_air = temp + + // If what you are pumping is empty, use the circuit's storage + if(source_air.total_moles() <= 0) + source_air = air_contents + + // Move gas from one place to another + move_gas(source_air, target_air) + air_update_turf() + +/obj/item/integrated_circuit/atmospherics/pump/proc/move_gas(datum/gas_mixture/source_air, datum/gas_mixture/target_air) + + // No moles = nothing to pump + if(source_air.total_moles() <= 0 || target_air.return_pressure() >= PUMP_MAX_PRESSURE) + return + + // Negative Kelvin temperatures should never happen and if they do, normalize them + if(source_air.temperature < TCMB) + source_air.temperature = TCMB + + var/pressure_delta = target_pressure - target_air.return_pressure() + if(pressure_delta > 0.1) + var/transfer_moles = (pressure_delta*target_air.volume/(source_air.temperature * R_IDEAL_GAS_EQUATION))*PUMP_EFFICIENCY + var/datum/gas_mixture/removed = source_air.remove(transfer_moles) + target_air.merge(removed) + + +// - volume pump - // **Works** +/obj/item/integrated_circuit/atmospherics/pump/volume + name = "volume pump" + desc = "Moves gases between two tanks, canisters, and other gas containers by using their volume, up to 200 L/s." + extended_desc = " Use negative volume to move air from target to source. Note that only part of the gas is moved on each transfer. Its maximum pumping volume is capped at 1000kPa." + + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + inputs = list( + "source" = IC_PINTYPE_REF, + "target" = IC_PINTYPE_REF, + "transfer volume" = IC_PINTYPE_NUMBER + ) + activators = list( + "transfer" = IC_PINTYPE_PULSE_IN, + "on transfer" = IC_PINTYPE_PULSE_OUT + ) + direction = SOURCE_TO_TARGET + var/transfer_rate = PUMP_MAX_VOLUME + power_draw_per_use = 20 + +/obj/item/integrated_circuit/atmospherics/pump/volume/update_target(new_amount) + if(!isnum(new_amount)) + new_amount = 0 + // See in which direction the gas moves + if(new_amount < 0) + direction = TARGET_TO_SOURCE + else + direction = SOURCE_TO_TARGET + target_pressure = min(PUMP_MAX_VOLUME,abs(new_amount)) + +/obj/item/integrated_circuit/atmospherics/pump/volume/move_gas(datum/gas_mixture/source_air, datum/gas_mixture/target_air) + // No moles = nothing to pump + if(source_air.total_moles() <= 0) + return + + // Negative Kelvin temperatures should never happen and if they do, normalize them + if(source_air.temperature < TCMB) + source_air.temperature = TCMB + + if((source_air.return_pressure() < 0.01) || (target_air.return_pressure() >= PUMP_MAX_PRESSURE)) + return + + //The second part of the min caps the pressure built by the volume pumps to the max pump pressure + var/transfer_ratio = min(transfer_rate,target_air.volume*PUMP_MAX_PRESSURE/source_air.return_pressure())/source_air.volume + + var/datum/gas_mixture/removed = source_air.remove_ratio(transfer_ratio * PUMP_EFFICIENCY) + + target_air.merge(removed) + + +// - gas vent - // **works** +/obj/item/integrated_circuit/atmospherics/pump/vent + name = "gas vent" + extended_desc = "Use negative volume to move air from target to environment. Note that only part of the gas is moved on each transfer. Unlike the gas pump, this one keeps pumping even further to pressures of 9000 pKa and it is not advised to use it on tank circuits." + desc = "Moves gases between the environment and adjacent gas containers." + inputs = list( + "container" = IC_PINTYPE_REF, + "target pressure" = IC_PINTYPE_NUMBER + ) + +/obj/item/integrated_circuit/atmospherics/pump/vent/on_data_written() + var/amt = get_pin_data(IC_INPUT, 2) + update_target(amt) + +/obj/item/integrated_circuit/atmospherics/pump/vent/do_work() + var/turf/source = get_turf(src) + var/obj/target = get_pin_data_as_type(IC_INPUT, 1, /obj) + perform_magic(source, target) + activate_pin(2) + +/obj/item/integrated_circuit/atmospherics/pump/vent/check_gastarget(atom/gasholder) + if(!gasholder) + return FALSE + if(!gasholder.Adjacent(get_object())) + return FALSE + if(!istype(gasholder, /obj/item/tank) && !istype(gasholder, /obj/machinery/portable_atmospherics) && !istype(gasholder, /obj/item/integrated_circuit/atmospherics)) + return FALSE + return TRUE + + +/obj/item/integrated_circuit/atmospherics/pump/vent/check_gassource(atom/target) + if(!target) + return FALSE + if(!istype(target, /turf)) + return FALSE + return TRUE + + +// - integrated connector - // Can connect and disconnect properly +/obj/item/integrated_circuit/atmospherics/connector + name = "integrated connector" + desc = "Creates an airtight seal with standard connectors found on the floor, \ + allowing the assembly to exchange gases with a pipe network." + extended_desc = "This circuit will automatically attempt to locate and connect to ports on the floor beneath it when activated. \ + You must set a target before connecting." + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + inputs = list( + "target" = IC_PINTYPE_REF + ) + activators = list( + "toggle connection" = IC_PINTYPE_PULSE_IN, + "on connected" = IC_PINTYPE_PULSE_OUT, + "on connection failed" = IC_PINTYPE_PULSE_OUT, + "on disconnected" = IC_PINTYPE_PULSE_OUT + ) + + var/obj/machinery/atmospherics/components/unary/portables_connector/connector + +/obj/item/integrated_circuit/atmospherics/connector/Initialize() + air_contents = new(volume) + START_PROCESSING(SSobj, src) + . = ..() + +//Sucks up the gas from the connector +/obj/item/integrated_circuit/atmospherics/connector/process() + set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) + +/obj/item/integrated_circuit/atmospherics/connector/check_gassource(atom/gasholder) + if(!gasholder) + return FALSE + if(!istype(gasholder,/obj/machinery/atmospherics/components/unary/portables_connector)) + return FALSE + return TRUE + +//If the assembly containing this is moved from the tile the connector pipe is in, the connection breaks +/obj/item/integrated_circuit/atmospherics/connector/ext_moved() + if(connector) + if(get_dist(get_object(), connector) > 0) + // The assembly is set as connected device and the connector handles the rest + connector.connected_device = null + connector = null + activate_pin(4) + +/obj/item/integrated_circuit/atmospherics/connector/do_work() + // If there is a connection, disconnect + if(connector) + connector.connected_device = null + connector = null + activate_pin(4) + return + + var/obj/machinery/atmospherics/components/unary/portables_connector/PC = locate() in get_turf(src) + // If no connector can't connect + if(!PC) + activate_pin(3) + return + connector = PC + connector.connected_device = src + activate_pin(2) + +// Required for making the connector port script work +obj/item/integrated_circuit/atmospherics/connector/portableConnectorReturnAir() + return air_contents + + +// - gas filter - // **works** +/obj/item/integrated_circuit/atmospherics/pump/filter + name = "gas filter" + desc = "Filters one gas out of a mixture." + complexity = 20 + size = 8 + spawn_flags = IC_SPAWN_RESEARCH + inputs = list( + "source" = IC_PINTYPE_REF, + "filtered output" = IC_PINTYPE_REF, + "contaminants output" = IC_PINTYPE_REF, + "wanted gases" = IC_PINTYPE_LIST, + "target pressure" = IC_PINTYPE_NUMBER + ) + power_draw_per_use = 30 + +/obj/item/integrated_circuit/atmospherics/pump/filter/on_data_written() + var/amt = get_pin_data(IC_INPUT, 5) + target_pressure = CLAMP(amt, 0, PUMP_MAX_PRESSURE) + +/obj/item/integrated_circuit/atmospherics/pump/filter/do_work() + activate_pin(2) + var/obj/source = get_pin_data_as_type(IC_INPUT, 1, /obj) + var/obj/filtered = get_pin_data_as_type(IC_INPUT, 2, /obj) + var/obj/contaminants = get_pin_data_as_type(IC_INPUT, 3, /obj) + + var/wanted = get_pin_data(IC_INPUT, 4) + + // If there is no filtered output, this whole thing makes no sense + if(!check_gassource(filtered)) + return + + var/datum/gas_mixture/filtered_air = filtered.return_air() + if(!filtered_air) + return + + // If no source is set, the source is possibly this circuit's content + if(!check_gassource(source)) + source = src + var/datum/gas_mixture/source_air = source.return_air() + + //No source air: source is this circuit + if(!source_air) + source_air = air_contents + + // If no filtering tank is set, filter through itself + if(!check_gassource(contaminants)) + contaminants = src + var/datum/gas_mixture/contaminated_air = contaminants.return_air() + + //If there is no gas mixture datum for unfiltered, pump the contaminants back into the circuit + if(!contaminated_air) + contaminated_air = air_contents + + if(contaminated_air.return_pressure() >= PUMP_MAX_PRESSURE || filtered_air.return_pressure() >= PUMP_MAX_PRESSURE) + return + + var/pressure_delta = target_pressure - contaminated_air.return_pressure() + var/transfer_moles + + //Negative Kelvins are an anomaly and should be normalized if encountered + if(source_air.temperature < TCMB) + source_air.temperature = TCMB + + transfer_moles = (pressure_delta*contaminated_air.volume/(source_air.temperature * R_IDEAL_GAS_EQUATION))*PUMP_EFFICIENCY + + //If there is nothing to transfer, just return + if(transfer_moles <= 0) + return + + //This is the var that holds the currently filtered part of the gas + var/datum/gas_mixture/removed = source_air.remove(transfer_moles) + if(!removed) + return + + //This is the gas that will be moved from source to filtered + var/datum/gas_mixture/filtered_out = new + + for(var/filtered_gas in removed.gases) + //Get the name of the gas and see if it is in the list + if(GLOB.meta_gas_names[filtered_gas] in wanted) + //The gas that is put in all the filtered out gases + filtered_out.temperature = removed.temperature + filtered_out.gases[filtered_gas] = removed.gases[filtered_gas] + + //The filtered out gas is entirely removed from the currently filtered gases + removed.gases[filtered_gas] = 0 + GAS_GARBAGE_COLLECT(removed.gases) + + //Check if the pressure is high enough to put stuff in filtered, or else just put it back in the source + var/datum/gas_mixture/target = (filtered_air.return_pressure() < target_pressure ? filtered_air : source_air) + target.merge(filtered_out) + contaminated_air.merge(removed) + + +/obj/item/integrated_circuit/atmospherics/pump/filter/Initialize() + air_contents = new(volume) + . = ..() + extended_desc = "Remember to properly spell and capitalize the filtered gas name. \ + Note that only part of the gas is moved on each transfer, \ + so multiple activations will be necessary to achieve target pressure. \ + The pressure limit for circuit pumps is [round(PUMP_MAX_PRESSURE)] kPa." + + +// - gas mixer - // **works** +/obj/item/integrated_circuit/atmospherics/pump/mixer + name = "gas mixer" + desc = "Mixes 2 different types of gases." + complexity = 20 + size = 8 + spawn_flags = IC_SPAWN_RESEARCH + inputs = list( + "first source" = IC_PINTYPE_REF, + "second source" = IC_PINTYPE_REF, + "output" = IC_PINTYPE_REF, + "first source percentage" = IC_PINTYPE_NUMBER, + "target pressure" = IC_PINTYPE_NUMBER + ) + power_draw_per_use = 30 + +/obj/item/integrated_circuit/atmospherics/pump/mixer/do_work() + activate_pin(2) + var/obj/source_1 = get_pin_data(IC_INPUT, 1) + var/obj/source_2 = get_pin_data(IC_INPUT, 2) + var/obj/gas_output = get_pin_data(IC_INPUT, 3) + if(!check_gassource(source_1)) + source_1 = src + + if(!check_gassource(source_2)) + source_2 = src + + if(!check_gassource(gas_output)) + gas_output = src + + if(source_1 == gas_output || source_2 == gas_output) + return + + var/datum/gas_mixture/source_1_gases = source_1.return_air() + var/datum/gas_mixture/source_2_gases = source_2.return_air() + var/datum/gas_mixture/output_gases = gas_output.return_air() + + if(!source_1_gases || !source_2_gases || !output_gases) + return + + if(output_gases.return_pressure() >= PUMP_MAX_PRESSURE) + return + + if(source_1_gases.return_pressure() <= 0 || source_2_gases.return_pressure() <= 0) + return + + //This calculates how much should be sent + var/gas_percentage = round(max(min(get_pin_data(IC_INPUT, 4),100),0) / 100) + + //Basically: number of moles = percentage of pressure filled up * efficiency coefficient * (pressure from both gases * volume of output) / (R * Temperature) + var/transfer_moles = (get_pin_data(IC_INPUT, 5) / max(1,output_gases.return_pressure())) * PUMP_EFFICIENCY * (source_1_gases.return_pressure() * gas_percentage + source_2_gases.return_pressure() * (1 - gas_percentage)) * output_gases.volume/ (R_IDEAL_GAS_EQUATION * max(output_gases.temperature,TCMB)) + + + if(transfer_moles <= 0) + return + + var/datum/gas_mixture/mix = source_1_gases.remove(transfer_moles * gas_percentage) + output_gases.merge(mix) + mix = source_2_gases.remove(transfer_moles * (1-gas_percentage)) + output_gases.merge(mix) + + +// - integrated tank - // **works** +/obj/item/integrated_circuit/atmospherics/tank + name = "integrated tank" + desc = "A small tank for the storage of gases." + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + size = 4 + activators = list( + "push ref" = IC_PINTYPE_PULSE_IN + ) + volume = 3 //emergency tank sized + var/broken = FALSE + +/obj/item/integrated_circuit/atmospherics/tank/Initialize() + air_contents = new(volume) + START_PROCESSING(SSobj, src) + extended_desc = "Take care not to pressurize it above [round(TANK_FAILURE_PRESSURE)] kPa, or else it will break." + . = ..() + +/obj/item/integrated_circuit/atmospherics/tank/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/integrated_circuit/atmospherics/tank/do_work() + set_pin_data(IC_OUTPUT, 1, WEAKREF(src)) + push_data() + +/obj/item/integrated_circuit/atmospherics/tank/process() + var/tank_pressure = air_contents.return_pressure() + set_pin_data(IC_OUTPUT, 2, tank_pressure) + push_data() + + //Check if tank broken + if(!broken && tank_pressure > TANK_FAILURE_PRESSURE) + broken = TRUE + to_chat(view(2),"The [name] ruptures, releasing its gases!") + if(broken) + release() + +/obj/item/integrated_circuit/atmospherics/tank/proc/release() + if(air_contents.total_moles() > 0) + playsound(loc, 'sound/effects/spray.ogg', 10, 1, -3) + var/datum/gas_mixture/expelled_gas = air_contents.remove(air_contents.total_moles()) + var/turf/current_turf = get_turf(src) + var/datum/gas_mixture/exterior_gas + if(!current_turf) + return + + exterior_gas = current_turf.return_air() + exterior_gas.merge(expelled_gas) + + +// - large integrated tank - // **works** +/obj/item/integrated_circuit/atmospherics/tank/large + name = "large integrated tank" + desc = "A less small tank for the storage of gases." + volume = 9 + size = 12 + spawn_flags = IC_SPAWN_RESEARCH + + +// - freezer tank - // **works** +/obj/item/integrated_circuit/atmospherics/tank/freezer + name = "freezer tank" + desc = "Cools the gas it contains to a preset temperature." + volume = 6 + size = 8 + inputs = list( + "target temperature" = IC_PINTYPE_NUMBER, + "on" = IC_PINTYPE_BOOLEAN + ) + inputs_default = list("1" = 300) + spawn_flags = IC_SPAWN_RESEARCH + var/temperature = 293.15 + var/heater_coefficient = 0.1 + +/obj/item/integrated_circuit/atmospherics/tank/freezer/on_data_written() + temperature = max(73.15,min(293.15,get_pin_data(IC_INPUT, 1))) + if(get_pin_data(IC_INPUT, 2)) + power_draw_idle = 30 + else + power_draw_idle = 0 + +/obj/item/integrated_circuit/atmospherics/tank/freezer/process() + var/tank_pressure = air_contents.return_pressure() + set_pin_data(IC_OUTPUT, 2, tank_pressure) + push_data() + + //Cool the tank if the power is on and the temp is above + if(!power_draw_idle || air_contents.temperature < temperature) + return + + air_contents.temperature = max(73.15,air_contents.temperature - (air_contents.temperature - temperature) * heater_coefficient) + + +// - heater tank - // **works** +/obj/item/integrated_circuit/atmospherics/tank/freezer/heater + name = "heater tank" + desc = "Heats the gas it contains to a preset temperature." + volume = 6 + inputs = list( + "target temperature" = IC_PINTYPE_NUMBER, + "on" = IC_PINTYPE_BOOLEAN + ) + spawn_flags = IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/atmospherics/tank/freezer/heater/on_data_written() + temperature = max(293.15,min(573.15,get_pin_data(IC_INPUT, 1))) + if(get_pin_data(IC_INPUT, 2)) + power_draw_idle = 30 + else + power_draw_idle = 0 + +/obj/item/integrated_circuit/atmospherics/tank/freezer/heater/process() + var/tank_pressure = air_contents.return_pressure() + set_pin_data(IC_OUTPUT, 2, tank_pressure) + push_data() + + //Heat the tank if the power is on or its temperature is below what is set + if(!power_draw_idle || air_contents.temperature > temperature) + return + + air_contents.temperature = min(573.15,air_contents.temperature + (temperature - air_contents.temperature) * heater_coefficient) + + +// - atmospheric cooler - // **works** +/obj/item/integrated_circuit/atmospherics/cooler + name = "atmospheric cooler circuit" + desc = "Cools the air around it." + volume = 6 + size = 13 + spawn_flags = IC_SPAWN_RESEARCH + inputs = list( + "target temperature" = IC_PINTYPE_NUMBER, + "on" = IC_PINTYPE_BOOLEAN + ) + var/temperature = 293.15 + var/heater_coefficient = 0.1 + +/obj/item/integrated_circuit/atmospherics/cooler/Initialize() + air_contents = new(volume) + START_PROCESSING(SSobj, src) + . = ..() + +/obj/item/integrated_circuit/atmospherics/cooler/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/integrated_circuit/atmospherics/cooler/on_data_written() + temperature = max(243.15,min(293.15,get_pin_data(IC_INPUT, 1))) + if(get_pin_data(IC_INPUT, 2)) + power_draw_idle = 30 + else + power_draw_idle = 0 + +/obj/item/integrated_circuit/atmospherics/cooler/process() + set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) + push_data() + + + //Get the turf you're on and its gas mixture + var/turf/current_turf = get_turf(src) + if(!current_turf) + return + + var/datum/gas_mixture/turf_air = current_turf.return_air() + if(!power_draw_idle || turf_air.temperature < temperature) + return + + //Cool the gas + turf_air.temperature = max(243.15,turf_air.temperature - (turf_air.temperature - temperature) * heater_coefficient) + + +// - atmospheric heater - // **works** +/obj/item/integrated_circuit/atmospherics/cooler/heater + name = "atmospheric heater circuit" + desc = "Heats the air around it." + +/obj/item/integrated_circuit/atmospherics/cooler/heater/on_data_written() + temperature = max(293.15,min(323.15,get_pin_data(IC_INPUT, 1))) + if(get_pin_data(IC_INPUT, 2)) + power_draw_idle = 30 + else + power_draw_idle = 0 + +/obj/item/integrated_circuit/atmospherics/cooler/heater/process() + set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) + push_data() + + //Get the turf and its air mixture + var/turf/current_turf = get_turf(src) + if(!current_turf) + return + + var/datum/gas_mixture/turf_air = current_turf.return_air() + if(!power_draw_idle || turf_air.temperature > temperature) + return + + //Heat the gas + turf_air.temperature = min(323.15,turf_air.temperature + (temperature - turf_air.temperature) * heater_coefficient) + + +// - tank slot - // **works** +/obj/item/integrated_circuit/input/tank_slot + category_text = "Atmospherics" + cooldown_per_use = 1 + name = "tank slot" + desc = "Lets you add a tank to your assembly and remove it even when the assembly is closed." + extended_desc = "It can help you extract gases easier." + complexity = 25 + size = 30 + inputs = list() + outputs = list( + "pressure used" = IC_PINTYPE_NUMBER, + "current tank" = IC_PINTYPE_REF + ) + activators = list( + "push ref" = IC_PINTYPE_PULSE_IN, + "on insert" = IC_PINTYPE_PULSE_OUT, + "on remove" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + can_be_asked_input = TRUE + demands_object_input = TRUE + can_input_object_when_closed = TRUE + + var/obj/item/tank/internals/current_tank + +/obj/item/integrated_circuit/input/tank_slot/Initialize() + START_PROCESSING(SSobj, src) + . = ..() + +/obj/item/integrated_circuit/input/tank_slot/process() + push_pressure() + +/obj/item/integrated_circuit/input/tank_slot/attackby(var/obj/item/tank/internals/I, var/mob/living/user) + //Check if it truly is a tank + if(!istype(I,/obj/item/tank/internals)) + to_chat(user,"The [I.name] doesn't seem to fit in here.") + return + + //Check if there is no other tank already inside + if(current_tank) + to_chat(user,"There is already a gas tank inside.") + return + + //The current tank is the one we just attached, its location is inside the circuit + current_tank = I + user.transferItemToLoc(I,src) + to_chat(user,"You put the [I.name] inside the tank slot.") + + //Set the pin to a weak reference of the current tank + push_pressure() + set_pin_data(IC_OUTPUT, 2, WEAKREF(current_tank)) + push_data() + do_work(1) + + +/obj/item/integrated_circuit/input/tank_slot/ask_for_input(mob/user) + attack_self(user) + +/obj/item/integrated_circuit/input/tank_slot/attack_self(mob/user) + //Check if no tank attached + if(!current_tank) + to_chat(user, "There is currently no tank attached.") + return + + //Remove tank and put in user's hands/location + to_chat(user, "You take [current_tank] out of the tank slot.") + user.put_in_hands(current_tank) + current_tank = null + + //Remove tank reference + push_pressure() + set_pin_data(IC_OUTPUT, 2, null) + push_data() + do_work(2) + +/obj/item/integrated_circuit/input/tank_slot/do_work() + set_pin_data(IC_OUTPUT, 2, WEAKREF(current_tank)) + push_data() + +/obj/item/integrated_circuit/input/tank_slot/proc/push_pressure() + if(!current_tank) + set_pin_data(IC_OUTPUT, 1, 0) + return + + var/datum/gas_mixture/tank_air = current_tank.return_air() + if(!tank_air) + set_pin_data(IC_OUTPUT, 1, 0) + return + + set_pin_data(IC_OUTPUT, 1, tank_air.return_pressure()) + push_data() + + +#undef SOURCE_TO_TARGET +#undef TARGET_TO_SOURCE +#undef PUMP_EFFICIENCY +#undef TANK_FAILURE_PRESSURE +#undef PUMP_MAX_PRESSURE +#undef PUMP_MAX_VOLUME diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm index ce2dce9053..6a4221479d 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 - display_order = JOB_DISPLAY_ORDER_ASSISTANT - dresscodecompliant = FALSE - -/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, visualsOnly = FALSE, client/preference_source) - ..() - var/suited = !preference_source || preference_source.prefs.jumpsuit_style == PREF_SUIT - if (CONFIG_GET(flag/grey_assistants)) - if(suited) - uniform = /obj/item/clothing/under/color/grey - else - uniform = /obj/item/clothing/under/skirt/color/grey - else - if(suited) - uniform = /obj/item/clothing/under/color/random - else - uniform = /obj/item/clothing/under/skirt/color/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 + display_order = JOB_DISPLAY_ORDER_ASSISTANT + dresscodecompliant = FALSE + +/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, visualsOnly = FALSE, client/preference_source) + ..() + var/suited = !preference_source || preference_source.prefs.jumpsuit_style == PREF_SUIT + if (CONFIG_GET(flag/grey_assistants)) + if(suited) + uniform = /obj/item/clothing/under/color/grey + else + uniform = /obj/item/clothing/under/skirt/color/grey + else + if(suited) + uniform = /obj/item/clothing/under/color/random + else + uniform = /obj/item/clothing/under/skirt/color/random diff --git a/code/modules/jobs/jobs.dm b/code/modules/jobs/jobs.dm index ca4280a2a1..92fd25a811 100644 --- a/code/modules/jobs/jobs.dm +++ b/code/modules/jobs/jobs.dm @@ -1,126 +1,126 @@ -GLOBAL_LIST_INIT(command_positions, list( - "Captain", - "Head of Personnel", - "Head of Security", - "Chief Engineer", - "Research Director", - "Chief Medical Officer", - "Quartermaster")) - -GLOBAL_LIST_INIT(engineering_positions, list( - "Chief Engineer", - "Station Engineer", - "Atmospheric Technician")) - - -GLOBAL_LIST_INIT(medical_positions, list( - "Chief Medical Officer", - "Medical Doctor", - "Geneticist", - "Virologist", - "Chemist")) - - -GLOBAL_LIST_INIT(science_positions, list( - "Research Director", - "Scientist", - "Roboticist")) - -GLOBAL_LIST_INIT(supply_positions, list( - "Quartermaster", - "Cargo Technician", - "Shaft Miner")) - -GLOBAL_LIST_INIT(civilian_positions, list( - "Head of Personnel", - "Bartender", - "Botanist", - "Cook", - "Janitor", - "Curator", - "Lawyer", - "Chaplain", - "Clown", - "Mime", - "Assistant")) - -GLOBAL_LIST_INIT(security_positions, list( - "Head of Security", - "Warden", - "Detective", - "Security Officer")) - - -GLOBAL_LIST_INIT(nonhuman_positions, list( - "AI", - "Cyborg", - ROLE_PAI)) - -GLOBAL_LIST_INIT(exp_jobsmap, list( - EXP_TYPE_CREW = list("titles" = command_positions | engineering_positions | medical_positions | science_positions | supply_positions | security_positions | civilian_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" = civilian_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 Cafe Visitor"), // Ghost roles - EXP_TYPE_GHOST = list() // dead people, observers -)) -GLOBAL_PROTECT(exp_jobsmap) -GLOBAL_PROTECT(exp_specialmap) - -/proc/guest_jobbans(job) - return ((job in GLOB.command_positions) || (job in GLOB.nonhuman_positions) || (job in GLOB.security_positions)) - - - -//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(input) - var/scrambled_text = "" - var/capitalize = TRUE - - while(length(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(scrambled_text, length(scrambled_text)) - if(ending == ".") - scrambled_text = copytext(scrambled_text,1,length(scrambled_text)-1) - var/input_ending = copytext(input, input_size) - if(input_ending in list("!","?",".")) - scrambled_text += input_ending - - add_to_cache(input, scrambled_text) - - return scrambled_text - -/datum/language/proc/get_spoken_verb(msg_end) - switch(msg_end) - if("!") - return exclaim_verb - if("?") - return ask_verb - return speech_verb - -#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/speech_verb = "says" // 'says', 'hisses', 'farts'. + var/ask_verb = "asks" // Used when sentence ends in a ? + var/exclaim_verb = "exclaims" // Used when sentence ends in a ! + var/whisper_verb = "whispers" // Optional. When not specified speech_verb + quietly/softly is used instead. + var/list/signlang_verb = list("signs", "gestures") // list of emotes that might be displayed if this language has NONVERBAL or SIGNLANG flags + 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(input) + var/scrambled_text = "" + var/capitalize = TRUE + + while(length(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(scrambled_text, length(scrambled_text)) + if(ending == ".") + scrambled_text = copytext(scrambled_text,1,length(scrambled_text)-1) + var/input_ending = copytext(input, input_size) + if(input_ending in list("!","?",".")) + scrambled_text += input_ending + + add_to_cache(input, scrambled_text) + + return scrambled_text + +/datum/language/proc/get_spoken_verb(msg_end) + switch(msg_end) + if("!") + return exclaim_verb + if("?") + return ask_verb + return speech_verb + +#undef SCRAMBLE_CACHE_LEN diff --git a/code/modules/language/machine.dm b/code/modules/language/machine.dm index 4d88bcb416..b7ef701e30 100644 --- a/code/modules/language/machine.dm +++ b/code/modules/language/machine.dm @@ -1,19 +1,19 @@ -/datum/language/machine - name = "Encoded Audio Language" - desc = "An efficient language of encoded tones developed by synthetics and cyborgs." - speech_verb = "whistles" - ask_verb = "chirps" - exclaim_verb = "whistles loudly" - 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." + speech_verb = "whistles" + ask_verb = "chirps" + exclaim_verb = "whistles loudly" + 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/monkey.dm b/code/modules/language/monkey.dm index 3f19008754..53e598b02b 100644 --- a/code/modules/language/monkey.dm +++ b/code/modules/language/monkey.dm @@ -1,12 +1,12 @@ -/datum/language/monkey - name = "Chimpanzee" - desc = "Ook ook ook." - speech_verb = "chimpers" - ask_verb = "chimpers" - exclaim_verb = "screeches" - 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." + speech_verb = "chimpers" + ask_verb = "chimpers" + exclaim_verb = "screeches" + key = "1" + space_chance = 100 + syllables = list("oop", "aak", "chee", "eek") + default_priority = 80 + + icon_state = "animal" diff --git a/code/modules/language/xenocommon.dm b/code/modules/language/xenocommon.dm index 6684ab31f8..f046ecd34a 100644 --- a/code/modules/language/xenocommon.dm +++ b/code/modules/language/xenocommon.dm @@ -1,11 +1,11 @@ -/datum/language/xenocommon - name = "Xenomorph" - desc = "The common tongue of the xenomorphs." - speech_verb = "hisses" - ask_verb = "hisses" - exclaim_verb = "hisses" - 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." + speech_verb = "hisses" + ask_verb = "hisses" + exclaim_verb = "hisses" + 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 06a988d109..5ff1cbceb1 100644 --- a/code/modules/library/lib_items.dm +++ b/code/modules/library/lib_items.dm @@ -1,352 +1,352 @@ -/* 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 - -/obj/structure/bookcase/examine(mob/user) - . = ..() - if(!anchored) - . += "The bolts on the bottom are unsecured." - if(anchored) - . += "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(istype(I, /obj/item/wrench)) - if(I.use_tool(src, user, 20, volume=50)) - to_chat(user, "You wrench the frame into place.") - anchored = TRUE - state = 1 - if(istype(I, /obj/item/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(istype(I, /obj/item/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(istype(I, /obj/item/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/user) - . = ..() - if(.) - return - if(contents.len) - var/obj/item/book/choice = input("Which book would you like to remove from the shelf?") as null|obj in contents - if(choice) - if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) - 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() - if(contents.len < 5) - icon_state = "book-[contents.len]" - else - icon_state = "book-5" - - -/obj/structure/bookcase/manuals/medical - name = "medical manuals bookcase" - -/obj/structure/bookcase/manuals/medical/Initialize() - . = ..() - new /obj/item/book/manual/wiki/medical_cloning(src) - update_icon() - - -/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 - 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(is_blind(user)) - to_chat(user, "As you are trying to read, you suddenly feel very stupid!") - return - if(ismonkey(user)) - to_chat(user, "You skim through the book but can't comprehend any of it.") - 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(is_blind(user)) - 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) || istype(I, /obj/item/wirecutters)) - 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/libraryconsole/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 + +/obj/structure/bookcase/examine(mob/user) + . = ..() + if(!anchored) + . += "The bolts on the bottom are unsecured." + if(anchored) + . += "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(istype(I, /obj/item/wrench)) + if(I.use_tool(src, user, 20, volume=50)) + to_chat(user, "You wrench the frame into place.") + anchored = TRUE + state = 1 + if(istype(I, /obj/item/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(istype(I, /obj/item/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(istype(I, /obj/item/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/user) + . = ..() + if(.) + return + if(contents.len) + var/obj/item/book/choice = input("Which book would you like to remove from the shelf?") as null|obj in contents + if(choice) + if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) + 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() + if(contents.len < 5) + icon_state = "book-[contents.len]" + else + icon_state = "book-5" + + +/obj/structure/bookcase/manuals/medical + name = "medical manuals bookcase" + +/obj/structure/bookcase/manuals/medical/Initialize() + . = ..() + new /obj/item/book/manual/wiki/medical_cloning(src) + update_icon() + + +/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 + 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(is_blind(user)) + to_chat(user, "As you are trying to read, you suddenly feel very stupid!") + return + if(ismonkey(user)) + to_chat(user, "You skim through the book but can't comprehend any of it.") + 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(is_blind(user)) + 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) || istype(I, /obj/item/wirecutters)) + 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/libraryconsole/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 ef58943731..253ba277e7 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -1,607 +1,607 @@ -/* 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 = null - 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/SQLquery - clockwork = TRUE //it'd look weird - -/obj/machinery/computer/libraryconsole/ui_interact(mob/user) - . = ..() - var/dat = "" // - 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 if(!SQLquery) - dat += "ERROR: Malformed search request. Please contact your system administrator for assistance.
                " - else - dat += "" - dat += "" - - var/datum/DBQuery/query_library_list_books = SSdbcore.NewQuery(SQLquery) - 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(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(..()) - 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 - title = sanitizeSQL(title) - 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" - category = sanitizeSQL(category) - if(href_list["setauthor"]) - var/newauthor = input("Enter an author to search for:") as text|null - if(newauthor) - author = sanitize(newauthor) - else - author = null - author = sanitizeSQL(author) - if(href_list["search"]) - SQLquery = "SELECT author, title, category, id FROM [format_table_name("library")] WHERE isnull(deleted) AND " - if(category == "Any") - SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%'" - else - SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%' AND category='[category]'" - screenstate = 1 - - 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 - -/* - * Cachedbook datum - */ -/datum/cachedbook // Datum used to cache the SQL DB books locally in order to achieve a performance gain. - var/id - var/title - var/author - var/category - -GLOBAL_LIST(cachedbooks) // List of our cached book datums - - -/proc/load_library_db_to_cache() - if(GLOB.cachedbooks) - return - if(!SSdbcore.Connect()) - return - GLOB.cachedbooks = list() - var/datum/DBQuery/query_library_cache = SSdbcore.NewQuery("SELECT id, author, title, category FROM [format_table_name("library")] WHERE isnull(deleted)") - if(!query_library_cache.Execute()) - qdel(query_library_cache) - return - while(query_library_cache.NextRow()) - var/datum/cachedbook/newbook = new() - newbook.id = query_library_cache.item[1] - newbook.author = query_library_cache.item[2] - newbook.title = query_library_cache.item[3] - newbook.category = query_library_cache.item[4] - GLOB.cachedbooks += newbook - qdel(query_library_cache) - - - -#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/libraryconsole/bookmanagement - name = "book inventory management console" - desc = "Librarian's command station." - screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book - verb_say = "beeps" - verb_ask = "beeps" - verb_exclaim = "beeps" - pass_flags = PASSTABLE - 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/list/libcomp_menu - var/page = 1 //current page of the external archives - var/cooldown = 0 - -/obj/machinery/computer/libraryconsole/bookmanagement/proc/build_library_menu() - if(libcomp_menu) - return - load_library_db_to_cache() - if(!GLOB.cachedbooks) - return - libcomp_menu = list("") - - for(var/i in 1 to GLOB.cachedbooks.len) - var/datum/cachedbook/C = GLOB.cachedbooks[i] - var/page = round(i/250)+1 - if (libcomp_menu.len < page) - libcomp_menu.len = page - libcomp_menu[page] = "" - libcomp_menu[page] += "[C.author][C.title][C.category]\[Order\]\n" - -/obj/machinery/computer/libraryconsole/bookmanagement/Initialize() - . = ..() - if(circuit) - circuit.name = "Book Inventory Management Console (Machine Board)" - circuit.build_path = /obj/machinery/computer/libraryconsole/bookmanagement - -/obj/machinery/computer/libraryconsole/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

                " - build_library_menu() - - if(!GLOB.cachedbooks) - dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." - else - dat += "(Order book by SS13BN)

                " - dat += "" - dat += "" - dat += libcomp_menu[CLAMP(page,1,libcomp_menu.len)] - dat += "" - dat += "
                AUTHORTITLECATEGORY
                <<<< >>>>
                " - 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/libraryconsole/bookmanagement/proc/findscanner(viewrange) - for(var/obj/machinery/libraryscanner/S in range(viewrange, get_turf(src))) - return S - return null - -/obj/machinery/computer/libraryconsole/bookmanagement/proc/print_forbidden_lore(mob/user) - var/spook = pick("blood", "brass") - var/turf/T = get_turf(src) - if(spook == "blood") - new /obj/item/melee/cultblade/dagger(T) - else - new /obj/item/clockwork/slab(T) - - 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 [spook == "blood" ? "sinister dagger" : "strange metal tablet"] 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/libraryconsole/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/libraryconsole/bookmanagement/emag_act(mob/user) - . = ..() - if(!density || obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - return TRUE - -/obj/machinery/computer/libraryconsole/bookmanagement/Topic(href, href_list) - 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 = copytext(sanitize(input("Enter the book's title:") as text|null),1,MAX_MESSAGE_LEN) - if(href_list["editmob"]) - buffer_mob = copytext(sanitize(input("Enter the recipient's name:") as text|null),1,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 = copytext(sanitize(input("Enter the author's name: ") as text|null),1,MAX_MESSAGE_LEN) - 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/sqltitle = sanitizeSQL(scanner.cache.name) - var/sqlauthor = sanitizeSQL(scanner.cache.author) - var/sqlcontent = sanitizeSQL(scanner.cache.dat) - var/sqlcategory = sanitizeSQL(upload_category) - var/sqlckey = sanitizeSQL(usr.ckey) - var/msg = "[key_name(usr)] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs" - var/datum/DBQuery/query_library_upload = SSdbcore.NewQuery("INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, datetime, round_id_created) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]', '[sqlckey]', Now(), '[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(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/sqlid = sanitizeSQL(href_list["targetid"]) - if (!SSdbcore.Connect()) - alert("Connection to Archive has been severed. Aborting.") - if(cooldown > world.time) - say("Printer unavailable. Please allow a short time before attempting to print.") - else - cooldown = world.time + PRINTER_COOLDOWN - var/datum/DBQuery/query_library_print = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE id=[sqlid] AND isnull(deleted)") - 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(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_item_state) - B.icon_state = GLOB.bible_icon_state - B.item_state = GLOB.bible_item_state - B.name = GLOB.bible_name - B.deity_name = GLOB.deity - cooldown = world.time + PRINTER_COOLDOWN - else - say("Printer currently unavailable, please wait a moment.") - if(href_list["printposter"]) - if(cooldown < world.time) - new /obj/item/poster/random_official(src.loc) - 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(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(!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 = null + 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/SQLquery + clockwork = TRUE //it'd look weird + +/obj/machinery/computer/libraryconsole/ui_interact(mob/user) + . = ..() + var/dat = "" // + 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 if(!SQLquery) + dat += "ERROR: Malformed search request. Please contact your system administrator for assistance.
                " + else + dat += "" + dat += "" + + var/datum/DBQuery/query_library_list_books = SSdbcore.NewQuery(SQLquery) + 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(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(..()) + 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 + title = sanitizeSQL(title) + 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" + category = sanitizeSQL(category) + if(href_list["setauthor"]) + var/newauthor = input("Enter an author to search for:") as text|null + if(newauthor) + author = sanitize(newauthor) + else + author = null + author = sanitizeSQL(author) + if(href_list["search"]) + SQLquery = "SELECT author, title, category, id FROM [format_table_name("library")] WHERE isnull(deleted) AND " + if(category == "Any") + SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%'" + else + SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%' AND category='[category]'" + screenstate = 1 + + 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 + +/* + * Cachedbook datum + */ +/datum/cachedbook // Datum used to cache the SQL DB books locally in order to achieve a performance gain. + var/id + var/title + var/author + var/category + +GLOBAL_LIST(cachedbooks) // List of our cached book datums + + +/proc/load_library_db_to_cache() + if(GLOB.cachedbooks) + return + if(!SSdbcore.Connect()) + return + GLOB.cachedbooks = list() + var/datum/DBQuery/query_library_cache = SSdbcore.NewQuery("SELECT id, author, title, category FROM [format_table_name("library")] WHERE isnull(deleted)") + if(!query_library_cache.Execute()) + qdel(query_library_cache) + return + while(query_library_cache.NextRow()) + var/datum/cachedbook/newbook = new() + newbook.id = query_library_cache.item[1] + newbook.author = query_library_cache.item[2] + newbook.title = query_library_cache.item[3] + newbook.category = query_library_cache.item[4] + GLOB.cachedbooks += newbook + qdel(query_library_cache) + + + +#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/libraryconsole/bookmanagement + name = "book inventory management console" + desc = "Librarian's command station." + screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book + verb_say = "beeps" + verb_ask = "beeps" + verb_exclaim = "beeps" + pass_flags = PASSTABLE + 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/list/libcomp_menu + var/page = 1 //current page of the external archives + var/cooldown = 0 + +/obj/machinery/computer/libraryconsole/bookmanagement/proc/build_library_menu() + if(libcomp_menu) + return + load_library_db_to_cache() + if(!GLOB.cachedbooks) + return + libcomp_menu = list("") + + for(var/i in 1 to GLOB.cachedbooks.len) + var/datum/cachedbook/C = GLOB.cachedbooks[i] + var/page = round(i/250)+1 + if (libcomp_menu.len < page) + libcomp_menu.len = page + libcomp_menu[page] = "" + libcomp_menu[page] += "[C.author][C.title][C.category]\[Order\]\n" + +/obj/machinery/computer/libraryconsole/bookmanagement/Initialize() + . = ..() + if(circuit) + circuit.name = "Book Inventory Management Console (Machine Board)" + circuit.build_path = /obj/machinery/computer/libraryconsole/bookmanagement + +/obj/machinery/computer/libraryconsole/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

                " + build_library_menu() + + if(!GLOB.cachedbooks) + dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." + else + dat += "(Order book by SS13BN)

                " + dat += "" + dat += "" + dat += libcomp_menu[CLAMP(page,1,libcomp_menu.len)] + dat += "" + dat += "
                AUTHORTITLECATEGORY
                <<<< >>>>
                " + 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/libraryconsole/bookmanagement/proc/findscanner(viewrange) + for(var/obj/machinery/libraryscanner/S in range(viewrange, get_turf(src))) + return S + return null + +/obj/machinery/computer/libraryconsole/bookmanagement/proc/print_forbidden_lore(mob/user) + var/spook = pick("blood", "brass") + var/turf/T = get_turf(src) + if(spook == "blood") + new /obj/item/melee/cultblade/dagger(T) + else + new /obj/item/clockwork/slab(T) + + 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 [spook == "blood" ? "sinister dagger" : "strange metal tablet"] 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/libraryconsole/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/libraryconsole/bookmanagement/emag_act(mob/user) + . = ..() + if(!density || obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + return TRUE + +/obj/machinery/computer/libraryconsole/bookmanagement/Topic(href, href_list) + 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 = copytext(sanitize(input("Enter the book's title:") as text|null),1,MAX_MESSAGE_LEN) + if(href_list["editmob"]) + buffer_mob = copytext(sanitize(input("Enter the recipient's name:") as text|null),1,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 = copytext(sanitize(input("Enter the author's name: ") as text|null),1,MAX_MESSAGE_LEN) + 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/sqltitle = sanitizeSQL(scanner.cache.name) + var/sqlauthor = sanitizeSQL(scanner.cache.author) + var/sqlcontent = sanitizeSQL(scanner.cache.dat) + var/sqlcategory = sanitizeSQL(upload_category) + var/sqlckey = sanitizeSQL(usr.ckey) + var/msg = "[key_name(usr)] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs" + var/datum/DBQuery/query_library_upload = SSdbcore.NewQuery("INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, datetime, round_id_created) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]', '[sqlckey]', Now(), '[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(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/sqlid = sanitizeSQL(href_list["targetid"]) + if (!SSdbcore.Connect()) + alert("Connection to Archive has been severed. Aborting.") + if(cooldown > world.time) + say("Printer unavailable. Please allow a short time before attempting to print.") + else + cooldown = world.time + PRINTER_COOLDOWN + var/datum/DBQuery/query_library_print = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE id=[sqlid] AND isnull(deleted)") + 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(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_item_state) + B.icon_state = GLOB.bible_icon_state + B.item_state = GLOB.bible_item_state + B.name = GLOB.bible_name + B.deity_name = GLOB.deity + cooldown = world.time + PRINTER_COOLDOWN + else + say("Printer currently unavailable, please wait a moment.") + if(href_list["printposter"]) + if(cooldown < world.time) + new /obj/item/poster/random_official(src.loc) + 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(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(!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 8fd12f6157..accd477387 100644 --- a/code/modules/library/random_books.dm +++ b/code/modules/library/random_books.dm @@ -1,87 +1,87 @@ -/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" - var/amount = 1 - var/category = null - -/obj/item/book/random/Initialize() - ..() - create_random_books(amount, src.loc, TRUE, category) - return INITIALIZE_HINT_QDEL - -/obj/item/book/random/triple - amount = 3 - -/obj/structure/bookcase/random - var/category = null - var/book_count = 2 - icon_state = "random_bookcase" - anchored = TRUE - state = 2 - -/obj/structure/bookcase/random/Initialize(mapload) - . = ..() - if(!book_count || !isnum(book_count)) - update_icon() - return - book_count += pick(-1,-1,0,1,1) - create_random_books(book_count, src, FALSE, category) - update_icon() - -/proc/create_random_books(amount = 2, location, fail_loud = FALSE, category = null) - . = list() - if(!isnum(amount) || amount<1) - return - if (!SSdbcore.Connect()) - if(fail_loud || prob(5)) - var/obj/item/paper/P = new(location) - P.info = "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!

                ~" - P.update_icon() - return - if(prob(25)) - category = null - var/c = category? " AND category='[sanitizeSQL(category)]'" :"" - var/datum/DBQuery/query_get_random_books = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE isnull(deleted)[c] GROUP BY title ORDER BY rand() LIMIT [amount];") // isdeleted copyright (c) not me - if(query_get_random_books.Execute()) - while(query_get_random_books.NextRow()) - var/obj/item/book/B = new(location) - . += B - B.author = query_get_random_books.item[2] - B.title = query_get_random_books.item[3] - B.dat = query_get_random_books.item[4] - B.name = "Book: [B.title]" - B.icon_state= "book[rand(1,8)]" - qdel(query_get_random_books) - -/obj/structure/bookcase/random/fiction - name = "bookcase (Fiction)" - category = "Fiction" -/obj/structure/bookcase/random/nonfiction - name = "bookcase (Non-Fiction)" - category = "Non-fiction" -/obj/structure/bookcase/random/religion - name = "bookcase (Religion)" - category = "Religion" -/obj/structure/bookcase/random/adult - name = "bookcase (Adult)" - category = "Adult" - -/obj/structure/bookcase/random/reference - name = "bookcase (Reference)" - category = "Reference" - var/ref_book_prob = 20 - -/obj/structure/bookcase/random/reference/Initialize(mapload) - . = ..() - while(book_count > 0 && prob(ref_book_prob)) - book_count-- - 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" + var/amount = 1 + var/category = null + +/obj/item/book/random/Initialize() + ..() + create_random_books(amount, src.loc, TRUE, category) + return INITIALIZE_HINT_QDEL + +/obj/item/book/random/triple + amount = 3 + +/obj/structure/bookcase/random + var/category = null + var/book_count = 2 + icon_state = "random_bookcase" + anchored = TRUE + state = 2 + +/obj/structure/bookcase/random/Initialize(mapload) + . = ..() + if(!book_count || !isnum(book_count)) + update_icon() + return + book_count += pick(-1,-1,0,1,1) + create_random_books(book_count, src, FALSE, category) + update_icon() + +/proc/create_random_books(amount = 2, location, fail_loud = FALSE, category = null) + . = list() + if(!isnum(amount) || amount<1) + return + if (!SSdbcore.Connect()) + if(fail_loud || prob(5)) + var/obj/item/paper/P = new(location) + P.info = "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!

                ~" + P.update_icon() + return + if(prob(25)) + category = null + var/c = category? " AND category='[sanitizeSQL(category)]'" :"" + var/datum/DBQuery/query_get_random_books = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE isnull(deleted)[c] GROUP BY title ORDER BY rand() LIMIT [amount];") // isdeleted copyright (c) not me + if(query_get_random_books.Execute()) + while(query_get_random_books.NextRow()) + var/obj/item/book/B = new(location) + . += B + B.author = query_get_random_books.item[2] + B.title = query_get_random_books.item[3] + B.dat = query_get_random_books.item[4] + B.name = "Book: [B.title]" + B.icon_state= "book[rand(1,8)]" + qdel(query_get_random_books) + +/obj/structure/bookcase/random/fiction + name = "bookcase (Fiction)" + category = "Fiction" +/obj/structure/bookcase/random/nonfiction + name = "bookcase (Non-Fiction)" + category = "Non-fiction" +/obj/structure/bookcase/random/religion + name = "bookcase (Religion)" + category = "Religion" +/obj/structure/bookcase/random/adult + name = "bookcase (Adult)" + category = "Adult" + +/obj/structure/bookcase/random/reference + name = "bookcase (Reference)" + category = "Reference" + var/ref_book_prob = 20 + +/obj/structure/bookcase/random/reference/Initialize(mapload) + . = ..() + while(book_count > 0 && prob(ref_book_prob)) + book_count-- + new /obj/item/book/manual/random(src) diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm index 8f4aea3925..6e48d0097c 100644 --- a/code/modules/lighting/lighting_source.dm +++ b/code/modules/lighting/lighting_source.dm @@ -1,311 +1,311 @@ -// This is where the fun begins. -// These are the main datums that emit light. - -/datum/light_source - var/atom/top_atom // The atom we're emitting light from (for example a mob if we're from a flashlight that's being held). - var/atom/source_atom // The atom that we belong to. - - var/turf/source_turf // The turf under the above. - var/turf/pixel_turf // The turf the top_atom appears to over. - var/light_power // Intensity of the emitter light. - var/light_range // The range of the emitted light. - var/light_color // The colour of the light, string, decomposed by parse_light_color() - - // Variables for keeping track of the colour. - var/lum_r - var/lum_g - var/lum_b - - // The lumcount values used to apply the light. - var/tmp/applied_lum_r - var/tmp/applied_lum_g - var/tmp/applied_lum_b - - var/list/datum/lighting_corner/effect_str // List used to store how much we're affecting corners. - var/list/turf/affecting_turfs - - var/applied = FALSE // Whether we have applied our light yet or not. - - var/needs_update = LIGHTING_NO_UPDATE // Whether we are queued for an update. - -// Thanks to Lohikar for flinging this tiny bit of code at me, increasing my brain cell count from 1 to 2 in the process. -// This macro will only offset up to 1 tile, but anything with a greater offset is an outlier and probably should handle its own lighting offsets. -// Anything pixelshifted 16px or more will be considered on the next tile. -#define GET_APPROXIMATE_PIXEL_DIR(PX, PY) ((!(PX) ? 0 : ((PX >= 16 ? EAST : (PX <= -16 ? WEST : 0)))) | (!PY ? 0 : (PY >= 16 ? NORTH : (PY <= -16 ? SOUTH : 0)))) -#define UPDATE_APPROXIMATE_PIXEL_TURF var/_mask = GET_APPROXIMATE_PIXEL_DIR(top_atom.pixel_x, top_atom.pixel_y); pixel_turf = _mask ? (get_step(source_turf, _mask) || source_turf) : source_turf - -/datum/light_source/New(var/atom/owner, var/atom/top) - source_atom = owner // Set our new owner. - LAZYADD(source_atom.light_sources, src) - top_atom = top - if (top_atom != source_atom) - LAZYADD(top_atom.light_sources, src) - - source_turf = top_atom - UPDATE_APPROXIMATE_PIXEL_TURF - - light_power = source_atom.light_power - light_range = source_atom.light_range - light_color = source_atom.light_color - - parse_light_color() - - update() - - return ..() - -/datum/light_source/Destroy(force) - remove_lum() - if (source_atom) - LAZYREMOVE(source_atom.light_sources, src) - - if (top_atom) - LAZYREMOVE(top_atom.light_sources, src) - - if (needs_update) - GLOB.lighting_update_lights -= src - - . = ..() - -// Yes this doesn't align correctly on anything other than 4 width tabs. -// If you want it to go switch everybody to elastic tab stops. -// Actually that'd be great if you could! -#define EFFECT_UPDATE(level) \ - if (needs_update == LIGHTING_NO_UPDATE) \ - GLOB.lighting_update_lights += src; \ - if (needs_update < level) \ - needs_update = level; \ - - -// This proc will cause the light source to update the top atom, and add itself to the update queue. -/datum/light_source/proc/update(var/atom/new_top_atom) - // This top atom is different. - if (new_top_atom && new_top_atom != top_atom) - if(top_atom != source_atom && top_atom.light_sources) // Remove ourselves from the light sources of that top atom. - LAZYREMOVE(top_atom.light_sources, src) - - top_atom = new_top_atom - - if (top_atom != source_atom) - LAZYADD(top_atom.light_sources, src) // Add ourselves to the light sources of our new top atom. - - EFFECT_UPDATE(LIGHTING_CHECK_UPDATE) - -// Will force an update without checking if it's actually needed. -/datum/light_source/proc/force_update() - EFFECT_UPDATE(LIGHTING_FORCE_UPDATE) - -// Will cause the light source to recalculate turfs that were removed or added to visibility only. -/datum/light_source/proc/vis_update() - EFFECT_UPDATE(LIGHTING_VIS_UPDATE) - -// Decompile the hexadecimal colour into lumcounts of each perspective. -/datum/light_source/proc/parse_light_color() - if (light_color) - lum_r = GetRedPart (light_color) / 255 - lum_g = GetGreenPart (light_color) / 255 - lum_b = GetBluePart (light_color) / 255 - else - lum_r = 1 - lum_g = 1 - lum_b = 1 - -// Macro that applies light to a new corner. -// It is a macro in the interest of speed, yet not having to copy paste it. -// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line. -// As such this all gets counted as a single line. -// The braces and semicolons are there to be able to do this on a single line. - -//Original lighting falloff calculation. This looks the best out of the three. However, this is also the most expensive. -//#define LUM_FALLOFF(C, T) (1 - CLAMP01(sqrt((C.x - T.x) ** 2 + (C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range))) - -//Cubic lighting falloff. This has the *exact* same range as the original lighting falloff calculation, down to the exact decimal, but it looks a little unnatural due to the harsher falloff and how it's generally brighter across the board. -//#define LUM_FALLOFF(C, T) (1 - CLAMP01((((C.x - T.x) * (C.x - T.x)) + ((C.y - T.y) * (C.y - T.y)) + LIGHTING_HEIGHT) / max(1, light_range*light_range))) - -//Linear lighting falloff. This resembles the original lighting falloff calculation the best, but results in lights having a slightly larger range, which is most noticable with large light sources. This also results in lights being diamond-shaped, fuck. This looks the darkest out of the three due to how lights are brighter closer to the source compared to the original falloff algorithm. This falloff method also does not at all take into account lighting height, as it acts as a flat reduction to light range with this method. -//#define LUM_FALLOFF(C, T) (1 - CLAMP01(((abs(C.x - T.x) + abs(C.y - T.y))) / max(1, light_range+1))) - -//Linear lighting falloff but with an octagonal shape in place of a diamond shape. Lummox JR please add pointer support. -#define GET_LUM_DIST(DISTX, DISTY) (DISTX + DISTY + abs(DISTX - DISTY)*0.4) -#define LUM_FALLOFF(C, T) (1 - CLAMP01(max(GET_LUM_DIST(abs(C.x - T.x), abs(C.y - T.y)),LIGHTING_HEIGHT) / max(1, light_range+1))) - -#define APPLY_CORNER(C) \ - . = LUM_FALLOFF(C, pixel_turf); \ - . *= light_power; \ - var/OLD = effect_str[C]; \ - effect_str[C] = .; \ - \ - C.update_lumcount \ - ( \ - (. * lum_r) - (OLD * applied_lum_r), \ - (. * lum_g) - (OLD * applied_lum_g), \ - (. * lum_b) - (OLD * applied_lum_b) \ - ); - -#define REMOVE_CORNER(C) \ - . = -effect_str[C]; \ - C.update_lumcount \ - ( \ - . * applied_lum_r, \ - . * applied_lum_g, \ - . * applied_lum_b \ - ); - -// This is the define used to calculate falloff. - -/datum/light_source/proc/remove_lum() - applied = FALSE - var/thing - for (thing in affecting_turfs) - var/turf/T = thing - LAZYREMOVE(T.affecting_lights, src) - - affecting_turfs = null - - var/datum/lighting_corner/C - for (thing in effect_str) - C = thing - REMOVE_CORNER(C) - - LAZYREMOVE(C.affecting, src) - - effect_str = null - -/datum/light_source/proc/recalc_corner(var/datum/lighting_corner/C) - LAZYINITLIST(effect_str) - if (effect_str[C]) // Already have one. - REMOVE_CORNER(C) - effect_str[C] = 0 - - APPLY_CORNER(C) - UNSETEMPTY(effect_str) - -/datum/light_source/proc/update_corners() - var/update = FALSE - var/atom/source_atom = src.source_atom - - if (QDELETED(source_atom)) - qdel(src) - return - - if (source_atom.light_power != light_power) - light_power = source_atom.light_power - update = TRUE - - if (source_atom.light_range != light_range) - light_range = source_atom.light_range - update = TRUE - - if (!top_atom) - top_atom = source_atom - update = TRUE - - if (!light_range || !light_power) - qdel(src) - return - - if (isturf(top_atom)) - if (source_turf != top_atom) - source_turf = top_atom - UPDATE_APPROXIMATE_PIXEL_TURF - update = TRUE - else if (top_atom.loc != source_turf) - source_turf = top_atom.loc - UPDATE_APPROXIMATE_PIXEL_TURF - update = TRUE - - if (!isturf(source_turf)) - if (applied) - remove_lum() - return - - if (light_range && light_power && !applied) - update = TRUE - - if (source_atom.light_color != light_color) - light_color = source_atom.light_color - parse_light_color() - update = TRUE - - else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b) - update = TRUE - - if (update) - needs_update = LIGHTING_CHECK_UPDATE - applied = TRUE - else if (needs_update == LIGHTING_CHECK_UPDATE) - return //nothing's changed - - var/list/datum/lighting_corner/corners = list() - var/list/turf/turfs = list() - var/thing - var/datum/lighting_corner/C - var/turf/T - if (source_turf) - var/oldlum = source_turf.luminosity - source_turf.luminosity = CEILING(light_range, 1) - for(T in view(CEILING(light_range, 1), source_turf)) - for (thing in T.get_corners(source_turf)) - C = thing - corners[C] = 0 - turfs += T - source_turf.luminosity = oldlum - - LAZYINITLIST(affecting_turfs) - var/list/L = turfs - affecting_turfs // New turfs, add us to the affecting lights of them. - affecting_turfs += L - for (thing in L) - T = thing - LAZYADD(T.affecting_lights, src) - - L = affecting_turfs - turfs // Now-gone turfs, remove us from the affecting lights. - affecting_turfs -= L - for (thing in L) - T = thing - LAZYREMOVE(T.affecting_lights, src) - - LAZYINITLIST(effect_str) - if (needs_update == LIGHTING_VIS_UPDATE) - for (thing in corners - effect_str) // New corners - C = thing - LAZYADD(C.affecting, src) - if (!C.active) - effect_str[C] = 0 - continue - APPLY_CORNER(C) - else - L = corners - effect_str - for (thing in L) // New corners - C = thing - LAZYADD(C.affecting, src) - if (!C.active) - effect_str[C] = 0 - continue - APPLY_CORNER(C) - - for (thing in corners - L) // Existing corners - C = thing - if (!C.active) - effect_str[C] = 0 - continue - APPLY_CORNER(C) - - L = effect_str - corners - for (thing in L) // Old, now gone, corners. - C = thing - REMOVE_CORNER(C) - LAZYREMOVE(C.affecting, src) - effect_str -= L - - applied_lum_r = lum_r - applied_lum_g = lum_g - applied_lum_b = lum_b - - UNSETEMPTY(effect_str) - UNSETEMPTY(affecting_turfs) - -#undef EFFECT_UPDATE -#undef LUM_FALLOFF -#undef GET_LUM_DIST -#undef REMOVE_CORNER -#undef APPLY_CORNER +// This is where the fun begins. +// These are the main datums that emit light. + +/datum/light_source + var/atom/top_atom // The atom we're emitting light from (for example a mob if we're from a flashlight that's being held). + var/atom/source_atom // The atom that we belong to. + + var/turf/source_turf // The turf under the above. + var/turf/pixel_turf // The turf the top_atom appears to over. + var/light_power // Intensity of the emitter light. + var/light_range // The range of the emitted light. + var/light_color // The colour of the light, string, decomposed by parse_light_color() + + // Variables for keeping track of the colour. + var/lum_r + var/lum_g + var/lum_b + + // The lumcount values used to apply the light. + var/tmp/applied_lum_r + var/tmp/applied_lum_g + var/tmp/applied_lum_b + + var/list/datum/lighting_corner/effect_str // List used to store how much we're affecting corners. + var/list/turf/affecting_turfs + + var/applied = FALSE // Whether we have applied our light yet or not. + + var/needs_update = LIGHTING_NO_UPDATE // Whether we are queued for an update. + +// Thanks to Lohikar for flinging this tiny bit of code at me, increasing my brain cell count from 1 to 2 in the process. +// This macro will only offset up to 1 tile, but anything with a greater offset is an outlier and probably should handle its own lighting offsets. +// Anything pixelshifted 16px or more will be considered on the next tile. +#define GET_APPROXIMATE_PIXEL_DIR(PX, PY) ((!(PX) ? 0 : ((PX >= 16 ? EAST : (PX <= -16 ? WEST : 0)))) | (!PY ? 0 : (PY >= 16 ? NORTH : (PY <= -16 ? SOUTH : 0)))) +#define UPDATE_APPROXIMATE_PIXEL_TURF var/_mask = GET_APPROXIMATE_PIXEL_DIR(top_atom.pixel_x, top_atom.pixel_y); pixel_turf = _mask ? (get_step(source_turf, _mask) || source_turf) : source_turf + +/datum/light_source/New(var/atom/owner, var/atom/top) + source_atom = owner // Set our new owner. + LAZYADD(source_atom.light_sources, src) + top_atom = top + if (top_atom != source_atom) + LAZYADD(top_atom.light_sources, src) + + source_turf = top_atom + UPDATE_APPROXIMATE_PIXEL_TURF + + light_power = source_atom.light_power + light_range = source_atom.light_range + light_color = source_atom.light_color + + parse_light_color() + + update() + + return ..() + +/datum/light_source/Destroy(force) + remove_lum() + if (source_atom) + LAZYREMOVE(source_atom.light_sources, src) + + if (top_atom) + LAZYREMOVE(top_atom.light_sources, src) + + if (needs_update) + GLOB.lighting_update_lights -= src + + . = ..() + +// Yes this doesn't align correctly on anything other than 4 width tabs. +// If you want it to go switch everybody to elastic tab stops. +// Actually that'd be great if you could! +#define EFFECT_UPDATE(level) \ + if (needs_update == LIGHTING_NO_UPDATE) \ + GLOB.lighting_update_lights += src; \ + if (needs_update < level) \ + needs_update = level; \ + + +// This proc will cause the light source to update the top atom, and add itself to the update queue. +/datum/light_source/proc/update(var/atom/new_top_atom) + // This top atom is different. + if (new_top_atom && new_top_atom != top_atom) + if(top_atom != source_atom && top_atom.light_sources) // Remove ourselves from the light sources of that top atom. + LAZYREMOVE(top_atom.light_sources, src) + + top_atom = new_top_atom + + if (top_atom != source_atom) + LAZYADD(top_atom.light_sources, src) // Add ourselves to the light sources of our new top atom. + + EFFECT_UPDATE(LIGHTING_CHECK_UPDATE) + +// Will force an update without checking if it's actually needed. +/datum/light_source/proc/force_update() + EFFECT_UPDATE(LIGHTING_FORCE_UPDATE) + +// Will cause the light source to recalculate turfs that were removed or added to visibility only. +/datum/light_source/proc/vis_update() + EFFECT_UPDATE(LIGHTING_VIS_UPDATE) + +// Decompile the hexadecimal colour into lumcounts of each perspective. +/datum/light_source/proc/parse_light_color() + if (light_color) + lum_r = GetRedPart (light_color) / 255 + lum_g = GetGreenPart (light_color) / 255 + lum_b = GetBluePart (light_color) / 255 + else + lum_r = 1 + lum_g = 1 + lum_b = 1 + +// Macro that applies light to a new corner. +// It is a macro in the interest of speed, yet not having to copy paste it. +// If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line. +// As such this all gets counted as a single line. +// The braces and semicolons are there to be able to do this on a single line. + +//Original lighting falloff calculation. This looks the best out of the three. However, this is also the most expensive. +//#define LUM_FALLOFF(C, T) (1 - CLAMP01(sqrt((C.x - T.x) ** 2 + (C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range))) + +//Cubic lighting falloff. This has the *exact* same range as the original lighting falloff calculation, down to the exact decimal, but it looks a little unnatural due to the harsher falloff and how it's generally brighter across the board. +//#define LUM_FALLOFF(C, T) (1 - CLAMP01((((C.x - T.x) * (C.x - T.x)) + ((C.y - T.y) * (C.y - T.y)) + LIGHTING_HEIGHT) / max(1, light_range*light_range))) + +//Linear lighting falloff. This resembles the original lighting falloff calculation the best, but results in lights having a slightly larger range, which is most noticable with large light sources. This also results in lights being diamond-shaped, fuck. This looks the darkest out of the three due to how lights are brighter closer to the source compared to the original falloff algorithm. This falloff method also does not at all take into account lighting height, as it acts as a flat reduction to light range with this method. +//#define LUM_FALLOFF(C, T) (1 - CLAMP01(((abs(C.x - T.x) + abs(C.y - T.y))) / max(1, light_range+1))) + +//Linear lighting falloff but with an octagonal shape in place of a diamond shape. Lummox JR please add pointer support. +#define GET_LUM_DIST(DISTX, DISTY) (DISTX + DISTY + abs(DISTX - DISTY)*0.4) +#define LUM_FALLOFF(C, T) (1 - CLAMP01(max(GET_LUM_DIST(abs(C.x - T.x), abs(C.y - T.y)),LIGHTING_HEIGHT) / max(1, light_range+1))) + +#define APPLY_CORNER(C) \ + . = LUM_FALLOFF(C, pixel_turf); \ + . *= light_power; \ + var/OLD = effect_str[C]; \ + effect_str[C] = .; \ + \ + C.update_lumcount \ + ( \ + (. * lum_r) - (OLD * applied_lum_r), \ + (. * lum_g) - (OLD * applied_lum_g), \ + (. * lum_b) - (OLD * applied_lum_b) \ + ); + +#define REMOVE_CORNER(C) \ + . = -effect_str[C]; \ + C.update_lumcount \ + ( \ + . * applied_lum_r, \ + . * applied_lum_g, \ + . * applied_lum_b \ + ); + +// This is the define used to calculate falloff. + +/datum/light_source/proc/remove_lum() + applied = FALSE + var/thing + for (thing in affecting_turfs) + var/turf/T = thing + LAZYREMOVE(T.affecting_lights, src) + + affecting_turfs = null + + var/datum/lighting_corner/C + for (thing in effect_str) + C = thing + REMOVE_CORNER(C) + + LAZYREMOVE(C.affecting, src) + + effect_str = null + +/datum/light_source/proc/recalc_corner(var/datum/lighting_corner/C) + LAZYINITLIST(effect_str) + if (effect_str[C]) // Already have one. + REMOVE_CORNER(C) + effect_str[C] = 0 + + APPLY_CORNER(C) + UNSETEMPTY(effect_str) + +/datum/light_source/proc/update_corners() + var/update = FALSE + var/atom/source_atom = src.source_atom + + if (QDELETED(source_atom)) + qdel(src) + return + + if (source_atom.light_power != light_power) + light_power = source_atom.light_power + update = TRUE + + if (source_atom.light_range != light_range) + light_range = source_atom.light_range + update = TRUE + + if (!top_atom) + top_atom = source_atom + update = TRUE + + if (!light_range || !light_power) + qdel(src) + return + + if (isturf(top_atom)) + if (source_turf != top_atom) + source_turf = top_atom + UPDATE_APPROXIMATE_PIXEL_TURF + update = TRUE + else if (top_atom.loc != source_turf) + source_turf = top_atom.loc + UPDATE_APPROXIMATE_PIXEL_TURF + update = TRUE + + if (!isturf(source_turf)) + if (applied) + remove_lum() + return + + if (light_range && light_power && !applied) + update = TRUE + + if (source_atom.light_color != light_color) + light_color = source_atom.light_color + parse_light_color() + update = TRUE + + else if (applied_lum_r != lum_r || applied_lum_g != lum_g || applied_lum_b != lum_b) + update = TRUE + + if (update) + needs_update = LIGHTING_CHECK_UPDATE + applied = TRUE + else if (needs_update == LIGHTING_CHECK_UPDATE) + return //nothing's changed + + var/list/datum/lighting_corner/corners = list() + var/list/turf/turfs = list() + var/thing + var/datum/lighting_corner/C + var/turf/T + if (source_turf) + var/oldlum = source_turf.luminosity + source_turf.luminosity = CEILING(light_range, 1) + for(T in view(CEILING(light_range, 1), source_turf)) + for (thing in T.get_corners(source_turf)) + C = thing + corners[C] = 0 + turfs += T + source_turf.luminosity = oldlum + + LAZYINITLIST(affecting_turfs) + var/list/L = turfs - affecting_turfs // New turfs, add us to the affecting lights of them. + affecting_turfs += L + for (thing in L) + T = thing + LAZYADD(T.affecting_lights, src) + + L = affecting_turfs - turfs // Now-gone turfs, remove us from the affecting lights. + affecting_turfs -= L + for (thing in L) + T = thing + LAZYREMOVE(T.affecting_lights, src) + + LAZYINITLIST(effect_str) + if (needs_update == LIGHTING_VIS_UPDATE) + for (thing in corners - effect_str) // New corners + C = thing + LAZYADD(C.affecting, src) + if (!C.active) + effect_str[C] = 0 + continue + APPLY_CORNER(C) + else + L = corners - effect_str + for (thing in L) // New corners + C = thing + LAZYADD(C.affecting, src) + if (!C.active) + effect_str[C] = 0 + continue + APPLY_CORNER(C) + + for (thing in corners - L) // Existing corners + C = thing + if (!C.active) + effect_str[C] = 0 + continue + APPLY_CORNER(C) + + L = effect_str - corners + for (thing in L) // Old, now gone, corners. + C = thing + REMOVE_CORNER(C) + LAZYREMOVE(C.affecting, src) + effect_str -= L + + applied_lum_r = lum_r + applied_lum_g = lum_g + applied_lum_b = lum_b + + UNSETEMPTY(effect_str) + UNSETEMPTY(affecting_turfs) + +#undef EFFECT_UPDATE +#undef LUM_FALLOFF +#undef GET_LUM_DIST +#undef REMOVE_CORNER +#undef APPLY_CORNER diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm index d88c7543f0..9c578badb1 100644 --- a/code/modules/mapping/ruins.dm +++ b/code/modules/mapping/ruins.dm @@ -1,171 +1,171 @@ -/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) - if(!(istype(new_area, allowed_areas)) || check.flags_1 & NO_RUINS_1) - valid = FALSE - 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/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 = /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) + if(!(istype(new_area, allowed_areas)) || check.flags_1 & NO_RUINS_1) + valid = FALSE + 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/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 = /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/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm index 43fe69d633..b514796bc5 100644 --- a/code/modules/mapping/space_management/space_reservation.dm +++ b/code/modules/mapping/space_management/space_reservation.dm @@ -1,85 +1,85 @@ - -//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 - var/borderturf - -/datum/turf_reservation/transit - turf_type = /turf/open/space/transit - borderturf = /turf/open/space/transit/border - -/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/transit/Release() - for(var/turf/open/space/transit/T in reserved_turfs) - for(var/atom/movable/AM in T) - T.throw_atom(AM) - . = ..() - -/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 - if(borderturf && (T.x == BL.x || T.x == TR.x || T.y == BL.y || T.y == TR.y)) - T.ChangeTurf(borderturf, borderturf) - else - 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 + var/borderturf + +/datum/turf_reservation/transit + turf_type = /turf/open/space/transit + borderturf = /turf/open/space/transit/border + +/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/transit/Release() + for(var/turf/open/space/transit/T in reserved_turfs) + for(var/atom/movable/AM in T) + T.throw_atom(AM) + . = ..() + +/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 + if(borderturf && (T.x == BL.x || T.x == TR.x || T.y == BL.y || T.y == TR.y)) + T.ChangeTurf(borderturf, borderturf) + else + 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 d776628def..cc41682f96 100644 --- a/code/modules/mining/equipment/mining_tools.dm +++ b/code/modules/mining/equipment/mining_tools.dm @@ -1,154 +1,154 @@ -/*****************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 - item_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 - materials = list(MAT_METAL=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") - var/digrange = 1 - -/obj/item/pickaxe/attack_self(mob/user) - if(initial(digrange) > 0) - if(digrange == 0) - digrange = initial(digrange) - toolspeed = initial(toolspeed) - to_chat(user, "You increase the tools dig range, decreasing its mining speed.") - else - digrange = 0 - toolspeed = toolspeed/2 - to_chat(user, "You decrease the tools dig range, increasing its mining speed.") - else - to_chat(user, "Tool does not have a configureable dig range.") - -/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/mini - name = "compact pickaxe" - desc = "A smaller, compact version of the standard pickaxe." - icon_state = "minipick" - force = 10 - throwforce = 7 - slot_flags = ITEM_SLOT_BELT - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=1000) - -/obj/item/pickaxe/silver - name = "silver-plated pickaxe" - icon_state = "spickaxe" - item_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 - materials = list(MAT_SILVER=4000) - -/obj/item/pickaxe/diamond - name = "diamond-tipped pickaxe" - icon_state = "dpickaxe" - item_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 - materials = list(MAT_DIAMOND=4000) - -/obj/item/pickaxe/drill - name = "mining drill" - icon_state = "handdrill" - item_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 - toolspeed = 0.5 - -/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.4 - 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.4 - digrange = 2 - -/obj/item/pickaxe/drill/jackhammer - name = "sonic jackhammer" - icon_state = "jackhammer" - item_state = "jackhammer" - w_class = WEIGHT_CLASS_HUGE - toolspeed = 0.2 //the epitome of powertools. extremely fast mining, laughs at puny walls - usesound = 'sound/weapons/sonic_jackhammer.ogg' - hitsound = 'sound/weapons/sonic_jackhammer.ogg' - desc = "Cracks rocks with sonic blasts, and doubles as a demolition power tool for smashing walls." - digrange = 2 - -/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 = 0.1 //Can only dig ash and thats about it, out classed by the picks and drills no more! - usesound = 'sound/effects/shovel_dig.ogg' - throwforce = 4 - item_state = "shovel" - w_class = WEIGHT_CLASS_NORMAL - materials = list(MAT_METAL=350) - 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" - item_state = "spade" - lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' - toolspeed = 0.5 - force = 5 - throwforce = 7 - materials = list(MAT_METAL=50) - w_class = WEIGHT_CLASS_SMALL +/*****************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 + item_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 + materials = list(MAT_METAL=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") + var/digrange = 1 + +/obj/item/pickaxe/attack_self(mob/user) + if(initial(digrange) > 0) + if(digrange == 0) + digrange = initial(digrange) + toolspeed = initial(toolspeed) + to_chat(user, "You increase the tools dig range, decreasing its mining speed.") + else + digrange = 0 + toolspeed = toolspeed/2 + to_chat(user, "You decrease the tools dig range, increasing its mining speed.") + else + to_chat(user, "Tool does not have a configureable dig range.") + +/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/mini + name = "compact pickaxe" + desc = "A smaller, compact version of the standard pickaxe." + icon_state = "minipick" + force = 10 + throwforce = 7 + slot_flags = ITEM_SLOT_BELT + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=1000) + +/obj/item/pickaxe/silver + name = "silver-plated pickaxe" + icon_state = "spickaxe" + item_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 + materials = list(MAT_SILVER=4000) + +/obj/item/pickaxe/diamond + name = "diamond-tipped pickaxe" + icon_state = "dpickaxe" + item_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 + materials = list(MAT_DIAMOND=4000) + +/obj/item/pickaxe/drill + name = "mining drill" + icon_state = "handdrill" + item_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 + toolspeed = 0.5 + +/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.4 + 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.4 + digrange = 2 + +/obj/item/pickaxe/drill/jackhammer + name = "sonic jackhammer" + icon_state = "jackhammer" + item_state = "jackhammer" + w_class = WEIGHT_CLASS_HUGE + toolspeed = 0.2 //the epitome of powertools. extremely fast mining, laughs at puny walls + usesound = 'sound/weapons/sonic_jackhammer.ogg' + hitsound = 'sound/weapons/sonic_jackhammer.ogg' + desc = "Cracks rocks with sonic blasts, and doubles as a demolition power tool for smashing walls." + digrange = 2 + +/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 = 0.1 //Can only dig ash and thats about it, out classed by the picks and drills no more! + usesound = 'sound/effects/shovel_dig.ogg' + throwforce = 4 + item_state = "shovel" + w_class = WEIGHT_CLASS_NORMAL + materials = list(MAT_METAL=350) + 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" + item_state = "spade" + lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' + toolspeed = 0.5 + force = 5 + throwforce = 7 + materials = list(MAT_METAL=50) + w_class = WEIGHT_CLASS_SMALL diff --git a/code/modules/mining/equipment/regenerative_core.dm b/code/modules/mining/equipment/regenerative_core.dm index 1e4244165f..ed0fc8e9bc 100644 --- a/code/modules/mining/equipment/regenerative_core.dm +++ b/code/modules/mining/equipment/regenerative_core.dm @@ -1,147 +1,147 @@ -/*********************Hivelord stabilizer****************/ -/obj/item/hivelordstabilizer - name = "stabilizing serum" - icon = 'icons/obj/chemical.dmi' - icon_state = "bottle19" - desc = "Inject certain types of monster organs with this stabilizer to preserve their healing powers indefinitely." - w_class = WEIGHT_CLASS_TINY - -/obj/item/hivelordstabilizer/afterattack(obj/item/organ/M, mob/user) - . = ..() - var/obj/item/organ/regenerative_core/C = M - if(!istype(C, /obj/item/organ/regenerative_core)) - to_chat(user, "The stabilizer only works on certain types of monster organs, generally regenerative in nature.") - return ..() - - C.preserved() - to_chat(user, "You inject the [M] with the stabilizer. It will no longer go inert.") - qdel(src) - -/************************Hivelord core*******************/ -/obj/item/organ/regenerative_core - name = "regenerative core" - desc = "All that remains of a hivelord. It can be used to heal completely, but it will rapidly decay into uselessness." - icon_state = "roro core 2" - item_flags = NOBLUDGEON - slot = "hivecore" - force = 0 - actions_types = list(/datum/action/item_action/organ_action/use) - var/inert = 0 - var/preserved = 0 - -/obj/item/organ/regenerative_core/Initialize() - . = ..() - addtimer(CALLBACK(src, .proc/inert_check), 2400) - -/obj/item/organ/regenerative_core/proc/inert_check() - if(!preserved) - go_inert() - -/obj/item/organ/regenerative_core/proc/preserved(implanted = 0) - inert = FALSE - preserved = TRUE - update_icon() - desc = "All that remains of a hivelord. It is preserved, allowing you to use it to heal completely without danger of decay." - if(implanted) - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "implanted")) - else - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "stabilizer")) - -/obj/item/organ/regenerative_core/proc/go_inert() - inert = TRUE - name = "decayed regenerative core" - desc = "All that remains of a hivelord. It has decayed, and is completely useless." - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "inert")) - update_icon() - -/obj/item/organ/regenerative_core/ui_action_click() - if(inert) - to_chat(owner, "[src] breaks down as it tries to activate.") - else - owner.revive(full_heal = 1) - owner.log_message("[owner] used an implanted [src] to heal themselves! Keep fighting, it's just a flesh wound!", LOG_ATTACK, color="green") //Logging for implanted legion core use - qdel(src) - -/obj/item/organ/regenerative_core/on_life() - ..() - if(owner.health < owner.crit_threshold) - ui_action_click() - -/obj/item/organ/regenerative_core/afterattack(atom/target, mob/user, proximity_flag) - . = ..() - if(proximity_flag && ishuman(target)) - var/mob/living/carbon/human/H = target - if(inert) - to_chat(user, "[src] has decayed and can no longer be used to heal.") - return - if(isvamp(user)) - to_chat(user, "[src] breaks down as it fails to heal your unholy self") - return - else - if(H.stat == DEAD) - to_chat(user, "[src] are useless on the dead.") - return - if(H != user) - H.visible_message("[user] forces [H] to apply [src]... [H.p_they()] quickly regenerate all injuries!") - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "other")) - else - to_chat(user, "You start to smear [src] on yourself. It feels and smells disgusting, but you feel amazingly refreshed in mere moments.") - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "self")) - H.revive(full_heal = 1) - qdel(src) - user.log_message("[user] used [src] to heal [H]! Wake the fuck up, Samurai!", LOG_ATTACK, color="green") //Logging for 'old' style legion core use, when clicking on a sprite of yourself or another. - -/obj/item/organ/regenerative_core/attack_self(mob/user) //Knouli's first hack! Allows for the use of the core in hand rather than needing to click on the target, yourself, to selfheal. Its a rip of the proc just above - but skips on distance check and only uses 'user' rather than 'target' - if(ishuman(user)) //Check if user is human, no need for distance check as it's self heal - var/mob/living/carbon/human/H = user //Set H to user rather than target - if(inert) //Inert cores are useless - to_chat(user, "[src] has decayed and can no longer be used to heal.") - return - else //Skip on check if the target to be healed is dead as, if you are dead, you're not going to be able to use it on yourself! - to_chat(user, "You start to smear [src] on yourself. It feels and smells disgusting, but you feel amazingly refreshed in mere moments.") - SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "self")) - H.revive(full_heal = 1) - qdel(src) - H.log_message("[H] used [src] to heal themselves! Making use of Knouli's sexy and intelligent use-in-hand proc!", LOG_ATTACK, color="green") //Logging for 'new' style legion core use, when using the core in-hand. - - -/obj/item/organ/regenerative_core/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE) - . = ..() - if(!preserved && !inert) - preserved(TRUE) - owner.visible_message("[src] stabilizes as it's inserted.") - -/obj/item/organ/regenerative_core/Remove(mob/living/carbon/M, special = 0) - if(!inert && !special) - owner.visible_message("[src] rapidly decays as it's removed.") - go_inert() - return ..() - -/obj/item/organ/regenerative_core/prepare_eat() - return null - -/*************************Legion core********************/ -/obj/item/organ/regenerative_core/legion - desc = "A strange rock that crackles with power. It can be used to heal completely, but it will rapidly decay into uselessness." - icon_state = "legion_soul" - -/obj/item/organ/regenerative_core/legion/Initialize() - . = ..() - update_icon() - -/obj/item/organ/regenerative_core/update_icon() - icon_state = inert ? "legion_soul_inert" : "legion_soul" - cut_overlays() - if(!inert && !preserved) - add_overlay("legion_soul_crackle") - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/organ/regenerative_core/legion/go_inert() - ..() - desc = "[src] has become inert. It has decayed, and is completely useless." - -/obj/item/organ/regenerative_core/legion/preserved(implanted = 0) - ..() - desc = "[src] has been stabilized. It is preserved, allowing you to use it to heal completely without danger of decay." +/*********************Hivelord stabilizer****************/ +/obj/item/hivelordstabilizer + name = "stabilizing serum" + icon = 'icons/obj/chemical.dmi' + icon_state = "bottle19" + desc = "Inject certain types of monster organs with this stabilizer to preserve their healing powers indefinitely." + w_class = WEIGHT_CLASS_TINY + +/obj/item/hivelordstabilizer/afterattack(obj/item/organ/M, mob/user) + . = ..() + var/obj/item/organ/regenerative_core/C = M + if(!istype(C, /obj/item/organ/regenerative_core)) + to_chat(user, "The stabilizer only works on certain types of monster organs, generally regenerative in nature.") + return ..() + + C.preserved() + to_chat(user, "You inject the [M] with the stabilizer. It will no longer go inert.") + qdel(src) + +/************************Hivelord core*******************/ +/obj/item/organ/regenerative_core + name = "regenerative core" + desc = "All that remains of a hivelord. It can be used to heal completely, but it will rapidly decay into uselessness." + icon_state = "roro core 2" + item_flags = NOBLUDGEON + slot = "hivecore" + force = 0 + actions_types = list(/datum/action/item_action/organ_action/use) + var/inert = 0 + var/preserved = 0 + +/obj/item/organ/regenerative_core/Initialize() + . = ..() + addtimer(CALLBACK(src, .proc/inert_check), 2400) + +/obj/item/organ/regenerative_core/proc/inert_check() + if(!preserved) + go_inert() + +/obj/item/organ/regenerative_core/proc/preserved(implanted = 0) + inert = FALSE + preserved = TRUE + update_icon() + desc = "All that remains of a hivelord. It is preserved, allowing you to use it to heal completely without danger of decay." + if(implanted) + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "implanted")) + else + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "stabilizer")) + +/obj/item/organ/regenerative_core/proc/go_inert() + inert = TRUE + name = "decayed regenerative core" + desc = "All that remains of a hivelord. It has decayed, and is completely useless." + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "inert")) + update_icon() + +/obj/item/organ/regenerative_core/ui_action_click() + if(inert) + to_chat(owner, "[src] breaks down as it tries to activate.") + else + owner.revive(full_heal = 1) + owner.log_message("[owner] used an implanted [src] to heal themselves! Keep fighting, it's just a flesh wound!", LOG_ATTACK, color="green") //Logging for implanted legion core use + qdel(src) + +/obj/item/organ/regenerative_core/on_life() + ..() + if(owner.health < owner.crit_threshold) + ui_action_click() + +/obj/item/organ/regenerative_core/afterattack(atom/target, mob/user, proximity_flag) + . = ..() + if(proximity_flag && ishuman(target)) + var/mob/living/carbon/human/H = target + if(inert) + to_chat(user, "[src] has decayed and can no longer be used to heal.") + return + if(isvamp(user)) + to_chat(user, "[src] breaks down as it fails to heal your unholy self") + return + else + if(H.stat == DEAD) + to_chat(user, "[src] are useless on the dead.") + return + if(H != user) + H.visible_message("[user] forces [H] to apply [src]... [H.p_they()] quickly regenerate all injuries!") + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "other")) + else + to_chat(user, "You start to smear [src] on yourself. It feels and smells disgusting, but you feel amazingly refreshed in mere moments.") + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "self")) + H.revive(full_heal = 1) + qdel(src) + user.log_message("[user] used [src] to heal [H]! Wake the fuck up, Samurai!", LOG_ATTACK, color="green") //Logging for 'old' style legion core use, when clicking on a sprite of yourself or another. + +/obj/item/organ/regenerative_core/attack_self(mob/user) //Knouli's first hack! Allows for the use of the core in hand rather than needing to click on the target, yourself, to selfheal. Its a rip of the proc just above - but skips on distance check and only uses 'user' rather than 'target' + if(ishuman(user)) //Check if user is human, no need for distance check as it's self heal + var/mob/living/carbon/human/H = user //Set H to user rather than target + if(inert) //Inert cores are useless + to_chat(user, "[src] has decayed and can no longer be used to heal.") + return + else //Skip on check if the target to be healed is dead as, if you are dead, you're not going to be able to use it on yourself! + to_chat(user, "You start to smear [src] on yourself. It feels and smells disgusting, but you feel amazingly refreshed in mere moments.") + SSblackbox.record_feedback("nested tally", "hivelord_core", 1, list("[type]", "used", "self")) + H.revive(full_heal = 1) + qdel(src) + H.log_message("[H] used [src] to heal themselves! Making use of Knouli's sexy and intelligent use-in-hand proc!", LOG_ATTACK, color="green") //Logging for 'new' style legion core use, when using the core in-hand. + + +/obj/item/organ/regenerative_core/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE) + . = ..() + if(!preserved && !inert) + preserved(TRUE) + owner.visible_message("[src] stabilizes as it's inserted.") + +/obj/item/organ/regenerative_core/Remove(mob/living/carbon/M, special = 0) + if(!inert && !special) + owner.visible_message("[src] rapidly decays as it's removed.") + go_inert() + return ..() + +/obj/item/organ/regenerative_core/prepare_eat() + return null + +/*************************Legion core********************/ +/obj/item/organ/regenerative_core/legion + desc = "A strange rock that crackles with power. It can be used to heal completely, but it will rapidly decay into uselessness." + icon_state = "legion_soul" + +/obj/item/organ/regenerative_core/legion/Initialize() + . = ..() + update_icon() + +/obj/item/organ/regenerative_core/update_icon() + icon_state = inert ? "legion_soul_inert" : "legion_soul" + cut_overlays() + if(!inert && !preserved) + add_overlay("legion_soul_crackle") + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/organ/regenerative_core/legion/go_inert() + ..() + desc = "[src] has become inert. It has decayed, and is completely useless." + +/obj/item/organ/regenerative_core/legion/preserved(implanted = 0) + ..() + desc = "[src] has been stabilized. It is preserved, allowing you to use it to heal completely without danger of decay." diff --git a/code/modules/mining/laborcamp/laborshuttle.dm b/code/modules/mining/laborcamp/laborshuttle.dm index 60983aa25a..dbe607f42f 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 ..() \ No newline at end of file diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm index 4e54c3e222..b99ae6bf65 100644 --- a/code/modules/mining/laborcamp/laborstacker.dm +++ b/code/modules/mining/laborcamp/laborstacker.dm @@ -1,157 +1,157 @@ -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 - 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, "labor_claim_console", name, 450, 475, 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 - - data["status_info"] = "No Prisoner ID detected." - 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." - - 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.") - 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.") - -/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) - return - obj_flags |= EMAGGED - to_chat(user, "PZZTTPFFFT") - return TRUE - -/**********************Prisoner Collection Unit**************************/ - -/obj/machinery/mineral/stacking_machine/laborstacker - force_connect = TRUE - var/points = 0 //The unclaimed value of ore stacked. - -/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 + 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, "labor_claim_console", name, 450, 475, 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 + + data["status_info"] = "No Prisoner ID detected." + 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." + + 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.") + 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.") + +/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) + return + obj_flags |= EMAGGED + to_chat(user, "PZZTTPFFFT") + return TRUE + +/**********************Prisoner Collection Unit**************************/ + +/obj/machinery/mineral/stacking_machine/laborstacker + force_connect = TRUE + var/points = 0 //The unclaimed value of ore stacked. + +/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 16e8b0b26c..f5150b4753 100644 --- a/code/modules/mining/machine_processing.dm +++ b/code/modules/mining/machine_processing.dm @@ -1,211 +1,211 @@ -#define SMELT_AMOUNT 10 - -/**********************Mineral processing unit console**************************/ - -/obj/machinery/mineral - var/input_dir = NORTH - var/output_dir = SOUTH - -/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 - speed_process = TRUE - -/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"]) - machine.selected_material = href_list["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") - - 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 - var/obj/machinery/mineral/CONSOLE = null - var/on = FALSE - var/selected_material = MAT_METAL - 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(MAT_METAL, MAT_GLASS, MAT_SILVER, MAT_GOLD, MAT_DIAMOND, MAT_PLASMA, MAT_URANIUM, MAT_BANANIUM, MAT_TITANIUM, MAT_BLUESPACE), INFINITY, TRUE, /obj/item/stack) - stored_research = new /datum/techweb/specialized/autounlocking/smelter - -/obj/machinery/mineral/processing_unit/Destroy() - CONSOLE = null - QDEL_NULL(stored_research) - return ..() - -/obj/machinery/mineral/processing_unit/HasProximity(atom/movable/AM) - if(istype(AM, /obj/item/stack/ore) && AM.loc == get_step(src, input_dir)) - process_ore(AM) - -/obj/machinery/mineral/processing_unit/proc/process_ore(obj/item/stack/ore/O) - 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/mat_id in materials.materials) - var/datum/material/M = materials.materials[mat_id] - dat += "[M.name]: [M.amount] cm³" - if (selected_material == mat_id) - 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/process() - if (on) - if(selected_material) - smelt_ore() - - else if(selected_alloy) - smelt_alloy() - - - if(CONSOLE) - CONSOLE.updateUsrDialog() - -/obj/machinery/mineral/processing_unit/proc/smelt_ore() - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - var/datum/material/mat = materials.materials[selected_material] - if(mat) - var/sheets_to_remove = (mat.amount >= (MINERAL_MATERIAL_AMOUNT * SMELT_AMOUNT) ) ? SMELT_AMOUNT : round(mat.amount / MINERAL_MATERIAL_AMOUNT) - if(!sheets_to_remove) - on = FALSE - else - var/out = get_step(src, output_dir) - materials.retrieve_sheets(sheets_to_remove, selected_material, 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_amount(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_id in D.materials) - var/M = D.materials[mat_id] - var/datum/material/smelter_mat = materials.materials[mat_id] - - if(!M || !smelter_mat) - return FALSE - - build_amount = min(build_amount, round(smelter_mat.amount / M)) - - 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 + var/input_dir = NORTH + var/output_dir = SOUTH + +/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 + speed_process = TRUE + +/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"]) + machine.selected_material = href_list["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") + + 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 + var/obj/machinery/mineral/CONSOLE = null + var/on = FALSE + var/selected_material = MAT_METAL + 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(MAT_METAL, MAT_GLASS, MAT_SILVER, MAT_GOLD, MAT_DIAMOND, MAT_PLASMA, MAT_URANIUM, MAT_BANANIUM, MAT_TITANIUM, MAT_BLUESPACE), INFINITY, TRUE, /obj/item/stack) + stored_research = new /datum/techweb/specialized/autounlocking/smelter + +/obj/machinery/mineral/processing_unit/Destroy() + CONSOLE = null + QDEL_NULL(stored_research) + return ..() + +/obj/machinery/mineral/processing_unit/HasProximity(atom/movable/AM) + if(istype(AM, /obj/item/stack/ore) && AM.loc == get_step(src, input_dir)) + process_ore(AM) + +/obj/machinery/mineral/processing_unit/proc/process_ore(obj/item/stack/ore/O) + 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/mat_id in materials.materials) + var/datum/material/M = materials.materials[mat_id] + dat += "[M.name]: [M.amount] cm³" + if (selected_material == mat_id) + 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/process() + if (on) + if(selected_material) + smelt_ore() + + else if(selected_alloy) + smelt_alloy() + + + if(CONSOLE) + CONSOLE.updateUsrDialog() + +/obj/machinery/mineral/processing_unit/proc/smelt_ore() + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/datum/material/mat = materials.materials[selected_material] + if(mat) + var/sheets_to_remove = (mat.amount >= (MINERAL_MATERIAL_AMOUNT * SMELT_AMOUNT) ) ? SMELT_AMOUNT : round(mat.amount / MINERAL_MATERIAL_AMOUNT) + if(!sheets_to_remove) + on = FALSE + else + var/out = get_step(src, output_dir) + materials.retrieve_sheets(sheets_to_remove, selected_material, 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_amount(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_id in D.materials) + var/M = D.materials[mat_id] + var/datum/material/smelter_mat = materials.materials[mat_id] + + if(!M || !smelter_mat) + return FALSE + + build_amount = min(build_amount, round(smelter_mat.amount / M)) + + 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 aa3ab240d8..31e977c6cf 100644 --- a/code/modules/mining/machine_stacking.dm +++ b/code/modules/mining/machine_stacking.dm @@ -1,121 +1,121 @@ -/**********************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(istype(I, /obj/item/multitool)) - 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/HasProximity(atom/movable/AM) - 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) - 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) - - if(materials.silo && !materials.on_hold()) //Dump the sheets to the silo - var/matlist = storage.materials & materials.mat_container.materials - if (length(matlist)) - var/inserted = materials.mat_container.insert_stack(storage) - materials.silo_log(src, "collected", inserted, "sheets", matlist) - if (QDELETED(storage)) - stack_list -= key - return - - 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(istype(I, /obj/item/multitool)) + 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/HasProximity(atom/movable/AM) + 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) + 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) + + if(materials.silo && !materials.on_hold()) //Dump the sheets to the silo + var/matlist = storage.materials & materials.mat_container.materials + if (length(matlist)) + var/inserted = materials.mat_container.insert_stack(storage) + materials.silo_log(src, "collected", inserted, "sheets", matlist) + if (QDELETED(storage)) + stack_list -= key + return + + 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 e84c2d6d0d..cea90bfce0 100644 --- a/code/modules/mining/machine_unloading.dm +++ b/code/modules/mining/machine_unloading.dm @@ -1,31 +1,31 @@ -/**********************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 - speed_process = TRUE - -/obj/machinery/mineral/unloading_machine/process() - var/turf/T = get_step(src,input_dir) - if(T) - var/limit - for(var/obj/structure/ore_box/B in T) - for (var/obj/item/stack/ore/O in B) - B.contents -= O - unload_mineral(O) - limit++ - if (limit>=10) - return - CHECK_TICK - CHECK_TICK - for(var/obj/item/I in T) - unload_mineral(I) - limit++ - if (limit>=10) - return +/**********************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 + speed_process = TRUE + +/obj/machinery/mineral/unloading_machine/process() + var/turf/T = get_step(src,input_dir) + if(T) + var/limit + for(var/obj/structure/ore_box/B in T) + for (var/obj/item/stack/ore/O in B) + B.contents -= O + unload_mineral(O) + limit++ + if (limit>=10) + return + CHECK_TICK + CHECK_TICK + for(var/obj/item/I in T) + unload_mineral(I) + limit++ + if (limit>=10) + return CHECK_TICK \ No newline at end of file diff --git a/code/modules/mining/mint.dm b/code/modules/mining/mint.dm index 8204977058..f32afb2898 100644 --- a/code/modules/mining/mint.dm +++ b/code/modules/mining/mint.dm @@ -1,105 +1,105 @@ -/**********************Mint**************************/ - - -/obj/machinery/mineral/mint - name = "coin press" - icon = 'icons/obj/economy.dmi' - icon_state = "coinpress0" - density = TRUE - var/newCoins = 0 //how many coins the machine made in it's last load - var/processing = FALSE - var/chosen = MAT_METAL //which material will be used to make coins - var/coinsToProduce = 10 - speed_process = TRUE - - -/obj/machinery/mineral/mint/Initialize() - . = ..() - AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_PLASMA, MAT_SILVER, MAT_GOLD, MAT_URANIUM, MAT_DIAMOND, MAT_BANANIUM), MINERAL_MATERIAL_AMOUNT * 50, FALSE, /obj/item/stack) - -/obj/machinery/mineral/mint/process() - var/turf/T = get_step(src, input_dir) - if(!T) - return - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/obj/item/stack/sheet/O in T) - materials.insert_stack(O, O.amount) - -/obj/machinery/mineral/mint/attack_hand(mob/user) - . = ..() - if(.) - return - var/dat = "Coin Press
                " - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/mat_id in materials.materials) - var/datum/material/M = materials.materials[mat_id] - if(!M.amount && chosen != mat_id) - continue - dat += "
                [M.name] amount: [M.amount] cm3 " - if (chosen == mat_id) - dat += "Chosen" - else - dat += "Choose" - - var/datum/material/M = materials.materials[chosen] - - dat += "

                Will produce [coinsToProduce] [lowertext(M.name)] coins if enough materials are available.
                " - dat += "-10 " - dat += "-5 " - dat += "-1 " - dat += "+1 " - dat += "+5 " - dat += "+10 " - - dat += "

                In total this machine produced [newCoins] coins." - dat += "
                Make coins" - user << browse(dat, "window=mint") - -/obj/machinery/mineral/mint/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - src.add_fingerprint(usr) - if(processing==1) - to_chat(usr, "The machine is processing.") - return - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - if(href_list["choose"]) - if(materials.materials[href_list["choose"]]) - chosen = href_list["choose"] - if(href_list["chooseAmt"]) - coinsToProduce = CLAMP(coinsToProduce + text2num(href_list["chooseAmt"]), 0, 1000) - if(href_list["makeCoins"]) - var/temp_coins = coinsToProduce - processing = TRUE - icon_state = "coinpress1" - var/coin_mat = MINERAL_MATERIAL_AMOUNT * 0.2 - var/datum/material/M = materials.materials[chosen] - if(!M || !M.coin_type) - updateUsrDialog() - return - - while(coinsToProduce > 0 && materials.use_amount_type(coin_mat, chosen)) - create_coins(M.coin_type) - coinsToProduce-- - newCoins++ - src.updateUsrDialog() - sleep(5) - - icon_state = "coinpress0" - processing = FALSE - coinsToProduce = temp_coins - src.updateUsrDialog() - return - -/obj/machinery/mineral/mint/proc/create_coins(P) - var/turf/T = get_step(src,output_dir) - if(T) - var/obj/item/O = new P(src) - var/obj/item/storage/bag/money/M = locate(/obj/item/storage/bag/money, T) - if(!M) - M = new /obj/item/storage/bag/money(src) - unload_mineral(M) - O.forceMove(M) +/**********************Mint**************************/ + + +/obj/machinery/mineral/mint + name = "coin press" + icon = 'icons/obj/economy.dmi' + icon_state = "coinpress0" + density = TRUE + var/newCoins = 0 //how many coins the machine made in it's last load + var/processing = FALSE + var/chosen = MAT_METAL //which material will be used to make coins + var/coinsToProduce = 10 + speed_process = TRUE + + +/obj/machinery/mineral/mint/Initialize() + . = ..() + AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_PLASMA, MAT_SILVER, MAT_GOLD, MAT_URANIUM, MAT_DIAMOND, MAT_BANANIUM), MINERAL_MATERIAL_AMOUNT * 50, FALSE, /obj/item/stack) + +/obj/machinery/mineral/mint/process() + var/turf/T = get_step(src, input_dir) + if(!T) + return + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/obj/item/stack/sheet/O in T) + materials.insert_stack(O, O.amount) + +/obj/machinery/mineral/mint/attack_hand(mob/user) + . = ..() + if(.) + return + var/dat = "Coin Press
                " + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/mat_id in materials.materials) + var/datum/material/M = materials.materials[mat_id] + if(!M.amount && chosen != mat_id) + continue + dat += "
                [M.name] amount: [M.amount] cm3 " + if (chosen == mat_id) + dat += "Chosen" + else + dat += "Choose" + + var/datum/material/M = materials.materials[chosen] + + dat += "

                Will produce [coinsToProduce] [lowertext(M.name)] coins if enough materials are available.
                " + dat += "-10 " + dat += "-5 " + dat += "-1 " + dat += "+1 " + dat += "+5 " + dat += "+10 " + + dat += "

                In total this machine produced [newCoins] coins." + dat += "
                Make coins" + user << browse(dat, "window=mint") + +/obj/machinery/mineral/mint/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + src.add_fingerprint(usr) + if(processing==1) + to_chat(usr, "The machine is processing.") + return + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + if(href_list["choose"]) + if(materials.materials[href_list["choose"]]) + chosen = href_list["choose"] + if(href_list["chooseAmt"]) + coinsToProduce = CLAMP(coinsToProduce + text2num(href_list["chooseAmt"]), 0, 1000) + if(href_list["makeCoins"]) + var/temp_coins = coinsToProduce + processing = TRUE + icon_state = "coinpress1" + var/coin_mat = MINERAL_MATERIAL_AMOUNT * 0.2 + var/datum/material/M = materials.materials[chosen] + if(!M || !M.coin_type) + updateUsrDialog() + return + + while(coinsToProduce > 0 && materials.use_amount_type(coin_mat, chosen)) + create_coins(M.coin_type) + coinsToProduce-- + newCoins++ + src.updateUsrDialog() + sleep(5) + + icon_state = "coinpress0" + processing = FALSE + coinsToProduce = temp_coins + src.updateUsrDialog() + return + +/obj/machinery/mineral/mint/proc/create_coins(P) + var/turf/T = get_step(src,output_dir) + if(T) + var/obj/item/O = new P(src) + var/obj/item/storage/bag/money/M = locate(/obj/item/storage/bag/money, T) + if(!M) + M = new /obj/item/storage/bag/money(src) + unload_mineral(M) + O.forceMove(M) diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 291664ce3e..29a835ddde 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -1,487 +1,487 @@ - -#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" - item_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 - novariants = TRUE // Ore stacks handle their icon updates themselves to keep the illusion that there's more going - var/list/stack_overlays - -/obj/item/stack/ore/update_icon() - 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. - cut_overlays() - if (LAZYLEN(stack_overlays)-difference <= 0) - stack_overlays = null; - else - stack_overlays.len += difference - else if(difference > 0) //amount > stack_overlays, add some. - cut_overlays() - 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) - add_overlay(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" - item_state = "Uranium ore" - singular_name = "uranium ore chunk" - points = 30 - materials = list(MAT_URANIUM=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/uranium - -/obj/item/stack/ore/iron - name = "iron ore" - icon_state = "Iron ore" - item_state = "Iron ore" - singular_name = "iron ore chunk" - points = 1 - materials = list(MAT_METAL=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/metal - -/obj/item/stack/ore/glass - name = "sand pile" - icon_state = "Glass ore" - item_state = "Glass ore" - singular_name = "sand pile" - points = 1 - materials = list(MAT_GLASS=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/glass - w_class = WEIGHT_CLASS_TINY - -GLOBAL_LIST_INIT(sand_recipes, list(\ - new /datum/stack_recipe("sandstone", /obj/item/stack/sheet/mineral/sandstone, 1, 1, 50)\ - )) - -/obj/item/stack/ore/glass/Initialize(mapload, new_amount, merge = TRUE) - recipes = GLOB.sand_recipes - . = ..() - -/obj/item/stack/ore/glass/throw_impact(atom/hit_atom) - if(..() || !ishuman(hit_atom)) - return - var/mob/living/carbon/human/C = hit_atom - if(C.head && C.head.flags_cover & HEADCOVERSEYES) - visible_message("[C]'s headgear blocks the sand!") - return - if(C.wear_mask && C.wear_mask.flags_cover & MASKCOVERSEYES) - visible_message("[C]'s mask blocks the sand!") - return - if(C.glasses && C.glasses.flags_cover & GLASSESCOVERSEYES) - visible_message("[C]'s glasses block 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" - icon_state = "volcanic_sand" - singular_name = "volcanic ash pile" - -/obj/item/stack/ore/plasma - name = "plasma ore" - icon_state = "Plasma ore" - item_state = "Plasma ore" - singular_name = "plasma ore chunk" - points = 15 - materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/plasma - -/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" - item_state = "Silver ore" - singular_name = "silver ore chunk" - points = 16 - materials = list(MAT_SILVER=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/silver - -/obj/item/stack/ore/gold - name = "gold ore" - icon_state = "Gold ore" - icon_state = "Gold ore" - singular_name = "gold ore chunk" - points = 18 - materials = list(MAT_GOLD=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/gold - -/obj/item/stack/ore/diamond - name = "diamond ore" - icon_state = "Diamond ore" - item_state = "Diamond ore" - singular_name = "diamond ore chunk" - points = 50 - materials = list(MAT_DIAMOND=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/diamond - -/obj/item/stack/ore/bananium - name = "bananium ore" - icon_state = "Bananium ore" - item_state = "Bananium ore" - singular_name = "bananium ore chunk" - points = 60 - materials = list(MAT_BANANIUM=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/bananium - -/obj/item/stack/ore/titanium - name = "titanium ore" - icon_state = "Titanium ore" - item_state = "Titanium ore" - singular_name = "titanium ore chunk" - points = 50 - materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT) - refined_type = /obj/item/stack/sheet/mineral/titanium - -/obj/item/stack/ore/slag - name = "slag" - desc = "Completely useless." - icon_state = "slag" - item_state = "slag" - singular_name = "slag chunk" - -/obj/item/twohanded/required/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" - item_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/twohanded/required/gibtonite/Destroy() - qdel(wires) - wires = null - return ..() - -/obj/item/twohanded/required/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) || istype(I, /obj/item/multitool)) - primed = FALSE - if(det_timer) - deltimer(det_timer) - user.visible_message("The chain reaction was 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/twohanded/required/gibtonite/attack_self(user) - if(wires) - wires.interact(user) - else - ..() - -/obj/item/twohanded/required/gibtonite/bullet_act(obj/item/projectile/P) - GibtoniteReaction(P.firer) - ..() - -/obj/item/twohanded/required/gibtonite/ex_act() - GibtoniteReaction(null, 1) - - - -/obj/item/twohanded/required/gibtonite/proc/GibtoniteReaction(mob/user, triggered_by = 0) - if(!primed) - primed = TRUE - playsound(src,'sound/effects/hit_on_shattered_glass.ogg',50,1) - icon_state = "Gibtonite active" - var/turf/bombturf = get_turf(src) - var/notify_admins = 0 - if(z != 5)//Only annoy the admins ingame if we're triggered off the mining zlevel - notify_admins = 1 - - if(notify_admins) - if(triggered_by == 1) - message_admins("An explosion has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)].") - else if(triggered_by == 2) - message_admins("A signal has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)]. Igniter attacher: [ADMIN_LOOKUPFLW(attacher)]") - else - message_admins("[ADMIN_LOOKUPFLW(attacher)] has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)].") - if(triggered_by == 1) - log_game("An explosion has primed a [name] for detonation at [AREACOORD(bombturf)]") - else if(triggered_by == 2) - log_game("A signal has primed a [name] for detonation at [AREACOORD(bombturf)]. Igniter attacher: [key_name(attacher)].") - else - user.visible_message("[user] strikes \the [src], causing a chain reaction!", "You strike \the [src], causing a chain reaction.") - log_game("[key_name(user)] has primed a [name] for detonation at [AREACOORD(bombturf)]") - det_timer = addtimer(CALLBACK(src, .proc/detonate, notify_admins), det_time, TIMER_STOPPABLE) - -/obj/item/twohanded/required/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__heads" - flags_1 = CONDUCT_1 - force = 1 - throwforce = 2 - w_class = WEIGHT_CLASS_TINY - var/string_attached - var/list/sideslist = list("heads","tails") - var/cmineral = null - var/cooldown = 0 - var/value = 1 - var/coinflip - -/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 - -/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) - else - user.visible_message("\the [src] lands on [coinflip]! [user] keeps on living!") - -/obj/item/coin/Initialize() - . = ..() - pixel_x = rand(0,16)-8 - pixel_y = rand(0,8)-8 - -/obj/item/coin/examine(mob/user) - . = ..() - if(value) - . += "It's worth [value] credit\s." - -/obj/item/coin/gold - name = "gold coin" - cmineral = "gold" - icon_state = "coin_gold_heads" - value = 50 - materials = list(MAT_GOLD = MINERAL_MATERIAL_AMOUNT*0.2) - grind_results = list(/datum/reagent/gold = 4) - -/obj/item/coin/silver - name = "silver coin" - cmineral = "silver" - icon_state = "coin_silver_heads" - value = 20 - materials = list(MAT_SILVER = MINERAL_MATERIAL_AMOUNT*0.2) - grind_results = list(/datum/reagent/silver = 4) - -/obj/item/coin/diamond - name = "diamond coin" - cmineral = "diamond" - icon_state = "coin_diamond_heads" - value = 500 - materials = list(MAT_DIAMOND = MINERAL_MATERIAL_AMOUNT*0.2) - grind_results = list(/datum/reagent/carbon = 4) - -/obj/item/coin/iron - name = "iron coin" - cmineral = "iron" - icon_state = "coin_iron_heads" - value = 1 - materials = list(MAT_METAL = MINERAL_MATERIAL_AMOUNT*0.2) - grind_results = list(/datum/reagent/iron = 4) - -/obj/item/coin/plasma - name = "plasma coin" - cmineral = "plasma" - icon_state = "coin_plasma_heads" - value = 100 - materials = list(MAT_PLASMA = MINERAL_MATERIAL_AMOUNT*0.2) - grind_results = list(/datum/reagent/toxin/plasma = 4) - -/obj/item/coin/uranium - name = "uranium coin" - cmineral = "uranium" - icon_state = "coin_uranium_heads" - value = 80 - materials = list(MAT_URANIUM = MINERAL_MATERIAL_AMOUNT*0.2) - grind_results = list(/datum/reagent/uranium = 4) - -/obj/item/coin/bananium - name = "bananium coin" - cmineral = "bananium" - icon_state = "coin_bananium_heads" - value = 1000 //makes the clown cry - materials = list(MAT_BANANIUM = MINERAL_MATERIAL_AMOUNT*0.2) - grind_results = list(/datum/reagent/consumable/banana = 4) - -/obj/item/coin/adamantine - name = "adamantine coin" - cmineral = "adamantine" - icon_state = "coin_adamantine_heads" - value = 1500 - -/obj/item/coin/mythril - name = "mythril coin" - cmineral = "mythril" - icon_state = "coin_mythril_heads" - value = 3000 - -/obj/item/coin/twoheaded - cmineral = "iron" - icon_state = "coin_iron_heads" - desc = "Hey, this coin's the same on both sides!" - sideslist = list("heads") - materials = list(MAT_METAL = MINERAL_MATERIAL_AMOUNT*0.2) - value = 1 - grind_results = list(/datum/reagent/iron = 4) - -/obj/item/coin/antagtoken - name = "antag token" - icon_state = "coin_valid_valid" - cmineral = "valid" - desc = "A novelty coin that helps the heart know what hard evidence cannot prove." - sideslist = list("valid", "salad") - value = 0 - grind_results = list(/datum/reagent/consumable/sodiumchloride = 4) - -/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 - coinflip = pick(sideslist) - cooldown = world.time + 15 - flick("coin_[cmineral]_flip", src) - icon_state = "coin_[cmineral]_[coinflip]" - playsound(user.loc, 'sound/items/coinflip.ogg', 50, 1) - var/oldloc = loc - sleep(15) - if(loc == oldloc && user && !user.incapacitated()) - user.visible_message("[user] has flipped [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 - - -#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" + item_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 + novariants = TRUE // Ore stacks handle their icon updates themselves to keep the illusion that there's more going + var/list/stack_overlays + +/obj/item/stack/ore/update_icon() + 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. + cut_overlays() + if (LAZYLEN(stack_overlays)-difference <= 0) + stack_overlays = null; + else + stack_overlays.len += difference + else if(difference > 0) //amount > stack_overlays, add some. + cut_overlays() + 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) + add_overlay(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" + item_state = "Uranium ore" + singular_name = "uranium ore chunk" + points = 30 + materials = list(MAT_URANIUM=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/uranium + +/obj/item/stack/ore/iron + name = "iron ore" + icon_state = "Iron ore" + item_state = "Iron ore" + singular_name = "iron ore chunk" + points = 1 + materials = list(MAT_METAL=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/metal + +/obj/item/stack/ore/glass + name = "sand pile" + icon_state = "Glass ore" + item_state = "Glass ore" + singular_name = "sand pile" + points = 1 + materials = list(MAT_GLASS=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/glass + w_class = WEIGHT_CLASS_TINY + +GLOBAL_LIST_INIT(sand_recipes, list(\ + new /datum/stack_recipe("sandstone", /obj/item/stack/sheet/mineral/sandstone, 1, 1, 50)\ + )) + +/obj/item/stack/ore/glass/Initialize(mapload, new_amount, merge = TRUE) + recipes = GLOB.sand_recipes + . = ..() + +/obj/item/stack/ore/glass/throw_impact(atom/hit_atom) + if(..() || !ishuman(hit_atom)) + return + var/mob/living/carbon/human/C = hit_atom + if(C.head && C.head.flags_cover & HEADCOVERSEYES) + visible_message("[C]'s headgear blocks the sand!") + return + if(C.wear_mask && C.wear_mask.flags_cover & MASKCOVERSEYES) + visible_message("[C]'s mask blocks the sand!") + return + if(C.glasses && C.glasses.flags_cover & GLASSESCOVERSEYES) + visible_message("[C]'s glasses block 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" + icon_state = "volcanic_sand" + singular_name = "volcanic ash pile" + +/obj/item/stack/ore/plasma + name = "plasma ore" + icon_state = "Plasma ore" + item_state = "Plasma ore" + singular_name = "plasma ore chunk" + points = 15 + materials = list(MAT_PLASMA=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/plasma + +/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" + item_state = "Silver ore" + singular_name = "silver ore chunk" + points = 16 + materials = list(MAT_SILVER=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/silver + +/obj/item/stack/ore/gold + name = "gold ore" + icon_state = "Gold ore" + icon_state = "Gold ore" + singular_name = "gold ore chunk" + points = 18 + materials = list(MAT_GOLD=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/gold + +/obj/item/stack/ore/diamond + name = "diamond ore" + icon_state = "Diamond ore" + item_state = "Diamond ore" + singular_name = "diamond ore chunk" + points = 50 + materials = list(MAT_DIAMOND=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/diamond + +/obj/item/stack/ore/bananium + name = "bananium ore" + icon_state = "Bananium ore" + item_state = "Bananium ore" + singular_name = "bananium ore chunk" + points = 60 + materials = list(MAT_BANANIUM=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/bananium + +/obj/item/stack/ore/titanium + name = "titanium ore" + icon_state = "Titanium ore" + item_state = "Titanium ore" + singular_name = "titanium ore chunk" + points = 50 + materials = list(MAT_TITANIUM=MINERAL_MATERIAL_AMOUNT) + refined_type = /obj/item/stack/sheet/mineral/titanium + +/obj/item/stack/ore/slag + name = "slag" + desc = "Completely useless." + icon_state = "slag" + item_state = "slag" + singular_name = "slag chunk" + +/obj/item/twohanded/required/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" + item_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/twohanded/required/gibtonite/Destroy() + qdel(wires) + wires = null + return ..() + +/obj/item/twohanded/required/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) || istype(I, /obj/item/multitool)) + primed = FALSE + if(det_timer) + deltimer(det_timer) + user.visible_message("The chain reaction was 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/twohanded/required/gibtonite/attack_self(user) + if(wires) + wires.interact(user) + else + ..() + +/obj/item/twohanded/required/gibtonite/bullet_act(obj/item/projectile/P) + GibtoniteReaction(P.firer) + ..() + +/obj/item/twohanded/required/gibtonite/ex_act() + GibtoniteReaction(null, 1) + + + +/obj/item/twohanded/required/gibtonite/proc/GibtoniteReaction(mob/user, triggered_by = 0) + if(!primed) + primed = TRUE + playsound(src,'sound/effects/hit_on_shattered_glass.ogg',50,1) + icon_state = "Gibtonite active" + var/turf/bombturf = get_turf(src) + var/notify_admins = 0 + if(z != 5)//Only annoy the admins ingame if we're triggered off the mining zlevel + notify_admins = 1 + + if(notify_admins) + if(triggered_by == 1) + message_admins("An explosion has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)].") + else if(triggered_by == 2) + message_admins("A signal has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)]. Igniter attacher: [ADMIN_LOOKUPFLW(attacher)]") + else + message_admins("[ADMIN_LOOKUPFLW(attacher)] has triggered a [name] to detonate at [ADMIN_VERBOSEJMP(bombturf)].") + if(triggered_by == 1) + log_game("An explosion has primed a [name] for detonation at [AREACOORD(bombturf)]") + else if(triggered_by == 2) + log_game("A signal has primed a [name] for detonation at [AREACOORD(bombturf)]. Igniter attacher: [key_name(attacher)].") + else + user.visible_message("[user] strikes \the [src], causing a chain reaction!", "You strike \the [src], causing a chain reaction.") + log_game("[key_name(user)] has primed a [name] for detonation at [AREACOORD(bombturf)]") + det_timer = addtimer(CALLBACK(src, .proc/detonate, notify_admins), det_time, TIMER_STOPPABLE) + +/obj/item/twohanded/required/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__heads" + flags_1 = CONDUCT_1 + force = 1 + throwforce = 2 + w_class = WEIGHT_CLASS_TINY + var/string_attached + var/list/sideslist = list("heads","tails") + var/cmineral = null + var/cooldown = 0 + var/value = 1 + var/coinflip + +/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 + +/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) + else + user.visible_message("\the [src] lands on [coinflip]! [user] keeps on living!") + +/obj/item/coin/Initialize() + . = ..() + pixel_x = rand(0,16)-8 + pixel_y = rand(0,8)-8 + +/obj/item/coin/examine(mob/user) + . = ..() + if(value) + . += "It's worth [value] credit\s." + +/obj/item/coin/gold + name = "gold coin" + cmineral = "gold" + icon_state = "coin_gold_heads" + value = 50 + materials = list(MAT_GOLD = MINERAL_MATERIAL_AMOUNT*0.2) + grind_results = list(/datum/reagent/gold = 4) + +/obj/item/coin/silver + name = "silver coin" + cmineral = "silver" + icon_state = "coin_silver_heads" + value = 20 + materials = list(MAT_SILVER = MINERAL_MATERIAL_AMOUNT*0.2) + grind_results = list(/datum/reagent/silver = 4) + +/obj/item/coin/diamond + name = "diamond coin" + cmineral = "diamond" + icon_state = "coin_diamond_heads" + value = 500 + materials = list(MAT_DIAMOND = MINERAL_MATERIAL_AMOUNT*0.2) + grind_results = list(/datum/reagent/carbon = 4) + +/obj/item/coin/iron + name = "iron coin" + cmineral = "iron" + icon_state = "coin_iron_heads" + value = 1 + materials = list(MAT_METAL = MINERAL_MATERIAL_AMOUNT*0.2) + grind_results = list(/datum/reagent/iron = 4) + +/obj/item/coin/plasma + name = "plasma coin" + cmineral = "plasma" + icon_state = "coin_plasma_heads" + value = 100 + materials = list(MAT_PLASMA = MINERAL_MATERIAL_AMOUNT*0.2) + grind_results = list(/datum/reagent/toxin/plasma = 4) + +/obj/item/coin/uranium + name = "uranium coin" + cmineral = "uranium" + icon_state = "coin_uranium_heads" + value = 80 + materials = list(MAT_URANIUM = MINERAL_MATERIAL_AMOUNT*0.2) + grind_results = list(/datum/reagent/uranium = 4) + +/obj/item/coin/bananium + name = "bananium coin" + cmineral = "bananium" + icon_state = "coin_bananium_heads" + value = 1000 //makes the clown cry + materials = list(MAT_BANANIUM = MINERAL_MATERIAL_AMOUNT*0.2) + grind_results = list(/datum/reagent/consumable/banana = 4) + +/obj/item/coin/adamantine + name = "adamantine coin" + cmineral = "adamantine" + icon_state = "coin_adamantine_heads" + value = 1500 + +/obj/item/coin/mythril + name = "mythril coin" + cmineral = "mythril" + icon_state = "coin_mythril_heads" + value = 3000 + +/obj/item/coin/twoheaded + cmineral = "iron" + icon_state = "coin_iron_heads" + desc = "Hey, this coin's the same on both sides!" + sideslist = list("heads") + materials = list(MAT_METAL = MINERAL_MATERIAL_AMOUNT*0.2) + value = 1 + grind_results = list(/datum/reagent/iron = 4) + +/obj/item/coin/antagtoken + name = "antag token" + icon_state = "coin_valid_valid" + cmineral = "valid" + desc = "A novelty coin that helps the heart know what hard evidence cannot prove." + sideslist = list("valid", "salad") + value = 0 + grind_results = list(/datum/reagent/consumable/sodiumchloride = 4) + +/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 + coinflip = pick(sideslist) + cooldown = world.time + 15 + flick("coin_[cmineral]_flip", src) + icon_state = "coin_[cmineral]_[coinflip]" + playsound(user.loc, 'sound/items/coinflip.ogg', 50, 1) + var/oldloc = loc + sleep(15) + if(loc == oldloc && user && !user.incapacitated()) + user.visible_message("[user] has flipped [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 + + +#undef ORESTACK_OVERLAYS_MAX diff --git a/code/modules/mining/satchel_ore_boxdm.dm b/code/modules/mining/satchel_ore_boxdm.dm index 3e9588d7dd..ed104d9eef 100644 --- a/code/modules/mining/satchel_ore_boxdm.dm +++ b/code/modules/mining/satchel_ore_boxdm.dm @@ -1,89 +1,89 @@ - -/**********************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 - -/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/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)) - show_contents(user) - return ..() - -/obj/structure/ore_box/attack_hand(mob/user) - . = ..() - if(.) - return - if(Adjacent(user)) - show_contents(user) - -/obj/structure/ore_box/attack_robot(mob/user) - if(Adjacent(user)) - show_contents(user) - -/obj/structure/ore_box/proc/show_contents(mob/user) - var/dat = text("The contents of the ore box reveal...
                ") - var/list/assembled = list() - for(var/obj/item/stack/ore/O in src) - assembled[O.type] += O.amount - for(var/type in assembled) - var/obj/item/stack/ore/O = type - dat += "[initial(O.name)] - [assembled[type]]
                " - dat += text("

                Empty box") - user << browse(dat, "window=orebox") - -/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/Topic(href, href_list) - if(..()) - return - if(!Adjacent(usr)) - return - - usr.set_machine(src) - add_fingerprint(usr) - if(href_list["removeall"]) - dump_box_contents() - to_chat(usr, "You open the release hatch on the box..") - updateUsrDialog() - -/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 + +/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/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)) + show_contents(user) + return ..() + +/obj/structure/ore_box/attack_hand(mob/user) + . = ..() + if(.) + return + if(Adjacent(user)) + show_contents(user) + +/obj/structure/ore_box/attack_robot(mob/user) + if(Adjacent(user)) + show_contents(user) + +/obj/structure/ore_box/proc/show_contents(mob/user) + var/dat = text("The contents of the ore box reveal...
                ") + var/list/assembled = list() + for(var/obj/item/stack/ore/O in src) + assembled[O.type] += O.amount + for(var/type in assembled) + var/obj/item/stack/ore/O = type + dat += "[initial(O.name)] - [assembled[type]]
                " + dat += text("

                Empty box") + user << browse(dat, "window=orebox") + +/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/Topic(href, href_list) + if(..()) + return + if(!Adjacent(usr)) + return + + usr.set_machine(src) + add_fingerprint(usr) + if(href_list["removeall"]) + dump_box_contents() + to_chat(usr, "You open the release hatch on the box..") + updateUsrDialog() + +/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 ee89167f29..a1f46c0a64 100644 --- a/code/modules/mob/camera/camera.dm +++ b/code/modules/mob/camera/camera.dm @@ -1,37 +1,37 @@ -// Camera mob, used by AI camera and blob. - -/mob/camera - name = "camera mob" - density = FALSE - anchored = TRUE - 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 - var/call_life = FALSE //TRUE if Life() should be called on this camera every tick of the mobs subystem, as if it were a living mob - -/mob/camera/Initialize() - . = ..() - if(call_life) - GLOB.living_cameras += src - -/mob/camera/Destroy() - . = ..() - if(call_life) - GLOB.living_cameras -= src - -/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 +// Camera mob, used by AI camera and blob. + +/mob/camera + name = "camera mob" + density = FALSE + anchored = TRUE + 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 + var/call_life = FALSE //TRUE if Life() should be called on this camera every tick of the mobs subystem, as if it were a living mob + +/mob/camera/Initialize() + . = ..() + if(call_life) + GLOB.living_cameras += src + +/mob/camera/Destroy() + . = ..() + if(call_life) + GLOB.living_cameras -= src + +/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 diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm index 35dbe0828f..90e16649c9 100644 --- a/code/modules/mob/dead/dead.dm +++ b/code/modules/mob/dead/dead.dm @@ -1,129 +1,129 @@ -//Dead mobs can exist whenever. This is needful - -INITIALIZE_IMMEDIATE(/mob/dead) - -/mob/dead - sight = SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF - throwforce = 0 - -/mob/dead/Initialize() - if(flags_1 & INITIALIZED_1) - stack_trace("Warning: [src]([type]) initialized multiple times!") - flags_1 |= INITIALIZED_1 - tag = "mob_[next_mob_id++]" - GLOB.mob_list += src - - 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/csa = CONFIG_GET(keyed_list/cross_server) - 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[0] - 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]?server_hop=[key]") - -/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() - . = ..() - var/turf/T = get_turf(src) - if (isturf(T)) - update_z(T.z) - -/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 + throwforce = 0 + +/mob/dead/Initialize() + if(flags_1 & INITIALIZED_1) + stack_trace("Warning: [src]([type]) initialized multiple times!") + flags_1 |= INITIALIZED_1 + tag = "mob_[next_mob_id++]" + GLOB.mob_list += src + + 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/csa = CONFIG_GET(keyed_list/cross_server) + 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[0] + 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]?server_hop=[key]") + +/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() + . = ..() + var/turf/T = get_turf(src) + if (isturf(T)) + update_z(T.z) + +/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 c93d9e05aa..887556500c 100644 --- a/code/modules/mob/dead/new_player/login.dm +++ b/code/modules/mob/dead/new_player/login.dm @@ -1,34 +1,34 @@ -/mob/dead/new_player/Login() - 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 - - ..() - - 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(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 + + ..() + + 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/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index d789fc6f5a..ae635fa371 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -1,630 +1,630 @@ -#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 - canmove = FALSE - - anchored = TRUE // don't get pushed around - - 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() - - . = ..() - -/mob/dead/new_player/prepare_huds() - return - -/mob/dead/new_player/proc/new_player_panel() - var/output = "

                Welcome, [client ? client.prefs.real_name : "Unknown User"]

                " - 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 = 0 - if(src.client && src.client.holder) - isadmin = 1 - var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")") - var/rs = REF(src) - if(query_get_new_polls.Execute()) - var/newpoll = 0 - if(query_get_new_polls.NextRow()) - newpoll = 1 - - if(newpoll) - 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 || !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 || !SSticker.IsRoundInProgress()) - var/msg = "[key_name(usr)] attempted to join the round using a href that shouldn't be available at this moment!" - log_admin(msg) - message_admins(msg) - 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 - - if(href_list["JoinAsGhostRole"]) - if(!GLOB.enter_allowed) - to_chat(usr, " There is an administrative lock on entering the game!") - - 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 - - var/obj/effect/mob_spawn/MS = pick(GLOB.mob_spawners[href_list["JoinAsGhostRole"]]) - if(MS.attack_ghost(src, latejoinercalling = TRUE)) - SSticker.queued_players -= src - SSticker.queue_delay = 4 - qdel(src) - - if(!ready && href_list["preference"]) - if(client) - client.prefs.process_link(src, href_list) - else if(!href_list["late_join"]) - new_player_panel() - - if(href_list["showpoll"]) - handle_player_polling() - return - - if(href_list["pollid"]) - var/pollid = href_list["pollid"] - if(istext(pollid)) - pollid = text2num(pollid) - if(isnum(pollid) && ISINTEGER(pollid)) - src.poll_player(pollid) - return - - if(href_list["votepollid"] && href_list["votetype"]) - var/pollid = text2num(href_list["votepollid"]) - var/votetype = href_list["votetype"] - //lets take data from the user to decide what kind of poll this is, without validating it - //what could go wrong - switch(votetype) - if(POLLTYPE_OPTION) - var/optionid = text2num(href_list["voteoptionid"]) - if(vote_on_poll(pollid, optionid)) - to_chat(usr, "Vote successful.") - else - to_chat(usr, "Vote failed, please try again or contact an administrator.") - if(POLLTYPE_TEXT) - var/replytext = href_list["replytext"] - if(log_text_poll_reply(pollid, replytext)) - to_chat(usr, "Feedback logging successful.") - else - to_chat(usr, "Feedback logging failed, please try again or contact an administrator.") - if(POLLTYPE_RATING) - var/id_min = text2num(href_list["minid"]) - var/id_max = text2num(href_list["maxid"]) - - if( (id_max - id_min) > 100 ) //Basic exploit prevention - //(protip, this stops no exploits) - to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.") - return - - for(var/optionid = id_min; optionid <= id_max; optionid++) - if(!isnull(href_list["o[optionid]"])) //Test if this optionid was replied to - var/rating - if(href_list["o[optionid]"] == "abstain") - rating = null - else - rating = text2num(href_list["o[optionid]"]) - if(!isnum(rating) || !ISINTEGER(rating)) - return - - if(!vote_on_numval_poll(pollid, optionid, rating)) - to_chat(usr, "Vote failed, please try again or contact an administrator.") - return - to_chat(usr, "Vote successful.") - if(POLLTYPE_MULTI) - var/id_min = text2num(href_list["minoptionid"]) - var/id_max = text2num(href_list["maxoptionid"]) - - if( (id_max - id_min) > 100 ) //Basic exploit prevention - to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.") - return - - for(var/optionid = id_min; optionid <= id_max; optionid++) - if(!isnull(href_list["option_[optionid]"])) //Test if this optionid was selected - var/i = vote_on_multi_poll(pollid, optionid) - switch(i) - if(0) - continue - if(1) - to_chat(usr, "Vote failed, please try again or contact an administrator.") - return - if(2) - to_chat(usr, "Maximum replies reached.") - break - to_chat(usr, "Vote successful.") - if(POLLTYPE_IRV) - if (!href_list["IRVdata"]) - to_chat(src, "No ordering data found. Please try again or contact an administrator.") - return - var/list/votelist = splittext(href_list["IRVdata"], ",") - if (!vote_on_irv_poll(pollid, votelist)) - to_chat(src, "Vote failed, please try again or contact an administrator.") - return - to_chat(src, "Vote successful.") - -//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") - transfer_ckey(observer, FALSE) - 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." - if(JOB_UNAVAILABLE_SPECIESLOCK) - return "Your species cannot play as a [jobtitle]." - 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(jobban_isbanned(src,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 - if(!client.prefs.pref_species.qualifies_for_rank(rank, client.prefs.features)) - return JOB_UNAVAILABLE_SPECIESLOCK - 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) - - GLOB.joined_player_list += character.ckey - GLOB.latejoiners += character - - 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, FALSE, job, FALSE) - - 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/level = "green" - switch(GLOB.security_level) - if(SEC_LEVEL_GREEN) - level = "green" - if(SEC_LEVEL_BLUE) - level = "blue" - if(SEC_LEVEL_AMBER) - level = "amber" - if(SEC_LEVEL_RED) - level = "red" - if(SEC_LEVEL_DELTA) - level = "delta" - - var/dat = "
                Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
                Alert Level: [capitalize(level)]
                " - 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 - var/free_space = 0 - for(var/list/category in list(GLOB.command_positions) + list(GLOB.supply_positions) + list(GLOB.engineering_positions) + list(GLOB.nonhuman_positions - "pAI") + list(GLOB.civilian_positions) + list(GLOB.medical_positions) + list(GLOB.science_positions) + list(GLOB.security_positions)) - var/cat_color = "fff" //random default - cat_color = SSjob.name_occupations[category[1]].selection_color //use the color of the first job in the category (the department head) as the category color - dat += "
                " - dat += "[SSjob.name_occupations[category[1]].exp_type_department]" - - var/list/dept_dat = list() - for(var/job in category) - 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(free_space <=4) - free_space++ - if(column_counter > 0 && (column_counter % 3 == 0)) - dat += "
                " - if(free_space >= 5 && (free_space % 5 == 0) && (column_counter % 3 != 0)) - free_space = 0 - column_counter = 0 - dat += "" - - dat += "
                " - - var/available_ghosts = 0 - for(var/spawner in GLOB.mob_spawners) - if(!LAZYLEN(spawner)) - continue - var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner]) - if(!istype(S) || !S.can_latejoin()) - continue - available_ghosts++ - break - - if(!available_ghosts) - dat += "
                There are currently no open ghost spawners.
                " - else - var/list/categorizedJobs = list("Ghost Role" = list(jobs = list(), titles = GLOB.mob_spawners, color = "#ffffff")) - for(var/spawner in GLOB.mob_spawners) - if(!LAZYLEN(spawner)) - continue - var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner]) - if(!istype(S) || !S.can_latejoin()) - continue - categorizedJobs["Ghost Role"]["jobs"] += spawner - - dat += "
                " - for(var/jobcat in categorizedJobs) - if(!length(categorizedJobs[jobcat]["jobs"])) - continue - var/color = categorizedJobs[jobcat]["color"] - dat += "
                " - dat += "[jobcat]" - for(var/spawner in categorizedJobs[jobcat]["jobs"]) - dat += "[spawner]" - - dat += "

                " - dat += "
                " - dat += "" - - var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 720, 600) - popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') - popup.set_content(jointext(dat, "")) - popup.open(FALSE) // FALSE 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) - if(!frn) - frn = jobban_isbanned(src, "appearance") - if(QDELETED(src)) - return - if(frn) - client.prefs.random_character() - client.prefs.real_name = client.prefs.pref_species.random_name(gender,1) - client.prefs.copy_to(H) - 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 - - 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() - var/dat = "" - dat += "

                Crew Manifest

                " - dat += GLOB.data_core.get_manifest(OOC = 1) - - 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 + canmove = FALSE + + anchored = TRUE // don't get pushed around + + 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() + + . = ..() + +/mob/dead/new_player/prepare_huds() + return + +/mob/dead/new_player/proc/new_player_panel() + var/output = "

                Welcome, [client ? client.prefs.real_name : "Unknown User"]

                " + 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 = 0 + if(src.client && src.client.holder) + isadmin = 1 + var/datum/DBQuery/query_get_new_polls = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime AND id NOT IN (SELECT pollid FROM [format_table_name("poll_vote")] WHERE ckey = \"[sanitizeSQL(ckey)]\") AND id NOT IN (SELECT pollid FROM [format_table_name("poll_textreply")] WHERE ckey = \"[sanitizeSQL(ckey)]\")") + var/rs = REF(src) + if(query_get_new_polls.Execute()) + var/newpoll = 0 + if(query_get_new_polls.NextRow()) + newpoll = 1 + + if(newpoll) + 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 || !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 || !SSticker.IsRoundInProgress()) + var/msg = "[key_name(usr)] attempted to join the round using a href that shouldn't be available at this moment!" + log_admin(msg) + message_admins(msg) + 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 + + if(href_list["JoinAsGhostRole"]) + if(!GLOB.enter_allowed) + to_chat(usr, " There is an administrative lock on entering the game!") + + 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 + + var/obj/effect/mob_spawn/MS = pick(GLOB.mob_spawners[href_list["JoinAsGhostRole"]]) + if(MS.attack_ghost(src, latejoinercalling = TRUE)) + SSticker.queued_players -= src + SSticker.queue_delay = 4 + qdel(src) + + if(!ready && href_list["preference"]) + if(client) + client.prefs.process_link(src, href_list) + else if(!href_list["late_join"]) + new_player_panel() + + if(href_list["showpoll"]) + handle_player_polling() + return + + if(href_list["pollid"]) + var/pollid = href_list["pollid"] + if(istext(pollid)) + pollid = text2num(pollid) + if(isnum(pollid) && ISINTEGER(pollid)) + src.poll_player(pollid) + return + + if(href_list["votepollid"] && href_list["votetype"]) + var/pollid = text2num(href_list["votepollid"]) + var/votetype = href_list["votetype"] + //lets take data from the user to decide what kind of poll this is, without validating it + //what could go wrong + switch(votetype) + if(POLLTYPE_OPTION) + var/optionid = text2num(href_list["voteoptionid"]) + if(vote_on_poll(pollid, optionid)) + to_chat(usr, "Vote successful.") + else + to_chat(usr, "Vote failed, please try again or contact an administrator.") + if(POLLTYPE_TEXT) + var/replytext = href_list["replytext"] + if(log_text_poll_reply(pollid, replytext)) + to_chat(usr, "Feedback logging successful.") + else + to_chat(usr, "Feedback logging failed, please try again or contact an administrator.") + if(POLLTYPE_RATING) + var/id_min = text2num(href_list["minid"]) + var/id_max = text2num(href_list["maxid"]) + + if( (id_max - id_min) > 100 ) //Basic exploit prevention + //(protip, this stops no exploits) + to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.") + return + + for(var/optionid = id_min; optionid <= id_max; optionid++) + if(!isnull(href_list["o[optionid]"])) //Test if this optionid was replied to + var/rating + if(href_list["o[optionid]"] == "abstain") + rating = null + else + rating = text2num(href_list["o[optionid]"]) + if(!isnum(rating) || !ISINTEGER(rating)) + return + + if(!vote_on_numval_poll(pollid, optionid, rating)) + to_chat(usr, "Vote failed, please try again or contact an administrator.") + return + to_chat(usr, "Vote successful.") + if(POLLTYPE_MULTI) + var/id_min = text2num(href_list["minoptionid"]) + var/id_max = text2num(href_list["maxoptionid"]) + + if( (id_max - id_min) > 100 ) //Basic exploit prevention + to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.") + return + + for(var/optionid = id_min; optionid <= id_max; optionid++) + if(!isnull(href_list["option_[optionid]"])) //Test if this optionid was selected + var/i = vote_on_multi_poll(pollid, optionid) + switch(i) + if(0) + continue + if(1) + to_chat(usr, "Vote failed, please try again or contact an administrator.") + return + if(2) + to_chat(usr, "Maximum replies reached.") + break + to_chat(usr, "Vote successful.") + if(POLLTYPE_IRV) + if (!href_list["IRVdata"]) + to_chat(src, "No ordering data found. Please try again or contact an administrator.") + return + var/list/votelist = splittext(href_list["IRVdata"], ",") + if (!vote_on_irv_poll(pollid, votelist)) + to_chat(src, "Vote failed, please try again or contact an administrator.") + return + to_chat(src, "Vote successful.") + +//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") + transfer_ckey(observer, FALSE) + 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." + if(JOB_UNAVAILABLE_SPECIESLOCK) + return "Your species cannot play as a [jobtitle]." + 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(jobban_isbanned(src,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 + if(!client.prefs.pref_species.qualifies_for_rank(rank, client.prefs.features)) + return JOB_UNAVAILABLE_SPECIESLOCK + 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) + + GLOB.joined_player_list += character.ckey + GLOB.latejoiners += character + + 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, FALSE, job, FALSE) + + 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/level = "green" + switch(GLOB.security_level) + if(SEC_LEVEL_GREEN) + level = "green" + if(SEC_LEVEL_BLUE) + level = "blue" + if(SEC_LEVEL_AMBER) + level = "amber" + if(SEC_LEVEL_RED) + level = "red" + if(SEC_LEVEL_DELTA) + level = "delta" + + var/dat = "
                Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
                Alert Level: [capitalize(level)]
                " + 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 + var/free_space = 0 + for(var/list/category in list(GLOB.command_positions) + list(GLOB.supply_positions) + list(GLOB.engineering_positions) + list(GLOB.nonhuman_positions - "pAI") + list(GLOB.civilian_positions) + list(GLOB.medical_positions) + list(GLOB.science_positions) + list(GLOB.security_positions)) + var/cat_color = "fff" //random default + cat_color = SSjob.name_occupations[category[1]].selection_color //use the color of the first job in the category (the department head) as the category color + dat += "
                " + dat += "[SSjob.name_occupations[category[1]].exp_type_department]" + + var/list/dept_dat = list() + for(var/job in category) + 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(free_space <=4) + free_space++ + if(column_counter > 0 && (column_counter % 3 == 0)) + dat += "
                " + if(free_space >= 5 && (free_space % 5 == 0) && (column_counter % 3 != 0)) + free_space = 0 + column_counter = 0 + dat += "" + + dat += "
                " + + var/available_ghosts = 0 + for(var/spawner in GLOB.mob_spawners) + if(!LAZYLEN(spawner)) + continue + var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner]) + if(!istype(S) || !S.can_latejoin()) + continue + available_ghosts++ + break + + if(!available_ghosts) + dat += "
                There are currently no open ghost spawners.
                " + else + var/list/categorizedJobs = list("Ghost Role" = list(jobs = list(), titles = GLOB.mob_spawners, color = "#ffffff")) + for(var/spawner in GLOB.mob_spawners) + if(!LAZYLEN(spawner)) + continue + var/obj/effect/mob_spawn/S = pick(GLOB.mob_spawners[spawner]) + if(!istype(S) || !S.can_latejoin()) + continue + categorizedJobs["Ghost Role"]["jobs"] += spawner + + dat += "
                " + for(var/jobcat in categorizedJobs) + if(!length(categorizedJobs[jobcat]["jobs"])) + continue + var/color = categorizedJobs[jobcat]["color"] + dat += "
                " + dat += "[jobcat]" + for(var/spawner in categorizedJobs[jobcat]["jobs"]) + dat += "[spawner]" + + dat += "

                " + dat += "
                " + dat += "" + + var/datum/browser/popup = new(src, "latechoices", "Choose Profession", 720, 600) + popup.add_stylesheet("playeroptions", 'html/browser/playeroptions.css') + popup.set_content(jointext(dat, "")) + popup.open(FALSE) // FALSE 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) + if(!frn) + frn = jobban_isbanned(src, "appearance") + if(QDELETED(src)) + return + if(frn) + client.prefs.random_character() + client.prefs.real_name = client.prefs.pref_species.random_name(gender,1) + client.prefs.copy_to(H) + 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 + + 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() + var/dat = "" + dat += "

                Crew Manifest

                " + dat += GLOB.data_core.get_manifest(OOC = 1) + + 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 db792084cf..b18b463528 100644 --- a/code/modules/mob/dead/new_player/poll.dm +++ b/code/modules/mob/dead/new_player/poll.dm @@ -1,619 +1,619 @@ -/datum/polloption - var/optionid - var/optiontext - -/mob/dead/new_player/proc/handle_player_polling() - if(!SSdbcore.IsConnected()) - to_chat(usr, "Failed to establish database connection.") - return - var/datum/DBQuery/query_poll_get = SSdbcore.NewQuery("SELECT id, question FROM [format_table_name("poll_question")] WHERE Now() BETWEEN starttime AND endtime [(client.holder ? "" : "AND adminonly = false")]") - if(!query_poll_get.warn_execute()) - qdel(query_poll_get) - return - var/output = "
                Player polls
                " - var/i = 0 - var/rs = REF(src) - while(query_poll_get.NextRow()) - var/pollid = query_poll_get.item[1] - var/pollquestion = query_poll_get.item[2] - output += "" - i++ - qdel(query_poll_get) - output += "
                [pollquestion]
                " - if(!QDELETED(src)) - src << browse(output,"window=playerpolllist;size=500x300") - -/mob/dead/new_player/proc/poll_player(pollid) - if(!pollid) - return - if (!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") - return - var/datum/DBQuery/query_poll_get_details = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid]") - if(!query_poll_get_details.warn_execute()) - qdel(query_poll_get_details) - return - var/pollstarttime = "" - var/pollendtime = "" - var/pollquestion = "" - var/polltype = "" - var/multiplechoiceoptions = 0 - if(query_poll_get_details.NextRow()) - pollstarttime = query_poll_get_details.item[1] - pollendtime = query_poll_get_details.item[2] - pollquestion = query_poll_get_details.item[3] - polltype = query_poll_get_details.item[4] - multiplechoiceoptions = text2num(query_poll_get_details.item[5]) - qdel(query_poll_get_details) - switch(polltype) - if(POLLTYPE_OPTION) - var/datum/DBQuery/query_option_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_option_get_votes.warn_execute()) - qdel(query_option_get_votes) - return - var/votedoptionid = 0 - if(query_option_get_votes.NextRow()) - votedoptionid = text2num(query_option_get_votes.item[1]) - qdel(query_option_get_votes) - var/list/datum/polloption/options = list() - var/datum/DBQuery/query_option_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - if(!query_option_options.warn_execute()) - qdel(query_option_options) - return - while(query_option_options.NextRow()) - var/datum/polloption/PO = new() - PO.optionid = text2num(query_option_options.item[1]) - PO.optiontext = query_option_options.item[2] - options += PO - qdel(query_option_options) - var/output = "
                Player poll
                " - output += "Question: [pollquestion]
                " - output += "Poll runs from [pollstarttime] until [pollendtime]

                " - if(!votedoptionid) - output += "

                " - output += "" - output += "" - output += "" - output += "
                " - for(var/datum/polloption/O in options) - if(O.optionid && O.optiontext) - if(votedoptionid) - if(votedoptionid == O.optionid) - output += "[O.optiontext]
                " - else - output += "[O.optiontext]
                " - else - output += "[O.optiontext]
                " - output += "
                " - if(!votedoptionid) - output += "

                " - output += "

                " - output += "
                " - src << browse(null ,"window=playerpolllist") - src << browse(output,"window=playerpoll;size=500x250") - if(POLLTYPE_TEXT) - var/datum/DBQuery/query_text_get_votes = SSdbcore.NewQuery("SELECT replytext FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_text_get_votes.warn_execute()) - qdel(query_text_get_votes) - return - var/vote_text = "" - if(query_text_get_votes.NextRow()) - vote_text = query_text_get_votes.item[1] - qdel(query_text_get_votes) - var/output = "
                Player poll
                " - output += "Question: [pollquestion]
                " - output += "Feedback gathering runs from [pollstarttime] until [pollendtime]

                " - output += "

                " - output += "" - output += "" - output += "" - output += "Please provide feedback below. You can use any letters of the English alphabet, numbers and the symbols: . , ! ? : ; -
                " - output += "" - output += "

                " - output += "
                " - output += "" - output += "" - output += "" - output += "" - output += "
                " - - src << browse(null ,"window=playerpolllist") - src << browse(output,"window=playerpoll;size=500x500") - if(POLLTYPE_RATING) - var/datum/DBQuery/query_rating_get_votes = SSdbcore.NewQuery("SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, [format_table_name("poll_vote")] v WHERE o.pollid = [pollid] AND v.ckey = '[ckey]' AND o.id = v.optionid") - if(!query_rating_get_votes.warn_execute()) - qdel(query_rating_get_votes) - return - var/output = "
                Player poll
                " - output += "Question: [pollquestion]
                " - output += "Poll runs from [pollstarttime] until [pollendtime]

                " - var/rating - while(query_rating_get_votes.NextRow()) - var/optiontext = query_rating_get_votes.item[1] - rating = query_rating_get_votes.item[2] - output += "
                [optiontext] - [rating]" - qdel(query_rating_get_votes) - if(!rating) - output += "

                " - output += "" - output += "" - output += "" - var/minid = 999999 - var/maxid = 0 - var/datum/DBQuery/query_rating_options = SSdbcore.NewQuery("SELECT id, text, minval, maxval, descmin, descmid, descmax FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - if(!query_rating_options.warn_execute()) - qdel(query_rating_options) - return - while(query_rating_options.NextRow()) - var/optionid = text2num(query_rating_options.item[1]) - var/optiontext = query_rating_options.item[2] - var/minvalue = text2num(query_rating_options.item[3]) - var/maxvalue = text2num(query_rating_options.item[4]) - var/descmin = query_rating_options.item[5] - var/descmid = query_rating_options.item[6] - var/descmax = query_rating_options.item[7] - if(optionid < minid) - minid = optionid - if(optionid > maxid) - maxid = optionid - var/midvalue = round( (maxvalue + minvalue) / 2) - output += "
                [optiontext]: " - qdel(query_rating_options) - output += "" - output += "" - output += "

                " - if(!QDELETED(src)) - src << browse(null ,"window=playerpolllist") - src << browse(output,"window=playerpoll;size=500x500") - if(POLLTYPE_MULTI) - var/datum/DBQuery/query_multi_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_multi_get_votes.warn_execute()) - qdel(query_multi_get_votes) - return - var/list/votedfor = list() - while(query_multi_get_votes.NextRow()) - votedfor.Add(text2num(query_multi_get_votes.item[1])) - qdel(query_multi_get_votes) - var/list/datum/polloption/options = list() - var/maxoptionid = 0 - var/minoptionid = 0 - var/datum/DBQuery/query_multi_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - if(!query_multi_options.warn_execute()) - qdel(query_multi_options) - return - while(query_multi_options.NextRow()) - var/datum/polloption/PO = new() - PO.optionid = text2num(query_multi_options.item[1]) - PO.optiontext = query_multi_options.item[2] - if(PO.optionid > maxoptionid) - maxoptionid = PO.optionid - if(PO.optionid < minoptionid || !minoptionid) - minoptionid = PO.optionid - options += PO - qdel(query_multi_options) - var/output = "
                Player poll
                " - output += "Question: [pollquestion]
                You can select up to [multiplechoiceoptions] options. If you select more, the first [multiplechoiceoptions] will be saved.
                " - output += "Poll runs from [pollstarttime] until [pollendtime]

                " - if(!votedfor.len) - output += "

                " - output += "" - output += "" - output += "" - output += "" - output += "" - output += "
                " - for(var/datum/polloption/O in options) - if(O.optionid && O.optiontext) - if(votedfor.len) - if(O.optionid in votedfor) - output += "[O.optiontext]
                " - else - output += "[O.optiontext]
                " - else - output += "[O.optiontext]
                " - output += "
                " - if(!votedfor.len) - output += "

                " - output += "
                " - src << browse(null ,"window=playerpolllist") - src << browse(output,"window=playerpoll;size=500x250") - if(POLLTYPE_IRV) - var/datum/asset/irv_assets = get_asset_datum(/datum/asset/group/IRV) - irv_assets.send(src) - - var/datum/DBQuery/query_irv_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_irv_get_votes.warn_execute()) - qdel(query_irv_get_votes) - return - - var/list/votedfor = list() - while(query_irv_get_votes.NextRow()) - votedfor.Add(text2num(query_irv_get_votes.item[1])) - qdel(query_irv_get_votes) - - var/list/datum/polloption/options = list() - - var/datum/DBQuery/query_irv_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - if(!query_irv_options.warn_execute()) - qdel(query_irv_options) - return - while(query_irv_options.NextRow()) - var/datum/polloption/PO = new() - PO.optionid = text2num(query_irv_options.item[1]) - PO.optiontext = query_irv_options.item[2] - options["[PO.optionid]"] += PO - qdel(query_irv_options) - - //if they already voted, use their sort - if (votedfor.len) - var/list/datum/polloption/newoptions = list() - for (var/V in votedfor) - var/datum/polloption/PO = options["[V]"] - if(PO) - newoptions["[V]"] = PO - options -= "[V]" - //add any options that they didn't vote on (some how, some way) - options = shuffle(options) - for (var/V in options) - newoptions["[V]"] = options["[V]"] - options = newoptions - //otherwise, lets shuffle it. - else - var/list/datum/polloption/newoptions = list() - while (options.len) - var/list/local_options = options.Copy() - var/key - //the jist is we randomly remove all options from a copy of options until only one reminds, - // move that over to our new list - // and repeat until we've moved all of them - while (local_options.len) - key = local_options[rand(1, local_options.len)] - local_options -= key - var/value = options[key] - options -= key - newoptions[key] = value - options = newoptions - - var/output = {" - - - - - - - - - -
                Player poll
                - Question: [pollquestion]
                Please sort the options in the order of most preferred to least preferred
                - Revoting has been enabled on this poll, if you think you made a mistake, simply revote
                - Poll runs from [pollstarttime] until [pollendtime]

                -

                -
                - - - - -
                -
                Most Preferred
                -
                  - "} - for(var/O in options) - var/datum/polloption/PO = options["[O]"] - if(PO.optionid && PO.optiontext) - output += "
                1. [PO.optiontext]
                2. \n" - output += {" -
                -
                Least Preferred

                -
                -

                - "} - src << browse(null ,"window=playerpolllist") - src << browse(output,"window=playerpoll;size=500x500") - return - -//Returns null on failure, TRUE if already voted, FALSE if not voted yet. -/mob/dead/new_player/proc/poll_check_voted(pollid, text = FALSE, silent = FALSE) - var/table = "poll_vote" - if (text) - table = "poll_textreply" - if (!SSdbcore.Connect()) - to_chat(usr, "Failed to establish database connection.") - return - var/datum/DBQuery/query_hasvoted = SSdbcore.NewQuery("SELECT id FROM `[format_table_name(table)]` WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_hasvoted.warn_execute()) - qdel(query_hasvoted) - return - if(query_hasvoted.NextRow()) - qdel(query_hasvoted) - if(!silent) - to_chat(usr, "You've already replied to this poll.") - return TRUE - qdel(query_hasvoted) - return FALSE - -//Returns adminrank for use in polls. -/mob/dead/new_player/proc/poll_rank() - . = "Player" - if(client.holder) - . = client.holder.rank.name - - -/mob/dead/new_player/proc/vote_rig_check() - if (usr != src) - if (!usr || !src) - return 0 - //we gots ourselfs a dirty cheater on our hands! - log_game("[key_name(usr)] attempted to rig the vote by voting as [key]") - message_admins("[key_name_admin(usr)] attempted to rig the vote by voting as [key]") - to_chat(usr, "You don't seem to be [key].") - to_chat(src, "Something went horribly wrong processing your vote. Please contact an administrator, they should have gotten a message about this") - return 0 - return 1 - -/mob/dead/new_player/proc/vote_valid_check(pollid, holder, type) - if (!SSdbcore.Connect()) - to_chat(src, "Failed to establish database connection.") - return 0 - pollid = text2num(pollid) - if (!pollid || pollid < 0) - return 0 - //validate the poll is actually the right type of poll and its still active - var/datum/DBQuery/query_validate_poll = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime AND polltype = '[type]' [(holder ? "" : "AND adminonly = false")]") - if(!query_validate_poll.warn_execute()) - qdel(query_validate_poll) - return 0 - if (!query_validate_poll.NextRow()) - qdel(query_validate_poll) - return 0 - qdel(query_validate_poll) - return 1 - -/mob/dead/new_player/proc/vote_on_irv_poll(pollid, list/votelist) - if (!SSdbcore.Connect()) - to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 - pollid = text2num(pollid) - if (!pollid || pollid < 0) - return 0 - if (!votelist || !istype(votelist) || !votelist.len) - return 0 - if (!client) - return 0 - //save these now so we can still process the vote if the client goes away while we process. - var/datum/admins/holder = client.holder - var/rank = "Player" - if (holder) - rank = holder.rank.name - var/ckey = client.ckey - var/address = client.address - - //validate the poll - if (!vote_valid_check(pollid, holder, POLLTYPE_IRV)) - return 0 - - //lets collect the options - var/datum/DBQuery/query_irv_id = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") - if(!query_irv_id.warn_execute()) - qdel(query_irv_id) - return 0 - var/list/optionlist = list() - while (query_irv_id.NextRow()) - optionlist += text2num(query_irv_id.item[1]) - qdel(query_irv_id) - - //validate their votes are actually in the list of options and actually numbers - var/list/numberedvotelist = list() - for (var/vote in votelist) - vote = text2num(vote) - numberedvotelist += vote - if (!vote) //this is fine because voteid starts at 1, so it will never be 0 - to_chat(src, "Error: Invalid (non-numeric) votes in the vote data.") - return 0 - if (!(vote in optionlist)) - to_chat(src, "Votes for choices that do not appear to be in the poll detected.") - return 0 - if (!numberedvotelist.len) - to_chat(src, "Invalid vote data") - return 0 - - //lets add the vote, first we generate an insert statement. - - var/sqlrowlist = "" - for (var/vote in numberedvotelist) - if (sqlrowlist != "") - sqlrowlist += ", " //a comma (,) at the start of the first row to insert will trigger a SQL error - sqlrowlist += "(Now(), [pollid], [vote], '[sanitizeSQL(ckey)]', INET_ATON('[sanitizeSQL(address)]'), '[sanitizeSQL(rank)]')" - - //now lets delete their old votes (if any) - var/datum/DBQuery/query_irv_del_old = SSdbcore.NewQuery("DELETE FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_irv_del_old.warn_execute()) - qdel(query_irv_del_old) - return 0 - qdel(query_irv_del_old) - - //now to add the new ones. - var/datum/DBQuery/query_irv_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES [sqlrowlist]") - if(!query_irv_vote.warn_execute()) - qdel(query_irv_vote) - return 0 - qdel(query_irv_vote) - if(!QDELETED(src)) - src << browse(null,"window=playerpoll") - return 1 - - -/mob/dead/new_player/proc/vote_on_poll(pollid, optionid) - if (!SSdbcore.Connect()) - to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 - if(!pollid || !optionid) - return - //validate the poll - if (!vote_valid_check(pollid, client.holder, POLLTYPE_OPTION)) - return 0 - var/voted = poll_check_voted(pollid) - if(isnull(voted) || voted) //Failed or already voted. - return - var/adminrank = sanitizeSQL(poll_rank()) - if(!adminrank) - return - var/datum/DBQuery/query_option_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]')") - if(!query_option_vote.warn_execute()) - qdel(query_option_vote) - return - qdel(query_option_vote) - if(!QDELETED(usr)) - usr << browse(null,"window=playerpoll") - return 1 - -/mob/dead/new_player/proc/log_text_poll_reply(pollid, replytext) - if (!SSdbcore.Connect()) - to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 - if(!pollid) - return - //validate the poll - if (!vote_valid_check(pollid, client.holder, POLLTYPE_TEXT)) - return 0 - if(!replytext) - to_chat(usr, "The text you entered was blank. Please correct the text and submit again.") - return - var/voted = poll_check_voted(pollid, text = TRUE, silent = TRUE) - if(isnull(voted)) - return - var/adminrank = sanitizeSQL(poll_rank()) - if(!adminrank) - return - replytext = sanitizeSQL(replytext) - if(!(length(replytext) > 0) || !(length(replytext) <= 8000)) - to_chat(usr, "The text you entered was invalid or too long. Please correct the text and submit again.") - return - var/datum/DBQuery/query_text_vote - if(!voted) - query_text_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_textreply")] (datetime ,pollid ,ckey ,ip ,replytext ,adminrank) VALUES (Now(), [pollid], '[ckey]', INET_ATON('[client.address]'), '[replytext]', '[adminrank]')") - else - query_text_vote = SSdbcore.NewQuery("UPDATE [format_table_name("poll_textreply")] SET datetime = Now(), ip = INET_ATON('[client.address]'), replytext = '[replytext]' WHERE pollid = '[pollid]' AND ckey = '[ckey]'") - if(!query_text_vote.warn_execute()) - qdel(query_text_vote) - return - qdel(query_text_vote) - if(!QDELETED(usr)) - usr << browse(null,"window=playerpoll") - return 1 - -/mob/dead/new_player/proc/vote_on_numval_poll(pollid, optionid, rating) - if (!SSdbcore.Connect()) - to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 - if(!pollid || !optionid || !rating) - return - //validate the poll - if (!vote_valid_check(pollid, client.holder, POLLTYPE_RATING)) - return 0 - var/datum/DBQuery/query_numval_hasvoted = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE optionid = [optionid] AND ckey = '[ckey]'") - if(!query_numval_hasvoted.warn_execute()) - qdel(query_numval_hasvoted) - return - if(query_numval_hasvoted.NextRow()) - qdel(query_numval_hasvoted) - to_chat(usr, "You've already replied to this poll.") - return - qdel(query_numval_hasvoted) - var/adminrank = "Player" - if(client.holder) - adminrank = client.holder.rank.name - adminrank = sanitizeSQL(adminrank) - var/datum/DBQuery/query_numval_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]', [(isnull(rating)) ? "null" : rating])") - if(!query_numval_vote.warn_execute()) - qdel(query_numval_vote) - return - qdel(query_numval_vote) - if(!QDELETED(usr)) - usr << browse(null,"window=playerpoll") - return 1 - -/mob/dead/new_player/proc/vote_on_multi_poll(pollid, optionid) - if (!SSdbcore.Connect()) - to_chat(src, "Failed to establish database connection.") - return 0 - if (!vote_rig_check()) - return 0 - if(!pollid || !optionid) - return 1 - //validate the poll - if (!vote_valid_check(pollid, client.holder, POLLTYPE_MULTI)) - return 0 - var/datum/DBQuery/query_multi_choicelen = SSdbcore.NewQuery("SELECT multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid]") - if(!query_multi_choicelen.warn_execute()) - qdel(query_multi_choicelen) - return 1 - var/i - if(query_multi_choicelen.NextRow()) - i = text2num(query_multi_choicelen.item[1]) - qdel(query_multi_choicelen) - var/datum/DBQuery/query_multi_hasvoted = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") - if(!query_multi_hasvoted.warn_execute()) - qdel(query_multi_hasvoted) - return 1 - while(i) - if(query_multi_hasvoted.NextRow()) - i-- - else - break - qdel(query_multi_hasvoted) - if(!i) - return 2 - var/adminrank = "Player" - if(!QDELETED(client) && client.holder) - adminrank = client.holder.rank.name - adminrank = sanitizeSQL(adminrank) - var/datum/DBQuery/query_multi_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]')") - if(!query_multi_vote.warn_execute()) - qdel(query_multi_vote) - return 1 - qdel(query_multi_vote) - if(!QDELETED(usr)) - usr << browse(null,"window=playerpoll") - return 0 +/datum/polloption + var/optionid + var/optiontext + +/mob/dead/new_player/proc/handle_player_polling() + if(!SSdbcore.IsConnected()) + to_chat(usr, "Failed to establish database connection.") + return + var/datum/DBQuery/query_poll_get = SSdbcore.NewQuery("SELECT id, question FROM [format_table_name("poll_question")] WHERE Now() BETWEEN starttime AND endtime [(client.holder ? "" : "AND adminonly = false")]") + if(!query_poll_get.warn_execute()) + qdel(query_poll_get) + return + var/output = "
                Player polls
                " + var/i = 0 + var/rs = REF(src) + while(query_poll_get.NextRow()) + var/pollid = query_poll_get.item[1] + var/pollquestion = query_poll_get.item[2] + output += "" + i++ + qdel(query_poll_get) + output += "
                [pollquestion]
                " + if(!QDELETED(src)) + src << browse(output,"window=playerpolllist;size=500x300") + +/mob/dead/new_player/proc/poll_player(pollid) + if(!pollid) + return + if (!SSdbcore.Connect()) + to_chat(usr, "Failed to establish database connection.") + return + var/datum/DBQuery/query_poll_get_details = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid]") + if(!query_poll_get_details.warn_execute()) + qdel(query_poll_get_details) + return + var/pollstarttime = "" + var/pollendtime = "" + var/pollquestion = "" + var/polltype = "" + var/multiplechoiceoptions = 0 + if(query_poll_get_details.NextRow()) + pollstarttime = query_poll_get_details.item[1] + pollendtime = query_poll_get_details.item[2] + pollquestion = query_poll_get_details.item[3] + polltype = query_poll_get_details.item[4] + multiplechoiceoptions = text2num(query_poll_get_details.item[5]) + qdel(query_poll_get_details) + switch(polltype) + if(POLLTYPE_OPTION) + var/datum/DBQuery/query_option_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + if(!query_option_get_votes.warn_execute()) + qdel(query_option_get_votes) + return + var/votedoptionid = 0 + if(query_option_get_votes.NextRow()) + votedoptionid = text2num(query_option_get_votes.item[1]) + qdel(query_option_get_votes) + var/list/datum/polloption/options = list() + var/datum/DBQuery/query_option_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + if(!query_option_options.warn_execute()) + qdel(query_option_options) + return + while(query_option_options.NextRow()) + var/datum/polloption/PO = new() + PO.optionid = text2num(query_option_options.item[1]) + PO.optiontext = query_option_options.item[2] + options += PO + qdel(query_option_options) + var/output = "
                Player poll
                " + output += "Question: [pollquestion]
                " + output += "Poll runs from [pollstarttime] until [pollendtime]

                " + if(!votedoptionid) + output += "

                " + output += "" + output += "" + output += "" + output += "
                " + for(var/datum/polloption/O in options) + if(O.optionid && O.optiontext) + if(votedoptionid) + if(votedoptionid == O.optionid) + output += "[O.optiontext]
                " + else + output += "[O.optiontext]
                " + else + output += "[O.optiontext]
                " + output += "
                " + if(!votedoptionid) + output += "

                " + output += "

                " + output += "
                " + src << browse(null ,"window=playerpolllist") + src << browse(output,"window=playerpoll;size=500x250") + if(POLLTYPE_TEXT) + var/datum/DBQuery/query_text_get_votes = SSdbcore.NewQuery("SELECT replytext FROM [format_table_name("poll_textreply")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + if(!query_text_get_votes.warn_execute()) + qdel(query_text_get_votes) + return + var/vote_text = "" + if(query_text_get_votes.NextRow()) + vote_text = query_text_get_votes.item[1] + qdel(query_text_get_votes) + var/output = "
                Player poll
                " + output += "Question: [pollquestion]
                " + output += "Feedback gathering runs from [pollstarttime] until [pollendtime]

                " + output += "

                " + output += "" + output += "" + output += "" + output += "Please provide feedback below. You can use any letters of the English alphabet, numbers and the symbols: . , ! ? : ; -
                " + output += "" + output += "

                " + output += "
                " + output += "" + output += "" + output += "" + output += "" + output += "
                " + + src << browse(null ,"window=playerpolllist") + src << browse(output,"window=playerpoll;size=500x500") + if(POLLTYPE_RATING) + var/datum/DBQuery/query_rating_get_votes = SSdbcore.NewQuery("SELECT o.text, v.rating FROM [format_table_name("poll_option")] o, [format_table_name("poll_vote")] v WHERE o.pollid = [pollid] AND v.ckey = '[ckey]' AND o.id = v.optionid") + if(!query_rating_get_votes.warn_execute()) + qdel(query_rating_get_votes) + return + var/output = "
                Player poll
                " + output += "Question: [pollquestion]
                " + output += "Poll runs from [pollstarttime] until [pollendtime]

                " + var/rating + while(query_rating_get_votes.NextRow()) + var/optiontext = query_rating_get_votes.item[1] + rating = query_rating_get_votes.item[2] + output += "
                [optiontext] - [rating]" + qdel(query_rating_get_votes) + if(!rating) + output += "

                " + output += "" + output += "" + output += "" + var/minid = 999999 + var/maxid = 0 + var/datum/DBQuery/query_rating_options = SSdbcore.NewQuery("SELECT id, text, minval, maxval, descmin, descmid, descmax FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + if(!query_rating_options.warn_execute()) + qdel(query_rating_options) + return + while(query_rating_options.NextRow()) + var/optionid = text2num(query_rating_options.item[1]) + var/optiontext = query_rating_options.item[2] + var/minvalue = text2num(query_rating_options.item[3]) + var/maxvalue = text2num(query_rating_options.item[4]) + var/descmin = query_rating_options.item[5] + var/descmid = query_rating_options.item[6] + var/descmax = query_rating_options.item[7] + if(optionid < minid) + minid = optionid + if(optionid > maxid) + maxid = optionid + var/midvalue = round( (maxvalue + minvalue) / 2) + output += "
                [optiontext]: " + qdel(query_rating_options) + output += "" + output += "" + output += "

                " + if(!QDELETED(src)) + src << browse(null ,"window=playerpolllist") + src << browse(output,"window=playerpoll;size=500x500") + if(POLLTYPE_MULTI) + var/datum/DBQuery/query_multi_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + if(!query_multi_get_votes.warn_execute()) + qdel(query_multi_get_votes) + return + var/list/votedfor = list() + while(query_multi_get_votes.NextRow()) + votedfor.Add(text2num(query_multi_get_votes.item[1])) + qdel(query_multi_get_votes) + var/list/datum/polloption/options = list() + var/maxoptionid = 0 + var/minoptionid = 0 + var/datum/DBQuery/query_multi_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + if(!query_multi_options.warn_execute()) + qdel(query_multi_options) + return + while(query_multi_options.NextRow()) + var/datum/polloption/PO = new() + PO.optionid = text2num(query_multi_options.item[1]) + PO.optiontext = query_multi_options.item[2] + if(PO.optionid > maxoptionid) + maxoptionid = PO.optionid + if(PO.optionid < minoptionid || !minoptionid) + minoptionid = PO.optionid + options += PO + qdel(query_multi_options) + var/output = "
                Player poll
                " + output += "Question: [pollquestion]
                You can select up to [multiplechoiceoptions] options. If you select more, the first [multiplechoiceoptions] will be saved.
                " + output += "Poll runs from [pollstarttime] until [pollendtime]

                " + if(!votedfor.len) + output += "

                " + output += "" + output += "" + output += "" + output += "" + output += "" + output += "
                " + for(var/datum/polloption/O in options) + if(O.optionid && O.optiontext) + if(votedfor.len) + if(O.optionid in votedfor) + output += "[O.optiontext]
                " + else + output += "[O.optiontext]
                " + else + output += "[O.optiontext]
                " + output += "
                " + if(!votedfor.len) + output += "

                " + output += "
                " + src << browse(null ,"window=playerpolllist") + src << browse(output,"window=playerpoll;size=500x250") + if(POLLTYPE_IRV) + var/datum/asset/irv_assets = get_asset_datum(/datum/asset/group/IRV) + irv_assets.send(src) + + var/datum/DBQuery/query_irv_get_votes = SSdbcore.NewQuery("SELECT optionid FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + if(!query_irv_get_votes.warn_execute()) + qdel(query_irv_get_votes) + return + + var/list/votedfor = list() + while(query_irv_get_votes.NextRow()) + votedfor.Add(text2num(query_irv_get_votes.item[1])) + qdel(query_irv_get_votes) + + var/list/datum/polloption/options = list() + + var/datum/DBQuery/query_irv_options = SSdbcore.NewQuery("SELECT id, text FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + if(!query_irv_options.warn_execute()) + qdel(query_irv_options) + return + while(query_irv_options.NextRow()) + var/datum/polloption/PO = new() + PO.optionid = text2num(query_irv_options.item[1]) + PO.optiontext = query_irv_options.item[2] + options["[PO.optionid]"] += PO + qdel(query_irv_options) + + //if they already voted, use their sort + if (votedfor.len) + var/list/datum/polloption/newoptions = list() + for (var/V in votedfor) + var/datum/polloption/PO = options["[V]"] + if(PO) + newoptions["[V]"] = PO + options -= "[V]" + //add any options that they didn't vote on (some how, some way) + options = shuffle(options) + for (var/V in options) + newoptions["[V]"] = options["[V]"] + options = newoptions + //otherwise, lets shuffle it. + else + var/list/datum/polloption/newoptions = list() + while (options.len) + var/list/local_options = options.Copy() + var/key + //the jist is we randomly remove all options from a copy of options until only one reminds, + // move that over to our new list + // and repeat until we've moved all of them + while (local_options.len) + key = local_options[rand(1, local_options.len)] + local_options -= key + var/value = options[key] + options -= key + newoptions[key] = value + options = newoptions + + var/output = {" + + + + + + + + + +
                Player poll
                + Question: [pollquestion]
                Please sort the options in the order of most preferred to least preferred
                + Revoting has been enabled on this poll, if you think you made a mistake, simply revote
                + Poll runs from [pollstarttime] until [pollendtime]

                +

                +
                + + + + +
                +
                Most Preferred
                +
                  + "} + for(var/O in options) + var/datum/polloption/PO = options["[O]"] + if(PO.optionid && PO.optiontext) + output += "
                1. [PO.optiontext]
                2. \n" + output += {" +
                +
                Least Preferred

                +
                +

                + "} + src << browse(null ,"window=playerpolllist") + src << browse(output,"window=playerpoll;size=500x500") + return + +//Returns null on failure, TRUE if already voted, FALSE if not voted yet. +/mob/dead/new_player/proc/poll_check_voted(pollid, text = FALSE, silent = FALSE) + var/table = "poll_vote" + if (text) + table = "poll_textreply" + if (!SSdbcore.Connect()) + to_chat(usr, "Failed to establish database connection.") + return + var/datum/DBQuery/query_hasvoted = SSdbcore.NewQuery("SELECT id FROM `[format_table_name(table)]` WHERE pollid = [pollid] AND ckey = '[ckey]'") + if(!query_hasvoted.warn_execute()) + qdel(query_hasvoted) + return + if(query_hasvoted.NextRow()) + qdel(query_hasvoted) + if(!silent) + to_chat(usr, "You've already replied to this poll.") + return TRUE + qdel(query_hasvoted) + return FALSE + +//Returns adminrank for use in polls. +/mob/dead/new_player/proc/poll_rank() + . = "Player" + if(client.holder) + . = client.holder.rank.name + + +/mob/dead/new_player/proc/vote_rig_check() + if (usr != src) + if (!usr || !src) + return 0 + //we gots ourselfs a dirty cheater on our hands! + log_game("[key_name(usr)] attempted to rig the vote by voting as [key]") + message_admins("[key_name_admin(usr)] attempted to rig the vote by voting as [key]") + to_chat(usr, "You don't seem to be [key].") + to_chat(src, "Something went horribly wrong processing your vote. Please contact an administrator, they should have gotten a message about this") + return 0 + return 1 + +/mob/dead/new_player/proc/vote_valid_check(pollid, holder, type) + if (!SSdbcore.Connect()) + to_chat(src, "Failed to establish database connection.") + return 0 + pollid = text2num(pollid) + if (!pollid || pollid < 0) + return 0 + //validate the poll is actually the right type of poll and its still active + var/datum/DBQuery/query_validate_poll = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_question")] WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime AND polltype = '[type]' [(holder ? "" : "AND adminonly = false")]") + if(!query_validate_poll.warn_execute()) + qdel(query_validate_poll) + return 0 + if (!query_validate_poll.NextRow()) + qdel(query_validate_poll) + return 0 + qdel(query_validate_poll) + return 1 + +/mob/dead/new_player/proc/vote_on_irv_poll(pollid, list/votelist) + if (!SSdbcore.Connect()) + to_chat(src, "Failed to establish database connection.") + return 0 + if (!vote_rig_check()) + return 0 + pollid = text2num(pollid) + if (!pollid || pollid < 0) + return 0 + if (!votelist || !istype(votelist) || !votelist.len) + return 0 + if (!client) + return 0 + //save these now so we can still process the vote if the client goes away while we process. + var/datum/admins/holder = client.holder + var/rank = "Player" + if (holder) + rank = holder.rank.name + var/ckey = client.ckey + var/address = client.address + + //validate the poll + if (!vote_valid_check(pollid, holder, POLLTYPE_IRV)) + return 0 + + //lets collect the options + var/datum/DBQuery/query_irv_id = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_option")] WHERE pollid = [pollid]") + if(!query_irv_id.warn_execute()) + qdel(query_irv_id) + return 0 + var/list/optionlist = list() + while (query_irv_id.NextRow()) + optionlist += text2num(query_irv_id.item[1]) + qdel(query_irv_id) + + //validate their votes are actually in the list of options and actually numbers + var/list/numberedvotelist = list() + for (var/vote in votelist) + vote = text2num(vote) + numberedvotelist += vote + if (!vote) //this is fine because voteid starts at 1, so it will never be 0 + to_chat(src, "Error: Invalid (non-numeric) votes in the vote data.") + return 0 + if (!(vote in optionlist)) + to_chat(src, "Votes for choices that do not appear to be in the poll detected.") + return 0 + if (!numberedvotelist.len) + to_chat(src, "Invalid vote data") + return 0 + + //lets add the vote, first we generate an insert statement. + + var/sqlrowlist = "" + for (var/vote in numberedvotelist) + if (sqlrowlist != "") + sqlrowlist += ", " //a comma (,) at the start of the first row to insert will trigger a SQL error + sqlrowlist += "(Now(), [pollid], [vote], '[sanitizeSQL(ckey)]', INET_ATON('[sanitizeSQL(address)]'), '[sanitizeSQL(rank)]')" + + //now lets delete their old votes (if any) + var/datum/DBQuery/query_irv_del_old = SSdbcore.NewQuery("DELETE FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + if(!query_irv_del_old.warn_execute()) + qdel(query_irv_del_old) + return 0 + qdel(query_irv_del_old) + + //now to add the new ones. + var/datum/DBQuery/query_irv_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES [sqlrowlist]") + if(!query_irv_vote.warn_execute()) + qdel(query_irv_vote) + return 0 + qdel(query_irv_vote) + if(!QDELETED(src)) + src << browse(null,"window=playerpoll") + return 1 + + +/mob/dead/new_player/proc/vote_on_poll(pollid, optionid) + if (!SSdbcore.Connect()) + to_chat(src, "Failed to establish database connection.") + return 0 + if (!vote_rig_check()) + return 0 + if(!pollid || !optionid) + return + //validate the poll + if (!vote_valid_check(pollid, client.holder, POLLTYPE_OPTION)) + return 0 + var/voted = poll_check_voted(pollid) + if(isnull(voted) || voted) //Failed or already voted. + return + var/adminrank = sanitizeSQL(poll_rank()) + if(!adminrank) + return + var/datum/DBQuery/query_option_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]')") + if(!query_option_vote.warn_execute()) + qdel(query_option_vote) + return + qdel(query_option_vote) + if(!QDELETED(usr)) + usr << browse(null,"window=playerpoll") + return 1 + +/mob/dead/new_player/proc/log_text_poll_reply(pollid, replytext) + if (!SSdbcore.Connect()) + to_chat(src, "Failed to establish database connection.") + return 0 + if (!vote_rig_check()) + return 0 + if(!pollid) + return + //validate the poll + if (!vote_valid_check(pollid, client.holder, POLLTYPE_TEXT)) + return 0 + if(!replytext) + to_chat(usr, "The text you entered was blank. Please correct the text and submit again.") + return + var/voted = poll_check_voted(pollid, text = TRUE, silent = TRUE) + if(isnull(voted)) + return + var/adminrank = sanitizeSQL(poll_rank()) + if(!adminrank) + return + replytext = sanitizeSQL(replytext) + if(!(length(replytext) > 0) || !(length(replytext) <= 8000)) + to_chat(usr, "The text you entered was invalid or too long. Please correct the text and submit again.") + return + var/datum/DBQuery/query_text_vote + if(!voted) + query_text_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_textreply")] (datetime ,pollid ,ckey ,ip ,replytext ,adminrank) VALUES (Now(), [pollid], '[ckey]', INET_ATON('[client.address]'), '[replytext]', '[adminrank]')") + else + query_text_vote = SSdbcore.NewQuery("UPDATE [format_table_name("poll_textreply")] SET datetime = Now(), ip = INET_ATON('[client.address]'), replytext = '[replytext]' WHERE pollid = '[pollid]' AND ckey = '[ckey]'") + if(!query_text_vote.warn_execute()) + qdel(query_text_vote) + return + qdel(query_text_vote) + if(!QDELETED(usr)) + usr << browse(null,"window=playerpoll") + return 1 + +/mob/dead/new_player/proc/vote_on_numval_poll(pollid, optionid, rating) + if (!SSdbcore.Connect()) + to_chat(src, "Failed to establish database connection.") + return 0 + if (!vote_rig_check()) + return 0 + if(!pollid || !optionid || !rating) + return + //validate the poll + if (!vote_valid_check(pollid, client.holder, POLLTYPE_RATING)) + return 0 + var/datum/DBQuery/query_numval_hasvoted = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE optionid = [optionid] AND ckey = '[ckey]'") + if(!query_numval_hasvoted.warn_execute()) + qdel(query_numval_hasvoted) + return + if(query_numval_hasvoted.NextRow()) + qdel(query_numval_hasvoted) + to_chat(usr, "You've already replied to this poll.") + return + qdel(query_numval_hasvoted) + var/adminrank = "Player" + if(client.holder) + adminrank = client.holder.rank.name + adminrank = sanitizeSQL(adminrank) + var/datum/DBQuery/query_numval_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]', [(isnull(rating)) ? "null" : rating])") + if(!query_numval_vote.warn_execute()) + qdel(query_numval_vote) + return + qdel(query_numval_vote) + if(!QDELETED(usr)) + usr << browse(null,"window=playerpoll") + return 1 + +/mob/dead/new_player/proc/vote_on_multi_poll(pollid, optionid) + if (!SSdbcore.Connect()) + to_chat(src, "Failed to establish database connection.") + return 0 + if (!vote_rig_check()) + return 0 + if(!pollid || !optionid) + return 1 + //validate the poll + if (!vote_valid_check(pollid, client.holder, POLLTYPE_MULTI)) + return 0 + var/datum/DBQuery/query_multi_choicelen = SSdbcore.NewQuery("SELECT multiplechoiceoptions FROM [format_table_name("poll_question")] WHERE id = [pollid]") + if(!query_multi_choicelen.warn_execute()) + qdel(query_multi_choicelen) + return 1 + var/i + if(query_multi_choicelen.NextRow()) + i = text2num(query_multi_choicelen.item[1]) + qdel(query_multi_choicelen) + var/datum/DBQuery/query_multi_hasvoted = SSdbcore.NewQuery("SELECT id FROM [format_table_name("poll_vote")] WHERE pollid = [pollid] AND ckey = '[ckey]'") + if(!query_multi_hasvoted.warn_execute()) + qdel(query_multi_hasvoted) + return 1 + while(i) + if(query_multi_hasvoted.NextRow()) + i-- + else + break + qdel(query_multi_hasvoted) + if(!i) + return 2 + var/adminrank = "Player" + if(!QDELETED(client) && client.holder) + adminrank = client.holder.rank.name + adminrank = sanitizeSQL(adminrank) + var/datum/DBQuery/query_multi_vote = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_vote")] (datetime, pollid, optionid, ckey, ip, adminrank) VALUES (Now(), [pollid], [optionid], '[ckey]', INET_ATON('[client.address]'), '[adminrank]')") + if(!query_multi_vote.warn_execute()) + qdel(query_multi_vote) + return 1 + qdel(query_multi_vote) + if(!QDELETED(usr)) + usr << browse(null,"window=playerpoll") + return 0 diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm index 5b664dc01a..a82054d500 100644 --- a/code/modules/mob/dead/new_player/preferences_setup.dm +++ b/code/modules/mob/dead/new_player/preferences_setup.dm @@ -1,62 +1,62 @@ - - //The mob should have a gender you want before running this proc. Will run fine without H -/datum/preferences/proc/random_character(gender_override) - if(gender_override) - gender = gender_override - else - gender = pick(MALE,FEMALE) - underwear = random_underwear(gender) - undie_color = random_short_color() - undershirt = random_undershirt(gender) - shirt_color = random_short_color() - socks = random_socks() - socks_color = random_short_color() - skin_tone = random_skin_tone() - hair_style = random_hair_style(gender) - facial_hair_style = random_facial_hair_style(gender) - hair_color = random_short_color() - facial_hair_color = hair_color - eye_color = random_eye_color() - horn_color = "85615a" - wing_color = "fff" - if(!pref_species) - var/rando_race = pick(GLOB.roundstart_races) - pref_species = new rando_race() - features = random_features() - age = rand(AGE_MIN,AGE_MAX) - -/datum/preferences/proc/update_preview_icon(equip_job = TRUE) - // Determine what job is marked as 'High' priority, and dress them up as such. - var/datum/job/previewJob = get_highest_job() - - if(previewJob) - // Silicons only need a very basic preview since there is no customization for them. - if(istype(previewJob,/datum/job/ai)) - parent.show_character_previews(image('icons/mob/ai.dmi', icon_state = resolve_ai_icon(preferred_ai_core_display), dir = SOUTH)) - return - if(istype(previewJob,/datum/job/cyborg)) - parent.show_character_previews(image('icons/mob/robots.dmi', icon_state = "robot", dir = SOUTH)) - return - - // Set up the dummy for its photoshoot - var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES) - // Apply the Dummy's preview background first so we properly layer everything else on top of it. - mannequin.add_overlay(mutable_appearance('modular_citadel/icons/ui/backgrounds.dmi', bgstate, layer = SPACE_LAYER)) - copy_to(mannequin) - - if(previewJob && equip_job) - mannequin.job = previewJob.title - previewJob.equip(mannequin, TRUE, preference_source = parent) - - COMPILE_OVERLAYS(mannequin) - parent.show_character_previews(new /mutable_appearance(mannequin)) - unset_busy_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES) - -/datum/preferences/proc/get_highest_job() - var/highest_pref = 0 - var/datum/job/highest_job - for(var/job in job_preferences) - if(job_preferences["[job]"] > highest_pref) - highest_job = SSjob.GetJob(job) - highest_pref = job_preferences["[job]"] - return highest_job + + //The mob should have a gender you want before running this proc. Will run fine without H +/datum/preferences/proc/random_character(gender_override) + if(gender_override) + gender = gender_override + else + gender = pick(MALE,FEMALE) + underwear = random_underwear(gender) + undie_color = random_short_color() + undershirt = random_undershirt(gender) + shirt_color = random_short_color() + socks = random_socks() + socks_color = random_short_color() + skin_tone = random_skin_tone() + hair_style = random_hair_style(gender) + facial_hair_style = random_facial_hair_style(gender) + hair_color = random_short_color() + facial_hair_color = hair_color + eye_color = random_eye_color() + horn_color = "85615a" + wing_color = "fff" + if(!pref_species) + var/rando_race = pick(GLOB.roundstart_races) + pref_species = new rando_race() + features = random_features() + age = rand(AGE_MIN,AGE_MAX) + +/datum/preferences/proc/update_preview_icon(equip_job = TRUE) + // Determine what job is marked as 'High' priority, and dress them up as such. + var/datum/job/previewJob = get_highest_job() + + if(previewJob) + // Silicons only need a very basic preview since there is no customization for them. + if(istype(previewJob,/datum/job/ai)) + parent.show_character_previews(image('icons/mob/ai.dmi', icon_state = resolve_ai_icon(preferred_ai_core_display), dir = SOUTH)) + return + if(istype(previewJob,/datum/job/cyborg)) + parent.show_character_previews(image('icons/mob/robots.dmi', icon_state = "robot", dir = SOUTH)) + return + + // Set up the dummy for its photoshoot + var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES) + // Apply the Dummy's preview background first so we properly layer everything else on top of it. + mannequin.add_overlay(mutable_appearance('modular_citadel/icons/ui/backgrounds.dmi', bgstate, layer = SPACE_LAYER)) + copy_to(mannequin) + + if(previewJob && equip_job) + mannequin.job = previewJob.title + previewJob.equip(mannequin, TRUE, preference_source = parent) + + COMPILE_OVERLAYS(mannequin) + parent.show_character_previews(new /mutable_appearance(mannequin)) + unset_busy_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES) + +/datum/preferences/proc/get_highest_job() + var/highest_pref = 0 + var/datum/job/highest_job + for(var/job in job_preferences) + if(job_preferences["[job]"] > highest_pref) + highest_job = SSjob.GetJob(job) + highest_pref = job_preferences["[job]"] + return highest_job diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm index 69cbd0830a..9bbacdd930 100644 --- a/code/modules/mob/dead/observer/say.dm +++ b/code/modules/mob/dead/observer/say.dm @@ -1,40 +1,40 @@ -/mob/dead/observer/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) - if (!message) - return - - var/message_mode = get_message_mode(message) - if(client && (message_mode == MODE_ADMIN || message_mode == MODE_DEADMIN)) - message = copytext(message, 3) - if(findtext(message, " ", 1, 2)) - message = copytext(message, 2) - - if(message_mode == MODE_ADMIN) - client.cmd_admin_say(message) - else if(message_mode == MODE_DEADMIN) - client.dsay(message) - return - - src.log_talk(message, LOG_SAY, tag="ghost") - - if(check_emote(message)) - return - - . = say_dead(message) - -/mob/dead/observer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) - . = ..() - var/atom/movable/to_follow = speaker - if(radio_freq) - var/atom/movable/virtualspeaker/V = speaker - - if(isAI(V.source)) - var/mob/living/silicon/ai/S = V.source - to_follow = S.eyeobj - else - to_follow = V.source - var/link = FOLLOW_LINK(src, to_follow) - // Recompose the message, because it's scrambled by default - message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) - to_chat(src, "[link] [message]") - +/mob/dead/observer/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) + message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) + if (!message) + return + + var/message_mode = get_message_mode(message) + if(client && (message_mode == MODE_ADMIN || message_mode == MODE_DEADMIN)) + message = copytext(message, 3) + if(findtext(message, " ", 1, 2)) + message = copytext(message, 2) + + if(message_mode == MODE_ADMIN) + client.cmd_admin_say(message) + else if(message_mode == MODE_DEADMIN) + client.dsay(message) + return + + src.log_talk(message, LOG_SAY, tag="ghost") + + if(check_emote(message)) + return + + . = say_dead(message) + +/mob/dead/observer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + var/atom/movable/to_follow = speaker + if(radio_freq) + var/atom/movable/virtualspeaker/V = speaker + + if(isAI(V.source)) + var/mob/living/silicon/ai/S = V.source + to_follow = S.eyeobj + else + to_follow = V.source + var/link = FOLLOW_LINK(src, to_follow) + // Recompose the message, because it's scrambled by default + message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) + to_chat(src, "[link] [message]") + diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index e77a91332e..9f7d2067de 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -1,473 +1,473 @@ -//These procs handle putting s tuff in your hands -//as they handle all relevant stuff like adding it to the player's screen and updating their overlays. - -//Returns the thing we're currently holding -/mob/proc/get_active_held_item() - return get_item_for_held_index(active_hand_index) - - -//Finds the opposite limb for the active one (eg: upper left arm will find the item in upper right arm) -//So we're treating each "pair" of limbs as a team, so "both" refers to them -/mob/proc/get_inactive_held_item() - return get_item_for_held_index(get_inactive_hand_index()) - - -//Finds the opposite index for the active one (eg: upper left arm will find the item in upper right arm) -//So we're treating each "pair" of limbs as a team, so "both" refers to them -/mob/proc/get_inactive_hand_index() - var/other_hand = 0 - if(!(active_hand_index % 2)) - other_hand = active_hand_index-1 //finding the matching "left" limb - else - other_hand = active_hand_index+1 //finding the matching "right" limb - if(other_hand < 0 || other_hand > held_items.len) - other_hand = 0 - return other_hand - - -/mob/proc/get_item_for_held_index(i) - if(i > 0 && i <= held_items.len) - return held_items[i] - return FALSE - - -//Odd = left. Even = right -/mob/proc/held_index_to_dir(i) - if(!(i % 2)) - return "r" - return "l" - - -//Check we have an organ for this hand slot (Dismemberment), Only relevant for humans -/mob/proc/has_hand_for_held_index(i) - return TRUE - - -//Check we have an organ for our active hand slot (Dismemberment),Only relevant for humans -/mob/proc/has_active_hand() - return has_hand_for_held_index(active_hand_index) - - -//Finds the first available (null) index OR all available (null) indexes in held_items based on a side. -//Lefts: 1, 3, 5, 7... -//Rights:2, 4, 6, 8... -/mob/proc/get_empty_held_index_for_side(side = "left", all = FALSE) - var/start = 0 - var/static/list/lefts = list("l" = TRUE,"L" = TRUE,"LEFT" = TRUE,"left" = TRUE) - var/static/list/rights = list("r" = TRUE,"R" = TRUE,"RIGHT" = TRUE,"right" = TRUE) //"to remain silent" - if(lefts[side]) - start = 1 - else if(rights[side]) - start = 2 - if(!start) - return FALSE - var/list/empty_indexes - for(var/i in start to held_items.len step 2) - if(!held_items[i]) - if(!all) - return i - if(!empty_indexes) - empty_indexes = list() - empty_indexes += i - return empty_indexes - - -//Same as the above, but returns the first or ALL held *ITEMS* for the side -/mob/proc/get_held_items_for_side(side = "left", all = FALSE) - var/start = 0 - var/static/list/lefts = list("l" = TRUE,"L" = TRUE,"LEFT" = TRUE,"left" = TRUE) - var/static/list/rights = list("r" = TRUE,"R" = TRUE,"RIGHT" = TRUE,"right" = TRUE) //"to remain silent" - if(lefts[side]) - start = 1 - else if(rights[side]) - start = 2 - if(!start) - return FALSE - var/list/holding_items - for(var/i in start to held_items.len step 2) - var/obj/item/I = held_items[i] - if(I) - if(!all) - return I - if(!holding_items) - holding_items = list() - holding_items += I - return holding_items - - -/mob/proc/get_empty_held_indexes() - var/list/L - for(var/i in 1 to held_items.len) - if(!held_items[i]) - if(!L) - L = list() - L += i - return L - -/mob/proc/get_held_index_of_item(obj/item/I) - return held_items.Find(I) - - -//Sad that this will cause some overhead, but the alias seems necessary -//*I* may be happy with a million and one references to "indexes" but others won't be -/mob/proc/is_holding(obj/item/I) - return get_held_index_of_item(I) - - -//Checks if we're holding an item of type: typepath -/mob/proc/is_holding_item_of_type(typepath) - for(var/obj/item/I in held_items) - if(istype(I, typepath)) - return I - return FALSE - -//Checks if we're holding a tool that has given quality -//Returns the tool that has the best version of this quality -/mob/proc/is_holding_tool_quality(quality) - var/obj/item/best_item - var/best_quality = INFINITY - - for(var/obj/item/I in held_items) - if(I.tool_behaviour == quality && I.toolspeed < best_quality) - best_item = I - best_quality = I.toolspeed - - return best_item - - -//To appropriately fluff things like "they are holding [I] in their [get_held_index_name(get_held_index_of_item(I))]" -//Can be overridden to pass off the fluff to something else (eg: science allowing people to add extra robotic limbs, and having this proc react to that -// with say "they are holding [I] in their Nanotrasen Brand Utility Arm - Right Edition" or w/e -/mob/proc/get_held_index_name(i) - var/list/hand = list() - if(i > 2) - hand += "upper " - var/num = 0 - if(!(i % 2)) - num = i-2 - hand += "right hand" - else - num = i-1 - hand += "left hand" - num -= (num*0.5) - if(num > 1) //"upper left hand #1" seems weird, but "upper left hand #2" is A-ok - hand += " #[num]" - return hand.Join() - - - -//Returns if a certain item can be equipped to a certain slot. -// Currently invalid for two-handed items - call obj/item/mob_can_equip() instead. -/mob/proc/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) - return FALSE - -/mob/proc/can_put_in_hand(I, hand_index) - if(hand_index > held_items.len) - return FALSE - if(!put_in_hand_check(I)) - return FALSE - if(!has_hand_for_held_index(hand_index)) - return FALSE - return !held_items[hand_index] - -/mob/proc/put_in_hand(obj/item/I, hand_index, forced = FALSE, ignore_anim = TRUE) - if(forced || can_put_in_hand(I, hand_index)) - if(isturf(I.loc) && !ignore_anim) - I.do_pickup_animation(src) - if(hand_index == null) - return FALSE - if(get_item_for_held_index(hand_index) != null) - dropItemToGround(get_item_for_held_index(hand_index), force = TRUE) - I.forceMove(src) - held_items[hand_index] = I - I.layer = ABOVE_HUD_LAYER - I.plane = ABOVE_HUD_PLANE - I.equipped(src, SLOT_HANDS) - if(I.pulledby) - I.pulledby.stop_pulling() - update_inv_hands() - I.pixel_x = initial(I.pixel_x) - I.pixel_y = initial(I.pixel_y) - I.transform = initial(I.transform) - return hand_index || TRUE - return FALSE - -//Puts the item into the first available left hand if possible and calls all necessary triggers/updates. returns 1 on success. -/mob/proc/put_in_l_hand(obj/item/I) - return put_in_hand(I, get_empty_held_index_for_side("l")) - - -//Puts the item into the first available right hand if possible and calls all necessary triggers/updates. returns 1 on success. -/mob/proc/put_in_r_hand(obj/item/I) - return put_in_hand(I, get_empty_held_index_for_side("r")) - - -/mob/proc/put_in_hand_check(obj/item/I) - if(incapacitated() && !(I.item_flags&ABSTRACT)) //Cit change - Changes lying to incapacitated so that it's plausible to pick things up while on the ground - return FALSE - if(!istype(I)) - return FALSE - return TRUE - - -//Puts the item into our active hand if possible. returns TRUE on success. -/mob/proc/put_in_active_hand(obj/item/I, forced = FALSE, ignore_animation = TRUE) - return put_in_hand(I, active_hand_index, forced, ignore_animation) - - -//Puts the item into our inactive hand if possible, returns TRUE on success -/mob/proc/put_in_inactive_hand(obj/item/I) - return put_in_hand(I, get_inactive_hand_index()) - - -//Puts the item our active hand if possible. Failing that it tries other hands. Returns TRUE on success. -//If both fail it drops it on the floor and returns FALSE. -//This is probably the main one you need to know :) -/mob/proc/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE, forced = FALSE) - if(!I) - return FALSE - - // If the item is a stack and we're already holding a stack then merge - if (istype(I, /obj/item/stack)) - var/obj/item/stack/I_stack = I - var/obj/item/stack/active_stack = get_active_held_item() - - if (I_stack.zero_amount()) - return FALSE - - if (merge_stacks) - if (istype(active_stack) && istype(I_stack, active_stack.merge_type)) - if (I_stack.merge(active_stack)) - to_chat(usr, "Your [active_stack.name] stack now contains [active_stack.get_amount()] [active_stack.singular_name]\s.") - return TRUE - else - var/obj/item/stack/inactive_stack = get_inactive_held_item() - if (istype(inactive_stack) && istype(I_stack, inactive_stack.merge_type)) - if (I_stack.merge(inactive_stack)) - to_chat(usr, "Your [inactive_stack.name] stack now contains [inactive_stack.get_amount()] [inactive_stack.singular_name]\s.") - return TRUE - - if(put_in_active_hand(I, forced)) - return TRUE - - var/hand = get_empty_held_index_for_side("l") - if(!hand) - hand = get_empty_held_index_for_side("r") - if(hand) - if(put_in_hand(I, hand, forced)) - return TRUE - if(del_on_fail) - qdel(I) - return FALSE - I.forceMove(drop_location()) - I.layer = initial(I.layer) - I.plane = initial(I.plane) - I.dropped(src) - return FALSE - -/mob/proc/drop_all_held_items() - . = FALSE - for(var/obj/item/I in held_items) - . |= dropItemToGround(I) - -//Here lie drop_from_inventory and before_item_take, already forgotten and not missed. - -/mob/proc/canUnEquip(obj/item/I, force) - if(!I) - return TRUE - if(HAS_TRAIT(I, TRAIT_NODROP) && !force) - return FALSE - return TRUE - -/mob/proc/putItemFromInventoryInHandIfPossible(obj/item/I, hand_index, force_removal = FALSE) - if(!can_put_in_hand(I, hand_index)) - return FALSE - if(!temporarilyRemoveItemFromInventory(I, force_removal)) - return FALSE - I.remove_item_from_storage(src) - if(!put_in_hand(I, hand_index)) - qdel(I) - CRASH("Assertion failure: putItemFromInventoryInHandIfPossible") //should never be possible - return TRUE - -//The following functions are the same save for one small difference - -//for when you want the item to end up on the ground -//will force move the item to the ground and call the turf's Entered -/mob/proc/dropItemToGround(obj/item/I, force = FALSE) - return doUnEquip(I, force, drop_location(), FALSE) - -//for when the item will be immediately placed in a loc other than the ground -/mob/proc/transferItemToLoc(obj/item/I, newloc = null, force = FALSE) - return doUnEquip(I, force, newloc, FALSE) - -//visibly unequips I but it is NOT MOVED AND REMAINS IN SRC -//item MUST BE FORCEMOVE'D OR QDEL'D -/mob/proc/temporarilyRemoveItemFromInventory(obj/item/I, force = FALSE, idrop = TRUE) - return doUnEquip(I, force, null, TRUE, idrop) - -//DO NOT CALL THIS PROC -//use one of the above 3 helper procs -//you may override it, but do not modify the args -/mob/proc/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) //Force overrides TRAIT_NODROP for things like wizarditis and admin undress. - //Use no_move if the item is just gonna be immediately moved afterward - //Invdrop is used to prevent stuff in pockets dropping. only set to false if it's going to immediately be replaced - if(!I) //If there's nothing to drop, the drop is automatically succesfull. If(unEquip) should generally be used to check for TRAIT_NODROP. - return TRUE - - if(HAS_TRAIT(I, TRAIT_NODROP) && !force) - return FALSE - - var/hand_index = get_held_index_of_item(I) - if(hand_index) - held_items[hand_index] = null - update_inv_hands() - if(I) - if(client) - client.screen -= I - I.layer = initial(I.layer) - I.plane = initial(I.plane) - I.appearance_flags &= ~NO_CLIENT_COLOR - if(!no_move && !(I.item_flags & DROPDEL)) //item may be moved/qdel'd immedietely, don't bother moving it - if (isnull(newloc)) - I.moveToNullspace() - else - I.forceMove(newloc) - I.dropped(src) - return TRUE - -//Outdated but still in use apparently. This should at least be a human proc. -//Daily reminder to murder this - Remie. -/mob/living/proc/get_equipped_items(include_pockets = FALSE) - return - -/mob/living/carbon/get_equipped_items(include_pockets = FALSE) - var/list/items = list() - if(back) - items += back - if(head) - items += head - if(wear_mask) - items += wear_mask - if(wear_neck) - items += wear_neck - return items - -/mob/living/carbon/human/get_equipped_items(include_pockets = FALSE) - var/list/items = ..() - if(belt) - items += belt - if(ears) - items += ears - if(glasses) - items += glasses - if(gloves) - items += gloves - if(shoes) - items += shoes - if(wear_id) - items += wear_id - if(wear_suit) - items += wear_suit - if(w_uniform) - items += w_uniform - if(include_pockets) - if(l_store) - items += l_store - if(r_store) - items += r_store - if(s_store) - items += s_store - return items - -/mob/living/proc/unequip_everything() - var/list/items = list() - items |= get_equipped_items(TRUE) - for(var/I in items) - dropItemToGround(I) - drop_all_held_items() - -/obj/item/proc/equip_to_best_slot(mob/M) - if(src != M.get_active_held_item()) - to_chat(M, "You are not holding anything to equip!") - return FALSE - - if(M.equip_to_appropriate_slot(src)) - M.update_inv_hands() - return TRUE - else - if(equip_delay_self) - return - - if(M.active_storage && M.active_storage.parent && SEND_SIGNAL(M.active_storage.parent, COMSIG_TRY_STORAGE_INSERT, src,M)) - return TRUE - - var/list/obj/item/possible = list(M.get_inactive_held_item(), M.get_item_by_slot(SLOT_BELT), M.get_item_by_slot(SLOT_GENERC_DEXTROUS_STORAGE), M.get_item_by_slot(SLOT_BACK)) - for(var/i in possible) - if(!i) - continue - var/obj/item/I = i - if(SEND_SIGNAL(I, COMSIG_TRY_STORAGE_INSERT, src, M)) - return TRUE - - to_chat(M, "You are unable to equip that!") - return FALSE - - -/mob/verb/quick_equip() - set name = "quick-equip" - set hidden = 1 - - var/obj/item/I = get_active_held_item() - if (I) - I.equip_to_best_slot(src) - -//used in code for items usable by both carbon and drones, this gives the proper back slot for each mob.(defibrillator, backpack watertank, ...) -/mob/proc/getBackSlot() - return SLOT_BACK - -/mob/proc/getBeltSlot() - return SLOT_BELT - - - -//Inventory.dm is -kind of- an ok place for this I guess - -//This is NOT for dismemberment, as the user still technically has 2 "hands" -//This is for multi-handed mobs, such as a human with a third limb installed -//This is a very rare proc to call (besides admin fuckery) so -//any cost it has isn't a worry -/mob/proc/change_number_of_hands(amt) - if(amt < held_items.len) - for(var/i in held_items.len to amt step -1) - dropItemToGround(held_items[i]) - held_items.len = amt - - if(hud_used) - hud_used.build_hand_slots() - - -/mob/living/carbon/human/change_number_of_hands(amt) - var/old_limbs = held_items.len - if(amt < old_limbs) - for(var/i in hand_bodyparts.len to amt step -1) - var/obj/item/bodypart/BP = hand_bodyparts[i] - BP.dismember() - hand_bodyparts[i] = null - hand_bodyparts.len = amt - else if(amt > old_limbs) - hand_bodyparts.len = amt - for(var/i in old_limbs+1 to amt) - var/path = /obj/item/bodypart/l_arm - if(!(i % 2)) - path = /obj/item/bodypart/r_arm - - var/obj/item/bodypart/BP = new path () - BP.owner = src - BP.held_index = i - bodyparts += BP - hand_bodyparts[i] = BP - ..() //Don't redraw hands until we have organs for them - -/mob/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool) - return ..() && (user == src) +//These procs handle putting s tuff in your hands +//as they handle all relevant stuff like adding it to the player's screen and updating their overlays. + +//Returns the thing we're currently holding +/mob/proc/get_active_held_item() + return get_item_for_held_index(active_hand_index) + + +//Finds the opposite limb for the active one (eg: upper left arm will find the item in upper right arm) +//So we're treating each "pair" of limbs as a team, so "both" refers to them +/mob/proc/get_inactive_held_item() + return get_item_for_held_index(get_inactive_hand_index()) + + +//Finds the opposite index for the active one (eg: upper left arm will find the item in upper right arm) +//So we're treating each "pair" of limbs as a team, so "both" refers to them +/mob/proc/get_inactive_hand_index() + var/other_hand = 0 + if(!(active_hand_index % 2)) + other_hand = active_hand_index-1 //finding the matching "left" limb + else + other_hand = active_hand_index+1 //finding the matching "right" limb + if(other_hand < 0 || other_hand > held_items.len) + other_hand = 0 + return other_hand + + +/mob/proc/get_item_for_held_index(i) + if(i > 0 && i <= held_items.len) + return held_items[i] + return FALSE + + +//Odd = left. Even = right +/mob/proc/held_index_to_dir(i) + if(!(i % 2)) + return "r" + return "l" + + +//Check we have an organ for this hand slot (Dismemberment), Only relevant for humans +/mob/proc/has_hand_for_held_index(i) + return TRUE + + +//Check we have an organ for our active hand slot (Dismemberment),Only relevant for humans +/mob/proc/has_active_hand() + return has_hand_for_held_index(active_hand_index) + + +//Finds the first available (null) index OR all available (null) indexes in held_items based on a side. +//Lefts: 1, 3, 5, 7... +//Rights:2, 4, 6, 8... +/mob/proc/get_empty_held_index_for_side(side = "left", all = FALSE) + var/start = 0 + var/static/list/lefts = list("l" = TRUE,"L" = TRUE,"LEFT" = TRUE,"left" = TRUE) + var/static/list/rights = list("r" = TRUE,"R" = TRUE,"RIGHT" = TRUE,"right" = TRUE) //"to remain silent" + if(lefts[side]) + start = 1 + else if(rights[side]) + start = 2 + if(!start) + return FALSE + var/list/empty_indexes + for(var/i in start to held_items.len step 2) + if(!held_items[i]) + if(!all) + return i + if(!empty_indexes) + empty_indexes = list() + empty_indexes += i + return empty_indexes + + +//Same as the above, but returns the first or ALL held *ITEMS* for the side +/mob/proc/get_held_items_for_side(side = "left", all = FALSE) + var/start = 0 + var/static/list/lefts = list("l" = TRUE,"L" = TRUE,"LEFT" = TRUE,"left" = TRUE) + var/static/list/rights = list("r" = TRUE,"R" = TRUE,"RIGHT" = TRUE,"right" = TRUE) //"to remain silent" + if(lefts[side]) + start = 1 + else if(rights[side]) + start = 2 + if(!start) + return FALSE + var/list/holding_items + for(var/i in start to held_items.len step 2) + var/obj/item/I = held_items[i] + if(I) + if(!all) + return I + if(!holding_items) + holding_items = list() + holding_items += I + return holding_items + + +/mob/proc/get_empty_held_indexes() + var/list/L + for(var/i in 1 to held_items.len) + if(!held_items[i]) + if(!L) + L = list() + L += i + return L + +/mob/proc/get_held_index_of_item(obj/item/I) + return held_items.Find(I) + + +//Sad that this will cause some overhead, but the alias seems necessary +//*I* may be happy with a million and one references to "indexes" but others won't be +/mob/proc/is_holding(obj/item/I) + return get_held_index_of_item(I) + + +//Checks if we're holding an item of type: typepath +/mob/proc/is_holding_item_of_type(typepath) + for(var/obj/item/I in held_items) + if(istype(I, typepath)) + return I + return FALSE + +//Checks if we're holding a tool that has given quality +//Returns the tool that has the best version of this quality +/mob/proc/is_holding_tool_quality(quality) + var/obj/item/best_item + var/best_quality = INFINITY + + for(var/obj/item/I in held_items) + if(I.tool_behaviour == quality && I.toolspeed < best_quality) + best_item = I + best_quality = I.toolspeed + + return best_item + + +//To appropriately fluff things like "they are holding [I] in their [get_held_index_name(get_held_index_of_item(I))]" +//Can be overridden to pass off the fluff to something else (eg: science allowing people to add extra robotic limbs, and having this proc react to that +// with say "they are holding [I] in their Nanotrasen Brand Utility Arm - Right Edition" or w/e +/mob/proc/get_held_index_name(i) + var/list/hand = list() + if(i > 2) + hand += "upper " + var/num = 0 + if(!(i % 2)) + num = i-2 + hand += "right hand" + else + num = i-1 + hand += "left hand" + num -= (num*0.5) + if(num > 1) //"upper left hand #1" seems weird, but "upper left hand #2" is A-ok + hand += " #[num]" + return hand.Join() + + + +//Returns if a certain item can be equipped to a certain slot. +// Currently invalid for two-handed items - call obj/item/mob_can_equip() instead. +/mob/proc/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) + return FALSE + +/mob/proc/can_put_in_hand(I, hand_index) + if(hand_index > held_items.len) + return FALSE + if(!put_in_hand_check(I)) + return FALSE + if(!has_hand_for_held_index(hand_index)) + return FALSE + return !held_items[hand_index] + +/mob/proc/put_in_hand(obj/item/I, hand_index, forced = FALSE, ignore_anim = TRUE) + if(forced || can_put_in_hand(I, hand_index)) + if(isturf(I.loc) && !ignore_anim) + I.do_pickup_animation(src) + if(hand_index == null) + return FALSE + if(get_item_for_held_index(hand_index) != null) + dropItemToGround(get_item_for_held_index(hand_index), force = TRUE) + I.forceMove(src) + held_items[hand_index] = I + I.layer = ABOVE_HUD_LAYER + I.plane = ABOVE_HUD_PLANE + I.equipped(src, SLOT_HANDS) + if(I.pulledby) + I.pulledby.stop_pulling() + update_inv_hands() + I.pixel_x = initial(I.pixel_x) + I.pixel_y = initial(I.pixel_y) + I.transform = initial(I.transform) + return hand_index || TRUE + return FALSE + +//Puts the item into the first available left hand if possible and calls all necessary triggers/updates. returns 1 on success. +/mob/proc/put_in_l_hand(obj/item/I) + return put_in_hand(I, get_empty_held_index_for_side("l")) + + +//Puts the item into the first available right hand if possible and calls all necessary triggers/updates. returns 1 on success. +/mob/proc/put_in_r_hand(obj/item/I) + return put_in_hand(I, get_empty_held_index_for_side("r")) + + +/mob/proc/put_in_hand_check(obj/item/I) + if(incapacitated() && !(I.item_flags&ABSTRACT)) //Cit change - Changes lying to incapacitated so that it's plausible to pick things up while on the ground + return FALSE + if(!istype(I)) + return FALSE + return TRUE + + +//Puts the item into our active hand if possible. returns TRUE on success. +/mob/proc/put_in_active_hand(obj/item/I, forced = FALSE, ignore_animation = TRUE) + return put_in_hand(I, active_hand_index, forced, ignore_animation) + + +//Puts the item into our inactive hand if possible, returns TRUE on success +/mob/proc/put_in_inactive_hand(obj/item/I) + return put_in_hand(I, get_inactive_hand_index()) + + +//Puts the item our active hand if possible. Failing that it tries other hands. Returns TRUE on success. +//If both fail it drops it on the floor and returns FALSE. +//This is probably the main one you need to know :) +/mob/proc/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE, forced = FALSE) + if(!I) + return FALSE + + // If the item is a stack and we're already holding a stack then merge + if (istype(I, /obj/item/stack)) + var/obj/item/stack/I_stack = I + var/obj/item/stack/active_stack = get_active_held_item() + + if (I_stack.zero_amount()) + return FALSE + + if (merge_stacks) + if (istype(active_stack) && istype(I_stack, active_stack.merge_type)) + if (I_stack.merge(active_stack)) + to_chat(usr, "Your [active_stack.name] stack now contains [active_stack.get_amount()] [active_stack.singular_name]\s.") + return TRUE + else + var/obj/item/stack/inactive_stack = get_inactive_held_item() + if (istype(inactive_stack) && istype(I_stack, inactive_stack.merge_type)) + if (I_stack.merge(inactive_stack)) + to_chat(usr, "Your [inactive_stack.name] stack now contains [inactive_stack.get_amount()] [inactive_stack.singular_name]\s.") + return TRUE + + if(put_in_active_hand(I, forced)) + return TRUE + + var/hand = get_empty_held_index_for_side("l") + if(!hand) + hand = get_empty_held_index_for_side("r") + if(hand) + if(put_in_hand(I, hand, forced)) + return TRUE + if(del_on_fail) + qdel(I) + return FALSE + I.forceMove(drop_location()) + I.layer = initial(I.layer) + I.plane = initial(I.plane) + I.dropped(src) + return FALSE + +/mob/proc/drop_all_held_items() + . = FALSE + for(var/obj/item/I in held_items) + . |= dropItemToGround(I) + +//Here lie drop_from_inventory and before_item_take, already forgotten and not missed. + +/mob/proc/canUnEquip(obj/item/I, force) + if(!I) + return TRUE + if(HAS_TRAIT(I, TRAIT_NODROP) && !force) + return FALSE + return TRUE + +/mob/proc/putItemFromInventoryInHandIfPossible(obj/item/I, hand_index, force_removal = FALSE) + if(!can_put_in_hand(I, hand_index)) + return FALSE + if(!temporarilyRemoveItemFromInventory(I, force_removal)) + return FALSE + I.remove_item_from_storage(src) + if(!put_in_hand(I, hand_index)) + qdel(I) + CRASH("Assertion failure: putItemFromInventoryInHandIfPossible") //should never be possible + return TRUE + +//The following functions are the same save for one small difference + +//for when you want the item to end up on the ground +//will force move the item to the ground and call the turf's Entered +/mob/proc/dropItemToGround(obj/item/I, force = FALSE) + return doUnEquip(I, force, drop_location(), FALSE) + +//for when the item will be immediately placed in a loc other than the ground +/mob/proc/transferItemToLoc(obj/item/I, newloc = null, force = FALSE) + return doUnEquip(I, force, newloc, FALSE) + +//visibly unequips I but it is NOT MOVED AND REMAINS IN SRC +//item MUST BE FORCEMOVE'D OR QDEL'D +/mob/proc/temporarilyRemoveItemFromInventory(obj/item/I, force = FALSE, idrop = TRUE) + return doUnEquip(I, force, null, TRUE, idrop) + +//DO NOT CALL THIS PROC +//use one of the above 3 helper procs +//you may override it, but do not modify the args +/mob/proc/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) //Force overrides TRAIT_NODROP for things like wizarditis and admin undress. + //Use no_move if the item is just gonna be immediately moved afterward + //Invdrop is used to prevent stuff in pockets dropping. only set to false if it's going to immediately be replaced + if(!I) //If there's nothing to drop, the drop is automatically succesfull. If(unEquip) should generally be used to check for TRAIT_NODROP. + return TRUE + + if(HAS_TRAIT(I, TRAIT_NODROP) && !force) + return FALSE + + var/hand_index = get_held_index_of_item(I) + if(hand_index) + held_items[hand_index] = null + update_inv_hands() + if(I) + if(client) + client.screen -= I + I.layer = initial(I.layer) + I.plane = initial(I.plane) + I.appearance_flags &= ~NO_CLIENT_COLOR + if(!no_move && !(I.item_flags & DROPDEL)) //item may be moved/qdel'd immedietely, don't bother moving it + if (isnull(newloc)) + I.moveToNullspace() + else + I.forceMove(newloc) + I.dropped(src) + return TRUE + +//Outdated but still in use apparently. This should at least be a human proc. +//Daily reminder to murder this - Remie. +/mob/living/proc/get_equipped_items(include_pockets = FALSE) + return + +/mob/living/carbon/get_equipped_items(include_pockets = FALSE) + var/list/items = list() + if(back) + items += back + if(head) + items += head + if(wear_mask) + items += wear_mask + if(wear_neck) + items += wear_neck + return items + +/mob/living/carbon/human/get_equipped_items(include_pockets = FALSE) + var/list/items = ..() + if(belt) + items += belt + if(ears) + items += ears + if(glasses) + items += glasses + if(gloves) + items += gloves + if(shoes) + items += shoes + if(wear_id) + items += wear_id + if(wear_suit) + items += wear_suit + if(w_uniform) + items += w_uniform + if(include_pockets) + if(l_store) + items += l_store + if(r_store) + items += r_store + if(s_store) + items += s_store + return items + +/mob/living/proc/unequip_everything() + var/list/items = list() + items |= get_equipped_items(TRUE) + for(var/I in items) + dropItemToGround(I) + drop_all_held_items() + +/obj/item/proc/equip_to_best_slot(mob/M) + if(src != M.get_active_held_item()) + to_chat(M, "You are not holding anything to equip!") + return FALSE + + if(M.equip_to_appropriate_slot(src)) + M.update_inv_hands() + return TRUE + else + if(equip_delay_self) + return + + if(M.active_storage && M.active_storage.parent && SEND_SIGNAL(M.active_storage.parent, COMSIG_TRY_STORAGE_INSERT, src,M)) + return TRUE + + var/list/obj/item/possible = list(M.get_inactive_held_item(), M.get_item_by_slot(SLOT_BELT), M.get_item_by_slot(SLOT_GENERC_DEXTROUS_STORAGE), M.get_item_by_slot(SLOT_BACK)) + for(var/i in possible) + if(!i) + continue + var/obj/item/I = i + if(SEND_SIGNAL(I, COMSIG_TRY_STORAGE_INSERT, src, M)) + return TRUE + + to_chat(M, "You are unable to equip that!") + return FALSE + + +/mob/verb/quick_equip() + set name = "quick-equip" + set hidden = 1 + + var/obj/item/I = get_active_held_item() + if (I) + I.equip_to_best_slot(src) + +//used in code for items usable by both carbon and drones, this gives the proper back slot for each mob.(defibrillator, backpack watertank, ...) +/mob/proc/getBackSlot() + return SLOT_BACK + +/mob/proc/getBeltSlot() + return SLOT_BELT + + + +//Inventory.dm is -kind of- an ok place for this I guess + +//This is NOT for dismemberment, as the user still technically has 2 "hands" +//This is for multi-handed mobs, such as a human with a third limb installed +//This is a very rare proc to call (besides admin fuckery) so +//any cost it has isn't a worry +/mob/proc/change_number_of_hands(amt) + if(amt < held_items.len) + for(var/i in held_items.len to amt step -1) + dropItemToGround(held_items[i]) + held_items.len = amt + + if(hud_used) + hud_used.build_hand_slots() + + +/mob/living/carbon/human/change_number_of_hands(amt) + var/old_limbs = held_items.len + if(amt < old_limbs) + for(var/i in hand_bodyparts.len to amt step -1) + var/obj/item/bodypart/BP = hand_bodyparts[i] + BP.dismember() + hand_bodyparts[i] = null + hand_bodyparts.len = amt + else if(amt > old_limbs) + hand_bodyparts.len = amt + for(var/i in old_limbs+1 to amt) + var/path = /obj/item/bodypart/l_arm + if(!(i % 2)) + path = /obj/item/bodypart/r_arm + + var/obj/item/bodypart/BP = new path () + BP.owner = src + BP.held_index = i + bodyparts += BP + hand_bodyparts[i] = BP + ..() //Don't redraw hands until we have organs for them + +/mob/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool) + return ..() && (user == src) diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm index 3ad7acfcdc..f7fdd2d32b 100644 --- a/code/modules/mob/living/carbon/alien/alien.dm +++ b/code/modules/mob/living/carbon/alien/alien.dm @@ -1,145 +1,145 @@ -/mob/living/carbon/alien - name = "alien" - icon = 'icons/mob/alien.dmi' - gender = FEMALE //All xenos are girls!! - dna = null - faction = list(ROLE_ALIEN) - ventcrawler = VENTCRAWLER_ALWAYS - sight = SEE_MOBS - see_in_dark = 4 - verb_say = "hisses" - initial_language_holder = /datum/language_holder/alien - bubble_icon = "alien" - type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab/xeno - - var/obj/item/card/id/wear_id = null // Fix for station bounced radios -- Skie - var/has_fine_manipulation = 0 - var/move_delay_add = 0 // movement delay to add - - status_flags = CANUNCONSCIOUS|CANPUSH - - var/heat_protection = 0.5 - var/leaping = 0 - gib_type = /obj/effect/decal/cleanable/blood/gibs/xeno - unique_name = 1 - - var/static/regex/alien_name_regex = new("alien (larva|sentinel|drone|hunter|praetorian|queen)( \\(\\d+\\))?") - -/mob/living/carbon/alien/Initialize() - verbs += /mob/living/proc/mob_sleep - verbs += /mob/living/proc/lay_down - - create_bodyparts() //initialize bodyparts - - create_internal_organs() - - . = ..() - -/mob/living/carbon/alien/create_internal_organs() - internal_organs += new /obj/item/organ/brain/alien - internal_organs += new /obj/item/organ/alien/hivenode - internal_organs += new /obj/item/organ/tongue/alien - internal_organs += new /obj/item/organ/eyes/night_vision/alien - internal_organs += new /obj/item/organ/ears - ..() - -/mob/living/carbon/alien/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) // beepsky won't hunt aliums - return -10 - -/mob/living/carbon/alien/handle_environment(datum/gas_mixture/environment) - if(!environment) - return - - var/loc_temp = get_temperature(environment) - - // Aliens are now weak to fire. - - //After then, it reacts to the surrounding atmosphere based on your thermal protection - if(!on_fire) // If you're on fire, ignore local air temperature - if(loc_temp > bodytemperature) - //Place is hotter than we are - var/thermal_protection = heat_protection //This returns a 0 - 1 value, which corresponds to the percentage of heat protection. - if(thermal_protection < 1) - adjust_bodytemperature((1-thermal_protection) * ((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR)) - else - adjust_bodytemperature(1 * ((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR)) - - if(bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT) - //Body temperature is too hot. - throw_alert("alien_fire", /obj/screen/alert/alien_fire) - switch(bodytemperature) - if(360 to 400) - apply_damage(HEAT_DAMAGE_LEVEL_1, BURN) - if(400 to 460) - apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) - if(460 to INFINITY) - if(on_fire) - apply_damage(HEAT_DAMAGE_LEVEL_3, BURN) - else - apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) - else - clear_alert("alien_fire") - -/mob/living/carbon/alien/reagent_check(datum/reagent/R) //can metabolize all reagents - return 0 - -/mob/living/carbon/alien/IsAdvancedToolUser() - return has_fine_manipulation - -/mob/living/carbon/alien/Stat() - ..() - - if(statpanel("Status")) - stat(null, "Intent: [a_intent]") - -/mob/living/carbon/alien/getTrail() - if(getBruteLoss() < 200) - return pick (list("xltrails_1", "xltrails2")) - else - return pick (list("xttrails_1", "xttrails2")) -/*---------------------------------------- -Proc: AddInfectionImages() -Des: Gives the client of the alien an image on each infected mob. -----------------------------------------*/ -/mob/living/carbon/alien/proc/AddInfectionImages() - if (client) - for (var/i in GLOB.mob_living_list) - var/mob/living/L = i - if(HAS_TRAIT(L, TRAIT_XENO_HOST)) - var/obj/item/organ/body_egg/alien_embryo/A = L.getorgan(/obj/item/organ/body_egg/alien_embryo) - if(A) - var/I = image('icons/mob/alien.dmi', loc = L, icon_state = "infected[A.stage]") - client.images += I - return - - -/*---------------------------------------- -Proc: RemoveInfectionImages() -Des: Removes all infected images from the alien. -----------------------------------------*/ -/mob/living/carbon/alien/proc/RemoveInfectionImages() - if (client) - for(var/image/I in client.images) - if(dd_hasprefix_case(I.icon_state, "infected")) - qdel(I) - return - -/mob/living/carbon/alien/canBeHandcuffed() - return 1 - -/mob/living/carbon/alien/get_standard_pixel_y_offset(lying = 0) - return initial(pixel_y) - -/mob/living/carbon/alien/proc/alien_evolve(mob/living/carbon/alien/new_xeno) - to_chat(src, "You begin to evolve!") - visible_message("[src] begins to twist and contort!") - new_xeno.setDir(dir) - if(!alien_name_regex.Find(name)) - new_xeno.name = name - new_xeno.real_name = real_name - if(mind) - mind.transfer_to(new_xeno) - qdel(src) - -/mob/living/carbon/alien/can_hold_items() - return has_fine_manipulation +/mob/living/carbon/alien + name = "alien" + icon = 'icons/mob/alien.dmi' + gender = FEMALE //All xenos are girls!! + dna = null + faction = list(ROLE_ALIEN) + ventcrawler = VENTCRAWLER_ALWAYS + sight = SEE_MOBS + see_in_dark = 4 + verb_say = "hisses" + initial_language_holder = /datum/language_holder/alien + bubble_icon = "alien" + type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab/xeno + + var/obj/item/card/id/wear_id = null // Fix for station bounced radios -- Skie + var/has_fine_manipulation = 0 + var/move_delay_add = 0 // movement delay to add + + status_flags = CANUNCONSCIOUS|CANPUSH + + var/heat_protection = 0.5 + var/leaping = 0 + gib_type = /obj/effect/decal/cleanable/blood/gibs/xeno + unique_name = 1 + + var/static/regex/alien_name_regex = new("alien (larva|sentinel|drone|hunter|praetorian|queen)( \\(\\d+\\))?") + +/mob/living/carbon/alien/Initialize() + verbs += /mob/living/proc/mob_sleep + verbs += /mob/living/proc/lay_down + + create_bodyparts() //initialize bodyparts + + create_internal_organs() + + . = ..() + +/mob/living/carbon/alien/create_internal_organs() + internal_organs += new /obj/item/organ/brain/alien + internal_organs += new /obj/item/organ/alien/hivenode + internal_organs += new /obj/item/organ/tongue/alien + internal_organs += new /obj/item/organ/eyes/night_vision/alien + internal_organs += new /obj/item/organ/ears + ..() + +/mob/living/carbon/alien/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) // beepsky won't hunt aliums + return -10 + +/mob/living/carbon/alien/handle_environment(datum/gas_mixture/environment) + if(!environment) + return + + var/loc_temp = get_temperature(environment) + + // Aliens are now weak to fire. + + //After then, it reacts to the surrounding atmosphere based on your thermal protection + if(!on_fire) // If you're on fire, ignore local air temperature + if(loc_temp > bodytemperature) + //Place is hotter than we are + var/thermal_protection = heat_protection //This returns a 0 - 1 value, which corresponds to the percentage of heat protection. + if(thermal_protection < 1) + adjust_bodytemperature((1-thermal_protection) * ((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR)) + else + adjust_bodytemperature(1 * ((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR)) + + if(bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT) + //Body temperature is too hot. + throw_alert("alien_fire", /obj/screen/alert/alien_fire) + switch(bodytemperature) + if(360 to 400) + apply_damage(HEAT_DAMAGE_LEVEL_1, BURN) + if(400 to 460) + apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) + if(460 to INFINITY) + if(on_fire) + apply_damage(HEAT_DAMAGE_LEVEL_3, BURN) + else + apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) + else + clear_alert("alien_fire") + +/mob/living/carbon/alien/reagent_check(datum/reagent/R) //can metabolize all reagents + return 0 + +/mob/living/carbon/alien/IsAdvancedToolUser() + return has_fine_manipulation + +/mob/living/carbon/alien/Stat() + ..() + + if(statpanel("Status")) + stat(null, "Intent: [a_intent]") + +/mob/living/carbon/alien/getTrail() + if(getBruteLoss() < 200) + return pick (list("xltrails_1", "xltrails2")) + else + return pick (list("xttrails_1", "xttrails2")) +/*---------------------------------------- +Proc: AddInfectionImages() +Des: Gives the client of the alien an image on each infected mob. +----------------------------------------*/ +/mob/living/carbon/alien/proc/AddInfectionImages() + if (client) + for (var/i in GLOB.mob_living_list) + var/mob/living/L = i + if(HAS_TRAIT(L, TRAIT_XENO_HOST)) + var/obj/item/organ/body_egg/alien_embryo/A = L.getorgan(/obj/item/organ/body_egg/alien_embryo) + if(A) + var/I = image('icons/mob/alien.dmi', loc = L, icon_state = "infected[A.stage]") + client.images += I + return + + +/*---------------------------------------- +Proc: RemoveInfectionImages() +Des: Removes all infected images from the alien. +----------------------------------------*/ +/mob/living/carbon/alien/proc/RemoveInfectionImages() + if (client) + for(var/image/I in client.images) + if(dd_hasprefix_case(I.icon_state, "infected")) + qdel(I) + return + +/mob/living/carbon/alien/canBeHandcuffed() + return 1 + +/mob/living/carbon/alien/get_standard_pixel_y_offset(lying = 0) + return initial(pixel_y) + +/mob/living/carbon/alien/proc/alien_evolve(mob/living/carbon/alien/new_xeno) + to_chat(src, "You begin to evolve!") + visible_message("[src] begins to twist and contort!") + new_xeno.setDir(dir) + if(!alien_name_regex.Find(name)) + new_xeno.name = name + new_xeno.real_name = real_name + if(mind) + mind.transfer_to(new_xeno) + qdel(src) + +/mob/living/carbon/alien/can_hold_items() + return has_fine_manipulation diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm index 8c74b7408b..d830957056 100644 --- a/code/modules/mob/living/carbon/alien/alien_defense.dm +++ b/code/modules/mob/living/carbon/alien/alien_defense.dm @@ -1,128 +1,128 @@ - -/mob/living/carbon/alien/get_eye_protection() - return ..() + 2 //potential cyber implants + natural eye protection - -/mob/living/carbon/alien/get_ear_protection() - return 2 //no ears - -/mob/living/carbon/alien/hitby(atom/movable/AM, skipcatch, hitpush) - return ..(AM, skipcatch = TRUE, hitpush = FALSE) - -/mob/living/carbon/alien/can_embed(obj/item/I) - return FALSE - -/*Code for aliens attacking aliens. Because aliens act on a hivemind, I don't see them as very aggressive with each other. -As such, they can either help or harm other aliens. Help works like the human help command while harm is a simple nibble. -In all, this is a lot like the monkey code. /N -*/ -/mob/living/carbon/alien/attack_alien(mob/living/carbon/alien/M) - . = ..() - if(!.) // the attack was blocked or was help/grab intent - return - switch(M.a_intent) - if (INTENT_HELP) - if(!recoveringstam) - resting = 0 - AdjustStun(-60) - AdjustKnockdown(-60) - AdjustUnconscious(-60) - AdjustSleeping(-100) - visible_message("[M.name] nuzzles [src] trying to wake [p_them()] up!") - if(INTENT_DISARM, INTENT_HARM) - if(health > 0) - M.do_attack_animation(src, ATTACK_EFFECT_BITE) - playsound(loc, 'sound/weapons/bite.ogg', 50, 1, -1) - visible_message("[M.name] bites [src]!", \ - "[M.name] bites [src]!", null, COMBAT_MESSAGE_RANGE) - adjustBruteLoss(1) - log_combat(M, src, "attacked") - updatehealth() - else - to_chat(M, "[name] is too injured for that.") - - -/mob/living/carbon/alien/attack_larva(mob/living/carbon/alien/larva/L) - return attack_alien(L) - - -/mob/living/carbon/alien/attack_hand(mob/living/carbon/human/M) - . = ..() - if(.) //To allow surgery to return properly. - return - switch(M.a_intent) - if(INTENT_HELP) - help_shake_act(M) - if(INTENT_GRAB) - grabbedby(M) - if (INTENT_HARM) - if(HAS_TRAIT(M, TRAIT_PACIFISM)) - to_chat(M, "You don't want to hurt [src]!") - return TRUE - M.do_attack_animation(src, ATTACK_EFFECT_PUNCH) - if(INTENT_DISARM) - if(HAS_TRAIT(M, TRAIT_PACIFISM)) - to_chat(M, "You don't want to hurt [src]!") - return TRUE - M.do_attack_animation(src, ATTACK_EFFECT_DISARM) - - -/mob/living/carbon/alien/attack_paw(mob/living/carbon/monkey/M) - . = ..() - if(.) //successful monkey bite. - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) - apply_damage(rand(1, 3), BRUTE, affecting) - - -/mob/living/carbon/alien/attack_animal(mob/living/simple_animal/M) - . = ..() - if(.) - var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) - switch(M.melee_damage_type) - if(BRUTE) - adjustBruteLoss(damage) - if(BURN) - adjustFireLoss(damage) - if(TOX) - adjustToxLoss(damage) - if(OXY) - adjustOxyLoss(damage) - if(CLONE) - adjustCloneLoss(damage) - if(STAMINA) - adjustStaminaLoss(damage) - -/mob/living/carbon/alien/attack_slime(mob/living/simple_animal/slime/M) - . = ..() - if(!.) //unsuccessful slime attack - return - var/damage = rand(5, 35) - if(M.is_adult) - damage = rand(10, 40) - adjustBruteLoss(damage) - log_combat(M, src, "attacked") - updatehealth() - -/mob/living/carbon/alien/ex_act(severity, target, origin) - if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) - return - ..() - switch (severity) - if (EXPLODE_DEVASTATE) - gib() - return - - if (EXPLODE_HEAVY) - take_overall_damage(60, 60) - adjustEarDamage(30,120) - - if(EXPLODE_LIGHT) - take_overall_damage(30,0) - if(prob(50)) - Unconscious(20) - adjustEarDamage(15,60) - -/mob/living/carbon/alien/soundbang_act(intensity = 1, stun_pwr = 20, damage_pwr = 5, deafen_pwr = 15) - return 0 - -/mob/living/carbon/alien/acid_act(acidpwr, acid_volume) - return 0//aliens are immune to acid. + +/mob/living/carbon/alien/get_eye_protection() + return ..() + 2 //potential cyber implants + natural eye protection + +/mob/living/carbon/alien/get_ear_protection() + return 2 //no ears + +/mob/living/carbon/alien/hitby(atom/movable/AM, skipcatch, hitpush) + return ..(AM, skipcatch = TRUE, hitpush = FALSE) + +/mob/living/carbon/alien/can_embed(obj/item/I) + return FALSE + +/*Code for aliens attacking aliens. Because aliens act on a hivemind, I don't see them as very aggressive with each other. +As such, they can either help or harm other aliens. Help works like the human help command while harm is a simple nibble. +In all, this is a lot like the monkey code. /N +*/ +/mob/living/carbon/alien/attack_alien(mob/living/carbon/alien/M) + . = ..() + if(!.) // the attack was blocked or was help/grab intent + return + switch(M.a_intent) + if (INTENT_HELP) + if(!recoveringstam) + resting = 0 + AdjustStun(-60) + AdjustKnockdown(-60) + AdjustUnconscious(-60) + AdjustSleeping(-100) + visible_message("[M.name] nuzzles [src] trying to wake [p_them()] up!") + if(INTENT_DISARM, INTENT_HARM) + if(health > 0) + M.do_attack_animation(src, ATTACK_EFFECT_BITE) + playsound(loc, 'sound/weapons/bite.ogg', 50, 1, -1) + visible_message("[M.name] bites [src]!", \ + "[M.name] bites [src]!", null, COMBAT_MESSAGE_RANGE) + adjustBruteLoss(1) + log_combat(M, src, "attacked") + updatehealth() + else + to_chat(M, "[name] is too injured for that.") + + +/mob/living/carbon/alien/attack_larva(mob/living/carbon/alien/larva/L) + return attack_alien(L) + + +/mob/living/carbon/alien/attack_hand(mob/living/carbon/human/M) + . = ..() + if(.) //To allow surgery to return properly. + return + switch(M.a_intent) + if(INTENT_HELP) + help_shake_act(M) + if(INTENT_GRAB) + grabbedby(M) + if (INTENT_HARM) + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt [src]!") + return TRUE + M.do_attack_animation(src, ATTACK_EFFECT_PUNCH) + if(INTENT_DISARM) + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt [src]!") + return TRUE + M.do_attack_animation(src, ATTACK_EFFECT_DISARM) + + +/mob/living/carbon/alien/attack_paw(mob/living/carbon/monkey/M) + . = ..() + if(.) //successful monkey bite. + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) + apply_damage(rand(1, 3), BRUTE, affecting) + + +/mob/living/carbon/alien/attack_animal(mob/living/simple_animal/M) + . = ..() + if(.) + var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) + switch(M.melee_damage_type) + if(BRUTE) + adjustBruteLoss(damage) + if(BURN) + adjustFireLoss(damage) + if(TOX) + adjustToxLoss(damage) + if(OXY) + adjustOxyLoss(damage) + if(CLONE) + adjustCloneLoss(damage) + if(STAMINA) + adjustStaminaLoss(damage) + +/mob/living/carbon/alien/attack_slime(mob/living/simple_animal/slime/M) + . = ..() + if(!.) //unsuccessful slime attack + return + var/damage = rand(5, 35) + if(M.is_adult) + damage = rand(10, 40) + adjustBruteLoss(damage) + log_combat(M, src, "attacked") + updatehealth() + +/mob/living/carbon/alien/ex_act(severity, target, origin) + if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) + return + ..() + switch (severity) + if (EXPLODE_DEVASTATE) + gib() + return + + if (EXPLODE_HEAVY) + take_overall_damage(60, 60) + adjustEarDamage(30,120) + + if(EXPLODE_LIGHT) + take_overall_damage(30,0) + if(prob(50)) + Unconscious(20) + adjustEarDamage(15,60) + +/mob/living/carbon/alien/soundbang_act(intensity = 1, stun_pwr = 20, damage_pwr = 5, deafen_pwr = 15) + return 0 + +/mob/living/carbon/alien/acid_act(acidpwr, acid_volume) + return 0//aliens are immune to acid. diff --git a/code/modules/mob/living/carbon/alien/death.dm b/code/modules/mob/living/carbon/alien/death.dm index afbd5bbe6f..a2c5db4814 100644 --- a/code/modules/mob/living/carbon/alien/death.dm +++ b/code/modules/mob/living/carbon/alien/death.dm @@ -1,15 +1,15 @@ -/mob/living/carbon/alien/spawn_gibs(with_bodyparts, atom/loc_override) - var/location = loc_override ? loc_override.drop_location() : drop_location() - if(with_bodyparts) - new /obj/effect/gibspawner/xeno(location, src) - else - new /obj/effect/gibspawner/xeno/bodypartless(location, src) - -/mob/living/carbon/alien/gib_animation() - new /obj/effect/temp_visual/gib_animation(loc, "gibbed-a") - -/mob/living/carbon/alien/spawn_dust() - new /obj/effect/decal/remains/xeno(loc) - -/mob/living/carbon/alien/dust_animation() - new /obj/effect/temp_visual/dust_animation(loc, "dust-a") +/mob/living/carbon/alien/spawn_gibs(with_bodyparts, atom/loc_override) + var/location = loc_override ? loc_override.drop_location() : drop_location() + if(with_bodyparts) + new /obj/effect/gibspawner/xeno(location, src) + else + new /obj/effect/gibspawner/xeno/bodypartless(location, src) + +/mob/living/carbon/alien/gib_animation() + new /obj/effect/temp_visual/gib_animation(loc, "gibbed-a") + +/mob/living/carbon/alien/spawn_dust() + new /obj/effect/decal/remains/xeno(loc) + +/mob/living/carbon/alien/dust_animation() + new /obj/effect/temp_visual/dust_animation(loc, "dust-a") diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm index d1ed09665b..3babab4586 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm @@ -1,90 +1,90 @@ -/mob/living/carbon/alien/humanoid/hunter - name = "alien hunter" - caste = "h" - maxHealth = 125 - health = 125 - icon_state = "alienh" - var/obj/screen/leap_icon = null - -/mob/living/carbon/alien/humanoid/hunter/create_internal_organs() - internal_organs += new /obj/item/organ/alien/plasmavessel/small - ..() - -//Hunter verbs - -/mob/living/carbon/alien/humanoid/hunter/proc/toggle_leap(message = 1) - leap_on_click = !leap_on_click - leap_icon.icon_state = "leap_[leap_on_click ? "on":"off"]" - update_icons() - if(message) - to_chat(src, "You will now [leap_on_click ? "leap at":"slash at"] enemies!") - else - return - -/mob/living/carbon/alien/humanoid/hunter/ClickOn(atom/A, params) - face_atom(A) - if(leap_on_click) - leap_at(A) - else - ..() - -#define MAX_ALIEN_LEAP_DIST 7 - -/mob/living/carbon/alien/humanoid/hunter/proc/leap_at(atom/A) - if(!canmove || leaping) - return - - if(pounce_cooldown > world.time) - to_chat(src, "You are too fatigued to pounce right now!") - return - - if(!has_gravity() || !A.has_gravity()) - to_chat(src, "It is unsafe to leap without gravity!") - //It's also extremely buggy visually, so it's balance+bugfix - return - - else //Maybe uses plasma in the future, although that wouldn't make any sense... - leaping = 1 - weather_immunities += "lava" - update_icons() - throw_at(A, MAX_ALIEN_LEAP_DIST, 1, src, FALSE, TRUE, callback = CALLBACK(src, .proc/leap_end)) - -/mob/living/carbon/alien/humanoid/hunter/proc/leap_end() - leaping = 0 - weather_immunities -= "lava" - update_icons() - -/mob/living/carbon/alien/humanoid/hunter/throw_impact(atom/A) - - if(!leaping) - return ..() - - pounce_cooldown = world.time + pounce_cooldown_time - if(A) - if(isliving(A)) - var/mob/living/L = A - if(!L.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK)) - L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") - L.Knockdown(100) - sleep(2)//Runtime prevention (infinite bump() calls on hulks) - step_towards(src,L) - else - Knockdown(40, 1, 1) - - toggle_leap(0) - else if(A.density && !A.CanPass(src)) - visible_message("[src] smashes into [A]!", "[src] smashes into [A]!") - Knockdown(40, 1, 1) - - if(leaping) - leaping = 0 - update_icons() - update_canmove() - - -/mob/living/carbon/alien/humanoid/float(on) - if(leaping) - return - ..() - - +/mob/living/carbon/alien/humanoid/hunter + name = "alien hunter" + caste = "h" + maxHealth = 125 + health = 125 + icon_state = "alienh" + var/obj/screen/leap_icon = null + +/mob/living/carbon/alien/humanoid/hunter/create_internal_organs() + internal_organs += new /obj/item/organ/alien/plasmavessel/small + ..() + +//Hunter verbs + +/mob/living/carbon/alien/humanoid/hunter/proc/toggle_leap(message = 1) + leap_on_click = !leap_on_click + leap_icon.icon_state = "leap_[leap_on_click ? "on":"off"]" + update_icons() + if(message) + to_chat(src, "You will now [leap_on_click ? "leap at":"slash at"] enemies!") + else + return + +/mob/living/carbon/alien/humanoid/hunter/ClickOn(atom/A, params) + face_atom(A) + if(leap_on_click) + leap_at(A) + else + ..() + +#define MAX_ALIEN_LEAP_DIST 7 + +/mob/living/carbon/alien/humanoid/hunter/proc/leap_at(atom/A) + if(!canmove || leaping) + return + + if(pounce_cooldown > world.time) + to_chat(src, "You are too fatigued to pounce right now!") + return + + if(!has_gravity() || !A.has_gravity()) + to_chat(src, "It is unsafe to leap without gravity!") + //It's also extremely buggy visually, so it's balance+bugfix + return + + else //Maybe uses plasma in the future, although that wouldn't make any sense... + leaping = 1 + weather_immunities += "lava" + update_icons() + throw_at(A, MAX_ALIEN_LEAP_DIST, 1, src, FALSE, TRUE, callback = CALLBACK(src, .proc/leap_end)) + +/mob/living/carbon/alien/humanoid/hunter/proc/leap_end() + leaping = 0 + weather_immunities -= "lava" + update_icons() + +/mob/living/carbon/alien/humanoid/hunter/throw_impact(atom/A) + + if(!leaping) + return ..() + + pounce_cooldown = world.time + pounce_cooldown_time + if(A) + if(isliving(A)) + var/mob/living/L = A + if(!L.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK)) + L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") + L.Knockdown(100) + sleep(2)//Runtime prevention (infinite bump() calls on hulks) + step_towards(src,L) + else + Knockdown(40, 1, 1) + + toggle_leap(0) + else if(A.density && !A.CanPass(src)) + visible_message("[src] smashes into [A]!", "[src] smashes into [A]!") + Knockdown(40, 1, 1) + + if(leaping) + leaping = 0 + update_icons() + update_canmove() + + +/mob/living/carbon/alien/humanoid/float(on) + if(leaping) + return + ..() + + diff --git a/code/modules/mob/living/carbon/alien/humanoid/death.dm b/code/modules/mob/living/carbon/alien/humanoid/death.dm index c6c675ead9..5625e98b75 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/death.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/death.dm @@ -1,23 +1,23 @@ -/mob/living/carbon/alien/humanoid/death(gibbed) - if(stat == DEAD) - return - - . = ..() - - update_canmove() - update_icons() - status_flags |= CANPUSH - -//When the alien queen dies, all others must pay the price for letting her die. -/mob/living/carbon/alien/humanoid/royal/queen/death(gibbed) - if(stat == DEAD) - return - - for(var/mob/living/carbon/C in GLOB.alive_mob_list) - if(C == src) //Make sure not to proc it on ourselves. - continue - var/obj/item/organ/alien/hivenode/node = C.getorgan(/obj/item/organ/alien/hivenode) - if(istype(node)) // just in case someone would ever add a diffirent node to hivenode slot - node.queen_death() - +/mob/living/carbon/alien/humanoid/death(gibbed) + if(stat == DEAD) + return + + . = ..() + + update_canmove() + update_icons() + status_flags |= CANPUSH + +//When the alien queen dies, all others must pay the price for letting her die. +/mob/living/carbon/alien/humanoid/royal/queen/death(gibbed) + if(stat == DEAD) + return + + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + if(C == src) //Make sure not to proc it on ourselves. + continue + var/obj/item/organ/alien/hivenode/node = C.getorgan(/obj/item/organ/alien/hivenode) + if(istype(node)) // just in case someone would ever add a diffirent node to hivenode slot + node.queen_death() + return ..() \ No newline at end of file diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index edf0fde83e..347106f6c1 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -1,119 +1,119 @@ -/mob/living/carbon/alien/humanoid - name = "alien" - icon_state = "alien" - pass_flags = PASSTABLE - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/xeno = 5, /obj/item/stack/sheet/animalhide/xeno = 1) - possible_a_intents = list(INTENT_HELP, INTENT_DISARM, INTENT_GRAB, INTENT_HARM) - limb_destroyer = 1 - hud_type = /datum/hud/alien - var/obj/item/r_store = null - var/obj/item/l_store = null - var/caste = "" - var/alt_icon = 'icons/mob/alienleap.dmi' //used to switch between the two alien icon files. - var/leap_on_click = 0 - var/pounce_cooldown = 0 - var/pounce_cooldown_time = 30 - var/custom_pixel_x_offset = 0 //for admin fuckery. - var/custom_pixel_y_offset = 0 - var/sneaking = 0 //For sneaky-sneaky mode and appropriate slowdown - var/drooling = 0 //For Neruotoxic spit overlays - bodyparts = list(/obj/item/bodypart/chest/alien, /obj/item/bodypart/head/alien, /obj/item/bodypart/l_arm/alien, - /obj/item/bodypart/r_arm/alien, /obj/item/bodypart/r_leg/alien, /obj/item/bodypart/l_leg/alien) - - -//This is fine right now, if we're adding organ specific damage this needs to be updated -/mob/living/carbon/alien/humanoid/Initialize() - AddAbility(new/obj/effect/proc_holder/alien/regurgitate(null)) - . = ..() - -/mob/living/carbon/alien/humanoid/restrained(ignore_grab) - return handcuffed - -/mob/living/carbon/alien/humanoid/show_inv(mob/user) - user.set_machine(src) - var/list/dat = list() - dat += {" -
                - [name] -
                "} - for(var/i in 1 to held_items.len) - var/obj/item/I = get_item_for_held_index(i) - dat += "
                [get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Empty"]" - dat += "
                Empty Pouches" - - if(handcuffed) - dat += "
                Handcuffed" - if(legcuffed) - dat += "
                Legcuffed" - - dat += {" -
                -
                Close - "} - user << browse(dat.Join(), "window=mob[REF(src)];size=325x500") - onclose(user, "mob[REF(src)]") - - -/mob/living/carbon/alien/humanoid/Topic(href, href_list) - ..() - //strip panel - if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - if(href_list["pouches"]) - visible_message("[usr] tries to empty [src]'s pouches.", \ - "[usr] tries to empty [src]'s pouches.") - if(do_mob(usr, src, POCKET_STRIP_DELAY * 0.5)) - dropItemToGround(r_store) - dropItemToGround(l_store) - -/mob/living/carbon/alien/humanoid/cuff_resist(obj/item/I) - playsound(src, 'sound/voice/hiss5.ogg', 40, 1, 1) //Alien roars when starting to break free - ..(I, cuff_break = INSTANT_CUFFBREAK) - -/mob/living/carbon/alien/humanoid/resist_grab(moving_resist) - if(pulledby.grab_state) - visible_message("[src] has broken free of [pulledby]'s grip!") - pulledby.stop_pulling() - . = 0 - -/mob/living/carbon/alien/humanoid/get_standard_pixel_y_offset(lying = 0) - if(leaping) - return -32 - else if(custom_pixel_y_offset) - return custom_pixel_y_offset - else - return initial(pixel_y) - -/mob/living/carbon/alien/humanoid/get_standard_pixel_x_offset(lying = 0) - if(leaping) - return -32 - else if(custom_pixel_x_offset) - return custom_pixel_x_offset - else - return initial(pixel_x) - -/mob/living/carbon/alien/humanoid/get_permeability_protection(list/target_zones) - return 0.8 - -/mob/living/carbon/alien/humanoid/alien_evolve(mob/living/carbon/alien/humanoid/new_xeno) - drop_all_held_items() - for(var/atom/movable/A in stomach_contents) - stomach_contents.Remove(A) - new_xeno.stomach_contents.Add(A) - A.forceMove(new_xeno) - ..() - -//For alien evolution/promotion/queen finder procs. Checks for an active alien of that type -/proc/get_alien_type(var/alienpath) - for(var/mob/living/carbon/alien/humanoid/A in GLOB.alive_mob_list) - if(!istype(A, alienpath)) - continue - if(!A.key || A.stat == DEAD) //Only living aliens with a ckey are valid. - continue - return A - return FALSE - - -/mob/living/carbon/alien/humanoid/check_breath(datum/gas_mixture/breath) - if(breath && breath.total_moles() > 0 && !sneaking) - playsound(get_turf(src), pick('sound/voice/lowHiss2.ogg', 'sound/voice/lowHiss3.ogg', 'sound/voice/lowHiss4.ogg'), 50, 0, -5) - ..() +/mob/living/carbon/alien/humanoid + name = "alien" + icon_state = "alien" + pass_flags = PASSTABLE + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/xeno = 5, /obj/item/stack/sheet/animalhide/xeno = 1) + possible_a_intents = list(INTENT_HELP, INTENT_DISARM, INTENT_GRAB, INTENT_HARM) + limb_destroyer = 1 + hud_type = /datum/hud/alien + var/obj/item/r_store = null + var/obj/item/l_store = null + var/caste = "" + var/alt_icon = 'icons/mob/alienleap.dmi' //used to switch between the two alien icon files. + var/leap_on_click = 0 + var/pounce_cooldown = 0 + var/pounce_cooldown_time = 30 + var/custom_pixel_x_offset = 0 //for admin fuckery. + var/custom_pixel_y_offset = 0 + var/sneaking = 0 //For sneaky-sneaky mode and appropriate slowdown + var/drooling = 0 //For Neruotoxic spit overlays + bodyparts = list(/obj/item/bodypart/chest/alien, /obj/item/bodypart/head/alien, /obj/item/bodypart/l_arm/alien, + /obj/item/bodypart/r_arm/alien, /obj/item/bodypart/r_leg/alien, /obj/item/bodypart/l_leg/alien) + + +//This is fine right now, if we're adding organ specific damage this needs to be updated +/mob/living/carbon/alien/humanoid/Initialize() + AddAbility(new/obj/effect/proc_holder/alien/regurgitate(null)) + . = ..() + +/mob/living/carbon/alien/humanoid/restrained(ignore_grab) + return handcuffed + +/mob/living/carbon/alien/humanoid/show_inv(mob/user) + user.set_machine(src) + var/list/dat = list() + dat += {" +
                + [name] +
                "} + for(var/i in 1 to held_items.len) + var/obj/item/I = get_item_for_held_index(i) + dat += "
                [get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Empty"]" + dat += "
                Empty Pouches" + + if(handcuffed) + dat += "
                Handcuffed" + if(legcuffed) + dat += "
                Legcuffed" + + dat += {" +
                +
                Close + "} + user << browse(dat.Join(), "window=mob[REF(src)];size=325x500") + onclose(user, "mob[REF(src)]") + + +/mob/living/carbon/alien/humanoid/Topic(href, href_list) + ..() + //strip panel + if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + if(href_list["pouches"]) + visible_message("[usr] tries to empty [src]'s pouches.", \ + "[usr] tries to empty [src]'s pouches.") + if(do_mob(usr, src, POCKET_STRIP_DELAY * 0.5)) + dropItemToGround(r_store) + dropItemToGround(l_store) + +/mob/living/carbon/alien/humanoid/cuff_resist(obj/item/I) + playsound(src, 'sound/voice/hiss5.ogg', 40, 1, 1) //Alien roars when starting to break free + ..(I, cuff_break = INSTANT_CUFFBREAK) + +/mob/living/carbon/alien/humanoid/resist_grab(moving_resist) + if(pulledby.grab_state) + visible_message("[src] has broken free of [pulledby]'s grip!") + pulledby.stop_pulling() + . = 0 + +/mob/living/carbon/alien/humanoid/get_standard_pixel_y_offset(lying = 0) + if(leaping) + return -32 + else if(custom_pixel_y_offset) + return custom_pixel_y_offset + else + return initial(pixel_y) + +/mob/living/carbon/alien/humanoid/get_standard_pixel_x_offset(lying = 0) + if(leaping) + return -32 + else if(custom_pixel_x_offset) + return custom_pixel_x_offset + else + return initial(pixel_x) + +/mob/living/carbon/alien/humanoid/get_permeability_protection(list/target_zones) + return 0.8 + +/mob/living/carbon/alien/humanoid/alien_evolve(mob/living/carbon/alien/humanoid/new_xeno) + drop_all_held_items() + for(var/atom/movable/A in stomach_contents) + stomach_contents.Remove(A) + new_xeno.stomach_contents.Add(A) + A.forceMove(new_xeno) + ..() + +//For alien evolution/promotion/queen finder procs. Checks for an active alien of that type +/proc/get_alien_type(var/alienpath) + for(var/mob/living/carbon/alien/humanoid/A in GLOB.alive_mob_list) + if(!istype(A, alienpath)) + continue + if(!A.key || A.stat == DEAD) //Only living aliens with a ckey are valid. + continue + return A + return FALSE + + +/mob/living/carbon/alien/humanoid/check_breath(datum/gas_mixture/breath) + if(breath && breath.total_moles() > 0 && !sneaking) + playsound(get_turf(src), pick('sound/voice/lowHiss2.ogg', 'sound/voice/lowHiss3.ogg', 'sound/voice/lowHiss4.ogg'), 50, 0, -5) + ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm index 76ede9276c..1333bc4399 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm @@ -1,139 +1,139 @@ -/mob/living/carbon/alien/humanoid/royal - //Common stuffs for Praetorian and Queen - icon = 'icons/mob/alienqueen.dmi' - status_flags = 0 - ventcrawler = VENTCRAWLER_NONE //pull over that ass too fat - unique_name = 0 - pixel_x = -16 - bubble_icon = "alienroyal" - mob_size = MOB_SIZE_LARGE - layer = LARGE_MOB_LAYER //above most mobs, but below speechbubbles - pressure_resistance = 200 //Because big, stompy xenos should not be blown around like paper. - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/xeno = 20, /obj/item/stack/sheet/animalhide/xeno = 3) - - var/alt_inhands_file = 'icons/mob/alienqueen.dmi' - -/mob/living/carbon/alien/humanoid/royal/can_inject() - return 0 - -/mob/living/carbon/alien/humanoid/royal/queen - name = "alien queen" - caste = "q" - maxHealth = 400 - health = 400 - icon_state = "alienq" - var/datum/action/small_sprite/smallsprite = new/datum/action/small_sprite/queen() - -/mob/living/carbon/alien/humanoid/royal/queen/Initialize() - //there should only be one queen - for(var/mob/living/carbon/alien/humanoid/royal/queen/Q in GLOB.carbon_list) - if(Q == src) - continue - if(Q.stat == DEAD) - continue - if(Q.client) - name = "alien princess ([rand(1, 999)])" //if this is too cutesy feel free to change it/remove it. - break - - real_name = src.name - - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/repulse/xeno(src)) - AddAbility(new/obj/effect/proc_holder/alien/royal/queen/promote()) - smallsprite.Grant(src) - return ..() - -/mob/living/carbon/alien/humanoid/royal/queen/create_internal_organs() - internal_organs += new /obj/item/organ/alien/plasmavessel/large/queen - internal_organs += new /obj/item/organ/alien/resinspinner - internal_organs += new /obj/item/organ/alien/acid - internal_organs += new /obj/item/organ/alien/neurotoxin - internal_organs += new /obj/item/organ/alien/eggsac - ..() - -//Queen verbs -/obj/effect/proc_holder/alien/lay_egg - name = "Lay Egg" - desc = "Lay an egg to produce huggers to impregnate prey with." - plasma_cost = 75 - check_turf = TRUE - action_icon_state = "alien_egg" - -/obj/effect/proc_holder/alien/lay_egg/fire(mob/living/carbon/user) - if(locate(/obj/structure/alien/egg) in get_turf(user)) - to_chat(user, "There's already an egg here.") - return FALSE - - if(!check_vent_block(user)) - return FALSE - - user.visible_message("[user] has laid an egg!") - new /obj/structure/alien/egg(user.loc) - return TRUE - -//Button to let queen choose her praetorian. -/obj/effect/proc_holder/alien/royal/queen/promote - name = "Create Royal Parasite" - desc = "Produce a royal parasite to grant one of your children the honor of being your Praetorian." - plasma_cost = 500 //Plasma cost used on promotion, not spawning the parasite. - - action_icon_state = "alien_queen_promote" - - - -/obj/effect/proc_holder/alien/royal/queen/promote/fire(mob/living/carbon/alien/user) - var/obj/item/queenpromote/prom - if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian/)) - to_chat(user, "You already have a Praetorian!") - return 0 - else - for(prom in user) - to_chat(user, "You discard [prom].") - qdel(prom) - return 0 - - prom = new (user.loc) - if(!user.put_in_active_hand(prom, 1)) - to_chat(user, "You must empty your hands before preparing the parasite.") - return 0 - else //Just in case telling the player only once is not enough! - to_chat(user, "Use the royal parasite on one of your children to promote her to Praetorian!") - return 0 - -/obj/item/queenpromote - name = "\improper royal parasite" - desc = "Inject this into one of your grown children to promote her to a Praetorian!" - icon_state = "alien_medal" - item_flags = ABSTRACT | DROPDEL - icon = 'icons/mob/alien.dmi' - -/obj/item/queenpromote/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - -/obj/item/queenpromote/attack(mob/living/M, mob/living/carbon/alien/humanoid/user) - if(!isalienadult(M) || isalienroyal(M)) - to_chat(user, "You may only use this with your adult, non-royal children!") - return - if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian/)) - to_chat(user, "You already have a Praetorian!") - return - - var/mob/living/carbon/alien/humanoid/A = M - if(A.stat == CONSCIOUS && A.mind && A.key) - if(!user.usePlasma(500)) - to_chat(user, "You must have 500 plasma stored to use this!") - return - - to_chat(A, "The queen has granted you a promotion to Praetorian!") - user.visible_message("[A] begins to expand, twist and contort!") - var/mob/living/carbon/alien/humanoid/royal/praetorian/new_prae = new (A.loc) - A.mind.transfer_to(new_prae) - qdel(A) - qdel(src) - return - else - to_chat(user, "This child must be alert and responsive to become a Praetorian!") - -/obj/item/queenpromote/attack_self(mob/user) - to_chat(user, "You discard [src].") - qdel(src) +/mob/living/carbon/alien/humanoid/royal + //Common stuffs for Praetorian and Queen + icon = 'icons/mob/alienqueen.dmi' + status_flags = 0 + ventcrawler = VENTCRAWLER_NONE //pull over that ass too fat + unique_name = 0 + pixel_x = -16 + bubble_icon = "alienroyal" + mob_size = MOB_SIZE_LARGE + layer = LARGE_MOB_LAYER //above most mobs, but below speechbubbles + pressure_resistance = 200 //Because big, stompy xenos should not be blown around like paper. + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/xeno = 20, /obj/item/stack/sheet/animalhide/xeno = 3) + + var/alt_inhands_file = 'icons/mob/alienqueen.dmi' + +/mob/living/carbon/alien/humanoid/royal/can_inject() + return 0 + +/mob/living/carbon/alien/humanoid/royal/queen + name = "alien queen" + caste = "q" + maxHealth = 400 + health = 400 + icon_state = "alienq" + var/datum/action/small_sprite/smallsprite = new/datum/action/small_sprite/queen() + +/mob/living/carbon/alien/humanoid/royal/queen/Initialize() + //there should only be one queen + for(var/mob/living/carbon/alien/humanoid/royal/queen/Q in GLOB.carbon_list) + if(Q == src) + continue + if(Q.stat == DEAD) + continue + if(Q.client) + name = "alien princess ([rand(1, 999)])" //if this is too cutesy feel free to change it/remove it. + break + + real_name = src.name + + AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/repulse/xeno(src)) + AddAbility(new/obj/effect/proc_holder/alien/royal/queen/promote()) + smallsprite.Grant(src) + return ..() + +/mob/living/carbon/alien/humanoid/royal/queen/create_internal_organs() + internal_organs += new /obj/item/organ/alien/plasmavessel/large/queen + internal_organs += new /obj/item/organ/alien/resinspinner + internal_organs += new /obj/item/organ/alien/acid + internal_organs += new /obj/item/organ/alien/neurotoxin + internal_organs += new /obj/item/organ/alien/eggsac + ..() + +//Queen verbs +/obj/effect/proc_holder/alien/lay_egg + name = "Lay Egg" + desc = "Lay an egg to produce huggers to impregnate prey with." + plasma_cost = 75 + check_turf = TRUE + action_icon_state = "alien_egg" + +/obj/effect/proc_holder/alien/lay_egg/fire(mob/living/carbon/user) + if(locate(/obj/structure/alien/egg) in get_turf(user)) + to_chat(user, "There's already an egg here.") + return FALSE + + if(!check_vent_block(user)) + return FALSE + + user.visible_message("[user] has laid an egg!") + new /obj/structure/alien/egg(user.loc) + return TRUE + +//Button to let queen choose her praetorian. +/obj/effect/proc_holder/alien/royal/queen/promote + name = "Create Royal Parasite" + desc = "Produce a royal parasite to grant one of your children the honor of being your Praetorian." + plasma_cost = 500 //Plasma cost used on promotion, not spawning the parasite. + + action_icon_state = "alien_queen_promote" + + + +/obj/effect/proc_holder/alien/royal/queen/promote/fire(mob/living/carbon/alien/user) + var/obj/item/queenpromote/prom + if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian/)) + to_chat(user, "You already have a Praetorian!") + return 0 + else + for(prom in user) + to_chat(user, "You discard [prom].") + qdel(prom) + return 0 + + prom = new (user.loc) + if(!user.put_in_active_hand(prom, 1)) + to_chat(user, "You must empty your hands before preparing the parasite.") + return 0 + else //Just in case telling the player only once is not enough! + to_chat(user, "Use the royal parasite on one of your children to promote her to Praetorian!") + return 0 + +/obj/item/queenpromote + name = "\improper royal parasite" + desc = "Inject this into one of your grown children to promote her to a Praetorian!" + icon_state = "alien_medal" + item_flags = ABSTRACT | DROPDEL + icon = 'icons/mob/alien.dmi' + +/obj/item/queenpromote/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) + +/obj/item/queenpromote/attack(mob/living/M, mob/living/carbon/alien/humanoid/user) + if(!isalienadult(M) || isalienroyal(M)) + to_chat(user, "You may only use this with your adult, non-royal children!") + return + if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian/)) + to_chat(user, "You already have a Praetorian!") + return + + var/mob/living/carbon/alien/humanoid/A = M + if(A.stat == CONSCIOUS && A.mind && A.key) + if(!user.usePlasma(500)) + to_chat(user, "You must have 500 plasma stored to use this!") + return + + to_chat(A, "The queen has granted you a promotion to Praetorian!") + user.visible_message("[A] begins to expand, twist and contort!") + var/mob/living/carbon/alien/humanoid/royal/praetorian/new_prae = new (A.loc) + A.mind.transfer_to(new_prae) + qdel(A) + qdel(src) + return + else + to_chat(user, "This child must be alert and responsive to become a Praetorian!") + +/obj/item/queenpromote/attack_self(mob/user) + to_chat(user, "You discard [src].") + qdel(src) diff --git a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm index d2788075e2..a1ef522f1a 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm @@ -1,98 +1,98 @@ - -/mob/living/carbon/alien/humanoid/update_icons() - cut_overlays() - for(var/I in overlays_standing) - add_overlay(I) - - var/asleep = IsSleeping() - if(stat == DEAD) - //If we mostly took damage from fire - if(fireloss > 125) - icon_state = "alien[caste]_husked" - else - icon_state = "alien[caste]_dead" - - else if((stat == UNCONSCIOUS && !asleep) || stat == SOFT_CRIT || IsKnockdown()) - icon_state = "alien[caste]_unconscious" - else if(leap_on_click) - icon_state = "alien[caste]_pounce" - - else if(lying || resting || asleep) - icon_state = "alien[caste]_sleep" - else if(mob_size == MOB_SIZE_LARGE) - icon_state = "alien[caste]" - if(drooling) - add_overlay("alienspit_[caste]") - else - icon_state = "alien[caste]" - if(drooling) - add_overlay("alienspit") - - if(leaping) - if(alt_icon == initial(alt_icon)) - var/old_icon = icon - icon = alt_icon - alt_icon = old_icon - icon_state = "alien[caste]_leap" - pixel_x = -32 - pixel_y = -32 - else - if(alt_icon != initial(alt_icon)) - var/old_icon = icon - icon = alt_icon - alt_icon = old_icon - pixel_x = get_standard_pixel_x_offset(lying) - pixel_y = get_standard_pixel_y_offset(lying) - update_inv_hands() - update_inv_handcuffed() - -/mob/living/carbon/alien/humanoid/regenerate_icons() - if(!..()) - // update_icons() //Handled in update_transform(), leaving this here as a reminder - update_transform() - -/mob/living/carbon/alien/humanoid/update_transform() //The old method of updating lying/standing was update_icons(). Aliens still expect that. - if(lying > 0) - lying = 90 //Anything else looks retarded - ..() - update_icons() - -/mob/living/carbon/alien/humanoid/update_inv_handcuffed() - remove_overlay(HANDCUFF_LAYER) - - if(handcuffed) - var/cuff_icon = handcuffed.item_state - var/dmi_file = 'icons/mob/alien.dmi' - - if(mob_size == MOB_SIZE_LARGE) - cuff_icon += "_[caste]" - dmi_file = 'icons/mob/alienqueen.dmi' - - var/mutable_appearance/cuffs = mutable_appearance(dmi_file, cuff_icon, -HANDCUFF_LAYER) - cuffs.color = handcuffed.color - - overlays_standing[HANDCUFF_LAYER] = cuffs - apply_overlay(HANDCUFF_LAYER) - -//Royals have bigger sprites, so inhand things must be handled differently. -/mob/living/carbon/alien/humanoid/royal/update_inv_hands() - ..() - remove_overlay(HANDS_LAYER) - var/list/hands = list() - - var/obj/item/l_hand = get_item_for_held_index(1) - if(l_hand) - var/itm_state = l_hand.item_state - if(!itm_state) - itm_state = l_hand.icon_state - hands += mutable_appearance(alt_inhands_file, "[itm_state][caste]_l", -HANDS_LAYER) - - var/obj/item/r_hand = get_item_for_held_index(2) - if(r_hand) - var/itm_state = r_hand.item_state - if(!itm_state) - itm_state = r_hand.icon_state - hands += mutable_appearance(alt_inhands_file, "[itm_state][caste]_r", -HANDS_LAYER) - - overlays_standing[HANDS_LAYER] = hands + +/mob/living/carbon/alien/humanoid/update_icons() + cut_overlays() + for(var/I in overlays_standing) + add_overlay(I) + + var/asleep = IsSleeping() + if(stat == DEAD) + //If we mostly took damage from fire + if(fireloss > 125) + icon_state = "alien[caste]_husked" + else + icon_state = "alien[caste]_dead" + + else if((stat == UNCONSCIOUS && !asleep) || stat == SOFT_CRIT || IsKnockdown()) + icon_state = "alien[caste]_unconscious" + else if(leap_on_click) + icon_state = "alien[caste]_pounce" + + else if(lying || resting || asleep) + icon_state = "alien[caste]_sleep" + else if(mob_size == MOB_SIZE_LARGE) + icon_state = "alien[caste]" + if(drooling) + add_overlay("alienspit_[caste]") + else + icon_state = "alien[caste]" + if(drooling) + add_overlay("alienspit") + + if(leaping) + if(alt_icon == initial(alt_icon)) + var/old_icon = icon + icon = alt_icon + alt_icon = old_icon + icon_state = "alien[caste]_leap" + pixel_x = -32 + pixel_y = -32 + else + if(alt_icon != initial(alt_icon)) + var/old_icon = icon + icon = alt_icon + alt_icon = old_icon + pixel_x = get_standard_pixel_x_offset(lying) + pixel_y = get_standard_pixel_y_offset(lying) + update_inv_hands() + update_inv_handcuffed() + +/mob/living/carbon/alien/humanoid/regenerate_icons() + if(!..()) + // update_icons() //Handled in update_transform(), leaving this here as a reminder + update_transform() + +/mob/living/carbon/alien/humanoid/update_transform() //The old method of updating lying/standing was update_icons(). Aliens still expect that. + if(lying > 0) + lying = 90 //Anything else looks retarded + ..() + update_icons() + +/mob/living/carbon/alien/humanoid/update_inv_handcuffed() + remove_overlay(HANDCUFF_LAYER) + + if(handcuffed) + var/cuff_icon = handcuffed.item_state + var/dmi_file = 'icons/mob/alien.dmi' + + if(mob_size == MOB_SIZE_LARGE) + cuff_icon += "_[caste]" + dmi_file = 'icons/mob/alienqueen.dmi' + + var/mutable_appearance/cuffs = mutable_appearance(dmi_file, cuff_icon, -HANDCUFF_LAYER) + cuffs.color = handcuffed.color + + overlays_standing[HANDCUFF_LAYER] = cuffs + apply_overlay(HANDCUFF_LAYER) + +//Royals have bigger sprites, so inhand things must be handled differently. +/mob/living/carbon/alien/humanoid/royal/update_inv_hands() + ..() + remove_overlay(HANDS_LAYER) + var/list/hands = list() + + var/obj/item/l_hand = get_item_for_held_index(1) + if(l_hand) + var/itm_state = l_hand.item_state + if(!itm_state) + itm_state = l_hand.icon_state + hands += mutable_appearance(alt_inhands_file, "[itm_state][caste]_l", -HANDS_LAYER) + + var/obj/item/r_hand = get_item_for_held_index(2) + if(r_hand) + var/itm_state = r_hand.item_state + if(!itm_state) + itm_state = r_hand.icon_state + hands += mutable_appearance(alt_inhands_file, "[itm_state][caste]_r", -HANDS_LAYER) + + overlays_standing[HANDS_LAYER] = hands apply_overlay(HANDS_LAYER) \ No newline at end of file diff --git a/code/modules/mob/living/carbon/alien/larva/death.dm b/code/modules/mob/living/carbon/alien/larva/death.dm index e0136d7036..9d0688e55c 100644 --- a/code/modules/mob/living/carbon/alien/larva/death.dm +++ b/code/modules/mob/living/carbon/alien/larva/death.dm @@ -1,23 +1,23 @@ -/mob/living/carbon/alien/larva/death(gibbed) - if(stat == DEAD) - return - - . = ..() - - update_icons() - -/mob/living/carbon/alien/larva/spawn_gibs(with_bodyparts, atom/loc_override) - var/location = loc_override ? loc_override.drop_location() : drop_location() - if(with_bodyparts) - new /obj/effect/gibspawner/larva(location, src) - else - new /obj/effect/gibspawner/larva/bodypartless(location, src) - -/mob/living/carbon/alien/larva/gib_animation() - new /obj/effect/temp_visual/gib_animation(loc, "gibbed-l") - -/mob/living/carbon/alien/larva/spawn_dust() - new /obj/effect/decal/remains/xeno(loc) - -/mob/living/carbon/alien/larva/dust_animation() - new /obj/effect/temp_visual/dust_animation(loc, "dust-l") +/mob/living/carbon/alien/larva/death(gibbed) + if(stat == DEAD) + return + + . = ..() + + update_icons() + +/mob/living/carbon/alien/larva/spawn_gibs(with_bodyparts, atom/loc_override) + var/location = loc_override ? loc_override.drop_location() : drop_location() + if(with_bodyparts) + new /obj/effect/gibspawner/larva(location, src) + else + new /obj/effect/gibspawner/larva/bodypartless(location, src) + +/mob/living/carbon/alien/larva/gib_animation() + new /obj/effect/temp_visual/gib_animation(loc, "gibbed-l") + +/mob/living/carbon/alien/larva/spawn_dust() + new /obj/effect/decal/remains/xeno(loc) + +/mob/living/carbon/alien/larva/dust_animation() + new /obj/effect/temp_visual/dust_animation(loc, "dust-l") diff --git a/code/modules/mob/living/carbon/alien/larva/powers.dm b/code/modules/mob/living/carbon/alien/larva/powers.dm index 9d5617b3e7..7204759db5 100644 --- a/code/modules/mob/living/carbon/alien/larva/powers.dm +++ b/code/modules/mob/living/carbon/alien/larva/powers.dm @@ -1,63 +1,63 @@ -/obj/effect/proc_holder/alien/hide - name = "Hide" - desc = "Allows aliens to hide beneath tables or certain items. Toggled on or off." - plasma_cost = 0 - - action_icon_state = "alien_hide" - -/obj/effect/proc_holder/alien/hide/fire(mob/living/carbon/alien/user) - if(user.stat != CONSCIOUS) - return - - if (user.layer != ABOVE_NORMAL_TURF_LAYER) - user.layer = ABOVE_NORMAL_TURF_LAYER - user.visible_message("[user] scurries to the ground!", \ - "You are now hiding.") - else - user.layer = MOB_LAYER - user.visible_message("[user] slowly peeks up from the ground...", \ - "You stop hiding.") - return 1 - - -/obj/effect/proc_holder/alien/larva_evolve - name = "Evolve" - desc = "Evolve into a higher alien caste." - plasma_cost = 0 - - action_icon_state = "alien_evolve_larva" - -/obj/effect/proc_holder/alien/larva_evolve/fire(mob/living/carbon/alien/user) - if(!islarva(user)) - return - var/mob/living/carbon/alien/larva/L = user - - if(L.handcuffed || L.legcuffed) // Cuffing larvas ? Eh ? - to_chat(user, "You cannot evolve when you are cuffed.") - return - - if(L.amount_grown >= L.max_grown) //TODO ~Carn - to_chat(L, "You are growing into a beautiful alien! It is time to choose a caste.") - to_chat(L, "There are three to choose from:") - to_chat(L, "Hunters are the most agile caste, tasked with hunting for hosts. They are faster than a human and can even pounce, but are not much tougher than a drone.") - to_chat(L, "Sentinels are tasked with protecting the hive. With their ranged spit, invisibility, and high health, they make formidable guardians and acceptable secondhand hunters.") - to_chat(L, "Drones are the weakest and slowest of the castes, but can grow into a praetorian and then queen if no queen exists, and are vital to maintaining a hive with their resin secretion abilities.") - var/alien_caste = alert(L, "Please choose which alien caste you shall belong to.",,"Hunter","Sentinel","Drone") - - if(user.incapacitated()) //something happened to us while we were choosing. - return - - var/mob/living/carbon/alien/humanoid/new_xeno - switch(alien_caste) - if("Hunter") - new_xeno = new /mob/living/carbon/alien/humanoid/hunter(L.loc) - if("Sentinel") - new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(L.loc) - if("Drone") - new_xeno = new /mob/living/carbon/alien/humanoid/drone(L.loc) - - L.alien_evolve(new_xeno) - return 0 - else - to_chat(user, "You are not fully grown.") - return 0 +/obj/effect/proc_holder/alien/hide + name = "Hide" + desc = "Allows aliens to hide beneath tables or certain items. Toggled on or off." + plasma_cost = 0 + + action_icon_state = "alien_hide" + +/obj/effect/proc_holder/alien/hide/fire(mob/living/carbon/alien/user) + if(user.stat != CONSCIOUS) + return + + if (user.layer != ABOVE_NORMAL_TURF_LAYER) + user.layer = ABOVE_NORMAL_TURF_LAYER + user.visible_message("[user] scurries to the ground!", \ + "You are now hiding.") + else + user.layer = MOB_LAYER + user.visible_message("[user] slowly peeks up from the ground...", \ + "You stop hiding.") + return 1 + + +/obj/effect/proc_holder/alien/larva_evolve + name = "Evolve" + desc = "Evolve into a higher alien caste." + plasma_cost = 0 + + action_icon_state = "alien_evolve_larva" + +/obj/effect/proc_holder/alien/larva_evolve/fire(mob/living/carbon/alien/user) + if(!islarva(user)) + return + var/mob/living/carbon/alien/larva/L = user + + if(L.handcuffed || L.legcuffed) // Cuffing larvas ? Eh ? + to_chat(user, "You cannot evolve when you are cuffed.") + return + + if(L.amount_grown >= L.max_grown) //TODO ~Carn + to_chat(L, "You are growing into a beautiful alien! It is time to choose a caste.") + to_chat(L, "There are three to choose from:") + to_chat(L, "Hunters are the most agile caste, tasked with hunting for hosts. They are faster than a human and can even pounce, but are not much tougher than a drone.") + to_chat(L, "Sentinels are tasked with protecting the hive. With their ranged spit, invisibility, and high health, they make formidable guardians and acceptable secondhand hunters.") + to_chat(L, "Drones are the weakest and slowest of the castes, but can grow into a praetorian and then queen if no queen exists, and are vital to maintaining a hive with their resin secretion abilities.") + var/alien_caste = alert(L, "Please choose which alien caste you shall belong to.",,"Hunter","Sentinel","Drone") + + if(user.incapacitated()) //something happened to us while we were choosing. + return + + var/mob/living/carbon/alien/humanoid/new_xeno + switch(alien_caste) + if("Hunter") + new_xeno = new /mob/living/carbon/alien/humanoid/hunter(L.loc) + if("Sentinel") + new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(L.loc) + if("Drone") + new_xeno = new /mob/living/carbon/alien/humanoid/drone(L.loc) + + L.alien_evolve(new_xeno) + return 0 + else + to_chat(user, "You are not fully grown.") + return 0 diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm index b8edd34ee9..75aadd69c9 100644 --- a/code/modules/mob/living/carbon/alien/life.dm +++ b/code/modules/mob/living/carbon/alien/life.dm @@ -1,52 +1,52 @@ -/mob/living/carbon/alien/Life() - findQueen() - return..() - -/mob/living/carbon/alien/check_breath(datum/gas_mixture/breath) - if(status_flags & GODMODE) - return - - if(!breath || (breath.total_moles() == 0)) - //Aliens breathe in vaccuum - return 0 - - var/toxins_used = 0 - var/tox_detect_threshold = 0.02 - var/breath_pressure = (breath.total_moles()*R_IDEAL_GAS_EQUATION*breath.temperature)/BREATH_VOLUME - var/list/breath_gases = breath.gases - - //Partial pressure of the toxins in our breath - var/Toxins_pp = (breath_gases[/datum/gas/plasma]/breath.total_moles())*breath_pressure - - if(Toxins_pp > tox_detect_threshold) // Detect toxins in air - adjustPlasma(breath_gases[/datum/gas/plasma]*250) - throw_alert("alien_tox", /obj/screen/alert/alien_tox) - - toxins_used = breath_gases[/datum/gas/plasma] - - else - clear_alert("alien_tox") - - //Breathe in toxins and out oxygen - breath_gases[/datum/gas/plasma] -= toxins_used - breath_gases[/datum/gas/oxygen] += toxins_used - - GAS_GARBAGE_COLLECT(breath.gases) - - //BREATH TEMPERATURE - handle_breath_temperature(breath) - -/mob/living/carbon/alien/handle_status_effects() - ..() - //natural reduction of movement delay due to stun. - if(move_delay_add > 0) - move_delay_add = max(0, move_delay_add - rand(1, 2)) - -/mob/living/carbon/alien/handle_changeling() - return - -/mob/living/carbon/alien/handle_fire()//Aliens on fire code - if(..()) - return - adjust_bodytemperature(BODYTEMP_HEATING_MAX) //If you're on fire, you heat up! - return +/mob/living/carbon/alien/Life() + findQueen() + return..() + +/mob/living/carbon/alien/check_breath(datum/gas_mixture/breath) + if(status_flags & GODMODE) + return + + if(!breath || (breath.total_moles() == 0)) + //Aliens breathe in vaccuum + return 0 + + var/toxins_used = 0 + var/tox_detect_threshold = 0.02 + var/breath_pressure = (breath.total_moles()*R_IDEAL_GAS_EQUATION*breath.temperature)/BREATH_VOLUME + var/list/breath_gases = breath.gases + + //Partial pressure of the toxins in our breath + var/Toxins_pp = (breath_gases[/datum/gas/plasma]/breath.total_moles())*breath_pressure + + if(Toxins_pp > tox_detect_threshold) // Detect toxins in air + adjustPlasma(breath_gases[/datum/gas/plasma]*250) + throw_alert("alien_tox", /obj/screen/alert/alien_tox) + + toxins_used = breath_gases[/datum/gas/plasma] + + else + clear_alert("alien_tox") + + //Breathe in toxins and out oxygen + breath_gases[/datum/gas/plasma] -= toxins_used + breath_gases[/datum/gas/oxygen] += toxins_used + + GAS_GARBAGE_COLLECT(breath.gases) + + //BREATH TEMPERATURE + handle_breath_temperature(breath) + +/mob/living/carbon/alien/handle_status_effects() + ..() + //natural reduction of movement delay due to stun. + if(move_delay_add > 0) + move_delay_add = max(0, move_delay_add - rand(1, 2)) + +/mob/living/carbon/alien/handle_changeling() + return + +/mob/living/carbon/alien/handle_fire()//Aliens on fire code + if(..()) + return + adjust_bodytemperature(BODYTEMP_HEATING_MAX) //If you're on fire, you heat up! + return diff --git a/code/modules/mob/living/carbon/alien/say.dm b/code/modules/mob/living/carbon/alien/say.dm index 5f1e1a2830..1ad0a1e7ff 100644 --- a/code/modules/mob/living/carbon/alien/say.dm +++ b/code/modules/mob/living/carbon/alien/say.dm @@ -1,23 +1,23 @@ -/mob/living/proc/alien_talk(message, shown_name = real_name) - src.log_talk(message, LOG_SAY) - message = trim(message) - if(!message) - return - - var/message_a = say_quote(message) - var/rendered = "Hivemind, [shown_name] [message_a]" - for(var/mob/S in GLOB.player_list) - if(!S.stat && S.hivecheck()) - to_chat(S, rendered) - if(S in GLOB.dead_mob_list) - var/link = FOLLOW_LINK(S, src) - to_chat(S, "[link] [rendered]") - -/mob/living/carbon/alien/humanoid/royal/queen/alien_talk(message, shown_name = name) - shown_name = "[shown_name]" - ..(message, shown_name) - -/mob/living/carbon/hivecheck() - var/obj/item/organ/alien/hivenode/N = getorgan(/obj/item/organ/alien/hivenode) - if(N && !N.recent_queen_death) //Mob has alien hive node and is not under the dead queen special effect. - return N +/mob/living/proc/alien_talk(message, shown_name = real_name) + src.log_talk(message, LOG_SAY) + message = trim(message) + if(!message) + return + + var/message_a = say_quote(message) + var/rendered = "Hivemind, [shown_name] [message_a]" + for(var/mob/S in GLOB.player_list) + if(!S.stat && S.hivecheck()) + to_chat(S, rendered) + if(S in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(S, src) + to_chat(S, "[link] [rendered]") + +/mob/living/carbon/alien/humanoid/royal/queen/alien_talk(message, shown_name = name) + shown_name = "[shown_name]" + ..(message, shown_name) + +/mob/living/carbon/hivecheck() + var/obj/item/organ/alien/hivenode/N = getorgan(/obj/item/organ/alien/hivenode) + if(N && !N.recent_queen_death) //Mob has alien hive node and is not under the dead queen special effect. + return N diff --git a/code/modules/mob/living/carbon/alien/screen.dm b/code/modules/mob/living/carbon/alien/screen.dm index b5b646b08b..1e7ff07631 100644 --- a/code/modules/mob/living/carbon/alien/screen.dm +++ b/code/modules/mob/living/carbon/alien/screen.dm @@ -1,33 +1,33 @@ - -/mob/living/carbon/alien/proc/updatePlasmaDisplay() - if(hud_used) //clientless aliens - hud_used.alien_plasma_display.maptext = "
                [round(getPlasma())]
                " - -/mob/living/carbon/alien/larva/updatePlasmaDisplay() - return - -/mob/living/carbon/alien/proc/findQueen() - if(hud_used) - hud_used.alien_queen_finder.cut_overlays() - var/mob/queen = get_alien_type(/mob/living/carbon/alien/humanoid/royal/queen) - if(!queen) - return - var/turf/Q = get_turf(queen) - var/turf/A = get_turf(src) - if(Q.z != A.z) //The queen is on a different Z level, we cannot sense that far. - return - var/Qdir = get_dir(src, Q) - var/Qdist = get_dist(src, Q) - var/finder_icon = "finder_center" //Overlay showed when adjacent to or on top of the queen! - switch(Qdist) - if(2 to 7) - finder_icon = "finder_near" - if(8 to 20) - finder_icon = "finder_med" - if(21 to INFINITY) - finder_icon = "finder_far" - var/image/finder_eye = image('icons/mob/screen_alien.dmi', finder_icon, dir = Qdir) - hud_used.alien_queen_finder.add_overlay(finder_eye) - -/mob/living/carbon/alien/humanoid/royal/queen/findQueen() + +/mob/living/carbon/alien/proc/updatePlasmaDisplay() + if(hud_used) //clientless aliens + hud_used.alien_plasma_display.maptext = "
                [round(getPlasma())]
                " + +/mob/living/carbon/alien/larva/updatePlasmaDisplay() + return + +/mob/living/carbon/alien/proc/findQueen() + if(hud_used) + hud_used.alien_queen_finder.cut_overlays() + var/mob/queen = get_alien_type(/mob/living/carbon/alien/humanoid/royal/queen) + if(!queen) + return + var/turf/Q = get_turf(queen) + var/turf/A = get_turf(src) + if(Q.z != A.z) //The queen is on a different Z level, we cannot sense that far. + return + var/Qdir = get_dir(src, Q) + var/Qdist = get_dist(src, Q) + var/finder_icon = "finder_center" //Overlay showed when adjacent to or on top of the queen! + switch(Qdist) + if(2 to 7) + finder_icon = "finder_near" + if(8 to 20) + finder_icon = "finder_med" + if(21 to INFINITY) + finder_icon = "finder_far" + var/image/finder_eye = image('icons/mob/screen_alien.dmi', finder_icon, dir = Qdir) + hud_used.alien_queen_finder.add_overlay(finder_eye) + +/mob/living/carbon/alien/humanoid/royal/queen/findQueen() return //Queen already knows where she is. Hopefully. \ No newline at end of file diff --git a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm index e74f2e54a9..dc3c45b79f 100644 --- a/code/modules/mob/living/carbon/alien/special/alien_embryo.dm +++ b/code/modules/mob/living/carbon/alien/special/alien_embryo.dm @@ -1,139 +1,139 @@ -// This is to replace the previous datum/disease/alien_embryo for slightly improved handling and maintainability -// It functions almost identically (see code/datums/diseases/alien_embryo.dm) -/obj/item/organ/body_egg/alien_embryo - name = "alien embryo" - icon = 'icons/mob/alien.dmi' - icon_state = "larva0_dead" - var/stage = 0 - var/bursting = FALSE - -/obj/item/organ/body_egg/alien_embryo/on_find(mob/living/finder) - ..() - if(stage < 4) - to_chat(finder, "It's small and weak, barely the size of a foetus.") - else - to_chat(finder, "It's grown quite large, and writhes slightly as you look at it.") - if(prob(10)) - AttemptGrow(0) - -/obj/item/organ/body_egg/alien_embryo/prepare_eat() - var/obj/S = ..() - S.reagents.add_reagent(/datum/reagent/toxin/acid, 10) - return S - -/obj/item/organ/body_egg/alien_embryo/on_life() - . = ..() - switch(stage) - if(2, 3) - if(prob(2)) - owner.emote("sneeze") - if(prob(2)) - owner.emote("cough") - if(prob(2)) - to_chat(owner, "Your throat feels sore.") - if(prob(2)) - to_chat(owner, "Mucous runs down the back of your throat.") - if(4) - if(prob(2)) - owner.emote("sneeze") - if(prob(2)) - owner.emote("cough") - if(prob(4)) - to_chat(owner, "Your muscles ache.") - if(prob(20)) - owner.take_bodypart_damage(1) - if(prob(4)) - to_chat(owner, "Your stomach hurts.") - if(prob(20)) - owner.adjustToxLoss(1) - if(5) - to_chat(owner, "You feel something tearing its way out of your stomach...") - owner.adjustToxLoss(10) - -/obj/item/organ/body_egg/alien_embryo/egg_process() - if(stage < 5 && prob(3)) - stage++ - INVOKE_ASYNC(src, .proc/RefreshInfectionImage) - - if(stage == 5 && prob(50)) - for(var/datum/surgery/S in owner.surgeries) - if(S.location == BODY_ZONE_CHEST && istype(S.get_surgery_step(), /datum/surgery_step/manipulate_organs)) - AttemptGrow(0) - return - AttemptGrow() - - - -/obj/item/organ/body_egg/alien_embryo/proc/AttemptGrow(var/kill_on_sucess=TRUE) - if(!owner || bursting) - return - - bursting = TRUE - - var/list/candidates = pollGhostCandidates("Do you want to play as an alien larva that will burst out of [owner]?", ROLE_ALIEN, null, ROLE_ALIEN, 100, POLL_IGNORE_ALIEN_LARVA) - - if(QDELETED(src) || QDELETED(owner)) - return - - if(!candidates.len || !owner) - bursting = FALSE - stage = 4 - return - - var/mob/dead/observer/ghost = pick(candidates) - - var/mutable_appearance/overlay = mutable_appearance('icons/mob/alien.dmi', "burst_lie") - owner.add_overlay(overlay) - - var/atom/xeno_loc = get_turf(owner) - var/mob/living/carbon/alien/larva/new_xeno = new(xeno_loc) - ghost.transfer_ckey(new_xeno, FALSE) - SEND_SOUND(new_xeno, sound('sound/voice/hiss5.ogg',0,0,0,100)) //To get the player's attention - new_xeno.canmove = 0 //so we don't move during the bursting animation - new_xeno.notransform = 1 - new_xeno.invisibility = INVISIBILITY_MAXIMUM - - sleep(6) - - if(QDELETED(src) || QDELETED(owner)) - return - - if(new_xeno) - new_xeno.canmove = 1 - new_xeno.notransform = 0 - new_xeno.invisibility = 0 - - var/mob/living/carbon/old_owner = owner - if(kill_on_sucess) //ITS TOO LATE - new_xeno.visible_message("[new_xeno] bursts out of [owner]!", "You exit [owner], your previous host.", "You hear organic matter ripping and tearing!") - owner.apply_damage(rand(100,300),BRUTE,zone,FALSE) //Random high damage to torso so health sensors don't metagame. - var/obj/item/bodypart/B = owner.get_bodypart(zone) - B.drop_organs(owner) //Lets still make the death gruesome and impossible to just simply defib someone. - owner.death(FALSE) //Just in case some freak occurance occurs where you somehow survive all your organs being removed from you and the 100-300 brute damage. - else //When it is removed via surgery at a late stage, rather than forced. - new_xeno.visible_message("[new_xeno] wriggles out of [owner]!", "You exit [owner], your previous host.") - owner.adjustBruteLoss(40) - old_owner.cut_overlay(overlay) - qdel(src) - - -/*---------------------------------------- -Proc: AddInfectionImages(C) -Des: Adds the infection image to all aliens for this embryo -----------------------------------------*/ -/obj/item/organ/body_egg/alien_embryo/AddInfectionImages(mob/living/carbon/C) - for(var/mob/living/carbon/alien/alien in GLOB.player_list) - if(alien.client) - var/I = image('icons/mob/alien.dmi', loc = C, icon_state = "infected[stage]") - alien.client.images += I - -/*---------------------------------------- -Proc: RemoveInfectionImage(C) -Des: Removes all images from the mob infected by this embryo -----------------------------------------*/ -/obj/item/organ/body_egg/alien_embryo/RemoveInfectionImages(mob/living/carbon/C) - for(var/mob/living/carbon/alien/alien in GLOB.player_list) - if(alien.client) - for(var/image/I in alien.client.images) - if(dd_hasprefix_case(I.icon_state, "infected") && I.loc == C) - qdel(I) +// This is to replace the previous datum/disease/alien_embryo for slightly improved handling and maintainability +// It functions almost identically (see code/datums/diseases/alien_embryo.dm) +/obj/item/organ/body_egg/alien_embryo + name = "alien embryo" + icon = 'icons/mob/alien.dmi' + icon_state = "larva0_dead" + var/stage = 0 + var/bursting = FALSE + +/obj/item/organ/body_egg/alien_embryo/on_find(mob/living/finder) + ..() + if(stage < 4) + to_chat(finder, "It's small and weak, barely the size of a foetus.") + else + to_chat(finder, "It's grown quite large, and writhes slightly as you look at it.") + if(prob(10)) + AttemptGrow(0) + +/obj/item/organ/body_egg/alien_embryo/prepare_eat() + var/obj/S = ..() + S.reagents.add_reagent(/datum/reagent/toxin/acid, 10) + return S + +/obj/item/organ/body_egg/alien_embryo/on_life() + . = ..() + switch(stage) + if(2, 3) + if(prob(2)) + owner.emote("sneeze") + if(prob(2)) + owner.emote("cough") + if(prob(2)) + to_chat(owner, "Your throat feels sore.") + if(prob(2)) + to_chat(owner, "Mucous runs down the back of your throat.") + if(4) + if(prob(2)) + owner.emote("sneeze") + if(prob(2)) + owner.emote("cough") + if(prob(4)) + to_chat(owner, "Your muscles ache.") + if(prob(20)) + owner.take_bodypart_damage(1) + if(prob(4)) + to_chat(owner, "Your stomach hurts.") + if(prob(20)) + owner.adjustToxLoss(1) + if(5) + to_chat(owner, "You feel something tearing its way out of your stomach...") + owner.adjustToxLoss(10) + +/obj/item/organ/body_egg/alien_embryo/egg_process() + if(stage < 5 && prob(3)) + stage++ + INVOKE_ASYNC(src, .proc/RefreshInfectionImage) + + if(stage == 5 && prob(50)) + for(var/datum/surgery/S in owner.surgeries) + if(S.location == BODY_ZONE_CHEST && istype(S.get_surgery_step(), /datum/surgery_step/manipulate_organs)) + AttemptGrow(0) + return + AttemptGrow() + + + +/obj/item/organ/body_egg/alien_embryo/proc/AttemptGrow(var/kill_on_sucess=TRUE) + if(!owner || bursting) + return + + bursting = TRUE + + var/list/candidates = pollGhostCandidates("Do you want to play as an alien larva that will burst out of [owner]?", ROLE_ALIEN, null, ROLE_ALIEN, 100, POLL_IGNORE_ALIEN_LARVA) + + if(QDELETED(src) || QDELETED(owner)) + return + + if(!candidates.len || !owner) + bursting = FALSE + stage = 4 + return + + var/mob/dead/observer/ghost = pick(candidates) + + var/mutable_appearance/overlay = mutable_appearance('icons/mob/alien.dmi', "burst_lie") + owner.add_overlay(overlay) + + var/atom/xeno_loc = get_turf(owner) + var/mob/living/carbon/alien/larva/new_xeno = new(xeno_loc) + ghost.transfer_ckey(new_xeno, FALSE) + SEND_SOUND(new_xeno, sound('sound/voice/hiss5.ogg',0,0,0,100)) //To get the player's attention + new_xeno.canmove = 0 //so we don't move during the bursting animation + new_xeno.notransform = 1 + new_xeno.invisibility = INVISIBILITY_MAXIMUM + + sleep(6) + + if(QDELETED(src) || QDELETED(owner)) + return + + if(new_xeno) + new_xeno.canmove = 1 + new_xeno.notransform = 0 + new_xeno.invisibility = 0 + + var/mob/living/carbon/old_owner = owner + if(kill_on_sucess) //ITS TOO LATE + new_xeno.visible_message("[new_xeno] bursts out of [owner]!", "You exit [owner], your previous host.", "You hear organic matter ripping and tearing!") + owner.apply_damage(rand(100,300),BRUTE,zone,FALSE) //Random high damage to torso so health sensors don't metagame. + var/obj/item/bodypart/B = owner.get_bodypart(zone) + B.drop_organs(owner) //Lets still make the death gruesome and impossible to just simply defib someone. + owner.death(FALSE) //Just in case some freak occurance occurs where you somehow survive all your organs being removed from you and the 100-300 brute damage. + else //When it is removed via surgery at a late stage, rather than forced. + new_xeno.visible_message("[new_xeno] wriggles out of [owner]!", "You exit [owner], your previous host.") + owner.adjustBruteLoss(40) + old_owner.cut_overlay(overlay) + qdel(src) + + +/*---------------------------------------- +Proc: AddInfectionImages(C) +Des: Adds the infection image to all aliens for this embryo +----------------------------------------*/ +/obj/item/organ/body_egg/alien_embryo/AddInfectionImages(mob/living/carbon/C) + for(var/mob/living/carbon/alien/alien in GLOB.player_list) + if(alien.client) + var/I = image('icons/mob/alien.dmi', loc = C, icon_state = "infected[stage]") + alien.client.images += I + +/*---------------------------------------- +Proc: RemoveInfectionImage(C) +Des: Removes all images from the mob infected by this embryo +----------------------------------------*/ +/obj/item/organ/body_egg/alien_embryo/RemoveInfectionImages(mob/living/carbon/C) + for(var/mob/living/carbon/alien/alien in GLOB.player_list) + if(alien.client) + for(var/image/I in alien.client.images) + if(dd_hasprefix_case(I.icon_state, "infected") && I.loc == C) + qdel(I) diff --git a/code/modules/mob/living/carbon/alien/special/facehugger.dm b/code/modules/mob/living/carbon/alien/special/facehugger.dm index 8baeb93195..0f6fc7e944 100644 --- a/code/modules/mob/living/carbon/alien/special/facehugger.dm +++ b/code/modules/mob/living/carbon/alien/special/facehugger.dm @@ -1,271 +1,271 @@ - - -//TODO: Make these simple_animals - -#define MIN_IMPREGNATION_TIME 100 //time it takes to impregnate someone -#define MAX_IMPREGNATION_TIME 150 - -#define MIN_ACTIVE_TIME 200 //time between being dropped and going idle -#define MAX_ACTIVE_TIME 400 - -/obj/item/clothing/mask/facehugger - name = "alien" - desc = "It has some sort of a tube at the end of its tail." - icon = 'icons/mob/alien.dmi' - icon_state = "facehugger" - item_state = "facehugger" - w_class = WEIGHT_CLASS_TINY //note: can be picked up by aliens unlike most other items of w_class below 4 - clothing_flags = ALLOWINTERNALS - throw_range = 5 - tint = 3 - flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH - layer = MOB_LAYER - max_integrity = 100 - mutantrace_variation = STYLE_MUZZLE - - var/stat = CONSCIOUS //UNCONSCIOUS is the idle state in this case - - var/sterile = FALSE - var/real = TRUE //0 for the toy, 1 for real. Sure I could istype, but fuck that. - var/strength = 5 - - var/attached = 0 - -/obj/item/clothing/mask/facehugger/lamarr - name = "Lamarr" - sterile = TRUE - -/obj/item/clothing/mask/facehugger/dead - icon_state = "facehugger_dead" - item_state = "facehugger_inactive" - sterile = TRUE - stat = DEAD - -/obj/item/clothing/mask/facehugger/impregnated - icon_state = "facehugger_impregnated" - item_state = "facehugger_impregnated" - sterile = TRUE - stat = DEAD - -/obj/item/clothing/mask/facehugger/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - ..() - if(obj_integrity < 90) - Die() - -/obj/item/clothing/mask/facehugger/attackby(obj/item/O, mob/user, params) - return O.attack_obj(src, user) - -/obj/item/clothing/mask/facehugger/attack_alien(mob/user) //can be picked up by aliens - return attack_hand(user) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/clothing/mask/facehugger/attack_hand(mob/user) - if((stat == CONSCIOUS && !sterile) && !isalien(user)) - if(Leap(user)) - return - . = ..() - -/obj/item/clothing/mask/facehugger/attack(mob/living/M, mob/user) - ..() - if(user.transferItemToLoc(src, get_turf(M))) - Leap(M) - -/obj/item/clothing/mask/facehugger/examine(mob/user) - . = ..() - if(!real)//So that giant red text about probisci doesn't show up. - return - switch(stat) - if(DEAD,UNCONSCIOUS) - . += "[src] is not moving." - if(CONSCIOUS) - . += "[src] seems to be active!" - if (sterile) - . += "It looks like the proboscis has been removed." - - -/obj/item/clothing/mask/facehugger/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(exposed_temperature > 300) - Die() - -/obj/item/clothing/mask/facehugger/equipped(mob/M) - Attach(M) - -/obj/item/clothing/mask/facehugger/Crossed(atom/target) - HasProximity(target) - return - -/obj/item/clothing/mask/facehugger/on_found(mob/finder) - if(stat == CONSCIOUS) - return HasProximity(finder) - return 0 - -/obj/item/clothing/mask/facehugger/HasProximity(atom/movable/AM as mob|obj) - if(CanHug(AM) && Adjacent(AM)) - return Leap(AM) - return 0 - -/obj/item/clothing/mask/facehugger/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback) - if(!..()) - return - if(stat == CONSCIOUS) - icon_state = "[initial(icon_state)]_thrown" - addtimer(CALLBACK(src, .proc/clear_throw_icon_state), 15) - -/obj/item/clothing/mask/facehugger/proc/clear_throw_icon_state() - if(icon_state == "[initial(icon_state)]_thrown") - icon_state = "[initial(icon_state)]" - -/obj/item/clothing/mask/facehugger/throw_impact(atom/hit_atom) - ..() - if(stat == CONSCIOUS) - icon_state = "[initial(icon_state)]" - Leap(hit_atom) - -/obj/item/clothing/mask/facehugger/proc/valid_to_attach(mob/living/M) - // valid targets: carbons except aliens and devils - // facehugger state early exit checks - if(stat != CONSCIOUS) - return FALSE - if(attached) - return FALSE - if(iscarbon(M)) - // disallowed carbons - if(isalien(M) || isdevil(M)) - return FALSE - var/mob/living/carbon/target = M - // gotta have a head to be implanted (no changelings or sentient plants) - if(!target.get_bodypart(BODY_ZONE_HEAD)) - return FALSE - - if(target.getorgan(/obj/item/organ/alien/hivenode) || target.getorgan(/obj/item/organ/body_egg/alien_embryo)) - return FALSE - // carbon, has head, not alien or devil, has no hivenode or embryo: valid - return TRUE - - return FALSE - -/obj/item/clothing/mask/facehugger/proc/Leap(mob/living/M) - if(!valid_to_attach(M)) - return FALSE - if(iscarbon(M)) - var/mob/living/carbon/target = M - if(target.wear_mask && istype(target.wear_mask, /obj/item/clothing/mask/facehugger)) - return FALSE - // passed initial checks - time to leap! - M.visible_message("[src] leaps at [M]'s face!", \ - "[src] leaps at [M]'s face!") - - // probiscis-blocker handling - if(iscarbon(M)) - var/mob/living/carbon/target = M - - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(H.is_mouth_covered(head_only = 1)) - H.visible_message("[src] smashes against [H]'s [H.head]!", \ - "[src] smashes against [H]'s [H.head]!") - Die() - return FALSE - - if(target.wear_mask) - var/obj/item/clothing/W = target.wear_mask - if(target.dropItemToGround(W)) - target.visible_message("[src] tears [W] off of [target]'s face!", \ - "[src] tears [W] off of [target]'s face!") - target.equip_to_slot_if_possible(src, SLOT_WEAR_MASK, 0, 1, 1) - return TRUE // time for a smoke - -/obj/item/clothing/mask/facehugger/proc/Attach(mob/living/M) - if(!valid_to_attach(M)) - return - // early returns and validity checks done: attach. - attached++ - //ensure we detach once we no longer need to be attached - addtimer(CALLBACK(src, .proc/detach), MAX_IMPREGNATION_TIME) - - - if(!sterile) - M.take_bodypart_damage(strength,0) //done here so that humans in helmets take damage - M.Unconscious(MAX_IMPREGNATION_TIME/0.3) //something like 25 ticks = 20 seconds with the default settings - - GoIdle() //so it doesn't jump the people that tear it off - - addtimer(CALLBACK(src, .proc/Impregnate, M), rand(MIN_IMPREGNATION_TIME, MAX_IMPREGNATION_TIME)) - -/obj/item/clothing/mask/facehugger/proc/detach() - attached = 0 - -/obj/item/clothing/mask/facehugger/proc/Impregnate(mob/living/target) - if(!target || target.stat == DEAD) //was taken off or something - return - - if(iscarbon(target)) - var/mob/living/carbon/C = target - if(C.wear_mask != src) - return - - if(!sterile) - target.visible_message("[src] falls limp after violating [target]'s face!", \ - "[src] falls limp after violating [target]'s face!") - - Die() - icon_state = "[initial(icon_state)]_impregnated" - - var/obj/item/bodypart/chest/LC = target.get_bodypart(BODY_ZONE_CHEST) - if((!LC || LC.status != BODYPART_ROBOTIC) && !target.getorgan(/obj/item/organ/body_egg/alien_embryo)) - new /obj/item/organ/body_egg/alien_embryo(target) - - else - target.visible_message("[src] violates [target]'s face!", \ - "[src] violates [target]'s face!") - -/obj/item/clothing/mask/facehugger/proc/GoActive() - if(stat == DEAD || stat == CONSCIOUS) - return - - stat = CONSCIOUS - icon_state = "[initial(icon_state)]" - -/obj/item/clothing/mask/facehugger/proc/GoIdle() - if(stat == DEAD || stat == UNCONSCIOUS) - return - - stat = UNCONSCIOUS - icon_state = "[initial(icon_state)]_inactive" - - addtimer(CALLBACK(src, .proc/GoActive), rand(MIN_ACTIVE_TIME, MAX_ACTIVE_TIME)) - -/obj/item/clothing/mask/facehugger/proc/Die() - if(stat == DEAD) - return - - icon_state = "[initial(icon_state)]_dead" - item_state = "facehugger_inactive" - stat = DEAD - - visible_message("[src] curls up into a ball!") - -/proc/CanHug(mob/living/M) - if(!istype(M)) - return 0 - if(M.stat == DEAD) - return 0 - if(M.getorgan(/obj/item/organ/alien/hivenode)) - return 0 - - if(ismonkey(M)) - return 1 - - var/mob/living/carbon/C = M - if(ishuman(C) && !(SLOT_WEAR_MASK in C.dna.species.no_equip)) - var/mob/living/carbon/human/H = C - if(H.is_mouth_covered(head_only = 1)) - return 0 - return 1 - return 0 - -#undef MIN_ACTIVE_TIME -#undef MAX_ACTIVE_TIME - -#undef MIN_IMPREGNATION_TIME -#undef MAX_IMPREGNATION_TIME + + +//TODO: Make these simple_animals + +#define MIN_IMPREGNATION_TIME 100 //time it takes to impregnate someone +#define MAX_IMPREGNATION_TIME 150 + +#define MIN_ACTIVE_TIME 200 //time between being dropped and going idle +#define MAX_ACTIVE_TIME 400 + +/obj/item/clothing/mask/facehugger + name = "alien" + desc = "It has some sort of a tube at the end of its tail." + icon = 'icons/mob/alien.dmi' + icon_state = "facehugger" + item_state = "facehugger" + w_class = WEIGHT_CLASS_TINY //note: can be picked up by aliens unlike most other items of w_class below 4 + clothing_flags = ALLOWINTERNALS + throw_range = 5 + tint = 3 + flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH + layer = MOB_LAYER + max_integrity = 100 + mutantrace_variation = STYLE_MUZZLE + + var/stat = CONSCIOUS //UNCONSCIOUS is the idle state in this case + + var/sterile = FALSE + var/real = TRUE //0 for the toy, 1 for real. Sure I could istype, but fuck that. + var/strength = 5 + + var/attached = 0 + +/obj/item/clothing/mask/facehugger/lamarr + name = "Lamarr" + sterile = TRUE + +/obj/item/clothing/mask/facehugger/dead + icon_state = "facehugger_dead" + item_state = "facehugger_inactive" + sterile = TRUE + stat = DEAD + +/obj/item/clothing/mask/facehugger/impregnated + icon_state = "facehugger_impregnated" + item_state = "facehugger_impregnated" + sterile = TRUE + stat = DEAD + +/obj/item/clothing/mask/facehugger/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + ..() + if(obj_integrity < 90) + Die() + +/obj/item/clothing/mask/facehugger/attackby(obj/item/O, mob/user, params) + return O.attack_obj(src, user) + +/obj/item/clothing/mask/facehugger/attack_alien(mob/user) //can be picked up by aliens + return attack_hand(user) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/clothing/mask/facehugger/attack_hand(mob/user) + if((stat == CONSCIOUS && !sterile) && !isalien(user)) + if(Leap(user)) + return + . = ..() + +/obj/item/clothing/mask/facehugger/attack(mob/living/M, mob/user) + ..() + if(user.transferItemToLoc(src, get_turf(M))) + Leap(M) + +/obj/item/clothing/mask/facehugger/examine(mob/user) + . = ..() + if(!real)//So that giant red text about probisci doesn't show up. + return + switch(stat) + if(DEAD,UNCONSCIOUS) + . += "[src] is not moving." + if(CONSCIOUS) + . += "[src] seems to be active!" + if (sterile) + . += "It looks like the proboscis has been removed." + + +/obj/item/clothing/mask/facehugger/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(exposed_temperature > 300) + Die() + +/obj/item/clothing/mask/facehugger/equipped(mob/M) + Attach(M) + +/obj/item/clothing/mask/facehugger/Crossed(atom/target) + HasProximity(target) + return + +/obj/item/clothing/mask/facehugger/on_found(mob/finder) + if(stat == CONSCIOUS) + return HasProximity(finder) + return 0 + +/obj/item/clothing/mask/facehugger/HasProximity(atom/movable/AM as mob|obj) + if(CanHug(AM) && Adjacent(AM)) + return Leap(AM) + return 0 + +/obj/item/clothing/mask/facehugger/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback) + if(!..()) + return + if(stat == CONSCIOUS) + icon_state = "[initial(icon_state)]_thrown" + addtimer(CALLBACK(src, .proc/clear_throw_icon_state), 15) + +/obj/item/clothing/mask/facehugger/proc/clear_throw_icon_state() + if(icon_state == "[initial(icon_state)]_thrown") + icon_state = "[initial(icon_state)]" + +/obj/item/clothing/mask/facehugger/throw_impact(atom/hit_atom) + ..() + if(stat == CONSCIOUS) + icon_state = "[initial(icon_state)]" + Leap(hit_atom) + +/obj/item/clothing/mask/facehugger/proc/valid_to_attach(mob/living/M) + // valid targets: carbons except aliens and devils + // facehugger state early exit checks + if(stat != CONSCIOUS) + return FALSE + if(attached) + return FALSE + if(iscarbon(M)) + // disallowed carbons + if(isalien(M) || isdevil(M)) + return FALSE + var/mob/living/carbon/target = M + // gotta have a head to be implanted (no changelings or sentient plants) + if(!target.get_bodypart(BODY_ZONE_HEAD)) + return FALSE + + if(target.getorgan(/obj/item/organ/alien/hivenode) || target.getorgan(/obj/item/organ/body_egg/alien_embryo)) + return FALSE + // carbon, has head, not alien or devil, has no hivenode or embryo: valid + return TRUE + + return FALSE + +/obj/item/clothing/mask/facehugger/proc/Leap(mob/living/M) + if(!valid_to_attach(M)) + return FALSE + if(iscarbon(M)) + var/mob/living/carbon/target = M + if(target.wear_mask && istype(target.wear_mask, /obj/item/clothing/mask/facehugger)) + return FALSE + // passed initial checks - time to leap! + M.visible_message("[src] leaps at [M]'s face!", \ + "[src] leaps at [M]'s face!") + + // probiscis-blocker handling + if(iscarbon(M)) + var/mob/living/carbon/target = M + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.is_mouth_covered(head_only = 1)) + H.visible_message("[src] smashes against [H]'s [H.head]!", \ + "[src] smashes against [H]'s [H.head]!") + Die() + return FALSE + + if(target.wear_mask) + var/obj/item/clothing/W = target.wear_mask + if(target.dropItemToGround(W)) + target.visible_message("[src] tears [W] off of [target]'s face!", \ + "[src] tears [W] off of [target]'s face!") + target.equip_to_slot_if_possible(src, SLOT_WEAR_MASK, 0, 1, 1) + return TRUE // time for a smoke + +/obj/item/clothing/mask/facehugger/proc/Attach(mob/living/M) + if(!valid_to_attach(M)) + return + // early returns and validity checks done: attach. + attached++ + //ensure we detach once we no longer need to be attached + addtimer(CALLBACK(src, .proc/detach), MAX_IMPREGNATION_TIME) + + + if(!sterile) + M.take_bodypart_damage(strength,0) //done here so that humans in helmets take damage + M.Unconscious(MAX_IMPREGNATION_TIME/0.3) //something like 25 ticks = 20 seconds with the default settings + + GoIdle() //so it doesn't jump the people that tear it off + + addtimer(CALLBACK(src, .proc/Impregnate, M), rand(MIN_IMPREGNATION_TIME, MAX_IMPREGNATION_TIME)) + +/obj/item/clothing/mask/facehugger/proc/detach() + attached = 0 + +/obj/item/clothing/mask/facehugger/proc/Impregnate(mob/living/target) + if(!target || target.stat == DEAD) //was taken off or something + return + + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(C.wear_mask != src) + return + + if(!sterile) + target.visible_message("[src] falls limp after violating [target]'s face!", \ + "[src] falls limp after violating [target]'s face!") + + Die() + icon_state = "[initial(icon_state)]_impregnated" + + var/obj/item/bodypart/chest/LC = target.get_bodypart(BODY_ZONE_CHEST) + if((!LC || LC.status != BODYPART_ROBOTIC) && !target.getorgan(/obj/item/organ/body_egg/alien_embryo)) + new /obj/item/organ/body_egg/alien_embryo(target) + + else + target.visible_message("[src] violates [target]'s face!", \ + "[src] violates [target]'s face!") + +/obj/item/clothing/mask/facehugger/proc/GoActive() + if(stat == DEAD || stat == CONSCIOUS) + return + + stat = CONSCIOUS + icon_state = "[initial(icon_state)]" + +/obj/item/clothing/mask/facehugger/proc/GoIdle() + if(stat == DEAD || stat == UNCONSCIOUS) + return + + stat = UNCONSCIOUS + icon_state = "[initial(icon_state)]_inactive" + + addtimer(CALLBACK(src, .proc/GoActive), rand(MIN_ACTIVE_TIME, MAX_ACTIVE_TIME)) + +/obj/item/clothing/mask/facehugger/proc/Die() + if(stat == DEAD) + return + + icon_state = "[initial(icon_state)]_dead" + item_state = "facehugger_inactive" + stat = DEAD + + visible_message("[src] curls up into a ball!") + +/proc/CanHug(mob/living/M) + if(!istype(M)) + return 0 + if(M.stat == DEAD) + return 0 + if(M.getorgan(/obj/item/organ/alien/hivenode)) + return 0 + + if(ismonkey(M)) + return 1 + + var/mob/living/carbon/C = M + if(ishuman(C) && !(SLOT_WEAR_MASK in C.dna.species.no_equip)) + var/mob/living/carbon/human/H = C + if(H.is_mouth_covered(head_only = 1)) + return 0 + return 1 + return 0 + +#undef MIN_ACTIVE_TIME +#undef MAX_ACTIVE_TIME + +#undef MIN_IMPREGNATION_TIME +#undef MAX_IMPREGNATION_TIME diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 2d58eb6a7e..e8f42b707c 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1,991 +1,991 @@ -/mob/living/carbon - blood_volume = BLOOD_VOLUME_NORMAL - -/mob/living/carbon/Initialize() - . = ..() - create_reagents(1000) - update_body_parts() //to update the carbon's new bodyparts appearance - GLOB.carbon_list += src - blood_volume = (BLOOD_VOLUME_NORMAL * blood_ratio) - -/mob/living/carbon/Destroy() - //This must be done first, so the mob ghosts correctly before DNA etc is nulled - . = ..() - - QDEL_LIST(internal_organs) - QDEL_LIST(stomach_contents) - QDEL_LIST(bodyparts) - QDEL_LIST(implants) - remove_from_all_data_huds() - QDEL_NULL(dna) - GLOB.carbon_list -= src - -/mob/living/carbon/initialize_footstep() - AddComponent(/datum/component/footstep, 0.6, 2) - -/mob/living/carbon/relaymove(mob/user, direction) - if(user in src.stomach_contents) - if(prob(40)) - if(prob(25)) - audible_message("You hear something rumbling inside [src]'s stomach...", \ - "You hear something rumbling.", 4,\ - "Something is rumbling inside your stomach!") - var/obj/item/I = user.get_active_held_item() - if(I && I.force) - var/d = rand(round(I.force / 4), I.force) - var/obj/item/bodypart/BP = get_bodypart(BODY_ZONE_CHEST) - if(BP.receive_damage(d, 0)) - update_damage_overlays() - visible_message("[user] attacks [src]'s stomach wall with the [I.name]!", \ - "[user] attacks your stomach wall with the [I.name]!") - playsound(user.loc, 'sound/effects/attackblob.ogg', 50, 1) - - if(prob(src.getBruteLoss() - 50)) - for(var/atom/movable/A in stomach_contents) - A.forceMove(drop_location()) - stomach_contents.Remove(A) - src.gib() - - -/mob/living/carbon/swap_hand(held_index) - if(!held_index) - held_index = (active_hand_index % held_items.len)+1 - - var/obj/item/item_in_hand = src.get_active_held_item() - if(item_in_hand) //this segment checks if the item in your hand is twohanded. - var/obj/item/twohanded/TH = item_in_hand - if(istype(TH)) - if(TH.wielded == 1) - to_chat(usr, "Your other hand is too busy holding [TH]") - return - var/oindex = active_hand_index - active_hand_index = held_index - if(hud_used) - var/obj/screen/inventory/hand/H - H = hud_used.hand_slots["[oindex]"] - if(H) - H.update_icon() - H = hud_used.hand_slots["[held_index]"] - if(H) - H.update_icon() - - -/mob/living/carbon/activate_hand(selhand) //l/r OR 1-held_items.len - if(!selhand) - selhand = (active_hand_index % held_items.len)+1 - - if(istext(selhand)) - selhand = lowertext(selhand) - if(selhand == "right" || selhand == "r") - selhand = 2 - if(selhand == "left" || selhand == "l") - selhand = 1 - - if(selhand != active_hand_index) - swap_hand(selhand) - else - mode() // Activate held item - -/mob/living/carbon/attackby(obj/item/I, mob/user, params) - if(lying && surgeries.len) - if(user != src && (user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM)) - for(var/datum/surgery/S in surgeries) - if(S.next_step(user,user.a_intent)) - return 1 - return ..() - -/mob/living/carbon/throw_impact(atom/hit_atom, throwingdatum) - . = ..() - var/hurt = TRUE - if(istype(throwingdatum, /datum/thrownthing)) - var/datum/thrownthing/D = throwingdatum - if(iscyborg(D.thrower)) - var/mob/living/silicon/robot/R = D.thrower - if(!R.emagged) - hurt = FALSE - if(hit_atom.density && isturf(hit_atom)) - if(hurt) - Knockdown(20) - take_bodypart_damage(10) - if(iscarbon(hit_atom) && hit_atom != src) - var/mob/living/carbon/victim = hit_atom - if(victim.movement_type & FLYING) - return - if(hurt) - victim.take_bodypart_damage(10) - take_bodypart_damage(10) - victim.Knockdown(20) - Knockdown(20) - visible_message("[src] crashes into [victim], knocking them both over!",\ - "You violently crash into [victim]!") - playsound(src,'sound/weapons/punch1.ogg',50,1) - - -//Throwing stuff -/mob/living/carbon/proc/toggle_throw_mode() - if(stat) - return - if(in_throw_mode) - throw_mode_off() - else - throw_mode_on() - - -/mob/living/carbon/proc/throw_mode_off() - in_throw_mode = 0 - if(client && hud_used) - hud_used.throw_icon.icon_state = "act_throw_off" - - -/mob/living/carbon/proc/throw_mode_on() - in_throw_mode = 1 - if(client && hud_used) - hud_used.throw_icon.icon_state = "act_throw_on" - -/mob/proc/throw_item(atom/target) - SEND_SIGNAL(src, COMSIG_MOB_THROW, target) - return - -/mob/living/carbon/throw_item(atom/target) - throw_mode_off() - if(!target || !isturf(loc)) - return - if(istype(target, /obj/screen)) - return - - //CIT CHANGES - makes it impossible to throw while in stamina softcrit - if(getStaminaLoss() >= STAMINA_SOFTCRIT) - to_chat(src, "You're too exhausted.") - return - var/random_turn = a_intent == INTENT_HARM - //END OF CIT CHANGES - - var/obj/item/I = src.get_active_held_item() - - var/atom/movable/thrown_thing - var/mob/living/throwable_mob - - if(istype(I, /obj/item/clothing/head/mob_holder)) - var/obj/item/clothing/head/mob_holder/holder = I - if(holder.held_mob) - throwable_mob = holder.held_mob - holder.release() - - if(!I || throwable_mob) - if(!throwable_mob && pulling && isliving(pulling) && grab_state >= GRAB_AGGRESSIVE) - throwable_mob = pulling - - if(throwable_mob && !throwable_mob.buckled) - thrown_thing = throwable_mob - if(pulling) - stop_pulling() - if(HAS_TRAIT(src, TRAIT_PACIFISM)) - to_chat(src, "You gently let go of [throwable_mob].") - return - - adjustStaminaLossBuffered(25)//CIT CHANGE - throwing an entire person shall be very tiring - var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors - var/turf/end_T = get_turf(target) - if(start_T && end_T) - log_combat(src, throwable_mob, "thrown", addition="grab from tile in [AREACOORD(start_T)] towards tile at [AREACOORD(end_T)]") - - else if(!CHECK_BITFIELD(I.item_flags, ABSTRACT) && !HAS_TRAIT(I, TRAIT_NODROP)) - thrown_thing = I - dropItemToGround(I) - - if(HAS_TRAIT(src, TRAIT_PACIFISM) && I.throwforce) - to_chat(src, "You set [I] down gently on the ground.") - return - - adjustStaminaLossBuffered(I.getweight()*2)//CIT CHANGE - throwing items shall be more tiring than swinging em. Doubly so. - - if(thrown_thing) - visible_message("[src] has thrown [thrown_thing].") - src.log_message("has thrown [thrown_thing]", LOG_ATTACK) - do_attack_animation(target, no_effect = 1) - playsound(loc, 'sound/weapons/punchmiss.ogg', 50, 1, -1) - newtonian_move(get_dir(target, src)) - thrown_thing.throw_at(target, thrown_thing.throw_range, thrown_thing.throw_speed, src, null, null, null, random_turn) - - - -/mob/living/carbon/restrained(ignore_grab) - . = (handcuffed || (!ignore_grab && pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE)) - -/mob/living/carbon/proc/canBeHandcuffed() - return 0 - - -/mob/living/carbon/show_inv(mob/user) - user.set_machine(src) - var/dat = {" -
                - [name] -
                -
                Head: [(head && !(head.item_flags & ABSTRACT)) ? head : "Nothing"] -
                Mask: [(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Nothing"] -
                Neck: [(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "Nothing"]"} - - for(var/i in 1 to held_items.len) - var/obj/item/I = get_item_for_held_index(i) - dat += "
                [get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Nothing"]" - - dat += "
                Back: [back ? back : "Nothing"]" - - if(istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank)) - dat += "
                [internal ? "Disable Internals" : "Set Internals"]" - - if(handcuffed) - dat += "
                Handcuffed" - if(legcuffed) - dat += "
                Legcuffed" - - dat += {" -
                -
                Close - "} - user << browse(dat, "window=mob[REF(src)];size=325x500") - onclose(user, "mob[REF(src)]") - -/mob/living/carbon/Topic(href, href_list) - ..() - //strip panel - if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - if(href_list["internal"]) - var/slot = text2num(href_list["internal"]) - var/obj/item/ITEM = get_item_by_slot(slot) - if(ITEM && istype(ITEM, /obj/item/tank) && wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) - visible_message("[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].", \ - "[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].") - if(do_mob(usr, src, POCKET_STRIP_DELAY)) - if(internal) - internal = null - update_internals_hud_icon(0) - else if(ITEM && istype(ITEM, /obj/item/tank)) - if((wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) || getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - internal = ITEM - update_internals_hud_icon(1) - - visible_message("[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].", \ - "[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].") - - -/mob/living/carbon/fall(forced) - loc.handle_fall(src, forced)//it's loc so it doesn't call the mob's handle_fall which does nothing - -/mob/living/carbon/is_muzzled() - return(istype(src.wear_mask, /obj/item/clothing/mask/muzzle)) - -/mob/living/carbon/hallucinating() - if(hallucination) - return TRUE - else - return FALSE - -/mob/living/carbon/resist_buckle() - if(restrained()) - changeNext_move(CLICK_CD_BREAKOUT) - last_special = world.time + CLICK_CD_BREAKOUT - var/buckle_cd = 600 - if(handcuffed) - var/obj/item/restraints/O = src.get_item_by_slot(SLOT_HANDCUFFED) - buckle_cd = O.breakouttime - visible_message("[src] attempts to unbuckle [p_them()]self!", \ - "You attempt to unbuckle yourself... (This will take around [round(buckle_cd/600,1)] minute\s, and you need to stay still.)") - if(do_after(src, buckle_cd, 0, target = src)) - if(!buckled) - return - buckled.user_unbuckle_mob(src,src) - else - if(src && buckled) - to_chat(src, "You fail to unbuckle yourself!") - else - buckled.user_unbuckle_mob(src,src) - -/mob/living/carbon/resist_fire() - fire_stacks -= 5 - Knockdown(60, TRUE, TRUE) - spin(32,2) - visible_message("[src] rolls on the floor, trying to put [p_them()]self out!", \ - "You stop, drop, and roll!") - sleep(30) - if(fire_stacks <= 0) - visible_message("[src] has successfully extinguished [p_them()]self!", \ - "You extinguish yourself.") - ExtinguishMob() - return - -/mob/living/carbon/resist_restraints() - var/obj/item/I = null - var/type = 0 - if(handcuffed) - I = handcuffed - type = 1 - else if(legcuffed) - I = legcuffed - type = 2 - if(I) - if(type == 1) - changeNext_move(CLICK_CD_BREAKOUT) - last_special = world.time + CLICK_CD_BREAKOUT - if(type == 2) - changeNext_move(CLICK_CD_RANGE) - last_special = world.time + CLICK_CD_RANGE - cuff_resist(I) - - -/mob/living/carbon/proc/cuff_resist(obj/item/I, breakouttime = 600, cuff_break = 0) - if(I.item_flags & BEING_REMOVED) - to_chat(src, "You're already attempting to remove [I]!") - return - I.item_flags |= BEING_REMOVED - breakouttime = I.breakouttime - if(!cuff_break) - visible_message("[src] attempts to remove [I]!") - to_chat(src, "You attempt to remove [I]... (This will take around [DisplayTimeText(breakouttime)] and you need to stand still.)") - if(do_after(src, breakouttime, 0, target = src)) - clear_cuffs(I, cuff_break) - else - to_chat(src, "You fail to remove [I]!") - - else if(cuff_break == FAST_CUFFBREAK) - breakouttime = 50 - visible_message("[src] is trying to break [I]!") - to_chat(src, "You attempt to break [I]... (This will take around 5 seconds and you need to stand still.)") - if(do_after(src, breakouttime, 0, target = src)) - clear_cuffs(I, cuff_break) - else - to_chat(src, "You fail to break [I]!") - - else if(cuff_break == INSTANT_CUFFBREAK) - clear_cuffs(I, cuff_break) - I.item_flags &= ~BEING_REMOVED - -/mob/living/carbon/proc/uncuff() - if (handcuffed) - var/obj/item/W = handcuffed - handcuffed = null - if (buckled && buckled.buckle_requires_restraints) - buckled.unbuckle_mob(src) - update_handcuffed() - if (client) - client.screen -= W - if (W) - W.forceMove(drop_location()) - W.dropped(src) - if (W) - W.layer = initial(W.layer) - W.plane = initial(W.plane) - changeNext_move(0) - if (legcuffed) - var/obj/item/W = legcuffed - legcuffed = null - update_inv_legcuffed() - if (client) - client.screen -= W - if (W) - W.forceMove(drop_location()) - W.dropped(src) - if (W) - W.layer = initial(W.layer) - W.plane = initial(W.plane) - changeNext_move(0) - -/mob/living/carbon/proc/clear_cuffs(obj/item/I, cuff_break) - if(!I.loc || buckled) - return - visible_message("[src] manages to [cuff_break ? "break" : "remove"] [I]!") - to_chat(src, "You successfully [cuff_break ? "break" : "remove"] [I].") - - if(cuff_break) - . = !((I == handcuffed) || (I == legcuffed)) - qdel(I) - return - - else - if(I == handcuffed) - handcuffed.forceMove(drop_location()) - handcuffed.dropped(src) - handcuffed = null - if(buckled && buckled.buckle_requires_restraints) - buckled.unbuckle_mob(src) - update_handcuffed() - return - if(I == legcuffed) - legcuffed.forceMove(drop_location()) - legcuffed.dropped() - legcuffed = null - update_inv_legcuffed() - return - else - dropItemToGround(I) - return - return TRUE - -/mob/living/carbon/get_standard_pixel_y_offset(lying = 0) - if(lying) - return -6 - else - return initial(pixel_y) - -/mob/living/carbon/proc/accident(obj/item/I) - if(!I || (I.item_flags & ABSTRACT) || HAS_TRAIT(I, TRAIT_NODROP)) - return - - //dropItemToGround(I) CIT CHANGE - makes it so the item doesn't drop if the modifier rolls above 100 - - var/modifier = 0 - - if(HAS_TRAIT(src, TRAIT_CLUMSY)) - modifier -= 40 //Clumsy people are more likely to hit themselves -Honk! - - //CIT CHANGES START HERE - else if(combatmode) - modifier += 50 - - if(modifier < 100) - dropItemToGround(I) - //END OF CIT CHANGES - - switch(rand(1,100)+modifier) //91-100=Nothing special happens - if(-INFINITY to 0) //attack yourself - I.attack(src,src) - if(1 to 30) //throw it at yourself - I.throw_impact(src) - if(31 to 60) //Throw object in facing direction - var/turf/target = get_turf(loc) - var/range = rand(2,I.throw_range) - for(var/i = 1; i < range; i++) - var/turf/new_turf = get_step(target, dir) - target = new_turf - if(new_turf.density) - break - I.throw_at(target,I.throw_range,I.throw_speed,src) - if(61 to 90) //throw it down to the floor - var/turf/target = get_turf(loc) - I.throw_at(target,I.throw_range,I.throw_speed,src) - -/mob/living/carbon/Stat() - ..() - if(statpanel("Status")) - var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) - if(vessel) - stat(null, "Plasma Stored: [vessel.storedPlasma]/[vessel.max_plasma]") - if(locate(/obj/item/assembly/health) in src) - stat(null, "Health: [health]") - - add_abilities_to_panel() - -/mob/living/carbon/attack_ui(slot) - if(!has_hand_for_held_index(active_hand_index)) - return 0 - return ..() - -/mob/living/carbon/proc/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, toxic = FALSE) - if(HAS_TRAIT(src, TRAIT_NOHUNGER)) - return 1 - - if(nutrition < 100 && !blood) - if(message) - visible_message("[src] dry heaves!", \ - "You try to throw up, but there's nothing in your stomach!") - if(stun) - Knockdown(200) - return 1 - - if(is_mouth_covered()) //make this add a blood/vomit overlay later it'll be hilarious - if(message) - visible_message("[src] throws up all over [p_them()]self!", \ - "You throw up all over yourself!") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomitself) - distance = 0 - else - if(message) - visible_message("[src] throws up!", "You throw up!") - if(!isflyperson(src)) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomit) - if(stun) - Stun(80) - - playsound(get_turf(src), 'sound/effects/splat.ogg', 50, 1) - var/turf/T = get_turf(src) - if(!blood) - nutrition -= lost_nutrition - adjustToxLoss(-3) - for(var/i=0 to distance) - if(blood) - if(T) - add_splatter_floor(T) - if(stun) - adjustBruteLoss(3) - else if(src.reagents.has_reagent(/datum/reagent/consumable/ethanol/blazaam)) - if(T) - T.add_vomit_floor(src, VOMIT_PURPLE) - else - if(T) - T.add_vomit_floor(src, VOMIT_TOXIC)//toxic barf looks different - T = get_step(T, dir) - if (is_blocked_turf(T)) - break - return 1 - -/mob/living/carbon/proc/spew_organ(power = 5, amt = 1) - for(var/i in 1 to amt) - if(!internal_organs.len) - break //Guess we're out of organs! - var/obj/item/organ/guts = pick(internal_organs) - var/turf/T = get_turf(src) - guts.Remove(src) - guts.forceMove(T) - var/atom/throw_target = get_edge_target_turf(guts, dir) - guts.throw_at(throw_target, power, 4, src) - - -/mob/living/carbon/fully_replace_character_name(oldname,newname) - ..() - if(dna) - dna.real_name = real_name - -//Updates the mob's health from bodyparts and mob damage variables -/mob/living/carbon/updatehealth() - if(status_flags & GODMODE) - return - var/total_burn = 0 - var/total_brute = 0 - var/total_stamina = 0 - for(var/X in bodyparts) //hardcoded to streamline things a bit - var/obj/item/bodypart/BP = X - total_brute += (BP.brute_dam * BP.body_damage_coeff) - total_burn += (BP.burn_dam * BP.body_damage_coeff) - total_stamina += (BP.stamina_dam * BP.stam_damage_coeff) - health = round(maxHealth - getOxyLoss() - getToxLoss() - getCloneLoss() - total_burn - total_brute, DAMAGE_PRECISION) - staminaloss = round(total_stamina, DAMAGE_PRECISION) - update_stat() - if(((maxHealth - total_burn) < HEALTH_THRESHOLD_DEAD) && stat == DEAD ) - become_husk("burn") - med_hud_set_health() - if(stat == SOFT_CRIT) - add_movespeed_modifier(MOVESPEED_ID_CARBON_SOFTCRIT, TRUE, multiplicative_slowdown = SOFTCRIT_ADD_SLOWDOWN) - else - remove_movespeed_modifier(MOVESPEED_ID_CARBON_SOFTCRIT, TRUE) - -/mob/living/carbon/update_stamina() - var/stam = getStaminaLoss() - if(stam > DAMAGE_PRECISION) - var/total_health = (health - stam) - if(total_health <= crit_threshold && !stat) - if(!IsKnockdown()) - to_chat(src, "You're too exhausted to keep going...") - Knockdown(100) - update_health_hud() - -/mob/living/carbon/update_sight() - if(!client) - return - if(stat == DEAD) - sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS) - see_in_dark = 8 - see_invisible = SEE_INVISIBLE_OBSERVER - return - - sight = initial(sight) - lighting_alpha = initial(lighting_alpha) - var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) - if(!E) - update_tint() - else - see_invisible = E.see_invisible - see_in_dark = E.see_in_dark - sight |= E.sight_flags - if(!isnull(E.lighting_alpha)) - lighting_alpha = E.lighting_alpha - if(HAS_TRAIT(src, TRAIT_NIGHT_VISION)) - lighting_alpha = min(LIGHTING_PLANE_ALPHA_NV_TRAIT, lighting_alpha) - see_in_dark = max(NIGHT_VISION_DARKSIGHT_RANGE, see_in_dark) - - if(client.eye && client.eye != src) - var/atom/A = client.eye - if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. - return - - if(glasses) - var/obj/item/clothing/glasses/G = glasses - sight |= G.vision_flags - see_in_dark = max(G.darkness_view, see_in_dark) - if(G.invis_override) - see_invisible = G.invis_override - else - see_invisible = min(G.invis_view, see_invisible) - if(!isnull(G.lighting_alpha)) - lighting_alpha = min(lighting_alpha, G.lighting_alpha) - if(dna) - for(var/X in dna.mutations) - var/datum/mutation/M = X - if(M.name == XRAY) - sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) - see_in_dark = max(see_in_dark, 8) - - if(see_override) - see_invisible = see_override - . = ..() - - -//to recalculate and update the mob's total tint from tinted equipment it's wearing. -/mob/living/carbon/proc/update_tint() - if(!GLOB.tinted_weldhelh) - return - tinttotal = get_total_tint() - if(tinttotal >= TINT_BLIND) - become_blind(EYES_COVERED) - else if(tinttotal >= TINT_DARKENED) - cure_blind(EYES_COVERED) - overlay_fullscreen("tint", /obj/screen/fullscreen/impaired, 2) - else - cure_blind(EYES_COVERED) - clear_fullscreen("tint", 0) - -/mob/living/carbon/proc/get_total_tint() - . = 0 - if(istype(head, /obj/item/clothing/head)) - var/obj/item/clothing/head/HT = head - . += HT.tint - if(wear_mask) - . += wear_mask.tint - - var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) - if(E) - . += E.tint - - else - . += INFINITY - -/mob/living/carbon/get_permeability_protection(list/target_zones = list(HANDS,CHEST,GROIN,LEGS,FEET,ARMS,HEAD)) - var/list/tally = list() - for(var/obj/item/I in get_equipped_items()) - for(var/zone in target_zones) - if(I.body_parts_covered & zone) - tally["[zone]"] = max(1 - I.permeability_coefficient, target_zones["[zone]"]) - var/protection = 0 - for(var/key in tally) - protection += tally[key] - protection *= INVERSE(target_zones.len) - return protection - -//this handles hud updates -/mob/living/carbon/update_damage_hud() - - if(!client) - return - - if(health <= crit_threshold) - var/severity = 0 - switch(health) - if(-20 to -10) - severity = 1 - if(-30 to -20) - severity = 2 - if(-40 to -30) - severity = 3 - if(-50 to -40) - severity = 4 - if(-50 to -40) - severity = 5 - if(-60 to -50) - severity = 6 - if(-70 to -60) - severity = 7 - if(-90 to -70) - severity = 8 - if(-95 to -90) - severity = 9 - if(-INFINITY to -95) - severity = 10 - if(!InFullCritical()) - var/visionseverity = 4 - switch(health) - if(-8 to -4) - visionseverity = 5 - if(-12 to -8) - visionseverity = 6 - if(-16 to -12) - visionseverity = 7 - if(-20 to -16) - visionseverity = 8 - if(-24 to -20) - visionseverity = 9 - if(-INFINITY to -24) - visionseverity = 10 - overlay_fullscreen("critvision", /obj/screen/fullscreen/crit/vision, visionseverity) - else - clear_fullscreen("critvision") - overlay_fullscreen("crit", /obj/screen/fullscreen/crit, severity) - else - clear_fullscreen("crit") - clear_fullscreen("critvision") - - //Oxygen damage overlay - var/windedup = getOxyLoss() + getStaminaLoss() * 0.2 - if(windedup) - var/severity = 0 - switch(windedup) - if(10 to 20) - severity = 1 - if(20 to 25) - severity = 2 - if(25 to 30) - severity = 3 - if(30 to 35) - severity = 4 - if(35 to 40) - severity = 5 - if(40 to 45) - severity = 6 - if(45 to INFINITY) - severity = 7 - overlay_fullscreen("oxy", /obj/screen/fullscreen/oxy, severity) - else - clear_fullscreen("oxy") - - //Fire and Brute damage overlay (BSSR) - var/hurtdamage = getBruteLoss() + getFireLoss() + damageoverlaytemp - if(hurtdamage) - var/severity = 0 - switch(hurtdamage) - if(5 to 15) - severity = 1 - if(15 to 30) - severity = 2 - if(30 to 45) - severity = 3 - if(45 to 70) - severity = 4 - if(70 to 85) - severity = 5 - if(85 to INFINITY) - severity = 6 - overlay_fullscreen("brute", /obj/screen/fullscreen/brute, severity) - else - clear_fullscreen("brute") - -/mob/living/carbon/update_health_hud(shown_health_amount) - if(!client || !hud_used) - return - if(hud_used.healths) - if(stat != DEAD) - . = 1 - if(!shown_health_amount) - shown_health_amount = health - if(shown_health_amount >= maxHealth) - hud_used.healths.icon_state = "health0" - else if(shown_health_amount > maxHealth*0.8) - hud_used.healths.icon_state = "health1" - else if(shown_health_amount > maxHealth*0.6) - hud_used.healths.icon_state = "health2" - else if(shown_health_amount > maxHealth*0.4) - hud_used.healths.icon_state = "health3" - else if(shown_health_amount > maxHealth*0.2) - hud_used.healths.icon_state = "health4" - else if(shown_health_amount > 0) - hud_used.healths.icon_state = "health5" - else - hud_used.healths.icon_state = "health6" - else - hud_used.healths.icon_state = "health7" - -/mob/living/carbon/proc/update_internals_hud_icon(internal_state = 0) - if(hud_used && hud_used.internals) - hud_used.internals.icon_state = "internal[internal_state]" - -/mob/living/carbon/update_stat() - if(status_flags & GODMODE) - return - if(stat != DEAD) - if(health <= HEALTH_THRESHOLD_DEAD && !HAS_TRAIT(src, TRAIT_NODEATH)) - death() - return - if(IsUnconscious() || IsSleeping() || getOxyLoss() > 50 || (HAS_TRAIT(src, TRAIT_DEATHCOMA)) || (health <= HEALTH_THRESHOLD_FULLCRIT && !HAS_TRAIT(src, TRAIT_NOHARDCRIT))) - stat = UNCONSCIOUS - blind_eyes(1) - if(combatmode) - toggle_combat_mode(TRUE, TRUE) - else - if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOSOFTCRIT)) - stat = SOFT_CRIT - if(combatmode) - toggle_combat_mode(TRUE, TRUE) - else - stat = CONSCIOUS - adjust_blindness(-1) - update_canmove() - update_damage_hud() - update_health_hud() - med_hud_set_status() - -//called when we get cuffed/uncuffed -/mob/living/carbon/proc/update_handcuffed() - if(handcuffed) - drop_all_held_items() - stop_pulling() - throw_alert("handcuffed", /obj/screen/alert/restrained/handcuffed, new_master = src.handcuffed) - if(handcuffed.demoralize_criminals) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "handcuffed", /datum/mood_event/handcuffed) - else - clear_alert("handcuffed") - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "handcuffed") - update_action_buttons_icon() //some of our action buttons might be unusable when we're handcuffed. - update_inv_handcuffed() - update_hud_handcuffed() - -/mob/living/carbon/fully_heal(admin_revive = FALSE) - if(reagents) - reagents.clear_reagents() - var/obj/item/organ/brain/B = getorgan(/obj/item/organ/brain) - if(B) - B.brain_death = FALSE - for(var/thing in diseases) - var/datum/disease/D = thing - if(D.severity != DISEASE_SEVERITY_POSITIVE) - D.cure(FALSE) - if(admin_revive) - regenerate_limbs() - regenerate_organs() - handcuffed = initial(handcuffed) - for(var/obj/item/restraints/R in contents) //actually remove cuffs from inventory - qdel(R) - update_handcuffed() - if(reagents) - reagents.addiction_list = list() - cure_all_traumas(TRAUMA_RESILIENCE_MAGIC) - ..() - // heal ears after healing traits, since ears check TRAIT_DEAF trait - // when healing. - restoreEars() - -/mob/living/carbon/can_be_revived() - . = ..() - if(!getorgan(/obj/item/organ/brain) && (!mind || !mind.has_antag_datum(/datum/antagonist/changeling))) - return 0 - -/mob/living/carbon/harvest(mob/living/user) - if(QDELETED(src)) - return - var/organs_amt = 0 - for(var/X in internal_organs) - var/obj/item/organ/O = X - if(prob(50)) - organs_amt++ - O.Remove(src) - O.forceMove(drop_location()) - if(organs_amt) - to_chat(user, "You retrieve some of [src]\'s internal organs!") - -/mob/living/carbon/ExtinguishMob() - for(var/X in get_equipped_items()) - var/obj/item/I = X - I.acid_level = 0 //washes off the acid on our clothes - I.extinguish() //extinguishes our clothes - ..() - -/mob/living/carbon/fakefire(var/fire_icon = "Generic_mob_burning") - var/mutable_appearance/new_fire_overlay = mutable_appearance('icons/mob/OnFire.dmi', fire_icon, -FIRE_LAYER) - new_fire_overlay.appearance_flags = RESET_COLOR - overlays_standing[FIRE_LAYER] = new_fire_overlay - apply_overlay(FIRE_LAYER) - -/mob/living/carbon/fakefireextinguish() - remove_overlay(FIRE_LAYER) - - -/mob/living/carbon/proc/devour_mob(mob/living/carbon/C, devour_time = 130) - C.visible_message("[src] is attempting to devour [C]!", \ - "[src] is attempting to devour you!") - if(!do_mob(src, C, devour_time)) - return - if(pulling && pulling == C && grab_state >= GRAB_AGGRESSIVE && a_intent == INTENT_GRAB) - C.visible_message("[src] devours [C]!", \ - "[src] devours you!") - C.forceMove(src) - stomach_contents.Add(C) - log_combat(src, C, "devoured") - -/mob/living/carbon/proc/create_bodyparts() - var/l_arm_index_next = -1 - var/r_arm_index_next = 0 - for(var/X in bodyparts) - var/obj/item/bodypart/O = new X() - O.owner = src - bodyparts.Remove(X) - bodyparts.Add(O) - if(O.body_part == ARM_LEFT) - l_arm_index_next += 2 - O.held_index = l_arm_index_next //1, 3, 5, 7... - hand_bodyparts += O - else if(O.body_part == ARM_RIGHT) - r_arm_index_next += 2 - O.held_index = r_arm_index_next //2, 4, 6, 8... - hand_bodyparts += O - -/mob/living/carbon/do_after_coefficent() - . = ..() - var/datum/component/mood/mood = src.GetComponent(/datum/component/mood) //Currently, only carbons or higher use mood, move this once that changes. - if(mood) - switch(mood.sanity) //Alters do_after delay based on how sane you are - if(SANITY_INSANE to SANITY_DISTURBED) - . *= 1.25 - if(SANITY_NEUTRAL to SANITY_GREAT) - . *= 0.90 - - -/mob/living/carbon/proc/create_internal_organs() - for(var/X in internal_organs) - var/obj/item/organ/I = X - I.Insert(src) - -/mob/living/carbon/proc/update_disabled_bodyparts() - for(var/B in bodyparts) - var/obj/item/bodypart/BP = B - BP.update_disabled() - -/mob/living/carbon/vv_get_dropdown() - . = ..() - . += "---" - .["Make AI"] = "?_src_=vars;[HrefToken()];makeai=[REF(src)]" - .["Modify bodypart"] = "?_src_=vars;[HrefToken()];editbodypart=[REF(src)]" - .["Modify organs"] = "?_src_=vars;[HrefToken()];editorgans=[REF(src)]" - .["Hallucinate"] = "?_src_=vars;[HrefToken()];hallucinate=[REF(src)]" - .["Give martial arts"] = "?_src_=vars;[HrefToken()];givemartialart=[REF(src)]" - .["Give brain trauma"] = "?_src_=vars;[HrefToken()];givetrauma=[REF(src)]" - .["Cure brain traumas"] = "?_src_=vars;[HrefToken()];curetraumas=[REF(src)]" - -/mob/living/carbon/can_resist() - return bodyparts.len > 2 && ..() - -/mob/living/carbon/proc/hypnosis_vulnerable()//unused atm, but added in case - if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) - return FALSE - if(hallucinating()) - return TRUE - if(IsSleeping()) - return TRUE - if(HAS_TRAIT(src, TRAIT_DUMB)) - return TRUE - var/datum/component/mood/mood = src.GetComponent(/datum/component/mood) - if(mood) - if(mood.sanity < SANITY_UNSTABLE) - return TRUE - -/mob/living/carbon/transfer_ckey(mob/new_mob, send_signal = TRUE) - if(combatmode) - toggle_combat_mode(TRUE, TRUE) - return ..() - -/mob/living/carbon/can_see_reagents() - . = ..() - if(.) //No need to run through all of this if it's already true. - return - if(isclothing(head)) - var/obj/item/clothing/H = head - if(H.clothing_flags & SCAN_REAGENTS) - return TRUE - if(isclothing(wear_mask) && (wear_mask.clothing_flags & SCAN_REAGENTS)) +/mob/living/carbon + blood_volume = BLOOD_VOLUME_NORMAL + +/mob/living/carbon/Initialize() + . = ..() + create_reagents(1000) + update_body_parts() //to update the carbon's new bodyparts appearance + GLOB.carbon_list += src + blood_volume = (BLOOD_VOLUME_NORMAL * blood_ratio) + +/mob/living/carbon/Destroy() + //This must be done first, so the mob ghosts correctly before DNA etc is nulled + . = ..() + + QDEL_LIST(internal_organs) + QDEL_LIST(stomach_contents) + QDEL_LIST(bodyparts) + QDEL_LIST(implants) + remove_from_all_data_huds() + QDEL_NULL(dna) + GLOB.carbon_list -= src + +/mob/living/carbon/initialize_footstep() + AddComponent(/datum/component/footstep, 0.6, 2) + +/mob/living/carbon/relaymove(mob/user, direction) + if(user in src.stomach_contents) + if(prob(40)) + if(prob(25)) + audible_message("You hear something rumbling inside [src]'s stomach...", \ + "You hear something rumbling.", 4,\ + "Something is rumbling inside your stomach!") + var/obj/item/I = user.get_active_held_item() + if(I && I.force) + var/d = rand(round(I.force / 4), I.force) + var/obj/item/bodypart/BP = get_bodypart(BODY_ZONE_CHEST) + if(BP.receive_damage(d, 0)) + update_damage_overlays() + visible_message("[user] attacks [src]'s stomach wall with the [I.name]!", \ + "[user] attacks your stomach wall with the [I.name]!") + playsound(user.loc, 'sound/effects/attackblob.ogg', 50, 1) + + if(prob(src.getBruteLoss() - 50)) + for(var/atom/movable/A in stomach_contents) + A.forceMove(drop_location()) + stomach_contents.Remove(A) + src.gib() + + +/mob/living/carbon/swap_hand(held_index) + if(!held_index) + held_index = (active_hand_index % held_items.len)+1 + + var/obj/item/item_in_hand = src.get_active_held_item() + if(item_in_hand) //this segment checks if the item in your hand is twohanded. + var/obj/item/twohanded/TH = item_in_hand + if(istype(TH)) + if(TH.wielded == 1) + to_chat(usr, "Your other hand is too busy holding [TH]") + return + var/oindex = active_hand_index + active_hand_index = held_index + if(hud_used) + var/obj/screen/inventory/hand/H + H = hud_used.hand_slots["[oindex]"] + if(H) + H.update_icon() + H = hud_used.hand_slots["[held_index]"] + if(H) + H.update_icon() + + +/mob/living/carbon/activate_hand(selhand) //l/r OR 1-held_items.len + if(!selhand) + selhand = (active_hand_index % held_items.len)+1 + + if(istext(selhand)) + selhand = lowertext(selhand) + if(selhand == "right" || selhand == "r") + selhand = 2 + if(selhand == "left" || selhand == "l") + selhand = 1 + + if(selhand != active_hand_index) + swap_hand(selhand) + else + mode() // Activate held item + +/mob/living/carbon/attackby(obj/item/I, mob/user, params) + if(lying && surgeries.len) + if(user != src && (user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM)) + for(var/datum/surgery/S in surgeries) + if(S.next_step(user,user.a_intent)) + return 1 + return ..() + +/mob/living/carbon/throw_impact(atom/hit_atom, throwingdatum) + . = ..() + var/hurt = TRUE + if(istype(throwingdatum, /datum/thrownthing)) + var/datum/thrownthing/D = throwingdatum + if(iscyborg(D.thrower)) + var/mob/living/silicon/robot/R = D.thrower + if(!R.emagged) + hurt = FALSE + if(hit_atom.density && isturf(hit_atom)) + if(hurt) + Knockdown(20) + take_bodypart_damage(10) + if(iscarbon(hit_atom) && hit_atom != src) + var/mob/living/carbon/victim = hit_atom + if(victim.movement_type & FLYING) + return + if(hurt) + victim.take_bodypart_damage(10) + take_bodypart_damage(10) + victim.Knockdown(20) + Knockdown(20) + visible_message("[src] crashes into [victim], knocking them both over!",\ + "You violently crash into [victim]!") + playsound(src,'sound/weapons/punch1.ogg',50,1) + + +//Throwing stuff +/mob/living/carbon/proc/toggle_throw_mode() + if(stat) + return + if(in_throw_mode) + throw_mode_off() + else + throw_mode_on() + + +/mob/living/carbon/proc/throw_mode_off() + in_throw_mode = 0 + if(client && hud_used) + hud_used.throw_icon.icon_state = "act_throw_off" + + +/mob/living/carbon/proc/throw_mode_on() + in_throw_mode = 1 + if(client && hud_used) + hud_used.throw_icon.icon_state = "act_throw_on" + +/mob/proc/throw_item(atom/target) + SEND_SIGNAL(src, COMSIG_MOB_THROW, target) + return + +/mob/living/carbon/throw_item(atom/target) + throw_mode_off() + if(!target || !isturf(loc)) + return + if(istype(target, /obj/screen)) + return + + //CIT CHANGES - makes it impossible to throw while in stamina softcrit + if(getStaminaLoss() >= STAMINA_SOFTCRIT) + to_chat(src, "You're too exhausted.") + return + var/random_turn = a_intent == INTENT_HARM + //END OF CIT CHANGES + + var/obj/item/I = src.get_active_held_item() + + var/atom/movable/thrown_thing + var/mob/living/throwable_mob + + if(istype(I, /obj/item/clothing/head/mob_holder)) + var/obj/item/clothing/head/mob_holder/holder = I + if(holder.held_mob) + throwable_mob = holder.held_mob + holder.release() + + if(!I || throwable_mob) + if(!throwable_mob && pulling && isliving(pulling) && grab_state >= GRAB_AGGRESSIVE) + throwable_mob = pulling + + if(throwable_mob && !throwable_mob.buckled) + thrown_thing = throwable_mob + if(pulling) + stop_pulling() + if(HAS_TRAIT(src, TRAIT_PACIFISM)) + to_chat(src, "You gently let go of [throwable_mob].") + return + + adjustStaminaLossBuffered(25)//CIT CHANGE - throwing an entire person shall be very tiring + var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors + var/turf/end_T = get_turf(target) + if(start_T && end_T) + log_combat(src, throwable_mob, "thrown", addition="grab from tile in [AREACOORD(start_T)] towards tile at [AREACOORD(end_T)]") + + else if(!CHECK_BITFIELD(I.item_flags, ABSTRACT) && !HAS_TRAIT(I, TRAIT_NODROP)) + thrown_thing = I + dropItemToGround(I) + + if(HAS_TRAIT(src, TRAIT_PACIFISM) && I.throwforce) + to_chat(src, "You set [I] down gently on the ground.") + return + + adjustStaminaLossBuffered(I.getweight()*2)//CIT CHANGE - throwing items shall be more tiring than swinging em. Doubly so. + + if(thrown_thing) + visible_message("[src] has thrown [thrown_thing].") + src.log_message("has thrown [thrown_thing]", LOG_ATTACK) + do_attack_animation(target, no_effect = 1) + playsound(loc, 'sound/weapons/punchmiss.ogg', 50, 1, -1) + newtonian_move(get_dir(target, src)) + thrown_thing.throw_at(target, thrown_thing.throw_range, thrown_thing.throw_speed, src, null, null, null, random_turn) + + + +/mob/living/carbon/restrained(ignore_grab) + . = (handcuffed || (!ignore_grab && pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE)) + +/mob/living/carbon/proc/canBeHandcuffed() + return 0 + + +/mob/living/carbon/show_inv(mob/user) + user.set_machine(src) + var/dat = {" +
                + [name] +
                +
                Head: [(head && !(head.item_flags & ABSTRACT)) ? head : "Nothing"] +
                Mask: [(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Nothing"] +
                Neck: [(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "Nothing"]"} + + for(var/i in 1 to held_items.len) + var/obj/item/I = get_item_for_held_index(i) + dat += "
                [get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Nothing"]" + + dat += "
                Back: [back ? back : "Nothing"]" + + if(istype(wear_mask, /obj/item/clothing/mask) && istype(back, /obj/item/tank)) + dat += "
                [internal ? "Disable Internals" : "Set Internals"]" + + if(handcuffed) + dat += "
                Handcuffed" + if(legcuffed) + dat += "
                Legcuffed" + + dat += {" +
                +
                Close + "} + user << browse(dat, "window=mob[REF(src)];size=325x500") + onclose(user, "mob[REF(src)]") + +/mob/living/carbon/Topic(href, href_list) + ..() + //strip panel + if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + if(href_list["internal"]) + var/slot = text2num(href_list["internal"]) + var/obj/item/ITEM = get_item_by_slot(slot) + if(ITEM && istype(ITEM, /obj/item/tank) && wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) + visible_message("[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].", \ + "[usr] tries to [internal ? "close" : "open"] the valve on [src]'s [ITEM.name].") + if(do_mob(usr, src, POCKET_STRIP_DELAY)) + if(internal) + internal = null + update_internals_hud_icon(0) + else if(ITEM && istype(ITEM, /obj/item/tank)) + if((wear_mask && (wear_mask.clothing_flags & ALLOWINTERNALS)) || getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + internal = ITEM + update_internals_hud_icon(1) + + visible_message("[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].", \ + "[usr] [internal ? "opens" : "closes"] the valve on [src]'s [ITEM.name].") + + +/mob/living/carbon/fall(forced) + loc.handle_fall(src, forced)//it's loc so it doesn't call the mob's handle_fall which does nothing + +/mob/living/carbon/is_muzzled() + return(istype(src.wear_mask, /obj/item/clothing/mask/muzzle)) + +/mob/living/carbon/hallucinating() + if(hallucination) + return TRUE + else + return FALSE + +/mob/living/carbon/resist_buckle() + if(restrained()) + changeNext_move(CLICK_CD_BREAKOUT) + last_special = world.time + CLICK_CD_BREAKOUT + var/buckle_cd = 600 + if(handcuffed) + var/obj/item/restraints/O = src.get_item_by_slot(SLOT_HANDCUFFED) + buckle_cd = O.breakouttime + visible_message("[src] attempts to unbuckle [p_them()]self!", \ + "You attempt to unbuckle yourself... (This will take around [round(buckle_cd/600,1)] minute\s, and you need to stay still.)") + if(do_after(src, buckle_cd, 0, target = src)) + if(!buckled) + return + buckled.user_unbuckle_mob(src,src) + else + if(src && buckled) + to_chat(src, "You fail to unbuckle yourself!") + else + buckled.user_unbuckle_mob(src,src) + +/mob/living/carbon/resist_fire() + fire_stacks -= 5 + Knockdown(60, TRUE, TRUE) + spin(32,2) + visible_message("[src] rolls on the floor, trying to put [p_them()]self out!", \ + "You stop, drop, and roll!") + sleep(30) + if(fire_stacks <= 0) + visible_message("[src] has successfully extinguished [p_them()]self!", \ + "You extinguish yourself.") + ExtinguishMob() + return + +/mob/living/carbon/resist_restraints() + var/obj/item/I = null + var/type = 0 + if(handcuffed) + I = handcuffed + type = 1 + else if(legcuffed) + I = legcuffed + type = 2 + if(I) + if(type == 1) + changeNext_move(CLICK_CD_BREAKOUT) + last_special = world.time + CLICK_CD_BREAKOUT + if(type == 2) + changeNext_move(CLICK_CD_RANGE) + last_special = world.time + CLICK_CD_RANGE + cuff_resist(I) + + +/mob/living/carbon/proc/cuff_resist(obj/item/I, breakouttime = 600, cuff_break = 0) + if(I.item_flags & BEING_REMOVED) + to_chat(src, "You're already attempting to remove [I]!") + return + I.item_flags |= BEING_REMOVED + breakouttime = I.breakouttime + if(!cuff_break) + visible_message("[src] attempts to remove [I]!") + to_chat(src, "You attempt to remove [I]... (This will take around [DisplayTimeText(breakouttime)] and you need to stand still.)") + if(do_after(src, breakouttime, 0, target = src)) + clear_cuffs(I, cuff_break) + else + to_chat(src, "You fail to remove [I]!") + + else if(cuff_break == FAST_CUFFBREAK) + breakouttime = 50 + visible_message("[src] is trying to break [I]!") + to_chat(src, "You attempt to break [I]... (This will take around 5 seconds and you need to stand still.)") + if(do_after(src, breakouttime, 0, target = src)) + clear_cuffs(I, cuff_break) + else + to_chat(src, "You fail to break [I]!") + + else if(cuff_break == INSTANT_CUFFBREAK) + clear_cuffs(I, cuff_break) + I.item_flags &= ~BEING_REMOVED + +/mob/living/carbon/proc/uncuff() + if (handcuffed) + var/obj/item/W = handcuffed + handcuffed = null + if (buckled && buckled.buckle_requires_restraints) + buckled.unbuckle_mob(src) + update_handcuffed() + if (client) + client.screen -= W + if (W) + W.forceMove(drop_location()) + W.dropped(src) + if (W) + W.layer = initial(W.layer) + W.plane = initial(W.plane) + changeNext_move(0) + if (legcuffed) + var/obj/item/W = legcuffed + legcuffed = null + update_inv_legcuffed() + if (client) + client.screen -= W + if (W) + W.forceMove(drop_location()) + W.dropped(src) + if (W) + W.layer = initial(W.layer) + W.plane = initial(W.plane) + changeNext_move(0) + +/mob/living/carbon/proc/clear_cuffs(obj/item/I, cuff_break) + if(!I.loc || buckled) + return + visible_message("[src] manages to [cuff_break ? "break" : "remove"] [I]!") + to_chat(src, "You successfully [cuff_break ? "break" : "remove"] [I].") + + if(cuff_break) + . = !((I == handcuffed) || (I == legcuffed)) + qdel(I) + return + + else + if(I == handcuffed) + handcuffed.forceMove(drop_location()) + handcuffed.dropped(src) + handcuffed = null + if(buckled && buckled.buckle_requires_restraints) + buckled.unbuckle_mob(src) + update_handcuffed() + return + if(I == legcuffed) + legcuffed.forceMove(drop_location()) + legcuffed.dropped() + legcuffed = null + update_inv_legcuffed() + return + else + dropItemToGround(I) + return + return TRUE + +/mob/living/carbon/get_standard_pixel_y_offset(lying = 0) + if(lying) + return -6 + else + return initial(pixel_y) + +/mob/living/carbon/proc/accident(obj/item/I) + if(!I || (I.item_flags & ABSTRACT) || HAS_TRAIT(I, TRAIT_NODROP)) + return + + //dropItemToGround(I) CIT CHANGE - makes it so the item doesn't drop if the modifier rolls above 100 + + var/modifier = 0 + + if(HAS_TRAIT(src, TRAIT_CLUMSY)) + modifier -= 40 //Clumsy people are more likely to hit themselves -Honk! + + //CIT CHANGES START HERE + else if(combatmode) + modifier += 50 + + if(modifier < 100) + dropItemToGround(I) + //END OF CIT CHANGES + + switch(rand(1,100)+modifier) //91-100=Nothing special happens + if(-INFINITY to 0) //attack yourself + I.attack(src,src) + if(1 to 30) //throw it at yourself + I.throw_impact(src) + if(31 to 60) //Throw object in facing direction + var/turf/target = get_turf(loc) + var/range = rand(2,I.throw_range) + for(var/i = 1; i < range; i++) + var/turf/new_turf = get_step(target, dir) + target = new_turf + if(new_turf.density) + break + I.throw_at(target,I.throw_range,I.throw_speed,src) + if(61 to 90) //throw it down to the floor + var/turf/target = get_turf(loc) + I.throw_at(target,I.throw_range,I.throw_speed,src) + +/mob/living/carbon/Stat() + ..() + if(statpanel("Status")) + var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) + if(vessel) + stat(null, "Plasma Stored: [vessel.storedPlasma]/[vessel.max_plasma]") + if(locate(/obj/item/assembly/health) in src) + stat(null, "Health: [health]") + + add_abilities_to_panel() + +/mob/living/carbon/attack_ui(slot) + if(!has_hand_for_held_index(active_hand_index)) + return 0 + return ..() + +/mob/living/carbon/proc/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, toxic = FALSE) + if(HAS_TRAIT(src, TRAIT_NOHUNGER)) + return 1 + + if(nutrition < 100 && !blood) + if(message) + visible_message("[src] dry heaves!", \ + "You try to throw up, but there's nothing in your stomach!") + if(stun) + Knockdown(200) + return 1 + + if(is_mouth_covered()) //make this add a blood/vomit overlay later it'll be hilarious + if(message) + visible_message("[src] throws up all over [p_them()]self!", \ + "You throw up all over yourself!") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomitself) + distance = 0 + else + if(message) + visible_message("[src] throws up!", "You throw up!") + if(!isflyperson(src)) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomit) + if(stun) + Stun(80) + + playsound(get_turf(src), 'sound/effects/splat.ogg', 50, 1) + var/turf/T = get_turf(src) + if(!blood) + nutrition -= lost_nutrition + adjustToxLoss(-3) + for(var/i=0 to distance) + if(blood) + if(T) + add_splatter_floor(T) + if(stun) + adjustBruteLoss(3) + else if(src.reagents.has_reagent(/datum/reagent/consumable/ethanol/blazaam)) + if(T) + T.add_vomit_floor(src, VOMIT_PURPLE) + else + if(T) + T.add_vomit_floor(src, VOMIT_TOXIC)//toxic barf looks different + T = get_step(T, dir) + if (is_blocked_turf(T)) + break + return 1 + +/mob/living/carbon/proc/spew_organ(power = 5, amt = 1) + for(var/i in 1 to amt) + if(!internal_organs.len) + break //Guess we're out of organs! + var/obj/item/organ/guts = pick(internal_organs) + var/turf/T = get_turf(src) + guts.Remove(src) + guts.forceMove(T) + var/atom/throw_target = get_edge_target_turf(guts, dir) + guts.throw_at(throw_target, power, 4, src) + + +/mob/living/carbon/fully_replace_character_name(oldname,newname) + ..() + if(dna) + dna.real_name = real_name + +//Updates the mob's health from bodyparts and mob damage variables +/mob/living/carbon/updatehealth() + if(status_flags & GODMODE) + return + var/total_burn = 0 + var/total_brute = 0 + var/total_stamina = 0 + for(var/X in bodyparts) //hardcoded to streamline things a bit + var/obj/item/bodypart/BP = X + total_brute += (BP.brute_dam * BP.body_damage_coeff) + total_burn += (BP.burn_dam * BP.body_damage_coeff) + total_stamina += (BP.stamina_dam * BP.stam_damage_coeff) + health = round(maxHealth - getOxyLoss() - getToxLoss() - getCloneLoss() - total_burn - total_brute, DAMAGE_PRECISION) + staminaloss = round(total_stamina, DAMAGE_PRECISION) + update_stat() + if(((maxHealth - total_burn) < HEALTH_THRESHOLD_DEAD) && stat == DEAD ) + become_husk("burn") + med_hud_set_health() + if(stat == SOFT_CRIT) + add_movespeed_modifier(MOVESPEED_ID_CARBON_SOFTCRIT, TRUE, multiplicative_slowdown = SOFTCRIT_ADD_SLOWDOWN) + else + remove_movespeed_modifier(MOVESPEED_ID_CARBON_SOFTCRIT, TRUE) + +/mob/living/carbon/update_stamina() + var/stam = getStaminaLoss() + if(stam > DAMAGE_PRECISION) + var/total_health = (health - stam) + if(total_health <= crit_threshold && !stat) + if(!IsKnockdown()) + to_chat(src, "You're too exhausted to keep going...") + Knockdown(100) + update_health_hud() + +/mob/living/carbon/update_sight() + if(!client) + return + if(stat == DEAD) + sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS) + see_in_dark = 8 + see_invisible = SEE_INVISIBLE_OBSERVER + return + + sight = initial(sight) + lighting_alpha = initial(lighting_alpha) + var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) + if(!E) + update_tint() + else + see_invisible = E.see_invisible + see_in_dark = E.see_in_dark + sight |= E.sight_flags + if(!isnull(E.lighting_alpha)) + lighting_alpha = E.lighting_alpha + if(HAS_TRAIT(src, TRAIT_NIGHT_VISION)) + lighting_alpha = min(LIGHTING_PLANE_ALPHA_NV_TRAIT, lighting_alpha) + see_in_dark = max(NIGHT_VISION_DARKSIGHT_RANGE, see_in_dark) + + if(client.eye && client.eye != src) + var/atom/A = client.eye + if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. + return + + if(glasses) + var/obj/item/clothing/glasses/G = glasses + sight |= G.vision_flags + see_in_dark = max(G.darkness_view, see_in_dark) + if(G.invis_override) + see_invisible = G.invis_override + else + see_invisible = min(G.invis_view, see_invisible) + if(!isnull(G.lighting_alpha)) + lighting_alpha = min(lighting_alpha, G.lighting_alpha) + if(dna) + for(var/X in dna.mutations) + var/datum/mutation/M = X + if(M.name == XRAY) + sight |= (SEE_TURFS|SEE_MOBS|SEE_OBJS) + see_in_dark = max(see_in_dark, 8) + + if(see_override) + see_invisible = see_override + . = ..() + + +//to recalculate and update the mob's total tint from tinted equipment it's wearing. +/mob/living/carbon/proc/update_tint() + if(!GLOB.tinted_weldhelh) + return + tinttotal = get_total_tint() + if(tinttotal >= TINT_BLIND) + become_blind(EYES_COVERED) + else if(tinttotal >= TINT_DARKENED) + cure_blind(EYES_COVERED) + overlay_fullscreen("tint", /obj/screen/fullscreen/impaired, 2) + else + cure_blind(EYES_COVERED) + clear_fullscreen("tint", 0) + +/mob/living/carbon/proc/get_total_tint() + . = 0 + if(istype(head, /obj/item/clothing/head)) + var/obj/item/clothing/head/HT = head + . += HT.tint + if(wear_mask) + . += wear_mask.tint + + var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) + if(E) + . += E.tint + + else + . += INFINITY + +/mob/living/carbon/get_permeability_protection(list/target_zones = list(HANDS,CHEST,GROIN,LEGS,FEET,ARMS,HEAD)) + var/list/tally = list() + for(var/obj/item/I in get_equipped_items()) + for(var/zone in target_zones) + if(I.body_parts_covered & zone) + tally["[zone]"] = max(1 - I.permeability_coefficient, target_zones["[zone]"]) + var/protection = 0 + for(var/key in tally) + protection += tally[key] + protection *= INVERSE(target_zones.len) + return protection + +//this handles hud updates +/mob/living/carbon/update_damage_hud() + + if(!client) + return + + if(health <= crit_threshold) + var/severity = 0 + switch(health) + if(-20 to -10) + severity = 1 + if(-30 to -20) + severity = 2 + if(-40 to -30) + severity = 3 + if(-50 to -40) + severity = 4 + if(-50 to -40) + severity = 5 + if(-60 to -50) + severity = 6 + if(-70 to -60) + severity = 7 + if(-90 to -70) + severity = 8 + if(-95 to -90) + severity = 9 + if(-INFINITY to -95) + severity = 10 + if(!InFullCritical()) + var/visionseverity = 4 + switch(health) + if(-8 to -4) + visionseverity = 5 + if(-12 to -8) + visionseverity = 6 + if(-16 to -12) + visionseverity = 7 + if(-20 to -16) + visionseverity = 8 + if(-24 to -20) + visionseverity = 9 + if(-INFINITY to -24) + visionseverity = 10 + overlay_fullscreen("critvision", /obj/screen/fullscreen/crit/vision, visionseverity) + else + clear_fullscreen("critvision") + overlay_fullscreen("crit", /obj/screen/fullscreen/crit, severity) + else + clear_fullscreen("crit") + clear_fullscreen("critvision") + + //Oxygen damage overlay + var/windedup = getOxyLoss() + getStaminaLoss() * 0.2 + if(windedup) + var/severity = 0 + switch(windedup) + if(10 to 20) + severity = 1 + if(20 to 25) + severity = 2 + if(25 to 30) + severity = 3 + if(30 to 35) + severity = 4 + if(35 to 40) + severity = 5 + if(40 to 45) + severity = 6 + if(45 to INFINITY) + severity = 7 + overlay_fullscreen("oxy", /obj/screen/fullscreen/oxy, severity) + else + clear_fullscreen("oxy") + + //Fire and Brute damage overlay (BSSR) + var/hurtdamage = getBruteLoss() + getFireLoss() + damageoverlaytemp + if(hurtdamage) + var/severity = 0 + switch(hurtdamage) + if(5 to 15) + severity = 1 + if(15 to 30) + severity = 2 + if(30 to 45) + severity = 3 + if(45 to 70) + severity = 4 + if(70 to 85) + severity = 5 + if(85 to INFINITY) + severity = 6 + overlay_fullscreen("brute", /obj/screen/fullscreen/brute, severity) + else + clear_fullscreen("brute") + +/mob/living/carbon/update_health_hud(shown_health_amount) + if(!client || !hud_used) + return + if(hud_used.healths) + if(stat != DEAD) + . = 1 + if(!shown_health_amount) + shown_health_amount = health + if(shown_health_amount >= maxHealth) + hud_used.healths.icon_state = "health0" + else if(shown_health_amount > maxHealth*0.8) + hud_used.healths.icon_state = "health1" + else if(shown_health_amount > maxHealth*0.6) + hud_used.healths.icon_state = "health2" + else if(shown_health_amount > maxHealth*0.4) + hud_used.healths.icon_state = "health3" + else if(shown_health_amount > maxHealth*0.2) + hud_used.healths.icon_state = "health4" + else if(shown_health_amount > 0) + hud_used.healths.icon_state = "health5" + else + hud_used.healths.icon_state = "health6" + else + hud_used.healths.icon_state = "health7" + +/mob/living/carbon/proc/update_internals_hud_icon(internal_state = 0) + if(hud_used && hud_used.internals) + hud_used.internals.icon_state = "internal[internal_state]" + +/mob/living/carbon/update_stat() + if(status_flags & GODMODE) + return + if(stat != DEAD) + if(health <= HEALTH_THRESHOLD_DEAD && !HAS_TRAIT(src, TRAIT_NODEATH)) + death() + return + if(IsUnconscious() || IsSleeping() || getOxyLoss() > 50 || (HAS_TRAIT(src, TRAIT_DEATHCOMA)) || (health <= HEALTH_THRESHOLD_FULLCRIT && !HAS_TRAIT(src, TRAIT_NOHARDCRIT))) + stat = UNCONSCIOUS + blind_eyes(1) + if(combatmode) + toggle_combat_mode(TRUE, TRUE) + else + if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOSOFTCRIT)) + stat = SOFT_CRIT + if(combatmode) + toggle_combat_mode(TRUE, TRUE) + else + stat = CONSCIOUS + adjust_blindness(-1) + update_canmove() + update_damage_hud() + update_health_hud() + med_hud_set_status() + +//called when we get cuffed/uncuffed +/mob/living/carbon/proc/update_handcuffed() + if(handcuffed) + drop_all_held_items() + stop_pulling() + throw_alert("handcuffed", /obj/screen/alert/restrained/handcuffed, new_master = src.handcuffed) + if(handcuffed.demoralize_criminals) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "handcuffed", /datum/mood_event/handcuffed) + else + clear_alert("handcuffed") + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "handcuffed") + update_action_buttons_icon() //some of our action buttons might be unusable when we're handcuffed. + update_inv_handcuffed() + update_hud_handcuffed() + +/mob/living/carbon/fully_heal(admin_revive = FALSE) + if(reagents) + reagents.clear_reagents() + var/obj/item/organ/brain/B = getorgan(/obj/item/organ/brain) + if(B) + B.brain_death = FALSE + for(var/thing in diseases) + var/datum/disease/D = thing + if(D.severity != DISEASE_SEVERITY_POSITIVE) + D.cure(FALSE) + if(admin_revive) + regenerate_limbs() + regenerate_organs() + handcuffed = initial(handcuffed) + for(var/obj/item/restraints/R in contents) //actually remove cuffs from inventory + qdel(R) + update_handcuffed() + if(reagents) + reagents.addiction_list = list() + cure_all_traumas(TRAUMA_RESILIENCE_MAGIC) + ..() + // heal ears after healing traits, since ears check TRAIT_DEAF trait + // when healing. + restoreEars() + +/mob/living/carbon/can_be_revived() + . = ..() + if(!getorgan(/obj/item/organ/brain) && (!mind || !mind.has_antag_datum(/datum/antagonist/changeling))) + return 0 + +/mob/living/carbon/harvest(mob/living/user) + if(QDELETED(src)) + return + var/organs_amt = 0 + for(var/X in internal_organs) + var/obj/item/organ/O = X + if(prob(50)) + organs_amt++ + O.Remove(src) + O.forceMove(drop_location()) + if(organs_amt) + to_chat(user, "You retrieve some of [src]\'s internal organs!") + +/mob/living/carbon/ExtinguishMob() + for(var/X in get_equipped_items()) + var/obj/item/I = X + I.acid_level = 0 //washes off the acid on our clothes + I.extinguish() //extinguishes our clothes + ..() + +/mob/living/carbon/fakefire(var/fire_icon = "Generic_mob_burning") + var/mutable_appearance/new_fire_overlay = mutable_appearance('icons/mob/OnFire.dmi', fire_icon, -FIRE_LAYER) + new_fire_overlay.appearance_flags = RESET_COLOR + overlays_standing[FIRE_LAYER] = new_fire_overlay + apply_overlay(FIRE_LAYER) + +/mob/living/carbon/fakefireextinguish() + remove_overlay(FIRE_LAYER) + + +/mob/living/carbon/proc/devour_mob(mob/living/carbon/C, devour_time = 130) + C.visible_message("[src] is attempting to devour [C]!", \ + "[src] is attempting to devour you!") + if(!do_mob(src, C, devour_time)) + return + if(pulling && pulling == C && grab_state >= GRAB_AGGRESSIVE && a_intent == INTENT_GRAB) + C.visible_message("[src] devours [C]!", \ + "[src] devours you!") + C.forceMove(src) + stomach_contents.Add(C) + log_combat(src, C, "devoured") + +/mob/living/carbon/proc/create_bodyparts() + var/l_arm_index_next = -1 + var/r_arm_index_next = 0 + for(var/X in bodyparts) + var/obj/item/bodypart/O = new X() + O.owner = src + bodyparts.Remove(X) + bodyparts.Add(O) + if(O.body_part == ARM_LEFT) + l_arm_index_next += 2 + O.held_index = l_arm_index_next //1, 3, 5, 7... + hand_bodyparts += O + else if(O.body_part == ARM_RIGHT) + r_arm_index_next += 2 + O.held_index = r_arm_index_next //2, 4, 6, 8... + hand_bodyparts += O + +/mob/living/carbon/do_after_coefficent() + . = ..() + var/datum/component/mood/mood = src.GetComponent(/datum/component/mood) //Currently, only carbons or higher use mood, move this once that changes. + if(mood) + switch(mood.sanity) //Alters do_after delay based on how sane you are + if(SANITY_INSANE to SANITY_DISTURBED) + . *= 1.25 + if(SANITY_NEUTRAL to SANITY_GREAT) + . *= 0.90 + + +/mob/living/carbon/proc/create_internal_organs() + for(var/X in internal_organs) + var/obj/item/organ/I = X + I.Insert(src) + +/mob/living/carbon/proc/update_disabled_bodyparts() + for(var/B in bodyparts) + var/obj/item/bodypart/BP = B + BP.update_disabled() + +/mob/living/carbon/vv_get_dropdown() + . = ..() + . += "---" + .["Make AI"] = "?_src_=vars;[HrefToken()];makeai=[REF(src)]" + .["Modify bodypart"] = "?_src_=vars;[HrefToken()];editbodypart=[REF(src)]" + .["Modify organs"] = "?_src_=vars;[HrefToken()];editorgans=[REF(src)]" + .["Hallucinate"] = "?_src_=vars;[HrefToken()];hallucinate=[REF(src)]" + .["Give martial arts"] = "?_src_=vars;[HrefToken()];givemartialart=[REF(src)]" + .["Give brain trauma"] = "?_src_=vars;[HrefToken()];givetrauma=[REF(src)]" + .["Cure brain traumas"] = "?_src_=vars;[HrefToken()];curetraumas=[REF(src)]" + +/mob/living/carbon/can_resist() + return bodyparts.len > 2 && ..() + +/mob/living/carbon/proc/hypnosis_vulnerable()//unused atm, but added in case + if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) + return FALSE + if(hallucinating()) + return TRUE + if(IsSleeping()) + return TRUE + if(HAS_TRAIT(src, TRAIT_DUMB)) + return TRUE + var/datum/component/mood/mood = src.GetComponent(/datum/component/mood) + if(mood) + if(mood.sanity < SANITY_UNSTABLE) + return TRUE + +/mob/living/carbon/transfer_ckey(mob/new_mob, send_signal = TRUE) + if(combatmode) + toggle_combat_mode(TRUE, TRUE) + return ..() + +/mob/living/carbon/can_see_reagents() + . = ..() + if(.) //No need to run through all of this if it's already true. + return + if(isclothing(head)) + var/obj/item/clothing/H = head + if(H.clothing_flags & SCAN_REAGENTS) + return TRUE + if(isclothing(wear_mask) && (wear_mask.clothing_flags & SCAN_REAGENTS)) return TRUE \ No newline at end of file diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 161b5875b1..bb4442559e 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -1,456 +1,456 @@ - -/mob/living/carbon/get_eye_protection() - var/number = ..() - - if(istype(src.head, /obj/item/clothing/head)) //are they wearing something on their head - var/obj/item/clothing/head/HFP = src.head //if yes gets the flash protection value from that item - number += HFP.flash_protect - - if(istype(src.glasses, /obj/item/clothing/glasses)) //glasses - var/obj/item/clothing/glasses/GFP = src.glasses - number += GFP.flash_protect - - if(istype(src.wear_mask, /obj/item/clothing/mask)) //mask - var/obj/item/clothing/mask/MFP = src.wear_mask - number += MFP.flash_protect - - var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) - if(!E) - number = INFINITY //Can't get flashed without eyes - else - number += E.flash_protect - - return number - -/mob/living/carbon/get_ear_protection() - var/number = ..() - var/obj/item/organ/ears/E = getorganslot(ORGAN_SLOT_EARS) - if(!E) - number = INFINITY - else - number += E.bang_protect - return number - -/mob/living/carbon/is_mouth_covered(head_only = 0, mask_only = 0) - if( (!mask_only && head && (head.flags_cover & HEADCOVERSMOUTH)) || (!head_only && wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH)) ) - return TRUE - -/mob/living/carbon/is_eyes_covered(check_glasses = 1, check_head = 1, check_mask = 1) - if(check_glasses && glasses && (glasses.flags_cover & GLASSESCOVERSEYES)) - return TRUE - if(check_head && head && (head.flags_cover & HEADCOVERSEYES)) - return TRUE - if(check_mask && wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH)) - return TRUE - -/mob/living/carbon/check_projectile_dismemberment(obj/item/projectile/P, def_zone) - var/obj/item/bodypart/affecting = get_bodypart(def_zone) - if(affecting && affecting.dismemberable && affecting.get_damage() >= (affecting.max_damage - P.dismemberment)) - affecting.dismember(P.damtype) - -/mob/living/carbon/catch_item(obj/item/I, skip_throw_mode_check = FALSE) - . = ..() - if(!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) && !skip_throw_mode_check && !in_throw_mode) - return - if(get_active_held_item() || restrained()) - return - I.attack_hand(src) - if(get_active_held_item() == I) //if our attack_hand() picks up the item... - visible_message("[src] catches [I]!") //catch that sucker! - throw_mode_off() - return TRUE - -/mob/living/carbon/embed_item(obj/item/I) - throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) - var/obj/item/bodypart/L = pick(bodyparts) - L.embedded_objects |= I - I.add_mob_blood(src)//it embedded itself in you, of course it's bloody! - I.forceMove(src) - L.receive_damage(I.w_class*I.embedding.embedded_impact_pain_multiplier) - visible_message("[I] embeds itself in [src]'s [L.name]!","[I] embeds itself in your [L.name]!") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded) - -/mob/living/carbon/attacked_by(obj/item/I, mob/living/user) - //CIT CHANGES START HERE - combatmode and resting checks - var/totitemdamage = I.force - if(iscarbon(user)) - var/mob/living/carbon/tempcarb = user - if(!tempcarb.combatmode) - totitemdamage *= 0.5 - if(user.resting) - totitemdamage *= 0.5 - if(!combatmode) - totitemdamage *= 1.5 - //CIT CHANGES END HERE - if(user != src && check_shields(I, totitemdamage, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) - return FALSE - var/obj/item/bodypart/affecting - if(user == src) - affecting = get_bodypart(check_zone(user.zone_selected)) //we're self-mutilating! yay! - else - affecting = get_bodypart(ran_zone(user.zone_selected)) - if(!affecting) //missing limb? we select the first bodypart (you can never have zero, because of chest) - affecting = bodyparts[1] - SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting) - send_item_attack_message(I, user, affecting.name) - if(I.force) - apply_damage(totitemdamage, I.damtype, affecting) //CIT CHANGE - replaces I.force with totitemdamage - if(I.damtype == BRUTE && affecting.status == BODYPART_ORGANIC) - var/basebloodychance = affecting.brute_dam + totitemdamage - if(prob(basebloodychance)) - I.add_mob_blood(src) - bleed(totitemdamage) - if(totitemdamage >= 10 && get_dist(user, src) <= 1) //people with TK won't get smeared with blood - user.add_mob_blood(src) - - if(affecting.body_zone == BODY_ZONE_HEAD) - if(wear_mask && prob(basebloodychance)) - wear_mask.add_mob_blood(src) - update_inv_wear_mask() - if(wear_neck && prob(basebloodychance)) - wear_neck.add_mob_blood(src) - update_inv_neck() - if(head && prob(basebloodychance)) - head.add_mob_blood(src) - update_inv_head() - - //dismemberment - var/probability = I.get_dismemberment_chance(affecting) - if(prob(probability)) - if(affecting.dismember(I.damtype)) - I.add_mob_blood(src) - playsound(get_turf(src), I.get_dismember_sound(), 80, 1) - return TRUE //successful attack - -/mob/living/carbon/attack_drone(mob/living/simple_animal/drone/user) - return //so we don't call the carbon's attack_hand(). - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/mob/living/carbon/attack_hand(mob/living/carbon/human/user) - . = ..() - if(.) //was the attack blocked? - return - for(var/thing in diseases) - var/datum/disease/D = thing - if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) - user.ContactContractDisease(D) - - for(var/thing in user.diseases) - var/datum/disease/D = thing - if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) - ContactContractDisease(D) - - if(lying && surgeries.len) - if(user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM) - for(var/datum/surgery/S in surgeries) - if(S.next_step(user, user.a_intent)) - return TRUE - - -/mob/living/carbon/attack_paw(mob/living/carbon/monkey/M) - - if(can_inject(M, TRUE)) - for(var/thing in diseases) - var/datum/disease/D = thing - if((D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) && prob(85)) - M.ContactContractDisease(D) - - for(var/thing in M.diseases) - var/datum/disease/D = thing - if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) - ContactContractDisease(D) - - if(M.a_intent == INTENT_HELP) - help_shake_act(M) - return 0 - - . = ..() - if(.) //successful monkey bite. - for(var/thing in M.diseases) - var/datum/disease/D = thing - ForceContractDisease(D) - return 1 - - -/mob/living/carbon/attack_slime(mob/living/simple_animal/slime/M) - . = ..() - if(!.) - return - if(M.powerlevel > 0) - var/stunprob = M.powerlevel * 7 + 10 // 17 at level 1, 80 at level 10 - if(prob(stunprob)) - M.powerlevel -= 3 - if(M.powerlevel < 0) - M.powerlevel = 0 - - visible_message("The [M.name] has shocked [src]!", \ - "The [M.name] has shocked [src]!") - - do_sparks(5, TRUE, src) - var/power = M.powerlevel + rand(0,3) - Knockdown(power*20) - if(stuttering < power) - stuttering = power - if (prob(stunprob) && M.powerlevel >= 8) - adjustFireLoss(M.powerlevel * rand(6,10)) - updatehealth() - -/mob/living/carbon/proc/dismembering_strike(mob/living/attacker, dam_zone) - if(!attacker.limb_destroyer) - return dam_zone - var/obj/item/bodypart/affecting - if(dam_zone && attacker.client) - affecting = get_bodypart(ran_zone(dam_zone)) - else - var/list/things_to_ruin = shuffle(bodyparts.Copy()) - for(var/B in things_to_ruin) - var/obj/item/bodypart/bodypart = B - if(bodypart.body_zone == BODY_ZONE_HEAD || bodypart.body_zone == BODY_ZONE_CHEST) - continue - if(!affecting || ((affecting.get_damage() / affecting.max_damage) < (bodypart.get_damage() / bodypart.max_damage))) - affecting = bodypart - if(affecting) - dam_zone = affecting.body_zone - if(affecting.get_damage() >= affecting.max_damage) - affecting.dismember() - return null - return affecting.body_zone - return dam_zone - - -/mob/living/carbon/blob_act(obj/structure/blob/B) - if (stat == DEAD) - return - else - show_message("The blob attacks!") - adjustBruteLoss(10) - -/mob/living/carbon/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_CONTENTS) - return - for(var/X in internal_organs) - var/obj/item/organ/O = X - O.emp_act(severity) - -/mob/living/carbon/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = 0, override = 0, tesla_shock = 0, illusion = 0, stun = TRUE) - if(tesla_shock && (flags_1 & TESLA_IGNORE_1)) - return FALSE - if(HAS_TRAIT(src, TRAIT_SHOCKIMMUNE)) - return FALSE - shock_damage *= siemens_coeff - if(dna && dna.species) - shock_damage *= dna.species.siemens_coeff - if(shock_damage<1 && !override) - return 0 - if(reagents.has_reagent(/datum/reagent/teslium)) - shock_damage *= 1.5 //If the mob has teslium in their body, shocks are 50% more damaging! - if(illusion) - adjustStaminaLoss(shock_damage) - else - take_overall_damage(0,shock_damage) - visible_message( - "[src] was shocked by \the [source]!", \ - "You feel a powerful shock coursing through your body!", \ - "You hear a heavy electrical crack." \ - ) - jitteriness += 1000 //High numbers for violent convulsions - do_jitter_animation(jitteriness) - stuttering += 2 - if((!tesla_shock || (tesla_shock && siemens_coeff > 0.5)) && stun) - Stun(40) - spawn(20) - jitteriness = max(jitteriness - 990, 10) //Still jittery, but vastly less - if((!tesla_shock || (tesla_shock && siemens_coeff > 0.5)) && stun) - Knockdown(60) - if(override) - return override - else - return shock_damage - -/mob/living/carbon/proc/help_shake_act(mob/living/carbon/M) - if(on_fire) - to_chat(M, "You can't put [p_them()] out with just your bare hands!") - return - - if(health >= 0 && !(HAS_TRAIT(src, TRAIT_FAKEDEATH))) - - if(lying) - if(buckled) - to_chat(M, "You need to unbuckle [src] first to do that!") - return - M.visible_message("[M] shakes [src] trying to get [p_them()] up!", \ - "You shake [src] trying to get [p_them()] up!") - - else if(check_zone(M.zone_selected) == "mouth") // I ADDED BOOP-EH-DEH-NOSEH - Jon - M.visible_message( \ - "[M] boops [src]'s nose.", \ - "You boop [src] on the nose.", ) - playsound(src, 'sound/items/Nose_boop.ogg', 50, 0) - - else if(check_zone(M.zone_selected) == "head") - var/mob/living/carbon/human/H = src - var/datum/species/pref_species = H.dna.species - - M.visible_message("[M] gives [H] a pat on the head to make [p_them()] feel better!", \ - "You give [H] a pat on the head to make [p_them()] feel better!") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "headpat", /datum/mood_event/headpat) - if(HAS_TRAIT(M, TRAIT_FRIENDLY)) - var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) - if (mood.sanity >= SANITY_GREAT) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/besthug, M) - else if (mood.sanity >= SANITY_DISTURBED) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/betterhug, M) - if(H.dna.species.can_wag_tail(H)) - if("tail_human" in pref_species.default_features) - if(H.dna.features["tail_human"] == "None") - return - else - if(!H.dna.species.is_wagging_tail()) - H.emote("wag") - - if("tail_lizard" in pref_species.default_features) - if(H.dna.features["tail_lizard"] == "None") - return - else - if(!H.dna.species.is_wagging_tail()) - H.emote("wag") - - if("mam_tail" in pref_species.default_features) - if(H.dna.features["mam_tail"] == "None") - return - else - if(!H.dna.species.is_wagging_tail()) - H.emote("wag") - - else - return - - else if(check_zone(M.zone_selected) == "r_arm" || check_zone(M.zone_selected) == "l_arm") - M.visible_message( \ - "[M] shakes [src]'s hand.", \ - "You shake [src]'s hand.", ) - - else - M.visible_message("[M] hugs [src] to make [p_them()] feel better!", \ - "You hug [src] to make [p_them()] feel better!") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "hug", /datum/mood_event/hug) - if(HAS_TRAIT(M, TRAIT_FRIENDLY)) - var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) - if (mood.sanity >= SANITY_GREAT) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/besthug, M) - else if (mood.sanity >= SANITY_DISTURBED) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/betterhug, M) - - AdjustStun(-60) - AdjustKnockdown(-60) - AdjustUnconscious(-60) - AdjustSleeping(-100) - if(recoveringstam) - adjustStaminaLoss(-15) - else if(resting) - resting = 0 - update_canmove() - - playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - - -/mob/living/carbon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0) - . = ..() - - var/damage = intensity - get_eye_protection() - if(.) // we've been flashed - var/obj/item/organ/eyes/eyes = getorganslot(ORGAN_SLOT_EYES) - if (!eyes) - return - if(visual) - return - - if (damage == 1) - to_chat(src, "Your eyes sting a little.") - if(prob(40)) - eyes.applyOrganDamage(1) - - else if (damage == 2) - to_chat(src, "Your eyes burn.") - eyes.applyOrganDamage(rand(2, 4)) - - else if( damage >= 3) - to_chat(src, "Your eyes itch and burn severely!") - eyes.applyOrganDamage(rand(12, 16)) - - if(eyes.damage > 10) - blind_eyes(damage) - blur_eyes(damage * rand(3, 6)) - - if(eyes.damage > 20) - if(prob(eyes.damage - 20)) - if(!HAS_TRAIT(src, TRAIT_NEARSIGHT)) - to_chat(src, "Your eyes start to burn badly!") - become_nearsighted(EYE_DAMAGE) - - else if(prob(eyes.damage - 25)) - if(!HAS_TRAIT(src, TRAIT_BLIND)) - to_chat(src, "You can't see anything!") - eyes.applyOrganDamage(eyes.maxHealth) - - else - to_chat(src, "Your eyes are really starting to hurt. This can't be good for you!") - if(has_bane(BANE_LIGHT)) - mind.disrupt_spells(-500) - return 1 - else if(damage == 0) // just enough protection - if(prob(20)) - to_chat(src, "Something bright flashes in the corner of your vision!") - if(has_bane(BANE_LIGHT)) - mind.disrupt_spells(0) - - -/mob/living/carbon/soundbang_act(intensity = 1, stun_pwr = 20, damage_pwr = 5, deafen_pwr = 15) - var/list/reflist = list(intensity) // Need to wrap this in a list so we can pass a reference - SEND_SIGNAL(src, COMSIG_CARBON_SOUNDBANG, reflist) - intensity = reflist[1] - var/ear_safety = get_ear_protection() - var/obj/item/organ/ears/ears = getorganslot(ORGAN_SLOT_EARS) - var/effect_amount = intensity - ear_safety - if(effect_amount > 0) - if(stun_pwr) - Knockdown(stun_pwr*effect_amount) - - if(istype(ears) && (deafen_pwr || damage_pwr)) - var/ear_damage = damage_pwr * effect_amount - var/deaf = deafen_pwr * effect_amount - adjustEarDamage(ear_damage,deaf) - - if(ears.damage >= 15) - to_chat(src, "Your ears start to ring badly!") - if(prob(ears.damage - 5)) - to_chat(src, "You can't hear anything!") - ears.damage = min(ears.damage, ears.maxHealth) - // you need earmuffs, inacusiate, or replacement - else if(ears.damage >= 5) - to_chat(src, "Your ears start to ring!") - SEND_SOUND(src, sound('sound/weapons/flash_ring.ogg',0,1,0,250)) - return effect_amount //how soundbanged we are - - -/mob/living/carbon/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone) - if(damage_type != BRUTE && damage_type != BURN) - return - damage_amount *= 0.5 //0.5 multiplier for balance reason, we don't want clothes to be too easily destroyed - if(!def_zone || def_zone == BODY_ZONE_HEAD) - var/obj/item/clothing/hit_clothes - if(wear_mask) - hit_clothes = wear_mask - if(wear_neck) - hit_clothes = wear_neck - if(head) - hit_clothes = head - if(hit_clothes) - hit_clothes.take_damage(damage_amount, damage_type, damage_flag, 0) - -/mob/living/carbon/can_hear() - . = FALSE - var/obj/item/organ/ears/ears = getorganslot(ORGAN_SLOT_EARS) - if(istype(ears) && !ears.deaf) - . = TRUE + +/mob/living/carbon/get_eye_protection() + var/number = ..() + + if(istype(src.head, /obj/item/clothing/head)) //are they wearing something on their head + var/obj/item/clothing/head/HFP = src.head //if yes gets the flash protection value from that item + number += HFP.flash_protect + + if(istype(src.glasses, /obj/item/clothing/glasses)) //glasses + var/obj/item/clothing/glasses/GFP = src.glasses + number += GFP.flash_protect + + if(istype(src.wear_mask, /obj/item/clothing/mask)) //mask + var/obj/item/clothing/mask/MFP = src.wear_mask + number += MFP.flash_protect + + var/obj/item/organ/eyes/E = getorganslot(ORGAN_SLOT_EYES) + if(!E) + number = INFINITY //Can't get flashed without eyes + else + number += E.flash_protect + + return number + +/mob/living/carbon/get_ear_protection() + var/number = ..() + var/obj/item/organ/ears/E = getorganslot(ORGAN_SLOT_EARS) + if(!E) + number = INFINITY + else + number += E.bang_protect + return number + +/mob/living/carbon/is_mouth_covered(head_only = 0, mask_only = 0) + if( (!mask_only && head && (head.flags_cover & HEADCOVERSMOUTH)) || (!head_only && wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH)) ) + return TRUE + +/mob/living/carbon/is_eyes_covered(check_glasses = 1, check_head = 1, check_mask = 1) + if(check_glasses && glasses && (glasses.flags_cover & GLASSESCOVERSEYES)) + return TRUE + if(check_head && head && (head.flags_cover & HEADCOVERSEYES)) + return TRUE + if(check_mask && wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH)) + return TRUE + +/mob/living/carbon/check_projectile_dismemberment(obj/item/projectile/P, def_zone) + var/obj/item/bodypart/affecting = get_bodypart(def_zone) + if(affecting && affecting.dismemberable && affecting.get_damage() >= (affecting.max_damage - P.dismemberment)) + affecting.dismember(P.damtype) + +/mob/living/carbon/catch_item(obj/item/I, skip_throw_mode_check = FALSE) + . = ..() + if(!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) && !skip_throw_mode_check && !in_throw_mode) + return + if(get_active_held_item() || restrained()) + return + I.attack_hand(src) + if(get_active_held_item() == I) //if our attack_hand() picks up the item... + visible_message("[src] catches [I]!") //catch that sucker! + throw_mode_off() + return TRUE + +/mob/living/carbon/embed_item(obj/item/I) + throw_alert("embeddedobject", /obj/screen/alert/embeddedobject) + var/obj/item/bodypart/L = pick(bodyparts) + L.embedded_objects |= I + I.add_mob_blood(src)//it embedded itself in you, of course it's bloody! + I.forceMove(src) + L.receive_damage(I.w_class*I.embedding.embedded_impact_pain_multiplier) + visible_message("[I] embeds itself in [src]'s [L.name]!","[I] embeds itself in your [L.name]!") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded) + +/mob/living/carbon/attacked_by(obj/item/I, mob/living/user) + //CIT CHANGES START HERE - combatmode and resting checks + var/totitemdamage = I.force + if(iscarbon(user)) + var/mob/living/carbon/tempcarb = user + if(!tempcarb.combatmode) + totitemdamage *= 0.5 + if(user.resting) + totitemdamage *= 0.5 + if(!combatmode) + totitemdamage *= 1.5 + //CIT CHANGES END HERE + if(user != src && check_shields(I, totitemdamage, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) + return FALSE + var/obj/item/bodypart/affecting + if(user == src) + affecting = get_bodypart(check_zone(user.zone_selected)) //we're self-mutilating! yay! + else + affecting = get_bodypart(ran_zone(user.zone_selected)) + if(!affecting) //missing limb? we select the first bodypart (you can never have zero, because of chest) + affecting = bodyparts[1] + SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting) + send_item_attack_message(I, user, affecting.name) + if(I.force) + apply_damage(totitemdamage, I.damtype, affecting) //CIT CHANGE - replaces I.force with totitemdamage + if(I.damtype == BRUTE && affecting.status == BODYPART_ORGANIC) + var/basebloodychance = affecting.brute_dam + totitemdamage + if(prob(basebloodychance)) + I.add_mob_blood(src) + bleed(totitemdamage) + if(totitemdamage >= 10 && get_dist(user, src) <= 1) //people with TK won't get smeared with blood + user.add_mob_blood(src) + + if(affecting.body_zone == BODY_ZONE_HEAD) + if(wear_mask && prob(basebloodychance)) + wear_mask.add_mob_blood(src) + update_inv_wear_mask() + if(wear_neck && prob(basebloodychance)) + wear_neck.add_mob_blood(src) + update_inv_neck() + if(head && prob(basebloodychance)) + head.add_mob_blood(src) + update_inv_head() + + //dismemberment + var/probability = I.get_dismemberment_chance(affecting) + if(prob(probability)) + if(affecting.dismember(I.damtype)) + I.add_mob_blood(src) + playsound(get_turf(src), I.get_dismember_sound(), 80, 1) + return TRUE //successful attack + +/mob/living/carbon/attack_drone(mob/living/simple_animal/drone/user) + return //so we don't call the carbon's attack_hand(). + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/mob/living/carbon/attack_hand(mob/living/carbon/human/user) + . = ..() + if(.) //was the attack blocked? + return + for(var/thing in diseases) + var/datum/disease/D = thing + if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + user.ContactContractDisease(D) + + for(var/thing in user.diseases) + var/datum/disease/D = thing + if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + ContactContractDisease(D) + + if(lying && surgeries.len) + if(user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM) + for(var/datum/surgery/S in surgeries) + if(S.next_step(user, user.a_intent)) + return TRUE + + +/mob/living/carbon/attack_paw(mob/living/carbon/monkey/M) + + if(can_inject(M, TRUE)) + for(var/thing in diseases) + var/datum/disease/D = thing + if((D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) && prob(85)) + M.ContactContractDisease(D) + + for(var/thing in M.diseases) + var/datum/disease/D = thing + if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + ContactContractDisease(D) + + if(M.a_intent == INTENT_HELP) + help_shake_act(M) + return 0 + + . = ..() + if(.) //successful monkey bite. + for(var/thing in M.diseases) + var/datum/disease/D = thing + ForceContractDisease(D) + return 1 + + +/mob/living/carbon/attack_slime(mob/living/simple_animal/slime/M) + . = ..() + if(!.) + return + if(M.powerlevel > 0) + var/stunprob = M.powerlevel * 7 + 10 // 17 at level 1, 80 at level 10 + if(prob(stunprob)) + M.powerlevel -= 3 + if(M.powerlevel < 0) + M.powerlevel = 0 + + visible_message("The [M.name] has shocked [src]!", \ + "The [M.name] has shocked [src]!") + + do_sparks(5, TRUE, src) + var/power = M.powerlevel + rand(0,3) + Knockdown(power*20) + if(stuttering < power) + stuttering = power + if (prob(stunprob) && M.powerlevel >= 8) + adjustFireLoss(M.powerlevel * rand(6,10)) + updatehealth() + +/mob/living/carbon/proc/dismembering_strike(mob/living/attacker, dam_zone) + if(!attacker.limb_destroyer) + return dam_zone + var/obj/item/bodypart/affecting + if(dam_zone && attacker.client) + affecting = get_bodypart(ran_zone(dam_zone)) + else + var/list/things_to_ruin = shuffle(bodyparts.Copy()) + for(var/B in things_to_ruin) + var/obj/item/bodypart/bodypart = B + if(bodypart.body_zone == BODY_ZONE_HEAD || bodypart.body_zone == BODY_ZONE_CHEST) + continue + if(!affecting || ((affecting.get_damage() / affecting.max_damage) < (bodypart.get_damage() / bodypart.max_damage))) + affecting = bodypart + if(affecting) + dam_zone = affecting.body_zone + if(affecting.get_damage() >= affecting.max_damage) + affecting.dismember() + return null + return affecting.body_zone + return dam_zone + + +/mob/living/carbon/blob_act(obj/structure/blob/B) + if (stat == DEAD) + return + else + show_message("The blob attacks!") + adjustBruteLoss(10) + +/mob/living/carbon/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_CONTENTS) + return + for(var/X in internal_organs) + var/obj/item/organ/O = X + O.emp_act(severity) + +/mob/living/carbon/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = 0, override = 0, tesla_shock = 0, illusion = 0, stun = TRUE) + if(tesla_shock && (flags_1 & TESLA_IGNORE_1)) + return FALSE + if(HAS_TRAIT(src, TRAIT_SHOCKIMMUNE)) + return FALSE + shock_damage *= siemens_coeff + if(dna && dna.species) + shock_damage *= dna.species.siemens_coeff + if(shock_damage<1 && !override) + return 0 + if(reagents.has_reagent(/datum/reagent/teslium)) + shock_damage *= 1.5 //If the mob has teslium in their body, shocks are 50% more damaging! + if(illusion) + adjustStaminaLoss(shock_damage) + else + take_overall_damage(0,shock_damage) + visible_message( + "[src] was shocked by \the [source]!", \ + "You feel a powerful shock coursing through your body!", \ + "You hear a heavy electrical crack." \ + ) + jitteriness += 1000 //High numbers for violent convulsions + do_jitter_animation(jitteriness) + stuttering += 2 + if((!tesla_shock || (tesla_shock && siemens_coeff > 0.5)) && stun) + Stun(40) + spawn(20) + jitteriness = max(jitteriness - 990, 10) //Still jittery, but vastly less + if((!tesla_shock || (tesla_shock && siemens_coeff > 0.5)) && stun) + Knockdown(60) + if(override) + return override + else + return shock_damage + +/mob/living/carbon/proc/help_shake_act(mob/living/carbon/M) + if(on_fire) + to_chat(M, "You can't put [p_them()] out with just your bare hands!") + return + + if(health >= 0 && !(HAS_TRAIT(src, TRAIT_FAKEDEATH))) + + if(lying) + if(buckled) + to_chat(M, "You need to unbuckle [src] first to do that!") + return + M.visible_message("[M] shakes [src] trying to get [p_them()] up!", \ + "You shake [src] trying to get [p_them()] up!") + + else if(check_zone(M.zone_selected) == "mouth") // I ADDED BOOP-EH-DEH-NOSEH - Jon + M.visible_message( \ + "[M] boops [src]'s nose.", \ + "You boop [src] on the nose.", ) + playsound(src, 'sound/items/Nose_boop.ogg', 50, 0) + + else if(check_zone(M.zone_selected) == "head") + var/mob/living/carbon/human/H = src + var/datum/species/pref_species = H.dna.species + + M.visible_message("[M] gives [H] a pat on the head to make [p_them()] feel better!", \ + "You give [H] a pat on the head to make [p_them()] feel better!") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "headpat", /datum/mood_event/headpat) + if(HAS_TRAIT(M, TRAIT_FRIENDLY)) + var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) + if (mood.sanity >= SANITY_GREAT) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/besthug, M) + else if (mood.sanity >= SANITY_DISTURBED) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/betterhug, M) + if(H.dna.species.can_wag_tail(H)) + if("tail_human" in pref_species.default_features) + if(H.dna.features["tail_human"] == "None") + return + else + if(!H.dna.species.is_wagging_tail()) + H.emote("wag") + + if("tail_lizard" in pref_species.default_features) + if(H.dna.features["tail_lizard"] == "None") + return + else + if(!H.dna.species.is_wagging_tail()) + H.emote("wag") + + if("mam_tail" in pref_species.default_features) + if(H.dna.features["mam_tail"] == "None") + return + else + if(!H.dna.species.is_wagging_tail()) + H.emote("wag") + + else + return + + else if(check_zone(M.zone_selected) == "r_arm" || check_zone(M.zone_selected) == "l_arm") + M.visible_message( \ + "[M] shakes [src]'s hand.", \ + "You shake [src]'s hand.", ) + + else + M.visible_message("[M] hugs [src] to make [p_them()] feel better!", \ + "You hug [src] to make [p_them()] feel better!") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "hug", /datum/mood_event/hug) + if(HAS_TRAIT(M, TRAIT_FRIENDLY)) + var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) + if (mood.sanity >= SANITY_GREAT) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/besthug, M) + else if (mood.sanity >= SANITY_DISTURBED) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "friendly_hug", /datum/mood_event/betterhug, M) + + AdjustStun(-60) + AdjustKnockdown(-60) + AdjustUnconscious(-60) + AdjustSleeping(-100) + if(recoveringstam) + adjustStaminaLoss(-15) + else if(resting) + resting = 0 + update_canmove() + + playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + + +/mob/living/carbon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0) + . = ..() + + var/damage = intensity - get_eye_protection() + if(.) // we've been flashed + var/obj/item/organ/eyes/eyes = getorganslot(ORGAN_SLOT_EYES) + if (!eyes) + return + if(visual) + return + + if (damage == 1) + to_chat(src, "Your eyes sting a little.") + if(prob(40)) + eyes.applyOrganDamage(1) + + else if (damage == 2) + to_chat(src, "Your eyes burn.") + eyes.applyOrganDamage(rand(2, 4)) + + else if( damage >= 3) + to_chat(src, "Your eyes itch and burn severely!") + eyes.applyOrganDamage(rand(12, 16)) + + if(eyes.damage > 10) + blind_eyes(damage) + blur_eyes(damage * rand(3, 6)) + + if(eyes.damage > 20) + if(prob(eyes.damage - 20)) + if(!HAS_TRAIT(src, TRAIT_NEARSIGHT)) + to_chat(src, "Your eyes start to burn badly!") + become_nearsighted(EYE_DAMAGE) + + else if(prob(eyes.damage - 25)) + if(!HAS_TRAIT(src, TRAIT_BLIND)) + to_chat(src, "You can't see anything!") + eyes.applyOrganDamage(eyes.maxHealth) + + else + to_chat(src, "Your eyes are really starting to hurt. This can't be good for you!") + if(has_bane(BANE_LIGHT)) + mind.disrupt_spells(-500) + return 1 + else if(damage == 0) // just enough protection + if(prob(20)) + to_chat(src, "Something bright flashes in the corner of your vision!") + if(has_bane(BANE_LIGHT)) + mind.disrupt_spells(0) + + +/mob/living/carbon/soundbang_act(intensity = 1, stun_pwr = 20, damage_pwr = 5, deafen_pwr = 15) + var/list/reflist = list(intensity) // Need to wrap this in a list so we can pass a reference + SEND_SIGNAL(src, COMSIG_CARBON_SOUNDBANG, reflist) + intensity = reflist[1] + var/ear_safety = get_ear_protection() + var/obj/item/organ/ears/ears = getorganslot(ORGAN_SLOT_EARS) + var/effect_amount = intensity - ear_safety + if(effect_amount > 0) + if(stun_pwr) + Knockdown(stun_pwr*effect_amount) + + if(istype(ears) && (deafen_pwr || damage_pwr)) + var/ear_damage = damage_pwr * effect_amount + var/deaf = deafen_pwr * effect_amount + adjustEarDamage(ear_damage,deaf) + + if(ears.damage >= 15) + to_chat(src, "Your ears start to ring badly!") + if(prob(ears.damage - 5)) + to_chat(src, "You can't hear anything!") + ears.damage = min(ears.damage, ears.maxHealth) + // you need earmuffs, inacusiate, or replacement + else if(ears.damage >= 5) + to_chat(src, "Your ears start to ring!") + SEND_SOUND(src, sound('sound/weapons/flash_ring.ogg',0,1,0,250)) + return effect_amount //how soundbanged we are + + +/mob/living/carbon/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone) + if(damage_type != BRUTE && damage_type != BURN) + return + damage_amount *= 0.5 //0.5 multiplier for balance reason, we don't want clothes to be too easily destroyed + if(!def_zone || def_zone == BODY_ZONE_HEAD) + var/obj/item/clothing/hit_clothes + if(wear_mask) + hit_clothes = wear_mask + if(wear_neck) + hit_clothes = wear_neck + if(head) + hit_clothes = head + if(hit_clothes) + hit_clothes.take_damage(damage_amount, damage_type, damage_flag, 0) + +/mob/living/carbon/can_hear() + . = FALSE + var/obj/item/organ/ears/ears = getorganslot(ORGAN_SLOT_EARS) + if(istype(ears) && !ears.deaf) + . = TRUE diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 9629dc3484..11c4d7dbef 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -1,66 +1,66 @@ -/mob/living/carbon - gender = MALE - pressure_resistance = 15 - possible_a_intents = list(INTENT_HELP, INTENT_HARM) - hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,GLAND_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD,RAD_HUD) - has_limbs = 1 - held_items = list(null, null) - var/list/stomach_contents = list() - var/list/internal_organs = list() //List of /obj/item/organ in the mob. They don't go in the contents for some reason I don't want to know. - var/list/internal_organs_slot= list() //Same as above, but stores "slot ID" - "organ" pairs for easy access. - var/silent = FALSE //Can't talk. Value goes down every life proc. //NOTE TO FUTURE CODERS: DO NOT INITIALIZE NUMERICAL VARS AS NULL OR I WILL MURDER YOU. - var/dreaming = 0 //How many dream images we have left to send - - var/obj/item/restraints/handcuffed //Whether or not the mob is handcuffed - var/obj/item/restraints/legcuffed //Same as handcuffs but for legs. Bear traps use this. - - var/disgust = 0 - -//inventory slots - var/obj/item/back = null - var/obj/item/clothing/mask/wear_mask = null - var/obj/item/clothing/neck/wear_neck = null - var/obj/item/tank/internal = null - var/obj/item/head = null - - var/obj/item/gloves = null //only used by humans - var/obj/item/shoes = null //only used by humans. - var/obj/item/clothing/glasses/glasses = null //only used by humans. - var/obj/item/ears = null //only used by humans. - - var/datum/dna/dna = null//Carbon - var/datum/mind/last_mind = null //last mind to control this mob, for blood-based cloning - - var/failed_last_breath = 0 //This is used to determine if the mob failed a breath. If they did fail a brath, they will attempt to breathe each tick, otherwise just once per 4 ticks. - - var/co2overloadtime = null - var/o2overloadtime = null //for Ash walker's weaker lungs, and future atmosia hazards - var/temperature_resistance = T0C+75 - var/obj/item/reagent_containers/food/snacks/meat/slab/type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab - - var/gib_type = /obj/effect/decal/cleanable/blood/gibs - - rotate_on_lying = TRUE - - var/tinttotal = 0 // Total level of visualy impairing items - - var/list/bodyparts = list(/obj/item/bodypart/chest, /obj/item/bodypart/head, /obj/item/bodypart/l_arm, - /obj/item/bodypart/r_arm, /obj/item/bodypart/r_leg, /obj/item/bodypart/l_leg) - //Gets filled up in create_bodyparts() - - var/list/hand_bodyparts = list() //a collection of arms (or actually whatever the fug /bodyparts you monsters use to wreck my systems) - var/list/leg_bodyparts = list() - - var/icon_render_key = "" - var/static/list/limb_icon_cache = list() - - //halucination vars - var/image/halimage - var/image/halbody - var/obj/halitem - var/hal_screwyhud = SCREWYHUD_NONE - var/next_hallucination = 0 - var/cpr_time = 1 //CPR cooldown. - var/damageoverlaytemp = 0 - - var/drunkenness = 0 //Overall drunkenness - check handle_alcohol() in life.dm for effects +/mob/living/carbon + gender = MALE + pressure_resistance = 15 + possible_a_intents = list(INTENT_HELP, INTENT_HARM) + hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,GLAND_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD,RAD_HUD) + has_limbs = 1 + held_items = list(null, null) + var/list/stomach_contents = list() + var/list/internal_organs = list() //List of /obj/item/organ in the mob. They don't go in the contents for some reason I don't want to know. + var/list/internal_organs_slot= list() //Same as above, but stores "slot ID" - "organ" pairs for easy access. + var/silent = FALSE //Can't talk. Value goes down every life proc. //NOTE TO FUTURE CODERS: DO NOT INITIALIZE NUMERICAL VARS AS NULL OR I WILL MURDER YOU. + var/dreaming = 0 //How many dream images we have left to send + + var/obj/item/restraints/handcuffed //Whether or not the mob is handcuffed + var/obj/item/restraints/legcuffed //Same as handcuffs but for legs. Bear traps use this. + + var/disgust = 0 + +//inventory slots + var/obj/item/back = null + var/obj/item/clothing/mask/wear_mask = null + var/obj/item/clothing/neck/wear_neck = null + var/obj/item/tank/internal = null + var/obj/item/head = null + + var/obj/item/gloves = null //only used by humans + var/obj/item/shoes = null //only used by humans. + var/obj/item/clothing/glasses/glasses = null //only used by humans. + var/obj/item/ears = null //only used by humans. + + var/datum/dna/dna = null//Carbon + var/datum/mind/last_mind = null //last mind to control this mob, for blood-based cloning + + var/failed_last_breath = 0 //This is used to determine if the mob failed a breath. If they did fail a brath, they will attempt to breathe each tick, otherwise just once per 4 ticks. + + var/co2overloadtime = null + var/o2overloadtime = null //for Ash walker's weaker lungs, and future atmosia hazards + var/temperature_resistance = T0C+75 + var/obj/item/reagent_containers/food/snacks/meat/slab/type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab + + var/gib_type = /obj/effect/decal/cleanable/blood/gibs + + rotate_on_lying = TRUE + + var/tinttotal = 0 // Total level of visualy impairing items + + var/list/bodyparts = list(/obj/item/bodypart/chest, /obj/item/bodypart/head, /obj/item/bodypart/l_arm, + /obj/item/bodypart/r_arm, /obj/item/bodypart/r_leg, /obj/item/bodypart/l_leg) + //Gets filled up in create_bodyparts() + + var/list/hand_bodyparts = list() //a collection of arms (or actually whatever the fug /bodyparts you monsters use to wreck my systems) + var/list/leg_bodyparts = list() + + var/icon_render_key = "" + var/static/list/limb_icon_cache = list() + + //halucination vars + var/image/halimage + var/image/halbody + var/obj/halitem + var/hal_screwyhud = SCREWYHUD_NONE + var/next_hallucination = 0 + var/cpr_time = 1 //CPR cooldown. + var/damageoverlaytemp = 0 + + var/drunkenness = 0 //Overall drunkenness - check handle_alcohol() in life.dm for effects diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm index 6a75429c27..6182befebd 100644 --- a/code/modules/mob/living/carbon/death.dm +++ b/code/modules/mob/living/carbon/death.dm @@ -1,72 +1,72 @@ -/mob/living/carbon/death(gibbed) - if(stat == DEAD) - return - - silent = FALSE - losebreath = 0 - - if(!gibbed) - emote("deathgasp") - if(combatmode) - toggle_combat_mode(TRUE, TRUE) - - . = ..() - - for(var/T in get_traumas()) - var/datum/brain_trauma/BT = T - BT.on_death() - - if(SSticker.mode) - SSticker.mode.check_win() //Calls the rounds wincheck, mainly for wizard, malf, and changeling now - -/mob/living/carbon/gib(no_brain, no_organs, no_bodyparts) - var/atom/Tsec = drop_location() - for(var/mob/M in src) - if(M in stomach_contents) - stomach_contents.Remove(M) - M.forceMove(Tsec) - visible_message("[M] bursts out of [src]!") - ..() - -/mob/living/carbon/spill_organs(no_brain, no_organs, no_bodyparts) - var/atom/Tsec = drop_location() - if(!no_bodyparts) - if(no_organs)//so the organs don't get transfered inside the bodyparts we'll drop. - for(var/X in internal_organs) - if(no_brain || !istype(X, /obj/item/organ/brain)) - qdel(X) - else //we're going to drop all bodyparts except chest, so the only organs that needs spilling are those inside it. - for(var/X in internal_organs) - var/obj/item/organ/O = X - if(no_brain && istype(O, /obj/item/organ/brain)) - qdel(O) //so the brain isn't transfered to the head when the head drops. - continue - var/org_zone = check_zone(O.zone) //both groin and chest organs. - if(org_zone == BODY_ZONE_CHEST) - O.Remove(src) - O.forceMove(Tsec) - O.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5) - else - for(var/X in internal_organs) - var/obj/item/organ/I = X - if(no_brain && istype(I, /obj/item/organ/brain)) - qdel(I) - continue - if(no_organs && !istype(I, /obj/item/organ/brain)) - qdel(I) - continue - I.Remove(src) - I.forceMove(Tsec) - I.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5) - - -/mob/living/carbon/spread_bodyparts() - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - BP.drop_limb() - BP.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5) - -/mob/living/carbon/ghostize(can_reenter_corpse = TRUE, special = FALSE, penalize = FALSE) - if(combatmode) - toggle_combat_mode(TRUE, TRUE) - return ..() +/mob/living/carbon/death(gibbed) + if(stat == DEAD) + return + + silent = FALSE + losebreath = 0 + + if(!gibbed) + emote("deathgasp") + if(combatmode) + toggle_combat_mode(TRUE, TRUE) + + . = ..() + + for(var/T in get_traumas()) + var/datum/brain_trauma/BT = T + BT.on_death() + + if(SSticker.mode) + SSticker.mode.check_win() //Calls the rounds wincheck, mainly for wizard, malf, and changeling now + +/mob/living/carbon/gib(no_brain, no_organs, no_bodyparts) + var/atom/Tsec = drop_location() + for(var/mob/M in src) + if(M in stomach_contents) + stomach_contents.Remove(M) + M.forceMove(Tsec) + visible_message("[M] bursts out of [src]!") + ..() + +/mob/living/carbon/spill_organs(no_brain, no_organs, no_bodyparts) + var/atom/Tsec = drop_location() + if(!no_bodyparts) + if(no_organs)//so the organs don't get transfered inside the bodyparts we'll drop. + for(var/X in internal_organs) + if(no_brain || !istype(X, /obj/item/organ/brain)) + qdel(X) + else //we're going to drop all bodyparts except chest, so the only organs that needs spilling are those inside it. + for(var/X in internal_organs) + var/obj/item/organ/O = X + if(no_brain && istype(O, /obj/item/organ/brain)) + qdel(O) //so the brain isn't transfered to the head when the head drops. + continue + var/org_zone = check_zone(O.zone) //both groin and chest organs. + if(org_zone == BODY_ZONE_CHEST) + O.Remove(src) + O.forceMove(Tsec) + O.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5) + else + for(var/X in internal_organs) + var/obj/item/organ/I = X + if(no_brain && istype(I, /obj/item/organ/brain)) + qdel(I) + continue + if(no_organs && !istype(I, /obj/item/organ/brain)) + qdel(I) + continue + I.Remove(src) + I.forceMove(Tsec) + I.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5) + + +/mob/living/carbon/spread_bodyparts() + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + BP.drop_limb() + BP.throw_at(get_edge_target_turf(src,pick(GLOB.alldirs)),rand(1,3),5) + +/mob/living/carbon/ghostize(can_reenter_corpse = TRUE, special = FALSE, penalize = FALSE) + if(combatmode) + toggle_combat_mode(TRUE, TRUE) + return ..() diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm index 559c2ebcdd..8c5dc6aa0b 100644 --- a/code/modules/mob/living/carbon/emote.dm +++ b/code/modules/mob/living/carbon/emote.dm @@ -1,100 +1,100 @@ -/datum/emote/living/carbon - mob_type_allowed_typecache = list(/mob/living/carbon) - -/datum/emote/living/carbon/airguitar - key = "airguitar" - message = "is strumming the air and headbanging like a safari chimp." - restraint_check = TRUE - -/datum/emote/living/carbon/blink - key = "blink" - key_third_person = "blinks" - message = "blinks." - -/datum/emote/living/carbon/blink_r - key = "blink_r" - message = "blinks rapidly." - -/datum/emote/living/carbon/clap - key = "clap" - key_third_person = "claps" - message = "claps." - muzzle_ignore = TRUE - restraint_check = TRUE - emote_type = EMOTE_AUDIBLE - mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai) - -/datum/emote/living/carbon/clap/run_emote(mob/living/user, params) - . = ..() - if (.) - if (ishuman(user)) - // Need hands to clap - if (!user.get_bodypart(BODY_ZONE_L_ARM) || !user.get_bodypart(BODY_ZONE_R_ARM)) - return - var/clap = pick('sound/misc/clap1.ogg', - 'sound/misc/clap2.ogg', - 'sound/misc/clap3.ogg', - 'sound/misc/clap4.ogg') - playsound(user, clap, 50, 1, -1) - -/datum/emote/living/carbon/gnarl - key = "gnarl" - key_third_person = "gnarls" - message = "gnarls and shows its teeth..." - mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - -/datum/emote/living/carbon/moan - key = "moan" - key_third_person = "moans" - message = "moans!" - message_mime = "appears to moan!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/carbon/roll - key = "roll" - key_third_person = "rolls" - message = "rolls." - mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - restraint_check = TRUE - -/datum/emote/living/carbon/scratch - key = "scratch" - key_third_person = "scratches" - message = "scratches." - mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - restraint_check = TRUE - -/datum/emote/living/carbon/screech - key = "screech" - key_third_person = "screeches" - message = "screeches." - mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - -/datum/emote/living/carbon/sign - key = "sign" - key_third_person = "signs" - message_param = "signs the number %t." - mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - restraint_check = TRUE - -/datum/emote/living/carbon/sign/select_param(mob/user, params) - . = ..() - if(!isnum(text2num(params))) - return message - -/datum/emote/living/carbon/sign/signal - key = "signal" - key_third_person = "signals" - message_param = "raises %t fingers." - mob_type_allowed_typecache = list(/mob/living/carbon/human) - restraint_check = TRUE - -/datum/emote/living/carbon/tail - key = "tail" - message = "waves their tail." - mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - -/datum/emote/living/carbon/wink - key = "wink" - key_third_person = "winks" - message = "winks." +/datum/emote/living/carbon + mob_type_allowed_typecache = list(/mob/living/carbon) + +/datum/emote/living/carbon/airguitar + key = "airguitar" + message = "is strumming the air and headbanging like a safari chimp." + restraint_check = TRUE + +/datum/emote/living/carbon/blink + key = "blink" + key_third_person = "blinks" + message = "blinks." + +/datum/emote/living/carbon/blink_r + key = "blink_r" + message = "blinks rapidly." + +/datum/emote/living/carbon/clap + key = "clap" + key_third_person = "claps" + message = "claps." + muzzle_ignore = TRUE + restraint_check = TRUE + emote_type = EMOTE_AUDIBLE + mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai) + +/datum/emote/living/carbon/clap/run_emote(mob/living/user, params) + . = ..() + if (.) + if (ishuman(user)) + // Need hands to clap + if (!user.get_bodypart(BODY_ZONE_L_ARM) || !user.get_bodypart(BODY_ZONE_R_ARM)) + return + var/clap = pick('sound/misc/clap1.ogg', + 'sound/misc/clap2.ogg', + 'sound/misc/clap3.ogg', + 'sound/misc/clap4.ogg') + playsound(user, clap, 50, 1, -1) + +/datum/emote/living/carbon/gnarl + key = "gnarl" + key_third_person = "gnarls" + message = "gnarls and shows its teeth..." + mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) + +/datum/emote/living/carbon/moan + key = "moan" + key_third_person = "moans" + message = "moans!" + message_mime = "appears to moan!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/roll + key = "roll" + key_third_person = "rolls" + message = "rolls." + mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) + restraint_check = TRUE + +/datum/emote/living/carbon/scratch + key = "scratch" + key_third_person = "scratches" + message = "scratches." + mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) + restraint_check = TRUE + +/datum/emote/living/carbon/screech + key = "screech" + key_third_person = "screeches" + message = "screeches." + mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) + +/datum/emote/living/carbon/sign + key = "sign" + key_third_person = "signs" + message_param = "signs the number %t." + mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) + restraint_check = TRUE + +/datum/emote/living/carbon/sign/select_param(mob/user, params) + . = ..() + if(!isnum(text2num(params))) + return message + +/datum/emote/living/carbon/sign/signal + key = "signal" + key_third_person = "signals" + message_param = "raises %t fingers." + mob_type_allowed_typecache = list(/mob/living/carbon/human) + restraint_check = TRUE + +/datum/emote/living/carbon/tail + key = "tail" + message = "waves their tail." + mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) + +/datum/emote/living/carbon/wink + key = "wink" + key_third_person = "winks" + message = "winks." diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index a09af656f5..a6595100a1 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -1,69 +1,69 @@ -/mob/living/carbon/human/gib_animation() - new /obj/effect/temp_visual/gib_animation(loc, "gibbed-h") - -/mob/living/carbon/human/dust_animation() - new /obj/effect/temp_visual/dust_animation(loc, "dust-h") - -/mob/living/carbon/human/spawn_gibs(with_bodyparts, atom/loc_override) - var/location = loc_override ? loc_override.drop_location() : drop_location() - if(dna?.species?.gib_types) - var/datum/species/S = dna.species - var/length = length(S.gib_types) - if(length) - var/path = (with_bodyparts && length > 1) ? S.gib_types[2] : S.gib_types[1] - new path(location, src, get_static_viruses()) - else - new S.gib_types(location, src, get_static_viruses()) - else - if(with_bodyparts) - new /obj/effect/gibspawner/human(location, src, get_static_viruses()) - else - new /obj/effect/gibspawner/human/bodypartless(location, src, get_static_viruses()) - -/mob/living/carbon/human/spawn_dust(just_ash = FALSE) - if(just_ash) - new /obj/effect/decal/cleanable/ash(loc) - else - new /obj/effect/decal/remains/human(loc) - -/mob/living/carbon/human/death(gibbed) - if(stat == DEAD) - return - stop_sound_channel(CHANNEL_HEARTBEAT) - var/obj/item/organ/heart/H = getorganslot(ORGAN_SLOT_HEART) - if(H) - H.beat = BEAT_NONE - - . = ..() - - dizziness = 0 - jitteriness = 0 - - if(ismecha(loc)) - var/obj/mecha/M = loc - if(M.occupant == src) - M.go_out() - - dna.species.spec_death(gibbed, src) - - if(SSticker.HasRoundStarted()) - SSblackbox.ReportDeath(src) - if(is_devil(src)) - INVOKE_ASYNC(is_devil(src), /datum/antagonist/devil.proc/beginResurrectionCheck, src) - -/mob/living/carbon/human/proc/makeSkeleton() - ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) - set_species(/datum/species/skeleton) - return TRUE - - -/mob/living/carbon/proc/Drain() - become_husk(CHANGELING_DRAIN) - ADD_TRAIT(src, TRAIT_NOCLONE, CHANGELING_DRAIN) - blood_volume = 0 - return TRUE - -/mob/living/carbon/proc/makeUncloneable() - ADD_TRAIT(src, TRAIT_NOCLONE, MADE_UNCLONEABLE) - blood_volume = 0 +/mob/living/carbon/human/gib_animation() + new /obj/effect/temp_visual/gib_animation(loc, "gibbed-h") + +/mob/living/carbon/human/dust_animation() + new /obj/effect/temp_visual/dust_animation(loc, "dust-h") + +/mob/living/carbon/human/spawn_gibs(with_bodyparts, atom/loc_override) + var/location = loc_override ? loc_override.drop_location() : drop_location() + if(dna?.species?.gib_types) + var/datum/species/S = dna.species + var/length = length(S.gib_types) + if(length) + var/path = (with_bodyparts && length > 1) ? S.gib_types[2] : S.gib_types[1] + new path(location, src, get_static_viruses()) + else + new S.gib_types(location, src, get_static_viruses()) + else + if(with_bodyparts) + new /obj/effect/gibspawner/human(location, src, get_static_viruses()) + else + new /obj/effect/gibspawner/human/bodypartless(location, src, get_static_viruses()) + +/mob/living/carbon/human/spawn_dust(just_ash = FALSE) + if(just_ash) + new /obj/effect/decal/cleanable/ash(loc) + else + new /obj/effect/decal/remains/human(loc) + +/mob/living/carbon/human/death(gibbed) + if(stat == DEAD) + return + stop_sound_channel(CHANNEL_HEARTBEAT) + var/obj/item/organ/heart/H = getorganslot(ORGAN_SLOT_HEART) + if(H) + H.beat = BEAT_NONE + + . = ..() + + dizziness = 0 + jitteriness = 0 + + if(ismecha(loc)) + var/obj/mecha/M = loc + if(M.occupant == src) + M.go_out() + + dna.species.spec_death(gibbed, src) + + if(SSticker.HasRoundStarted()) + SSblackbox.ReportDeath(src) + if(is_devil(src)) + INVOKE_ASYNC(is_devil(src), /datum/antagonist/devil.proc/beginResurrectionCheck, src) + +/mob/living/carbon/human/proc/makeSkeleton() + ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) + set_species(/datum/species/skeleton) + return TRUE + + +/mob/living/carbon/proc/Drain() + become_husk(CHANGELING_DRAIN) + ADD_TRAIT(src, TRAIT_NOCLONE, CHANGELING_DRAIN) + blood_volume = 0 + return TRUE + +/mob/living/carbon/proc/makeUncloneable() + ADD_TRAIT(src, TRAIT_NOCLONE, MADE_UNCLONEABLE) + blood_volume = 0 return TRUE \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index 74593a20a0..95746afb73 100644 --- a/code/modules/mob/living/carbon/human/dummy.dm +++ b/code/modules/mob/living/carbon/human/dummy.dm @@ -1,48 +1,48 @@ - -/mob/living/carbon/human/dummy - real_name = "Test Dummy" - status_flags = GODMODE|CANPUSH - mouse_drag_pointer = MOUSE_INACTIVE_POINTER - var/in_use = FALSE - no_vore = TRUE - -INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy) - -/mob/living/carbon/human/dummy/Destroy() - in_use = FALSE - return ..() - -/mob/living/carbon/human/dummy/Life() - return - -/mob/living/carbon/human/dummy/proc/wipe_state() - delete_equipment() - icon_render_key = null - cut_overlays(TRUE) - -//Inefficient pooling/caching way. -GLOBAL_LIST_EMPTY(human_dummy_list) -GLOBAL_LIST_EMPTY(dummy_mob_list) - -/proc/generate_or_wait_for_human_dummy(slotkey) - if(!slotkey) - return new /mob/living/carbon/human/dummy - var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotkey] - if(istype(D)) - UNTIL(!D.in_use) - else - pass() - if(QDELETED(D)) - D = new - GLOB.human_dummy_list[slotkey] = D - GLOB.dummy_mob_list += D - D.in_use = TRUE - return D - -/proc/unset_busy_human_dummy(slotnumber) - if(!slotnumber) - return - var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotnumber] - if(istype(D)) - D.wipe_state() - D.in_use = FALSE + +/mob/living/carbon/human/dummy + real_name = "Test Dummy" + status_flags = GODMODE|CANPUSH + mouse_drag_pointer = MOUSE_INACTIVE_POINTER + var/in_use = FALSE + no_vore = TRUE + +INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy) + +/mob/living/carbon/human/dummy/Destroy() + in_use = FALSE + return ..() + +/mob/living/carbon/human/dummy/Life() + return + +/mob/living/carbon/human/dummy/proc/wipe_state() + delete_equipment() + icon_render_key = null + cut_overlays(TRUE) + +//Inefficient pooling/caching way. +GLOBAL_LIST_EMPTY(human_dummy_list) +GLOBAL_LIST_EMPTY(dummy_mob_list) + +/proc/generate_or_wait_for_human_dummy(slotkey) + if(!slotkey) + return new /mob/living/carbon/human/dummy + var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotkey] + if(istype(D)) + UNTIL(!D.in_use) + else + pass() + if(QDELETED(D)) + D = new + GLOB.human_dummy_list[slotkey] = D + GLOB.dummy_mob_list += D + D.in_use = TRUE + return D + +/proc/unset_busy_human_dummy(slotnumber) + if(!slotnumber) + return + var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotnumber] + if(istype(D)) + D.wipe_state() + D.in_use = FALSE diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm index d1d5fb7cd4..acaf713e28 100644 --- a/code/modules/mob/living/carbon/human/emote.dm +++ b/code/modules/mob/living/carbon/human/emote.dm @@ -1,189 +1,189 @@ -/datum/emote/living/carbon/human - mob_type_allowed_typecache = list(/mob/living/carbon/human) - -/datum/emote/living/carbon/human/cry - key = "cry" - key_third_person = "cries" - message = "cries." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/carbon/human/dap - key = "dap" - key_third_person = "daps" - message = "sadly can't find anybody to give daps to, and daps themself. Shameful." - message_param = "give daps to %t." - restraint_check = TRUE - -/datum/emote/living/carbon/human/eyebrow - key = "eyebrow" - message = "raises an eyebrow." - -/datum/emote/living/carbon/human/grumble - key = "grumble" - key_third_person = "grumbles" - message = "grumbles!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/carbon/human/handshake - key = "handshake" - message = "shakes their own hands." - message_param = "shakes hands with %t." - restraint_check = TRUE - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/carbon/human/hug - key = "hug" - key_third_person = "hugs" - message = "hugs themself." - message_param = "hugs %t." - restraint_check = TRUE - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/carbon/human/mawp - key = "mawp" - key_third_person = "mawps" - message = "mawps annoyingly." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/carbon/human/mawp/run_emote(mob/living/user, params) - . = ..() - if(.) - if(ishuman(user)) - if(prob(10)) - user.adjustEarDamage(-5, -5) - -/datum/emote/living/carbon/human/mumble - key = "mumble" - key_third_person = "mumbles" - message = "mumbles!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/carbon/human/pale - key = "pale" - message = "goes pale for a second." - -/datum/emote/living/carbon/human/raise - key = "raise" - key_third_person = "raises" - message = "raises a hand." - restraint_check = TRUE - -/datum/emote/living/carbon/human/salute - key = "salute" - key_third_person = "salutes" - message = "salutes." - message_param = "salutes to %t." - restraint_check = TRUE - -/datum/emote/living/carbon/human/shrug - key = "shrug" - key_third_person = "shrugs" - message = "shrugs." - -/datum/emote/living/carbon/human/wag - key = "wag" - key_third_person = "wags" - message = "wags their tail." - -/datum/emote/living/carbon/human/wag/run_emote(mob/user, params) - . = ..() - if(!.) - return - var/mob/living/carbon/human/H = user - if(!istype(H) || !H.dna || !H.dna.species || !H.dna.species.can_wag_tail(H)) - return - if(!H.dna.species.is_wagging_tail()) - H.dna.species.start_wagging_tail(H) - else - H.dna.species.stop_wagging_tail(H) - -/datum/emote/living/carbon/human/wag/can_run_emote(mob/user, status_check = TRUE) - if(!..()) - return FALSE - var/mob/living/carbon/human/H = user - return H.dna && H.dna.species && H.dna.species.can_wag_tail(user) - -/datum/emote/living/carbon/human/wag/select_message_type(mob/user) - . = ..() - var/mob/living/carbon/human/H = user - if(!H.dna || !H.dna.species) - return - if(H.dna.species.is_wagging_tail()) - . = null - -/datum/emote/living/carbon/human/wing - key = "wing" - key_third_person = "wings" - message = "their wings." - -/datum/emote/living/carbon/human/wing/run_emote(mob/user, params) - . = ..() - if(.) - var/mob/living/carbon/human/H = user - if(findtext(select_message_type(user), "open")) - H.OpenWings() - else - H.CloseWings() - -/datum/emote/living/carbon/human/wing/select_message_type(mob/user) - . = ..() - var/mob/living/carbon/human/H = user - if("wings" in H.dna.species.mutant_bodyparts) - . = "opens " + message - else - . = "closes " + message - -/datum/emote/living/carbon/human/wing/can_run_emote(mob/user, status_check = TRUE) - if(!..()) - return FALSE - var/mob/living/carbon/human/H = user - if(H.dna && H.dna.species && (H.dna.features["wings"] != "None")) - return TRUE - -/mob/living/carbon/human/proc/OpenWings() - if(!dna || !dna.species) - return - if("wings" in dna.species.mutant_bodyparts) - dna.species.mutant_bodyparts -= "wings" - dna.species.mutant_bodyparts |= "wingsopen" - update_body() - -/mob/living/carbon/human/proc/CloseWings() - if(!dna || !dna.species) - return - if("wingsopen" in dna.species.mutant_bodyparts) - dna.species.mutant_bodyparts -= "wingsopen" - dna.species.mutant_bodyparts |= "wings" - update_body() - if(isturf(loc)) - var/turf/T = loc - T.Entered(src) - -/datum/emote/sound/human - mob_type_allowed_typecache = list(/mob/living/carbon/human) - emote_type = EMOTE_AUDIBLE - -/datum/emote/sound/human/buzz - key = "buzz" - key_third_person = "buzzes" - message = "buzzes." - message_param = "buzzes at %t." - sound = 'sound/machines/buzz-sigh.ogg' - -/datum/emote/sound/human/buzz2 - key = "buzz2" - message = "buzzes twice." - sound = 'sound/machines/buzz-two.ogg' - -/datum/emote/sound/human/ping - key = "ping" - key_third_person = "pings" - message = "pings." - message_param = "pings at %t." - sound = 'sound/machines/ping.ogg' - -/datum/emote/sound/human/chime - key = "chime" - key_third_person = "chimes" - message = "chimes." - sound = 'sound/machines/chime.ogg' +/datum/emote/living/carbon/human + mob_type_allowed_typecache = list(/mob/living/carbon/human) + +/datum/emote/living/carbon/human/cry + key = "cry" + key_third_person = "cries" + message = "cries." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/dap + key = "dap" + key_third_person = "daps" + message = "sadly can't find anybody to give daps to, and daps themself. Shameful." + message_param = "give daps to %t." + restraint_check = TRUE + +/datum/emote/living/carbon/human/eyebrow + key = "eyebrow" + message = "raises an eyebrow." + +/datum/emote/living/carbon/human/grumble + key = "grumble" + key_third_person = "grumbles" + message = "grumbles!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/handshake + key = "handshake" + message = "shakes their own hands." + message_param = "shakes hands with %t." + restraint_check = TRUE + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/hug + key = "hug" + key_third_person = "hugs" + message = "hugs themself." + message_param = "hugs %t." + restraint_check = TRUE + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/mawp + key = "mawp" + key_third_person = "mawps" + message = "mawps annoyingly." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/mawp/run_emote(mob/living/user, params) + . = ..() + if(.) + if(ishuman(user)) + if(prob(10)) + user.adjustEarDamage(-5, -5) + +/datum/emote/living/carbon/human/mumble + key = "mumble" + key_third_person = "mumbles" + message = "mumbles!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/pale + key = "pale" + message = "goes pale for a second." + +/datum/emote/living/carbon/human/raise + key = "raise" + key_third_person = "raises" + message = "raises a hand." + restraint_check = TRUE + +/datum/emote/living/carbon/human/salute + key = "salute" + key_third_person = "salutes" + message = "salutes." + message_param = "salutes to %t." + restraint_check = TRUE + +/datum/emote/living/carbon/human/shrug + key = "shrug" + key_third_person = "shrugs" + message = "shrugs." + +/datum/emote/living/carbon/human/wag + key = "wag" + key_third_person = "wags" + message = "wags their tail." + +/datum/emote/living/carbon/human/wag/run_emote(mob/user, params) + . = ..() + if(!.) + return + var/mob/living/carbon/human/H = user + if(!istype(H) || !H.dna || !H.dna.species || !H.dna.species.can_wag_tail(H)) + return + if(!H.dna.species.is_wagging_tail()) + H.dna.species.start_wagging_tail(H) + else + H.dna.species.stop_wagging_tail(H) + +/datum/emote/living/carbon/human/wag/can_run_emote(mob/user, status_check = TRUE) + if(!..()) + return FALSE + var/mob/living/carbon/human/H = user + return H.dna && H.dna.species && H.dna.species.can_wag_tail(user) + +/datum/emote/living/carbon/human/wag/select_message_type(mob/user) + . = ..() + var/mob/living/carbon/human/H = user + if(!H.dna || !H.dna.species) + return + if(H.dna.species.is_wagging_tail()) + . = null + +/datum/emote/living/carbon/human/wing + key = "wing" + key_third_person = "wings" + message = "their wings." + +/datum/emote/living/carbon/human/wing/run_emote(mob/user, params) + . = ..() + if(.) + var/mob/living/carbon/human/H = user + if(findtext(select_message_type(user), "open")) + H.OpenWings() + else + H.CloseWings() + +/datum/emote/living/carbon/human/wing/select_message_type(mob/user) + . = ..() + var/mob/living/carbon/human/H = user + if("wings" in H.dna.species.mutant_bodyparts) + . = "opens " + message + else + . = "closes " + message + +/datum/emote/living/carbon/human/wing/can_run_emote(mob/user, status_check = TRUE) + if(!..()) + return FALSE + var/mob/living/carbon/human/H = user + if(H.dna && H.dna.species && (H.dna.features["wings"] != "None")) + return TRUE + +/mob/living/carbon/human/proc/OpenWings() + if(!dna || !dna.species) + return + if("wings" in dna.species.mutant_bodyparts) + dna.species.mutant_bodyparts -= "wings" + dna.species.mutant_bodyparts |= "wingsopen" + update_body() + +/mob/living/carbon/human/proc/CloseWings() + if(!dna || !dna.species) + return + if("wingsopen" in dna.species.mutant_bodyparts) + dna.species.mutant_bodyparts -= "wingsopen" + dna.species.mutant_bodyparts |= "wings" + update_body() + if(isturf(loc)) + var/turf/T = loc + T.Entered(src) + +/datum/emote/sound/human + mob_type_allowed_typecache = list(/mob/living/carbon/human) + emote_type = EMOTE_AUDIBLE + +/datum/emote/sound/human/buzz + key = "buzz" + key_third_person = "buzzes" + message = "buzzes." + message_param = "buzzes at %t." + sound = 'sound/machines/buzz-sigh.ogg' + +/datum/emote/sound/human/buzz2 + key = "buzz2" + message = "buzzes twice." + sound = 'sound/machines/buzz-two.ogg' + +/datum/emote/sound/human/ping + key = "ping" + key_third_person = "pings" + message = "pings." + message_param = "pings at %t." + sound = 'sound/machines/ping.ogg' + +/datum/emote/sound/human/chime + key = "chime" + key_third_person = "chimes" + message = "chimes." + sound = 'sound/machines/chime.ogg' diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index ff356d7001..e212e0e713 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -1,415 +1,415 @@ -/mob/living/carbon/human/examine(mob/user) -//this is very slightly better than it was because you can use it more places. still can't do \his[src] though. - var/t_He = p_they(TRUE) - var/t_His = p_their(TRUE) - var/t_his = p_their() - var/t_him = p_them() - var/t_has = p_have() - var/t_is = p_are() - var/obscure_name - - if(isliving(user)) - var/mob/living/L = user - if(HAS_TRAIT(L, TRAIT_PROSOPAGNOSIA)) - obscure_name = TRUE - - . = list("*---------*\nThis is [!obscure_name ? name : "Unknown"]!") - - var/vampDesc = ReturnVampExamine(user) // Vamps recognize the names of other vamps. - var/vassDesc = ReturnVassalExamine(user) // Vassals recognize each other's marks. - if (vampDesc != "") // If we don't do it this way, we add a blank space to the string...something to do with this --> . += "" - . += vampDesc - if (vassDesc != "") - . += vassDesc - - var/list/obscured = check_obscured_slots() - var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE)) - - if(ishuman(src)) //user just returned, y'know, the user's own species. dumb. - var/mob/living/carbon/human/H = src - var/datum/species/pref_species = H.dna.species - if(get_visible_name() == "Unknown") // same as flavor text, but hey it works. - . += "You can't make out what species they are." - else if(skipface) - . += "You can't make out what species they are." - else - . += "[t_He] [t_is] a [H.dna.custom_species ? H.dna.custom_species : pref_species.name]!" - - //uniform - if(w_uniform && !(SLOT_W_UNIFORM in obscured)) - //accessory - var/accessory_msg - if(istype(w_uniform, /obj/item/clothing/under)) - var/obj/item/clothing/under/U = w_uniform - if(U.attached_accessory) - accessory_msg += " with [icon2html(U.attached_accessory, user)] \a [U.attached_accessory]" - - . += "[t_He] [t_is] wearing [w_uniform.get_examine_string(user)][accessory_msg]." - //head - if(head) - . += "[t_He] [t_is] wearing [head.get_examine_string(user)] on [t_his] head." - //suit/armor - if(wear_suit) - . += "[t_He] [t_is] wearing [wear_suit.get_examine_string(user)]." - //suit/armor storage - if(s_store && !(SLOT_S_STORE in obscured)) - . += "[t_He] [t_is] carrying [s_store.get_examine_string(user)] on [t_his] [wear_suit.name]." - //back - if(back) - . += "[t_He] [t_has] [back.get_examine_string(user)] on [t_his] back." - - //Hands - for(var/obj/item/I in held_items) - if(!(I.item_flags & ABSTRACT)) - . += "[t_He] [t_is] holding [I.get_examine_string(user)] in [t_his] [get_held_index_name(get_held_index_of_item(I))]." - - //gloves - if(gloves && !(SLOT_GLOVES in obscured)) - . += "[t_He] [t_has] [gloves.get_examine_string(user)] on [t_his] hands." - else if(length(blood_DNA)) - var/hand_number = get_num_arms(FALSE) - if(hand_number) - . += "[t_He] [t_has] [hand_number > 1 ? "" : "a"] blood-stained hand[hand_number > 1 ? "s" : ""]!" - - //handcuffed? - if(handcuffed) - if(istype(handcuffed, /obj/item/restraints/handcuffs/cable)) - . += "[t_He] [t_is] [icon2html(handcuffed, user)] restrained with cable!" - else - . += "[t_He] [t_is] [icon2html(handcuffed, user)] handcuffed!" - - //belt - if(belt) - . += "[t_He] [t_has] [belt.get_examine_string(user)] about [t_his] waist." - - //shoes - if(shoes && !(SLOT_SHOES in obscured)) - . += "[t_He] [t_is] wearing [shoes.get_examine_string(user)] on [t_his] feet." - - //mask - if(wear_mask && !(SLOT_WEAR_MASK in obscured)) - . += "[t_He] [t_has] [wear_mask.get_examine_string(user)] on [t_his] face." - - if(wear_neck && !(SLOT_NECK in obscured)) - . += "[t_He] [t_is] wearing [wear_neck.get_examine_string(user)] around [t_his] neck." - - //eyes - if(!(SLOT_GLASSES in obscured)) - if(glasses) - . += "[t_He] [t_has] [glasses.get_examine_string(user)] covering [t_his] eyes." - else if(eye_color == BLOODCULT_EYE && iscultist(src) && HAS_TRAIT(src, TRAIT_CULT_EYES)) - . += "[t_His] eyes are glowing an unnatural red!" - - //ears - if(ears && !(SLOT_EARS in obscured)) - . += "[t_He] [t_has] [ears.get_examine_string(user)] on [t_his] ears." - - //ID - if(wear_id) - . += "[t_He] [t_is] wearing [wear_id.get_examine_string(user)]." - - //Status effects - var/effects_exam = status_effect_examines() - if(!isnull(effects_exam)) - . += effects_exam - - //CIT CHANGES START HERE - adds genital details to examine text - if(LAZYLEN(internal_organs)) - for(var/obj/item/organ/genital/dicc in internal_organs) - if(istype(dicc) && dicc.is_exposed()) - . += "[dicc.desc]" - - var/cursed_stuff = attempt_vr(src,"examine_bellies",args) //vore Code - if(cursed_stuff) - . += cursed_stuff -//END OF CIT CHANGES - - //Jitters - switch(jitteriness) - if(300 to INFINITY) - . += "[t_He] [t_is] convulsing violently!" - if(200 to 300) - . += "[t_He] [t_is] extremely jittery." - if(100 to 200) - . += "[t_He] [t_is] twitching ever so slightly." - - var/appears_dead = 0 - if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH))) - appears_dead = 1 - if(suiciding) - . += "[t_He] appear[p_s()] to have committed suicide... there is no hope of recovery." - if(hellbound) - . += "[t_His] soul seems to have been ripped out of [t_his] body. Revival is impossible." - if(getorgan(/obj/item/organ/brain) && !key && !get_ghost(FALSE, TRUE)) - . += "[t_He] [t_is] limp and unresponsive; there are no signs of life and [t_his] soul has departed..." - else - . += "[t_He] [t_is] limp and unresponsive; there are no signs of life..." - - if(get_bodypart(BODY_ZONE_HEAD) && !getorgan(/obj/item/organ/brain)) - . += "It appears that [t_his] brain is missing..." - - var/temp = getBruteLoss() //no need to calculate each of these twice - - var/list/msg = list() - - var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - var/list/disabled = list() - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - if(BP.disabled) - disabled += BP - missing -= BP.body_zone - for(var/obj/item/I in BP.embedded_objects) - msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n" - - for(var/X in disabled) - var/obj/item/bodypart/BP = X - var/damage_text - if(!(BP.get_damage(include_stamina = FALSE) >= BP.max_damage)) //Stamina is disabling the limb - damage_text = "limp and lifeless" - else - damage_text = (BP.brute_dam >= BP.burn_dam) ? BP.heavy_brute_msg : BP.heavy_burn_msg - msg += "[capitalize(t_his)] [BP.name] is [damage_text]!\n" - - //stores missing limbs - var/l_limbs_missing = 0 - var/r_limbs_missing = 0 - for(var/t in missing) - if(t==BODY_ZONE_HEAD) - msg += "[t_His] [parse_zone(t)] is missing!\n" - continue - if(t == BODY_ZONE_L_ARM || t == BODY_ZONE_L_LEG) - l_limbs_missing++ - else if(t == BODY_ZONE_R_ARM || t == BODY_ZONE_R_LEG) - r_limbs_missing++ - - msg += "[capitalize(t_his)] [parse_zone(t)] is missing!\n" - - if(l_limbs_missing >= 2 && r_limbs_missing == 0) - msg += "[t_He] look[p_s()] all right now.\n" - else if(l_limbs_missing == 0 && r_limbs_missing >= 2) - msg += "[t_He] really keeps to the left.\n" - else if(l_limbs_missing >= 2 && r_limbs_missing >= 2) - msg += "[t_He] [p_do()]n't seem all there.\n" - - if(!(user == src && src.hal_screwyhud == SCREWYHUD_HEALTHY)) //fake healthy - if(temp) - if(temp < 25) - msg += "[t_He] [t_has] minor bruising.\n" - else if(temp < 50) - msg += "[t_He] [t_has] moderate bruising!\n" - else - msg += "[t_He] [t_has] severe bruising!\n" - - temp = getFireLoss() - if(temp) - if(temp < 25) - msg += "[t_He] [t_has] minor burns.\n" - else if (temp < 50) - msg += "[t_He] [t_has] moderate burns!\n" - else - msg += "[t_He] [t_has] severe burns!\n" - - temp = getCloneLoss() - if(temp) - if(temp < 25) - msg += "[t_He] [t_has] minor cellular damage.\n" - else if(temp < 50) - msg += "[t_He] [t_has] moderate cellular damage!\n" - else - msg += "[t_He] [t_has] severe cellular damage!\n" - - - if(fire_stacks > 0) - msg += "[t_He] [t_is] covered in something flammable.\n" - if(fire_stacks < 0) - msg += "[t_He] look[p_s()] a little soaked.\n" - - - if(pulledby && pulledby.grab_state) - msg += "[t_He] [t_is] restrained by [pulledby]'s grip.\n" - - if(nutrition < NUTRITION_LEVEL_STARVING - 50) - msg += "[t_He] [t_is] severely malnourished.\n" - else if(nutrition >= NUTRITION_LEVEL_FAT) - if(user.nutrition < NUTRITION_LEVEL_STARVING - 50) - msg += "[t_He] [t_is] plump and delicious looking - Like a fat little piggy. A tasty piggy.\n" - else - msg += "[t_He] [t_is] quite chubby.\n" - switch(disgust) - if(DISGUST_LEVEL_GROSS to DISGUST_LEVEL_VERYGROSS) - msg += "[t_He] look[p_s()] a bit grossed out.\n" - if(DISGUST_LEVEL_VERYGROSS to DISGUST_LEVEL_DISGUSTED) - msg += "[t_He] look[p_s()] really grossed out.\n" - if(DISGUST_LEVEL_DISGUSTED to INFINITY) - msg += "[t_He] look[p_s()] extremely disgusted.\n" - - if(ShowAsPaleExamine()) - msg += "[t_He] [t_has] pale skin.\n" - - if(bleedsuppress) - msg += "[t_He] [t_is] bandaged with something.\n" - else if(bleed_rate) - if(bleed_rate >= 8) //8 is the rate at which heparin causes you to bleed - msg += "[t_He] [t_is] bleeding uncontrollably!\n" - else - msg += "[t_He] [t_is] bleeding!\n" - - if(reagents.has_reagent(/datum/reagent/teslium)) - msg += "[t_He] [t_is] emitting a gentle blue glow!\n" - - if(islist(stun_absorption)) - for(var/i in stun_absorption) - if(stun_absorption[i]["end_time"] > world.time && stun_absorption[i]["examine_message"]) - msg += "[t_He] [t_is][stun_absorption[i]["examine_message"]]\n" - - if(drunkenness && !skipface && !appears_dead) //Drunkenness - switch(drunkenness) - if(11 to 21) - msg += "[t_He] [t_is] slightly flushed.\n" - if(21.01 to 41) //.01s are used in case drunkenness ends up to be a small decimal - msg += "[t_He] [t_is] flushed.\n" - if(41.01 to 51) - msg += "[t_He] [t_is] quite flushed and [t_his] breath smells of alcohol.\n" - if(51.01 to 61) - msg += "[t_He] [t_is] very flushed and [t_his] movements jerky, with breath reeking of alcohol.\n" - if(61.01 to 91) - msg += "[t_He] look[p_s()] like a drunken mess.\n" - if(91.01 to INFINITY) - msg += "[t_He] [t_is] a shitfaced, slobbering wreck.\n" - - if(reagents.has_reagent(/datum/reagent/fermi/astral)) - if(mind) - msg += "[t_He] has wild, spacey eyes and they have a strange, abnormal look to them.\n" - else - msg += "[t_He] has wild, spacey eyes and they don't look like they're all there.\n" - - if(isliving(user)) - var/mob/living/L = user - if(src != user && HAS_TRAIT(L, TRAIT_EMPATH) && !appears_dead) - if (a_intent != INTENT_HELP) - msg += "[t_He] seem[p_s()] to be on guard.\n" - if (getOxyLoss() >= 10) - msg += "[t_He] seem[p_s()] winded.\n" - if (getToxLoss() >= 10) - msg += "[t_He] seem[p_s()] sickly.\n" - var/datum/component/mood/mood = GetComponent(/datum/component/mood) - if(mood.sanity <= SANITY_DISTURBED) - msg += "[t_He] seem[p_s()] distressed.\n" - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "empath", /datum/mood_event/sad_empath, src) - if(mood.shown_mood >= 6) //So roundstart people aren't all "happy" and that antags don't show their true happiness. - msg += "[t_He] seem[p_s()] to have had something nice happen to them recently.\n" - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "empathH", /datum/mood_event/happy_empath, src) - if (HAS_TRAIT(src, TRAIT_BLIND)) - msg += "[t_He] appear[p_s()] to be staring off into space.\n" - if (HAS_TRAIT(src, TRAIT_DEAF)) - msg += "[t_He] appear[p_s()] to not be responding to noises.\n" - - var/obj/item/organ/vocal_cords/Vc = user.getorganslot(ORGAN_SLOT_VOICE) - if(Vc) - if(istype(Vc, /obj/item/organ/vocal_cords/velvet)) - if(client.prefs.cit_toggles & HYPNO) - msg += "You feel your chords resonate looking at them.\n" - - - if(!appears_dead) - if(stat == UNCONSCIOUS) - msg += "[t_He] [t_is]n't responding to anything around [t_him] and seem[p_s()] to be asleep.\n" - else - if(HAS_TRAIT(src, TRAIT_DUMB)) - msg += "[t_He] [t_has] a stupid expression on [t_his] face.\n" - if(InCritical()) - msg += "[t_He] [t_is] barely conscious.\n" - if(getorgan(/obj/item/organ/brain)) - if(!key) - msg += "[t_He] [t_is] totally catatonic. The stresses of life in deep-space must have been too much for [t_him]. Any recovery is unlikely.\n" - else if(!client) - msg += "[t_He] [t_has] a blank, absent-minded stare and appears completely unresponsive to anything. [t_He] may snap out of it soon.\n" - - if(digitalcamo) - msg += "[t_He] [t_is] moving [t_his] body in an unnatural and blatantly inhuman manner.\n" - - - if (length(msg)) - . += "[msg.Join("")]" - - var/trait_exam = common_trait_examine() - if (!isnull(trait_exam)) - . += trait_exam - - var/traitstring = get_trait_string() - if(ishuman(user)) - var/mob/living/carbon/human/H = user - var/obj/item/organ/cyberimp/eyes/hud/CIH = H.getorgan(/obj/item/organ/cyberimp/eyes/hud) - if(istype(H.glasses, /obj/item/clothing/glasses/hud) || CIH) - var/perpname = get_face_name(get_id_name("")) - if(perpname) - var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.general) - if(R) - . += "Rank: [R.fields["rank"]]\n\[Front photo\]\[Side photo\]" - if(istype(H.glasses, /obj/item/clothing/glasses/hud/health) || istype(CIH, /obj/item/organ/cyberimp/eyes/hud/medical)) - var/cyberimp_detect - for(var/obj/item/organ/cyberimp/CI in internal_organs) - if(CI.status == ORGAN_ROBOTIC && !CI.syndicate_implant) - cyberimp_detect += "[name] is modified with a [CI.name]." - if(cyberimp_detect) - . += "Detected cybernetic modifications:" - . += cyberimp_detect - if(R) - var/health_r = R.fields["p_stat"] - . += "\[[health_r]\]" - health_r = R.fields["m_stat"] - . += "\[[health_r]\]" - R = find_record("name", perpname, GLOB.data_core.medical) - if(R) - . += "\[Medical evaluation\]" - if(traitstring) - . += "Detected physiological traits:\n[traitstring]" - - - - if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(CIH, /obj/item/organ/cyberimp/eyes/hud/security)) - if(!user.stat && user != src) - //|| !user.canmove || user.restrained()) Fluff: Sechuds have eye-tracking technology and sets 'arrest' to people that the wearer looks and blinks at. - var/criminal = "None" - - R = find_record("name", perpname, GLOB.data_core.security) - if(R) - criminal = R.fields["criminal"] - - . += jointext(list("Criminal status: \[[criminal]\]", - "Security record: \[View\]", - "\[Add crime\]", - "\[View comment log\]", - "\[Add comment\]"), "") - else if(isobserver(user) && traitstring) - . += "Traits: [traitstring]" - - if(print_flavor_text()) - if(get_visible_name() == "Unknown") //Are we sure we know who this is? Don't show flavor text unless we can recognize them. Prevents certain metagaming with impersonation. - . += "...?" - else if(skipface) //Sometimes we're not unknown, but impersonating someone in a hardsuit, let's not reveal our flavor text then either. - . += "...?" - else - . += "[print_flavor_text()]" - if(print_flavor_text_2()) - if(get_visible_name() == "Unknown") //Are we sure we know who this is? Don't show flavor text unless we can recognize them. Prevents certain metagaming with impersonation. - . += "...?" - else if(skipface) //Sometimes we're not unknown, but impersonating someone in a hardsuit, let's not reveal our flavor text then either. - . += "...?" - else - . += "[print_flavor_text_2()]" - . += "*---------*" - -/mob/living/proc/status_effect_examines(pronoun_replacement) //You can include this in any mob's examine() to show the examine texts of status effects! - var/list/dat = list() - if(!pronoun_replacement) - pronoun_replacement = p_they(TRUE) - for(var/V in status_effects) - var/datum/status_effect/E = V - if(E.examine_text) - var/new_text = replacetext(E.examine_text, "SUBJECTPRONOUN", pronoun_replacement) - new_text = replacetext(new_text, "[pronoun_replacement] is", "[pronoun_replacement] [p_are()]") //To make sure something become "They are" or "She is", not "They are" and "She are" - dat += "[new_text]\n" //dat.Join("\n") doesn't work here, for some reason - if(dat.len) - return dat.Join() +/mob/living/carbon/human/examine(mob/user) +//this is very slightly better than it was because you can use it more places. still can't do \his[src] though. + var/t_He = p_they(TRUE) + var/t_His = p_their(TRUE) + var/t_his = p_their() + var/t_him = p_them() + var/t_has = p_have() + var/t_is = p_are() + var/obscure_name + + if(isliving(user)) + var/mob/living/L = user + if(HAS_TRAIT(L, TRAIT_PROSOPAGNOSIA)) + obscure_name = TRUE + + . = list("*---------*\nThis is [!obscure_name ? name : "Unknown"]!") + + var/vampDesc = ReturnVampExamine(user) // Vamps recognize the names of other vamps. + var/vassDesc = ReturnVassalExamine(user) // Vassals recognize each other's marks. + if (vampDesc != "") // If we don't do it this way, we add a blank space to the string...something to do with this --> . += "" + . += vampDesc + if (vassDesc != "") + . += vassDesc + + var/list/obscured = check_obscured_slots() + var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE)) + + if(ishuman(src)) //user just returned, y'know, the user's own species. dumb. + var/mob/living/carbon/human/H = src + var/datum/species/pref_species = H.dna.species + if(get_visible_name() == "Unknown") // same as flavor text, but hey it works. + . += "You can't make out what species they are." + else if(skipface) + . += "You can't make out what species they are." + else + . += "[t_He] [t_is] a [H.dna.custom_species ? H.dna.custom_species : pref_species.name]!" + + //uniform + if(w_uniform && !(SLOT_W_UNIFORM in obscured)) + //accessory + var/accessory_msg + if(istype(w_uniform, /obj/item/clothing/under)) + var/obj/item/clothing/under/U = w_uniform + if(U.attached_accessory) + accessory_msg += " with [icon2html(U.attached_accessory, user)] \a [U.attached_accessory]" + + . += "[t_He] [t_is] wearing [w_uniform.get_examine_string(user)][accessory_msg]." + //head + if(head) + . += "[t_He] [t_is] wearing [head.get_examine_string(user)] on [t_his] head." + //suit/armor + if(wear_suit) + . += "[t_He] [t_is] wearing [wear_suit.get_examine_string(user)]." + //suit/armor storage + if(s_store && !(SLOT_S_STORE in obscured)) + . += "[t_He] [t_is] carrying [s_store.get_examine_string(user)] on [t_his] [wear_suit.name]." + //back + if(back) + . += "[t_He] [t_has] [back.get_examine_string(user)] on [t_his] back." + + //Hands + for(var/obj/item/I in held_items) + if(!(I.item_flags & ABSTRACT)) + . += "[t_He] [t_is] holding [I.get_examine_string(user)] in [t_his] [get_held_index_name(get_held_index_of_item(I))]." + + //gloves + if(gloves && !(SLOT_GLOVES in obscured)) + . += "[t_He] [t_has] [gloves.get_examine_string(user)] on [t_his] hands." + else if(length(blood_DNA)) + var/hand_number = get_num_arms(FALSE) + if(hand_number) + . += "[t_He] [t_has] [hand_number > 1 ? "" : "a"] blood-stained hand[hand_number > 1 ? "s" : ""]!" + + //handcuffed? + if(handcuffed) + if(istype(handcuffed, /obj/item/restraints/handcuffs/cable)) + . += "[t_He] [t_is] [icon2html(handcuffed, user)] restrained with cable!" + else + . += "[t_He] [t_is] [icon2html(handcuffed, user)] handcuffed!" + + //belt + if(belt) + . += "[t_He] [t_has] [belt.get_examine_string(user)] about [t_his] waist." + + //shoes + if(shoes && !(SLOT_SHOES in obscured)) + . += "[t_He] [t_is] wearing [shoes.get_examine_string(user)] on [t_his] feet." + + //mask + if(wear_mask && !(SLOT_WEAR_MASK in obscured)) + . += "[t_He] [t_has] [wear_mask.get_examine_string(user)] on [t_his] face." + + if(wear_neck && !(SLOT_NECK in obscured)) + . += "[t_He] [t_is] wearing [wear_neck.get_examine_string(user)] around [t_his] neck." + + //eyes + if(!(SLOT_GLASSES in obscured)) + if(glasses) + . += "[t_He] [t_has] [glasses.get_examine_string(user)] covering [t_his] eyes." + else if(eye_color == BLOODCULT_EYE && iscultist(src) && HAS_TRAIT(src, TRAIT_CULT_EYES)) + . += "[t_His] eyes are glowing an unnatural red!" + + //ears + if(ears && !(SLOT_EARS in obscured)) + . += "[t_He] [t_has] [ears.get_examine_string(user)] on [t_his] ears." + + //ID + if(wear_id) + . += "[t_He] [t_is] wearing [wear_id.get_examine_string(user)]." + + //Status effects + var/effects_exam = status_effect_examines() + if(!isnull(effects_exam)) + . += effects_exam + + //CIT CHANGES START HERE - adds genital details to examine text + if(LAZYLEN(internal_organs)) + for(var/obj/item/organ/genital/dicc in internal_organs) + if(istype(dicc) && dicc.is_exposed()) + . += "[dicc.desc]" + + var/cursed_stuff = attempt_vr(src,"examine_bellies",args) //vore Code + if(cursed_stuff) + . += cursed_stuff +//END OF CIT CHANGES + + //Jitters + switch(jitteriness) + if(300 to INFINITY) + . += "[t_He] [t_is] convulsing violently!" + if(200 to 300) + . += "[t_He] [t_is] extremely jittery." + if(100 to 200) + . += "[t_He] [t_is] twitching ever so slightly." + + var/appears_dead = 0 + if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH))) + appears_dead = 1 + if(suiciding) + . += "[t_He] appear[p_s()] to have committed suicide... there is no hope of recovery." + if(hellbound) + . += "[t_His] soul seems to have been ripped out of [t_his] body. Revival is impossible." + if(getorgan(/obj/item/organ/brain) && !key && !get_ghost(FALSE, TRUE)) + . += "[t_He] [t_is] limp and unresponsive; there are no signs of life and [t_his] soul has departed..." + else + . += "[t_He] [t_is] limp and unresponsive; there are no signs of life..." + + if(get_bodypart(BODY_ZONE_HEAD) && !getorgan(/obj/item/organ/brain)) + . += "It appears that [t_his] brain is missing..." + + var/temp = getBruteLoss() //no need to calculate each of these twice + + var/list/msg = list() + + var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + var/list/disabled = list() + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + if(BP.disabled) + disabled += BP + missing -= BP.body_zone + for(var/obj/item/I in BP.embedded_objects) + msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n" + + for(var/X in disabled) + var/obj/item/bodypart/BP = X + var/damage_text + if(!(BP.get_damage(include_stamina = FALSE) >= BP.max_damage)) //Stamina is disabling the limb + damage_text = "limp and lifeless" + else + damage_text = (BP.brute_dam >= BP.burn_dam) ? BP.heavy_brute_msg : BP.heavy_burn_msg + msg += "[capitalize(t_his)] [BP.name] is [damage_text]!\n" + + //stores missing limbs + var/l_limbs_missing = 0 + var/r_limbs_missing = 0 + for(var/t in missing) + if(t==BODY_ZONE_HEAD) + msg += "[t_His] [parse_zone(t)] is missing!\n" + continue + if(t == BODY_ZONE_L_ARM || t == BODY_ZONE_L_LEG) + l_limbs_missing++ + else if(t == BODY_ZONE_R_ARM || t == BODY_ZONE_R_LEG) + r_limbs_missing++ + + msg += "[capitalize(t_his)] [parse_zone(t)] is missing!\n" + + if(l_limbs_missing >= 2 && r_limbs_missing == 0) + msg += "[t_He] look[p_s()] all right now.\n" + else if(l_limbs_missing == 0 && r_limbs_missing >= 2) + msg += "[t_He] really keeps to the left.\n" + else if(l_limbs_missing >= 2 && r_limbs_missing >= 2) + msg += "[t_He] [p_do()]n't seem all there.\n" + + if(!(user == src && src.hal_screwyhud == SCREWYHUD_HEALTHY)) //fake healthy + if(temp) + if(temp < 25) + msg += "[t_He] [t_has] minor bruising.\n" + else if(temp < 50) + msg += "[t_He] [t_has] moderate bruising!\n" + else + msg += "[t_He] [t_has] severe bruising!\n" + + temp = getFireLoss() + if(temp) + if(temp < 25) + msg += "[t_He] [t_has] minor burns.\n" + else if (temp < 50) + msg += "[t_He] [t_has] moderate burns!\n" + else + msg += "[t_He] [t_has] severe burns!\n" + + temp = getCloneLoss() + if(temp) + if(temp < 25) + msg += "[t_He] [t_has] minor cellular damage.\n" + else if(temp < 50) + msg += "[t_He] [t_has] moderate cellular damage!\n" + else + msg += "[t_He] [t_has] severe cellular damage!\n" + + + if(fire_stacks > 0) + msg += "[t_He] [t_is] covered in something flammable.\n" + if(fire_stacks < 0) + msg += "[t_He] look[p_s()] a little soaked.\n" + + + if(pulledby && pulledby.grab_state) + msg += "[t_He] [t_is] restrained by [pulledby]'s grip.\n" + + if(nutrition < NUTRITION_LEVEL_STARVING - 50) + msg += "[t_He] [t_is] severely malnourished.\n" + else if(nutrition >= NUTRITION_LEVEL_FAT) + if(user.nutrition < NUTRITION_LEVEL_STARVING - 50) + msg += "[t_He] [t_is] plump and delicious looking - Like a fat little piggy. A tasty piggy.\n" + else + msg += "[t_He] [t_is] quite chubby.\n" + switch(disgust) + if(DISGUST_LEVEL_GROSS to DISGUST_LEVEL_VERYGROSS) + msg += "[t_He] look[p_s()] a bit grossed out.\n" + if(DISGUST_LEVEL_VERYGROSS to DISGUST_LEVEL_DISGUSTED) + msg += "[t_He] look[p_s()] really grossed out.\n" + if(DISGUST_LEVEL_DISGUSTED to INFINITY) + msg += "[t_He] look[p_s()] extremely disgusted.\n" + + if(ShowAsPaleExamine()) + msg += "[t_He] [t_has] pale skin.\n" + + if(bleedsuppress) + msg += "[t_He] [t_is] bandaged with something.\n" + else if(bleed_rate) + if(bleed_rate >= 8) //8 is the rate at which heparin causes you to bleed + msg += "[t_He] [t_is] bleeding uncontrollably!\n" + else + msg += "[t_He] [t_is] bleeding!\n" + + if(reagents.has_reagent(/datum/reagent/teslium)) + msg += "[t_He] [t_is] emitting a gentle blue glow!\n" + + if(islist(stun_absorption)) + for(var/i in stun_absorption) + if(stun_absorption[i]["end_time"] > world.time && stun_absorption[i]["examine_message"]) + msg += "[t_He] [t_is][stun_absorption[i]["examine_message"]]\n" + + if(drunkenness && !skipface && !appears_dead) //Drunkenness + switch(drunkenness) + if(11 to 21) + msg += "[t_He] [t_is] slightly flushed.\n" + if(21.01 to 41) //.01s are used in case drunkenness ends up to be a small decimal + msg += "[t_He] [t_is] flushed.\n" + if(41.01 to 51) + msg += "[t_He] [t_is] quite flushed and [t_his] breath smells of alcohol.\n" + if(51.01 to 61) + msg += "[t_He] [t_is] very flushed and [t_his] movements jerky, with breath reeking of alcohol.\n" + if(61.01 to 91) + msg += "[t_He] look[p_s()] like a drunken mess.\n" + if(91.01 to INFINITY) + msg += "[t_He] [t_is] a shitfaced, slobbering wreck.\n" + + if(reagents.has_reagent(/datum/reagent/fermi/astral)) + if(mind) + msg += "[t_He] has wild, spacey eyes and they have a strange, abnormal look to them.\n" + else + msg += "[t_He] has wild, spacey eyes and they don't look like they're all there.\n" + + if(isliving(user)) + var/mob/living/L = user + if(src != user && HAS_TRAIT(L, TRAIT_EMPATH) && !appears_dead) + if (a_intent != INTENT_HELP) + msg += "[t_He] seem[p_s()] to be on guard.\n" + if (getOxyLoss() >= 10) + msg += "[t_He] seem[p_s()] winded.\n" + if (getToxLoss() >= 10) + msg += "[t_He] seem[p_s()] sickly.\n" + var/datum/component/mood/mood = GetComponent(/datum/component/mood) + if(mood.sanity <= SANITY_DISTURBED) + msg += "[t_He] seem[p_s()] distressed.\n" + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "empath", /datum/mood_event/sad_empath, src) + if(mood.shown_mood >= 6) //So roundstart people aren't all "happy" and that antags don't show their true happiness. + msg += "[t_He] seem[p_s()] to have had something nice happen to them recently.\n" + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "empathH", /datum/mood_event/happy_empath, src) + if (HAS_TRAIT(src, TRAIT_BLIND)) + msg += "[t_He] appear[p_s()] to be staring off into space.\n" + if (HAS_TRAIT(src, TRAIT_DEAF)) + msg += "[t_He] appear[p_s()] to not be responding to noises.\n" + + var/obj/item/organ/vocal_cords/Vc = user.getorganslot(ORGAN_SLOT_VOICE) + if(Vc) + if(istype(Vc, /obj/item/organ/vocal_cords/velvet)) + if(client.prefs.cit_toggles & HYPNO) + msg += "You feel your chords resonate looking at them.\n" + + + if(!appears_dead) + if(stat == UNCONSCIOUS) + msg += "[t_He] [t_is]n't responding to anything around [t_him] and seem[p_s()] to be asleep.\n" + else + if(HAS_TRAIT(src, TRAIT_DUMB)) + msg += "[t_He] [t_has] a stupid expression on [t_his] face.\n" + if(InCritical()) + msg += "[t_He] [t_is] barely conscious.\n" + if(getorgan(/obj/item/organ/brain)) + if(!key) + msg += "[t_He] [t_is] totally catatonic. The stresses of life in deep-space must have been too much for [t_him]. Any recovery is unlikely.\n" + else if(!client) + msg += "[t_He] [t_has] a blank, absent-minded stare and appears completely unresponsive to anything. [t_He] may snap out of it soon.\n" + + if(digitalcamo) + msg += "[t_He] [t_is] moving [t_his] body in an unnatural and blatantly inhuman manner.\n" + + + if (length(msg)) + . += "[msg.Join("")]" + + var/trait_exam = common_trait_examine() + if (!isnull(trait_exam)) + . += trait_exam + + var/traitstring = get_trait_string() + if(ishuman(user)) + var/mob/living/carbon/human/H = user + var/obj/item/organ/cyberimp/eyes/hud/CIH = H.getorgan(/obj/item/organ/cyberimp/eyes/hud) + if(istype(H.glasses, /obj/item/clothing/glasses/hud) || CIH) + var/perpname = get_face_name(get_id_name("")) + if(perpname) + var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.general) + if(R) + . += "Rank: [R.fields["rank"]]\n\[Front photo\]\[Side photo\]" + if(istype(H.glasses, /obj/item/clothing/glasses/hud/health) || istype(CIH, /obj/item/organ/cyberimp/eyes/hud/medical)) + var/cyberimp_detect + for(var/obj/item/organ/cyberimp/CI in internal_organs) + if(CI.status == ORGAN_ROBOTIC && !CI.syndicate_implant) + cyberimp_detect += "[name] is modified with a [CI.name]." + if(cyberimp_detect) + . += "Detected cybernetic modifications:" + . += cyberimp_detect + if(R) + var/health_r = R.fields["p_stat"] + . += "\[[health_r]\]" + health_r = R.fields["m_stat"] + . += "\[[health_r]\]" + R = find_record("name", perpname, GLOB.data_core.medical) + if(R) + . += "\[Medical evaluation\]" + if(traitstring) + . += "Detected physiological traits:\n[traitstring]" + + + + if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(CIH, /obj/item/organ/cyberimp/eyes/hud/security)) + if(!user.stat && user != src) + //|| !user.canmove || user.restrained()) Fluff: Sechuds have eye-tracking technology and sets 'arrest' to people that the wearer looks and blinks at. + var/criminal = "None" + + R = find_record("name", perpname, GLOB.data_core.security) + if(R) + criminal = R.fields["criminal"] + + . += jointext(list("Criminal status: \[[criminal]\]", + "Security record: \[View\]", + "\[Add crime\]", + "\[View comment log\]", + "\[Add comment\]"), "") + else if(isobserver(user) && traitstring) + . += "Traits: [traitstring]" + + if(print_flavor_text()) + if(get_visible_name() == "Unknown") //Are we sure we know who this is? Don't show flavor text unless we can recognize them. Prevents certain metagaming with impersonation. + . += "...?" + else if(skipface) //Sometimes we're not unknown, but impersonating someone in a hardsuit, let's not reveal our flavor text then either. + . += "...?" + else + . += "[print_flavor_text()]" + if(print_flavor_text_2()) + if(get_visible_name() == "Unknown") //Are we sure we know who this is? Don't show flavor text unless we can recognize them. Prevents certain metagaming with impersonation. + . += "...?" + else if(skipface) //Sometimes we're not unknown, but impersonating someone in a hardsuit, let's not reveal our flavor text then either. + . += "...?" + else + . += "[print_flavor_text_2()]" + . += "*---------*" + +/mob/living/proc/status_effect_examines(pronoun_replacement) //You can include this in any mob's examine() to show the examine texts of status effects! + var/list/dat = list() + if(!pronoun_replacement) + pronoun_replacement = p_they(TRUE) + for(var/V in status_effects) + var/datum/status_effect/E = V + if(E.examine_text) + var/new_text = replacetext(E.examine_text, "SUBJECTPRONOUN", pronoun_replacement) + new_text = replacetext(new_text, "[pronoun_replacement] is", "[pronoun_replacement] [p_are()]") //To make sure something become "They are" or "She is", not "They are" and "She are" + dat += "[new_text]\n" //dat.Join("\n") doesn't work here, for some reason + if(dat.len) + return dat.Join() diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 0007233975..0f3b3dc6d3 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1,1140 +1,1140 @@ -/mob/living/carbon/human - name = "Unknown" - real_name = "Unknown" - icon = 'icons/mob/human.dmi' - icon_state = "caucasian_m" - appearance_flags = KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE - -/mob/living/carbon/human/Initialize() - verbs += /mob/living/proc/mob_sleep - verbs += /mob/living/proc/lay_down - verbs += /mob/living/carbon/human/proc/underwear_toggle //fwee - - //initialize limbs first - create_bodyparts() - - //initialize dna. for spawned humans; overwritten by other code - create_dna(src) - randomize_human(src) - dna.initialize_dna() - - if(dna.species) - set_species(dna.species.type) - - //initialise organs - create_internal_organs() //most of it is done in set_species now, this is only for parent call - physiology = new() - - handcrafting = new() - - . = ..() - - if(CONFIG_GET(flag/disable_stambuffer)) - togglesprint() - - RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.proc/clean_blood) - - -/mob/living/carbon/human/ComponentInitialize() - . = ..() - if(!CONFIG_GET(flag/disable_human_mood)) - AddComponent(/datum/component/mood) - -/mob/living/carbon/human/Destroy() - QDEL_NULL(physiology) - QDEL_NULL_LIST(vore_organs) // CITADEL EDIT belly stuff - return ..() - - -/mob/living/carbon/human/OpenCraftingMenu() - handcrafting.ui_interact(src) - -/mob/living/carbon/human/prepare_data_huds() - //Update med hud images... - ..() - //...sec hud images... - sec_hud_set_ID() - sec_hud_set_implants() - sec_hud_set_security_status() - //...and display them. - add_to_all_human_data_huds() - -/mob/living/carbon/human/Stat() - ..() - - if(statpanel("Status")) - stat(null, "Intent: [a_intent]") - stat(null, "Move Mode: [m_intent]") - if (internal) - if (!internal.air_contents) - qdel(internal) - else - stat("Internal Atmosphere Info", internal.name) - stat("Tank Pressure", internal.air_contents.return_pressure()) - stat("Distribution Pressure", internal.distribute_pressure) - - if(mind) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - stat("Chemical Storage", "[changeling.chem_charges]/[changeling.chem_storage]") - stat("Absorbed DNA", changeling.absorbedcount) - - - //NINJACODE - if(istype(wear_suit, /obj/item/clothing/suit/space/space_ninja)) //Only display if actually a ninja. - var/obj/item/clothing/suit/space/space_ninja/SN = wear_suit - if(statpanel("SpiderOS")) - stat("SpiderOS Status:","[SN.s_initialized ? "Initialized" : "Disabled"]") - stat("Current Time:", "[STATION_TIME_TIMESTAMP("hh:mm:ss")]") - if(SN.s_initialized) - //Suit gear - stat("Energy Charge:", "[round(SN.cell.charge/100)]%") - stat("Smoke Bombs:", "\Roman [SN.s_bombs]") - //Ninja status - stat("Fingerprints:", "[md5(dna.uni_identity)]") - stat("Unique Identity:", "[dna.unique_enzymes]") - stat("Overall Status:", "[stat > 1 ? "dead" : "[health]% healthy"]") - stat("Nutrition Status:", "[nutrition]") - stat("Oxygen Loss:", "[getOxyLoss()]") - stat("Toxin Levels:", "[getToxLoss()]") - stat("Burn Severity:", "[getFireLoss()]") - stat("Brute Trauma:", "[getBruteLoss()]") - stat("Radiation Levels:","[radiation] rad") - stat("Body Temperature:","[bodytemperature-T0C] degrees C ([bodytemperature*1.8-459.67] degrees F)") - - //Diseases - if(diseases.len) - stat("Viruses:", null) - for(var/thing in diseases) - var/datum/disease/D = thing - stat("*", "[D.name], Type: [D.spread_text], Stage: [D.stage]/[D.max_stages], Possible Cure: [D.cure_text]") - - -/mob/living/carbon/human/show_inv(mob/user) - user.set_machine(src) - var/has_breathable_mask = istype(wear_mask, /obj/item/clothing/mask) - var/list/obscured = check_obscured_slots() - var/list/dat = list() - - dat += "" - for(var/i in 1 to held_items.len) - var/obj/item/I = get_item_for_held_index(i) - dat += "" - dat += "" - - dat += "" - - dat += "" - - if(SLOT_WEAR_MASK in obscured) - dat += "" - else - dat += "" - - if(SLOT_NECK in obscured) - dat += "" - else - dat += "" - - if(SLOT_GLASSES in obscured) - dat += "" - else - dat += "" - - if(SLOT_EARS in obscured) - dat += "" - else - dat += "" - - dat += "" - - dat += "" - if(wear_suit) - dat += "" - else - dat += "" - - if(SLOT_SHOES in obscured) - dat += "" - else - dat += "" - - if(SLOT_GLOVES in obscured) - dat += "" - else - dat += "" - - if(SLOT_W_UNIFORM in obscured) - dat += "" - else - dat += "" - - if((w_uniform == null && !(dna && dna.species.nojumpsuit)) || (SLOT_W_UNIFORM in obscured)) - dat += "" - dat += "" - dat += "" - else - dat += "" - dat += "" - dat += "" - - if(handcuffed) - dat += "" - if(legcuffed) - dat += "" - - dat += {"
                [get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Empty"]
                 
                Back:[(back && !(back.item_flags & ABSTRACT)) ? back : "Empty"]" - if(has_breathable_mask && istype(back, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - - dat += "
                 
                Head:[(head && !(head.item_flags & ABSTRACT)) ? head : "Empty"]
                Mask:Obscured
                Mask:[(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Empty"]
                Neck:Obscured
                Neck:[(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "Empty"]
                Eyes:Obscured
                Eyes:[(glasses && !(glasses.item_flags & ABSTRACT)) ? glasses : "Empty"]
                Ears:Obscured
                Ears:[(ears && !(ears.item_flags & ABSTRACT)) ? ears : "Empty"]
                 
                Exosuit:[(wear_suit && !(wear_suit.item_flags & ABSTRACT)) ? wear_suit : "Empty"]
                 ↳Suit Storage:[(s_store && !(s_store.item_flags & ABSTRACT)) ? s_store : "Empty"]" - if(has_breathable_mask && istype(s_store, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - dat += "
                 ↳Suit Storage:
                Shoes:Obscured
                Shoes:[(shoes && !(shoes.item_flags & ABSTRACT)) ? shoes : "Empty"]
                Gloves:Obscured
                Gloves:[(gloves && !(gloves.item_flags & ABSTRACT)) ? gloves : "Empty"]
                Uniform:Obscured
                Uniform:[(w_uniform && !(w_uniform.item_flags & ABSTRACT)) ? w_uniform : "Empty"]
                 ↳Pockets:
                 ↳ID:
                 ↳Belt:
                 ↳Belt:[(belt && !(belt.item_flags & ABSTRACT)) ? belt : "Empty"]" - if(has_breathable_mask && istype(belt, /obj/item/tank)) - dat += " [internal ? "Disable Internals" : "Set Internals"]" - dat += "
                 ↳Pockets:[(l_store && !(l_store.item_flags & ABSTRACT)) ? "Left (Full)" : "Left (Empty)"]" - dat += " [(r_store && !(r_store.item_flags & ABSTRACT)) ? "Right (Full)" : "Right (Empty)"]
                 ↳ID:[(wear_id && !(wear_id.item_flags & ABSTRACT)) ? wear_id : "Empty"]
                Handcuffed: Remove
                Legcuffed
                - Close - "} - - var/datum/browser/popup = new(user, "mob[REF(src)]", "[src]", 440, 510) - popup.set_content(dat.Join()) - popup.open() - -// called when something steps onto a human -// this could be made more general, but for now just handle mulebot -/mob/living/carbon/human/Crossed(atom/movable/AM) - var/mob/living/simple_animal/bot/mulebot/MB = AM - if(istype(MB)) - MB.RunOver(src) - - spreadFire(AM) - -/mob/living/carbon/human/Topic(href, href_list) - if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - if(href_list["embedded_object"]) - var/obj/item/bodypart/L = locate(href_list["embedded_limb"]) in bodyparts - if(!L) - return - var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects - if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore - return - var/time_taken = I.embedding.embedded_unsafe_removal_time/I.w_class //Citadel Change from * to / - usr.visible_message("[usr] attempts to remove [I] from [usr.p_their()] [L.name].","You attempt to remove [I] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)") - if(do_after(usr, time_taken, needhand = 1, target = src)) - remove_embedded_unsafe(L, I, usr) - /* CITADEL EDIT: remove_embedded_unsafe replaces this code - if(!I || !L || I.loc != src || !(I in L.embedded_objects)) - return - L.embedded_objects -= I - L.receive_damage(I.embedding.embedded_unsafe_removal_pain_multiplier*I.w_class)//It hurts to rip it out, get surgery you dingus. - I.forceMove(get_turf(src)) - usr.put_in_hands(I) - usr.emote("scream") - usr.visible_message("[usr] successfully rips [I] out of [usr.p_their()] [L.name]!","You successfully remove [I] from your [L.name].") - if(!has_embedded_objects()) - clear_alert("embeddedobject") - SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded") */ - return - - if(href_list["item"]) - var/slot = text2num(href_list["item"]) - if(slot in check_obscured_slots()) - to_chat(usr, "You can't reach that! Something is covering it.") - return - - if(href_list["pockets"]) - var/pocket_side = href_list["pockets"] - var/pocket_id = (pocket_side == "right" ? SLOT_R_STORE : SLOT_L_STORE) - var/obj/item/pocket_item = (pocket_id == SLOT_R_STORE ? r_store : l_store) - var/obj/item/place_item = usr.get_active_held_item() // Item to place in the pocket, if it's empty - - var/delay_denominator = 1 - if(pocket_item && !(pocket_item.item_flags & ABSTRACT)) - if(HAS_TRAIT(pocket_item, TRAIT_NODROP)) - to_chat(usr, "You try to empty [src]'s [pocket_side] pocket, it seems to be stuck!") - to_chat(usr, "You try to empty [src]'s [pocket_side] pocket.") - else if(place_item && place_item.mob_can_equip(src, usr, pocket_id, 1) && !(place_item.item_flags & ABSTRACT)) - to_chat(usr, "You try to place [place_item] into [src]'s [pocket_side] pocket.") - delay_denominator = 4 - else - return - - if(do_mob(usr, src, POCKET_STRIP_DELAY/delay_denominator, ignorehelditem = TRUE)) //placing an item into the pocket is 4 times faster - if(pocket_item) - if(pocket_item == (pocket_id == SLOT_R_STORE ? r_store : l_store)) //item still in the pocket we search - dropItemToGround(pocket_item) - if(!usr.can_hold_items() || !usr.put_in_hands(pocket_item)) - pocket_item.forceMove(drop_location()) - else - if(place_item) - if(place_item.mob_can_equip(src, usr, pocket_id, FALSE, TRUE)) - usr.temporarilyRemoveItemFromInventory(place_item, TRUE) - equip_to_slot(place_item, pocket_id, TRUE) - //do nothing otherwise - - // Update strip window - if(usr.machine == src && in_range(src, usr)) - show_inv(usr) - else - // Display a warning if the user mocks up - to_chat(src, "You feel your [pocket_side] pocket being fumbled with!") - - ..() //CITADEL CHANGE - removes a tab from behind this ..() so that flavortext can actually be examined - - -///////HUDs/////// - if(href_list["hud"]) - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - var/perpname = get_face_name(get_id_name("")) - if(istype(H.glasses, /obj/item/clothing/glasses/hud) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud)) - var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.general) - if(href_list["photo_front"] || href_list["photo_side"]) - if(R) - if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) - return - var/obj/item/photo/P = null - if(href_list["photo_front"]) - P = R.fields["photo_front"] - else if(href_list["photo_side"]) - P = R.fields["photo_side"] - if(P) - P.show(H) - - if(href_list["hud"] == "m") - if(istype(H.glasses, /obj/item/clothing/glasses/hud/health) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) - if(href_list["p_stat"]) - var/health_status = input(usr, "Specify a new physical status for this person.", "Medical HUD", R.fields["p_stat"]) in list("Active", "Physically Unfit", "*Unconscious*", "*Deceased*", "Cancel") - if(R) - if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/health) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) - return - if(health_status && health_status != "Cancel") - R.fields["p_stat"] = health_status - return - if(href_list["m_stat"]) - var/health_status = input(usr, "Specify a new mental status for this person.", "Medical HUD", R.fields["m_stat"]) in list("Stable", "*Watch*", "*Unstable*", "*Insane*", "Cancel") - if(R) - if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/health) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) - return - if(health_status && health_status != "Cancel") - R.fields["m_stat"] = health_status - return - if(href_list["evaluation"]) - if(!getBruteLoss() && !getFireLoss() && !getOxyLoss() && getToxLoss() < 20) - to_chat(usr, "No external injuries detected.
                ") - return - var/span = "notice" - var/status = "" - if(getBruteLoss()) - to_chat(usr, "Physical trauma analysis:") - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - var/brutedamage = BP.brute_dam - if(brutedamage > 0) - status = "received minor physical injuries." - span = "notice" - if(brutedamage > 20) - status = "been seriously damaged." - span = "danger" - if(brutedamage > 40) - status = "sustained major trauma!" - span = "userdanger" - if(brutedamage) - to_chat(usr, "[BP] appears to have [status]") - if(getFireLoss()) - to_chat(usr, "Analysis of skin burns:") - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - var/burndamage = BP.burn_dam - if(burndamage > 0) - status = "signs of minor burns." - span = "notice" - if(burndamage > 20) - status = "serious burns." - span = "danger" - if(burndamage > 40) - status = "major burns!" - span = "userdanger" - if(burndamage) - to_chat(usr, "[BP] appears to have [status]") - if(getOxyLoss()) - to_chat(usr, "Patient has signs of suffocation, emergency treatment may be required!") - if(getToxLoss() > 20) - to_chat(usr, "Gathered data is inconsistent with the analysis, possible cause: poisoning.") - - if(href_list["hud"] == "s") - if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - if(usr.stat || usr == src) //|| !usr.canmove || usr.restrained()) Fluff: Sechuds have eye-tracking technology and sets 'arrest' to people that the wearer looks and blinks at. - return //Non-fluff: This allows sec to set people to arrest as they get disarmed or beaten - // Checks the user has security clearence before allowing them to change arrest status via hud, comment out to enable all access - var/allowed_access = null - var/obj/item/clothing/glasses/G = H.glasses - if (!(G.obj_flags |= EMAGGED)) - if(H.wear_id) - var/list/access = H.wear_id.GetAccess() - if(ACCESS_SEC_DOORS in access) - allowed_access = H.get_authentification_name() - else - allowed_access = "@%&ERROR_%$*" - - - if(!allowed_access) - to_chat(H, "ERROR: Invalid Access") - return - - if(perpname) - R = find_record("name", perpname, GLOB.data_core.security) - if(R) - if(href_list["status"]) - var/setcriminal = input(usr, "Specify a new criminal status for this person.", "Security HUD", R.fields["criminal"]) in list("None", "*Arrest*", "Incarcerated", "Paroled", "Discharged", "Cancel") - if(setcriminal != "Cancel") - if(R) - if(H.canUseHUD()) - if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - investigate_log("[key_name(src)] has been set from [R.fields["criminal"]] to [setcriminal] by [key_name(usr)].", INVESTIGATE_RECORDS) - R.fields["criminal"] = setcriminal - sec_hud_set_security_status() - return - - if(href_list["view"]) - if(R) - if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - return - to_chat(usr, "Name: [R.fields["name"]] Criminal Status: [R.fields["criminal"]]") - to_chat(usr, "Minor Crimes:") - for(var/datum/data/crime/c in R.fields["mi_crim"]) - to_chat(usr, "Crime: [c.crimeName]") - to_chat(usr, "Details: [c.crimeDetails]") - to_chat(usr, "Added by [c.author] at [c.time]") - to_chat(usr, "----------") - to_chat(usr, "Major Crimes:") - for(var/datum/data/crime/c in R.fields["ma_crim"]) - to_chat(usr, "Crime: [c.crimeName]") - to_chat(usr, "Details: [c.crimeDetails]") - to_chat(usr, "Added by [c.author] at [c.time]") - to_chat(usr, "----------") - to_chat(usr, "Notes: [R.fields["notes"]]") - return - - if(href_list["add_crime"]) - switch(alert("What crime would you like to add?","Security HUD","Minor Crime","Major Crime","Cancel")) - if("Minor Crime") - if(R) - var/t1 = stripped_input("Please input minor crime names:", "Security HUD", "", null) - var/t2 = stripped_multiline_input("Please input minor crime details:", "Security HUD", "", null) - if(R) - if (!t1 || !t2 || !allowed_access) - return - else if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - return - var/crime = GLOB.data_core.createCrimeEntry(t1, t2, allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss")) - GLOB.data_core.addMinorCrime(R.fields["id"], crime) - investigate_log("New Minor Crime: [t1]: [t2] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) - to_chat(usr, "Successfully added a minor crime.") - return - if("Major Crime") - if(R) - var/t1 = stripped_input("Please input major crime names:", "Security HUD", "", null) - var/t2 = stripped_multiline_input("Please input major crime details:", "Security HUD", "", null) - if(R) - if (!t1 || !t2 || !allowed_access) - return - else if (!H.canUseHUD()) - return - else if (!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - return - var/crime = GLOB.data_core.createCrimeEntry(t1, t2, allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss")) - GLOB.data_core.addMajorCrime(R.fields["id"], crime) - investigate_log("New Major Crime: [t1]: [t2] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) - to_chat(usr, "Successfully added a major crime.") - return - - if(href_list["view_comment"]) - if(R) - if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - return - to_chat(usr, "Comments/Log:") - var/counter = 1 - while(R.fields[text("com_[]", counter)]) - to_chat(usr, R.fields[text("com_[]", counter)]) - to_chat(usr, "----------") - counter++ - return - - if(href_list["add_comment"]) - if(R) - var/t1 = stripped_multiline_input("Add Comment:", "Secure. records", null, null) - if(R) - if (!t1 || !allowed_access) - return - else if(!H.canUseHUD()) - return - else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) - return - var/counter = 1 - while(R.fields[text("com_[]", counter)]) - counter++ - R.fields[text("com_[]", counter)] = text("Made by [] on [] [], []
                []", allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss"), time2text(world.realtime, "MMM DD"), GLOB.year_integer, t1) - to_chat(usr, "Successfully added comment.") - return - to_chat(usr, "Unable to locate a data core entry for this person.") - -/mob/living/carbon/human/proc/canUseHUD() - return !(src.stat || IsKnockdown() || IsStun() || src.restrained()) - -/mob/living/carbon/human/can_inject(mob/user, error_msg, target_zone, penetrate_thick = FALSE, bypass_immunity = FALSE) - . = 1 // Default to returning true. - if(user && !target_zone) - target_zone = user.zone_selected - if(HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && !bypass_immunity) - . = 0 - // If targeting the head, see if the head item is thin enough. - // If targeting anything else, see if the wear suit is thin enough. - if (!penetrate_thick) - if(above_neck(target_zone)) - if(head && istype(head, /obj/item/clothing)) - var/obj/item/clothing/CH = head - if (CH.clothing_flags & THICKMATERIAL) - . = 0 - else - if(wear_suit && istype(wear_suit, /obj/item/clothing)) - var/obj/item/clothing/CS = wear_suit - if (CS.clothing_flags & THICKMATERIAL) - . = 0 - if(!. && error_msg && user) - // Might need re-wording. - to_chat(user, "There is no exposed flesh or thin material [above_neck(target_zone) ? "on [p_their()] head" : "on [p_their()] body"].") - -/mob/living/carbon/human/proc/check_obscured_slots() - var/list/obscured = list() - - if(wear_suit) - if(wear_suit.flags_inv & HIDEGLOVES) - obscured |= SLOT_GLOVES - if(wear_suit.flags_inv & HIDEJUMPSUIT) - obscured |= SLOT_W_UNIFORM - if(wear_suit.flags_inv & HIDESHOES) - obscured |= SLOT_SHOES - - if(head) - if(head.flags_inv & HIDEMASK) - obscured |= SLOT_WEAR_MASK - if(head.flags_inv & HIDEEYES) - obscured |= SLOT_GLASSES - if(head.flags_inv & HIDEEARS) - obscured |= SLOT_EARS - - if(wear_mask) - if(wear_mask.flags_inv & HIDEEYES) - obscured |= SLOT_GLASSES - - if(obscured.len) - return obscured - else - return null - -/mob/living/carbon/human/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) - if(judgement_criteria & JUDGE_EMAGGED) - return 10 //Everyone is a criminal! - - var/threatcount = 0 - - //Lasertag bullshit - if(lasercolor) - if(lasercolor == "b")//Lasertag turrets target the opposing team, how great is that? -Sieve - if(istype(wear_suit, /obj/item/clothing/suit/redtag)) - threatcount += 4 - if(is_holding_item_of_type(/obj/item/gun/energy/laser/redtag)) - threatcount += 4 - if(istype(belt, /obj/item/gun/energy/laser/redtag)) - threatcount += 2 - - if(lasercolor == "r") - if(istype(wear_suit, /obj/item/clothing/suit/bluetag)) - threatcount += 4 - if(is_holding_item_of_type(/obj/item/gun/energy/laser/bluetag)) - threatcount += 4 - if(istype(belt, /obj/item/gun/energy/laser/bluetag)) - threatcount += 2 - - return threatcount - - //Check for ID - var/obj/item/card/id/idcard = get_idcard(FALSE) - if( (judgement_criteria & JUDGE_IDCHECK) && !idcard && name=="Unknown") - threatcount += 4 - - //Check for weapons - if( (judgement_criteria & JUDGE_WEAPONCHECK) && weaponcheck) - if(!idcard || !(ACCESS_WEAPONS in idcard.access)) - for(var/obj/item/I in held_items) //if they're holding a gun - if(weaponcheck.Invoke(I)) - threatcount += 4 - if(weaponcheck.Invoke(belt) || weaponcheck.Invoke(back)) //if a weapon is present in the belt or back slot - threatcount += 2 //not enough to trigger look_for_perp() on it's own unless they also have criminal status. - - //Check for arrest warrant - if(judgement_criteria & JUDGE_RECORDCHECK) - var/perpname = get_face_name(get_id_name()) - var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security) - if(R && R.fields["criminal"]) - switch(R.fields["criminal"]) - if("*Arrest*") - threatcount += 5 - if("Incarcerated") - threatcount += 2 - if("Paroled") - threatcount += 2 - - //Check for dresscode violations - if(istype(head, /obj/item/clothing/head/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/syndi) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/shielded/syndi)) - threatcount += 4 //fuk u antags <3 //no you - - //mindshield implants imply trustworthyness - if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) - threatcount -= 1 - - //Agent cards lower threatlevel. - if(istype(idcard, /obj/item/card/id/syndicate)) - threatcount -= 2 - - return threatcount - - -//Used for new human mobs created by cloning/goleming/podding -/mob/living/carbon/human/proc/set_cloned_appearance() - if(gender == MALE) - facial_hair_style = "Full Beard" - else - facial_hair_style = "Shaved" - hair_style = pick("Bedhead", "Bedhead 2", "Bedhead 3") - underwear = "Nude" - undershirt = "Nude" - update_body() - update_hair() - update_genitals() - -/mob/living/carbon/human/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_THREE) - for(var/obj/item/hand in held_items) - if(prob(current_size * 5) && hand.w_class >= ((11-current_size)/2) && dropItemToGround(hand)) - step_towards(hand, src) - to_chat(src, "\The [S] pulls \the [hand] from your grip!") - rad_act(current_size * 3) - if(mob_negates_gravity()) - return - -/mob/living/carbon/human/proc/do_cpr(mob/living/carbon/C) - CHECK_DNA_AND_SPECIES(C) - - if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) - to_chat(src, "[C.name] is dead!") - return - if(is_mouth_covered()) - to_chat(src, "Remove your mask first!") - return 0 - if(C.is_mouth_covered()) - to_chat(src, "Remove [p_their()] mask first!") - return 0 - - if(C.cpr_time < world.time + 30) - visible_message("[src] is trying to perform CPR on [C.name]!", \ - "You try to perform CPR on [C.name]... Hold still!") - if(!do_mob(src, C)) - to_chat(src, "You fail to perform CPR on [C]!") - return 0 - - var/they_breathe = !HAS_TRAIT(C, TRAIT_NOBREATH) - var/they_lung = C.getorganslot(ORGAN_SLOT_LUNGS) - - if(C.health > C.crit_threshold) - return - - src.visible_message("[src] performs CPR on [C.name]!", "You perform CPR on [C.name].") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "perform_cpr", /datum/mood_event/perform_cpr) - C.cpr_time = world.time - log_combat(src, C, "CPRed") - - if(they_breathe && they_lung) - var/suff = min(C.getOxyLoss(), 7) - C.adjustOxyLoss(-suff) - C.updatehealth() - to_chat(C, "You feel a breath of fresh air enter your lungs... It feels good...") - else if(they_breathe && !they_lung) - to_chat(C, "You feel a breath of fresh air... but you don't feel any better...") - else - to_chat(C, "You feel a breath of fresh air... which is a sensation you don't recognise...") - -/mob/living/carbon/human/cuff_resist(obj/item/I) - if(dna && dna.check_mutation(HULK)) - say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") - if(..(I, cuff_break = FAST_CUFFBREAK)) - dropItemToGround(I) - else - if(..()) - dropItemToGround(I) - -/mob/living/carbon/human/clean_blood() - var/mob/living/carbon/human/H = src - if(H.gloves) - if(H.gloves.clean_blood()) - H.update_inv_gloves() - else - ..() // Clear the Blood_DNA list - if(H.bloody_hands) - H.bloody_hands = 0 - H.update_inv_gloves() - update_icons() //apply the now updated overlays to the mob - -/mob/living/carbon/human/wash_cream() - if(creamed) //clean both to prevent a rare bug - cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_lizard")) - cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_human")) - creamed = FALSE - -//Turns a mob black, flashes a skeleton overlay -//Just like a cartoon! -/mob/living/carbon/human/proc/electrocution_animation(anim_duration) - //Handle mutant parts if possible - if(dna && dna.species) - add_atom_colour("#000000", TEMPORARY_COLOUR_PRIORITY) - var/static/mutable_appearance/electrocution_skeleton_anim - if(!electrocution_skeleton_anim) - electrocution_skeleton_anim = mutable_appearance(icon, "electrocuted_base") - electrocution_skeleton_anim.appearance_flags |= RESET_COLOR|KEEP_APART - add_overlay(electrocution_skeleton_anim) - addtimer(CALLBACK(src, .proc/end_electrocution_animation, electrocution_skeleton_anim), anim_duration) - - else //or just do a generic animation - flick_overlay_view(image(icon,src,"electrocuted_generic",ABOVE_MOB_LAYER), src, anim_duration) - -/mob/living/carbon/human/proc/end_electrocution_animation(mutable_appearance/MA) - remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#000000") - cut_overlay(MA) - -/mob/living/carbon/human/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) - if(incapacitated() || lying ) - to_chat(src, "You can't do that right now!") - return FALSE - if(!Adjacent(M) && (M.loc != src)) - if((be_close == 0) || (!no_tk && (dna.check_mutation(TK) && tkMaxRangeCheck(src, M)))) - return TRUE - to_chat(src, "You are too far away!") - return FALSE - return TRUE - -/mob/living/carbon/human/resist_restraints() - if(wear_suit && wear_suit.breakouttime) - changeNext_move(CLICK_CD_BREAKOUT) - last_special = world.time + CLICK_CD_BREAKOUT - cuff_resist(wear_suit) - else - ..() - -/mob/living/carbon/human/replace_records_name(oldname,newname) // Only humans have records right now, move this up if changed. - for(var/list/L in list(GLOB.data_core.general,GLOB.data_core.medical,GLOB.data_core.security,GLOB.data_core.locked)) - var/datum/data/record/R = find_record("name", oldname, L) - if(R) - R.fields["name"] = newname - -/mob/living/carbon/human/get_total_tint() - . = ..() - if(glasses) - . += glasses.tint - -/mob/living/carbon/human/update_health_hud() - if(!client || !hud_used) - return - if(dna.species.update_health_hud()) - return - else - if(hud_used.healths) - var/health_amount = health - CLAMP(getStaminaLoss()-50, 0, 80)//CIT CHANGE - makes staminaloss have less of an impact on the health hud - if(..(health_amount)) //not dead - switch(hal_screwyhud) - if(SCREWYHUD_CRIT) - hud_used.healths.icon_state = "health6" - if(SCREWYHUD_DEAD) - hud_used.healths.icon_state = "health7" - if(SCREWYHUD_HEALTHY) - hud_used.healths.icon_state = "health0" - if(hud_used.healthdoll) - hud_used.healthdoll.cut_overlays() - if(stat != DEAD) - hud_used.healthdoll.icon_state = "healthdoll_OVERLAY" - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - var/damage = BP.burn_dam + BP.brute_dam - var/comparison = (BP.max_damage/5) - var/icon_num = 0 - if(damage) - icon_num = 1 - if(damage > (comparison)) - icon_num = 2 - if(damage > (comparison*2)) - icon_num = 3 - if(damage > (comparison*3)) - icon_num = 4 - if(damage > (comparison*4)) - icon_num = 5 - if(hal_screwyhud == SCREWYHUD_HEALTHY) - icon_num = 0 - if(icon_num) - hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[BP.body_zone][icon_num]")) - for(var/t in get_missing_limbs()) //Missing limbs - hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[t]6")) - for(var/t in get_disabled_limbs()) //Disabled limbs - hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[t]7")) - else - hud_used.healthdoll.icon_state = "healthdoll_DEAD" - - hud_used.staminas?.update_icon_state() - hud_used.staminabuffer?.update_icon_state() - -/mob/living/carbon/human/fully_heal(admin_revive = 0) - if(admin_revive) - regenerate_limbs() - regenerate_organs() - remove_all_embedded_objects() - set_heartattack(FALSE) - drunkenness = 0 - for(var/datum/mutation/human/HM in dna.mutations) - if(HM.quality != POSITIVE) - dna.remove_mutation(HM.name) - if(blood_volume < (BLOOD_VOLUME_NORMAL*blood_ratio)) - blood_volume = (BLOOD_VOLUME_NORMAL*blood_ratio) - ..() - -/mob/living/carbon/human/check_weakness(obj/item/weapon, mob/living/attacker) - . = ..() - if (dna && dna.species) - . += dna.species.check_weakness(weapon, attacker) - -/mob/living/carbon/human/is_literate() - return 1 - -/mob/living/carbon/human/can_hold_items() - return TRUE - -/mob/living/carbon/human/update_gravity(has_gravity,override = 0) - if(dna && dna.species) //prevents a runtime while a human is being monkeyfied - override = dna.species.override_float - ..() - -/mob/living/carbon/human/vomit(lost_nutrition = 10, blood = 0, stun = 1, distance = 0, message = 1, toxic = 0) - if(blood && (NOBLOOD in dna.species.species_traits)) - if(message) - visible_message("[src] dry heaves!", \ - "You try to throw up, but there's nothing in your stomach!") - if(stun) - Knockdown(200) - return 1 - ..() - -/mob/living/carbon/human/vv_get_dropdown() - . = ..() - . += "---" - .["Make monkey"] = "?_src_=vars;[HrefToken()];makemonkey=[REF(src)]" - .["Set Species"] = "?_src_=vars;[HrefToken()];setspecies=[REF(src)]" - .["Make cyborg"] = "?_src_=vars;[HrefToken()];makerobot=[REF(src)]" - .["Make alien"] = "?_src_=vars;[HrefToken()];makealien=[REF(src)]" - .["Make slime"] = "?_src_=vars;[HrefToken()];makeslime=[REF(src)]" - .["Toggle Purrbation"] = "?_src_=vars;[HrefToken()];purrbation=[REF(src)]" - .["Copy outfit"] = "?_src_=vars;[HrefToken()];copyoutfit=[REF(src)]" - .["Add/Remove Quirks"] = "?_src_=vars;[HrefToken()];modquirks=[REF(src)]" - -/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user) - if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS) - //If they dragged themselves and we're currently aggressively grabbing them try to piggyback - if(user == target && can_piggyback(target)) - piggyback(target) - return - //If you dragged them to you and you're aggressively grabbing try to fireman carry them - else if(user != target) - if(user.a_intent == INTENT_GRAB) - fireman_carry(target) - return - . = ..() - -//src is the user that will be carrying, target is the mob to be carried -/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/target) - return (istype(target) && target.stat == CONSCIOUS) - -/mob/living/carbon/human/proc/can_be_firemanned(mob/living/carbon/target) - return (ishuman(target) && target.lying) - -/mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target) - if(can_be_firemanned(target)) - visible_message("[src] starts lifting [target] onto their back...", - "You start lifting [target] onto your back...") - if(do_after(src, 30, TRUE, target)) - //Second check to make sure they're still valid to be carried - if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE)) - target.resting = FALSE - buckle_mob(target, TRUE, TRUE, 90, 1, 0) - return - visible_message("[src] fails to fireman carry [target]!") - else - if (ishuman(target)) - to_chat(src, "You can't fireman carry [target] while they're standing!") - else - to_chat(src, "You can't seem to fireman carry that kind of species.") - -/mob/living/carbon/human/proc/piggyback(mob/living/carbon/target) - if(can_piggyback(target)) - visible_message("[target] starts to climb onto [src]...") - if(do_after(target, 15, target = src)) - if(can_piggyback(target)) - if(target.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE)) - target.visible_message("[target] can't hang onto [src]!") - return - buckle_mob(target, TRUE, TRUE, FALSE, 0, 2) - else - visible_message("[target] fails to climb onto [src]!") - else - to_chat(target, "You can't piggyback ride [src] right now!") - -/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = FALSE, hands_needed = 0, target_hands_needed = 0) - if(!force)//humans are only meant to be ridden through piggybacking and special cases - return - if(!is_type_in_typecache(target, can_ride_typecache)) - target.visible_message("[target] really can't seem to mount [src]...") - return - buckle_lying = lying_buckle - var/datum/component/riding/human/riding_datum = LoadComponent(/datum/component/riding/human) - if(target_hands_needed) - riding_datum.ride_check_rider_restrained = TRUE - if(buckled_mobs && ((target in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled) - return - var/equipped_hands_self - var/equipped_hands_target - if(hands_needed) - equipped_hands_self = riding_datum.equip_buckle_inhands(src, hands_needed, target) - if(target_hands_needed) - equipped_hands_target = riding_datum.equip_buckle_inhands(target, target_hands_needed) - - if(hands_needed || target_hands_needed) - if(hands_needed && !equipped_hands_self) - src.visible_message("[src] can't get a grip on [target] because their hands are full!", - "You can't get a grip on [target] because your hands are full!") - return - else if(target_hands_needed && !equipped_hands_target) - target.visible_message("[target] can't get a grip on [src] because their hands are full!", - "You can't get a grip on [src] because your hands are full!") - return - - stop_pulling() - riding_datum.handle_vehicle_layer() - . = ..(target, force, check_loc) - -/mob/living/carbon/human/proc/is_shove_knockdown_blocked() //If you want to add more things that block shove knockdown, extend this - for(var/obj/item/clothing/C in get_equipped_items()) //doesn't include pockets - if(C.blocks_shove_knockdown) - return TRUE - return FALSE - -/mob/living/carbon/human/proc/clear_shove_slowdown() - remove_movespeed_modifier(MOVESPEED_ID_SHOVE) - var/active_item = get_active_held_item() - if(is_type_in_typecache(active_item, GLOB.shove_disarming_types)) - visible_message("[src.name] regains their grip on \the [active_item]!", "You regain your grip on \the [active_item]", null, COMBAT_MESSAGE_RANGE) - -/mob/living/carbon/human/do_after_coefficent() - . = ..() - . *= physiology.do_after_speed - -/mob/living/carbon/human/species - var/race = null - -/mob/living/carbon/human/species/Initialize() - . = ..() - set_species(race) - -/mob/living/carbon/human/species/abductor - race = /datum/species/abductor - -/mob/living/carbon/human/species/android - race = /datum/species/android - -/mob/living/carbon/human/species/angel - race = /datum/species/angel - -/mob/living/carbon/human/species/corporate - race = /datum/species/corporate - -/mob/living/carbon/human/species/dullahan - race = /datum/species/dullahan - -/mob/living/carbon/human/species/felinid - race = /datum/species/human/felinid - -/mob/living/carbon/human/species/fly - race = /datum/species/fly - -/mob/living/carbon/human/species/golem - race = /datum/species/golem - -/mob/living/carbon/human/species/golem/random - race = /datum/species/golem/random - -/mob/living/carbon/human/species/golem/adamantine - race = /datum/species/golem/adamantine - -/mob/living/carbon/human/species/golem/plasma - race = /datum/species/golem/plasma - -/mob/living/carbon/human/species/golem/diamond - race = /datum/species/golem/diamond - -/mob/living/carbon/human/species/golem/gold - race = /datum/species/golem/gold - -/mob/living/carbon/human/species/golem/silver - race = /datum/species/golem/silver - -/mob/living/carbon/human/species/golem/plasteel - race = /datum/species/golem/plasteel - -/mob/living/carbon/human/species/golem/titanium - race = /datum/species/golem/titanium - -/mob/living/carbon/human/species/golem/plastitanium - race = /datum/species/golem/plastitanium - -/mob/living/carbon/human/species/golem/alien_alloy - race = /datum/species/golem/alloy - -/mob/living/carbon/human/species/golem/wood - race = /datum/species/golem/wood - -/mob/living/carbon/human/species/golem/uranium - race = /datum/species/golem/uranium - -/mob/living/carbon/human/species/golem/sand - race = /datum/species/golem/sand - -/mob/living/carbon/human/species/golem/glass - race = /datum/species/golem/glass - -/mob/living/carbon/human/species/golem/bluespace - race = /datum/species/golem/bluespace - -/mob/living/carbon/human/species/golem/bananium - race = /datum/species/golem/bananium - -/mob/living/carbon/human/species/golem/blood_cult - race = /datum/species/golem/runic - -/mob/living/carbon/human/species/golem/cloth - race = /datum/species/golem/cloth - -/mob/living/carbon/human/species/golem/plastic - race = /datum/species/golem/plastic - -/mob/living/carbon/human/species/golem/bronze - race = /datum/species/golem/bronze - -/mob/living/carbon/human/species/golem/cardboard - race = /datum/species/golem/cardboard - -/mob/living/carbon/human/species/golem/leather - race = /datum/species/golem/leather - -/mob/living/carbon/human/species/golem/bone - race = /datum/species/golem/bone - -/mob/living/carbon/human/species/golem/durathread - race = /datum/species/golem/durathread - -/mob/living/carbon/human/species/golem/clockwork - race = /datum/species/golem/clockwork - -/mob/living/carbon/human/species/golem/clockwork/no_scrap - race = /datum/species/golem/clockwork/no_scrap - -/mob/living/carbon/human/species/jelly - race = /datum/species/jelly - -/mob/living/carbon/human/species/jelly/slime - race = /datum/species/jelly/slime - -/mob/living/carbon/human/species/jelly/stargazer - race = /datum/species/jelly/stargazer - -/mob/living/carbon/human/species/jelly/luminescent - race = /datum/species/jelly/luminescent - -/mob/living/carbon/human/species/lizard - race = /datum/species/lizard - -/mob/living/carbon/human/species/lizard/ashwalker - race = /datum/species/lizard/ashwalker - -/mob/living/carbon/human/species/insect - race = /datum/species/insect - -/mob/living/carbon/human/species/mush - race = /datum/species/mush - -/mob/living/carbon/human/species/plasma - race = /datum/species/plasmaman - -/mob/living/carbon/human/species/pod - race = /datum/species/pod - -/mob/living/carbon/human/species/shadow - race = /datum/species/shadow - -/mob/living/carbon/human/species/shadow/nightmare - race = /datum/species/shadow/nightmare - -/mob/living/carbon/human/species/skeleton - race = /datum/species/skeleton - -/mob/living/carbon/human/species/synth - race = /datum/species/synth - -/mob/living/carbon/human/species/synth/military - race = /datum/species/synth/military - -/mob/living/carbon/human/species/vampire - race = /datum/species/vampire - -/mob/living/carbon/human/species/zombie - race = /datum/species/zombie - -/mob/living/carbon/human/species/zombie/infectious - race = /datum/species/zombie/infectious - -/mob/living/carbon/human/species/zombie/krokodil_addict - race = /datum/species/krokodil_addict - -/mob/living/carbon/human/species/mammal - race = /datum/species/mammal - -/mob/living/carbon/human/species/insect - race = /datum/species/insect - -/mob/living/carbon/human/species/xeno - race = /datum/species/xeno - -/mob/living/carbon/human/species/ipc - race = /datum/species/ipc - -/mob/living/carbon/human/species/roundstartslime - race = /datum/species/jelly/roundstartslime +/mob/living/carbon/human + name = "Unknown" + real_name = "Unknown" + icon = 'icons/mob/human.dmi' + icon_state = "caucasian_m" + appearance_flags = KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE + +/mob/living/carbon/human/Initialize() + verbs += /mob/living/proc/mob_sleep + verbs += /mob/living/proc/lay_down + verbs += /mob/living/carbon/human/proc/underwear_toggle //fwee + + //initialize limbs first + create_bodyparts() + + //initialize dna. for spawned humans; overwritten by other code + create_dna(src) + randomize_human(src) + dna.initialize_dna() + + if(dna.species) + set_species(dna.species.type) + + //initialise organs + create_internal_organs() //most of it is done in set_species now, this is only for parent call + physiology = new() + + handcrafting = new() + + . = ..() + + if(CONFIG_GET(flag/disable_stambuffer)) + togglesprint() + + RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.proc/clean_blood) + + +/mob/living/carbon/human/ComponentInitialize() + . = ..() + if(!CONFIG_GET(flag/disable_human_mood)) + AddComponent(/datum/component/mood) + +/mob/living/carbon/human/Destroy() + QDEL_NULL(physiology) + QDEL_NULL_LIST(vore_organs) // CITADEL EDIT belly stuff + return ..() + + +/mob/living/carbon/human/OpenCraftingMenu() + handcrafting.ui_interact(src) + +/mob/living/carbon/human/prepare_data_huds() + //Update med hud images... + ..() + //...sec hud images... + sec_hud_set_ID() + sec_hud_set_implants() + sec_hud_set_security_status() + //...and display them. + add_to_all_human_data_huds() + +/mob/living/carbon/human/Stat() + ..() + + if(statpanel("Status")) + stat(null, "Intent: [a_intent]") + stat(null, "Move Mode: [m_intent]") + if (internal) + if (!internal.air_contents) + qdel(internal) + else + stat("Internal Atmosphere Info", internal.name) + stat("Tank Pressure", internal.air_contents.return_pressure()) + stat("Distribution Pressure", internal.distribute_pressure) + + if(mind) + var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling) + stat("Chemical Storage", "[changeling.chem_charges]/[changeling.chem_storage]") + stat("Absorbed DNA", changeling.absorbedcount) + + + //NINJACODE + if(istype(wear_suit, /obj/item/clothing/suit/space/space_ninja)) //Only display if actually a ninja. + var/obj/item/clothing/suit/space/space_ninja/SN = wear_suit + if(statpanel("SpiderOS")) + stat("SpiderOS Status:","[SN.s_initialized ? "Initialized" : "Disabled"]") + stat("Current Time:", "[STATION_TIME_TIMESTAMP("hh:mm:ss")]") + if(SN.s_initialized) + //Suit gear + stat("Energy Charge:", "[round(SN.cell.charge/100)]%") + stat("Smoke Bombs:", "\Roman [SN.s_bombs]") + //Ninja status + stat("Fingerprints:", "[md5(dna.uni_identity)]") + stat("Unique Identity:", "[dna.unique_enzymes]") + stat("Overall Status:", "[stat > 1 ? "dead" : "[health]% healthy"]") + stat("Nutrition Status:", "[nutrition]") + stat("Oxygen Loss:", "[getOxyLoss()]") + stat("Toxin Levels:", "[getToxLoss()]") + stat("Burn Severity:", "[getFireLoss()]") + stat("Brute Trauma:", "[getBruteLoss()]") + stat("Radiation Levels:","[radiation] rad") + stat("Body Temperature:","[bodytemperature-T0C] degrees C ([bodytemperature*1.8-459.67] degrees F)") + + //Diseases + if(diseases.len) + stat("Viruses:", null) + for(var/thing in diseases) + var/datum/disease/D = thing + stat("*", "[D.name], Type: [D.spread_text], Stage: [D.stage]/[D.max_stages], Possible Cure: [D.cure_text]") + + +/mob/living/carbon/human/show_inv(mob/user) + user.set_machine(src) + var/has_breathable_mask = istype(wear_mask, /obj/item/clothing/mask) + var/list/obscured = check_obscured_slots() + var/list/dat = list() + + dat += "" + for(var/i in 1 to held_items.len) + var/obj/item/I = get_item_for_held_index(i) + dat += "" + dat += "" + + dat += "" + + dat += "" + + if(SLOT_WEAR_MASK in obscured) + dat += "" + else + dat += "" + + if(SLOT_NECK in obscured) + dat += "" + else + dat += "" + + if(SLOT_GLASSES in obscured) + dat += "" + else + dat += "" + + if(SLOT_EARS in obscured) + dat += "" + else + dat += "" + + dat += "" + + dat += "" + if(wear_suit) + dat += "" + else + dat += "" + + if(SLOT_SHOES in obscured) + dat += "" + else + dat += "" + + if(SLOT_GLOVES in obscured) + dat += "" + else + dat += "" + + if(SLOT_W_UNIFORM in obscured) + dat += "" + else + dat += "" + + if((w_uniform == null && !(dna && dna.species.nojumpsuit)) || (SLOT_W_UNIFORM in obscured)) + dat += "" + dat += "" + dat += "" + else + dat += "" + dat += "" + dat += "" + + if(handcuffed) + dat += "" + if(legcuffed) + dat += "" + + dat += {"
                [get_held_index_name(i)]:[(I && !(I.item_flags & ABSTRACT)) ? I : "Empty"]
                 
                Back:[(back && !(back.item_flags & ABSTRACT)) ? back : "Empty"]" + if(has_breathable_mask && istype(back, /obj/item/tank)) + dat += " [internal ? "Disable Internals" : "Set Internals"]" + + dat += "
                 
                Head:[(head && !(head.item_flags & ABSTRACT)) ? head : "Empty"]
                Mask:Obscured
                Mask:[(wear_mask && !(wear_mask.item_flags & ABSTRACT)) ? wear_mask : "Empty"]
                Neck:Obscured
                Neck:[(wear_neck && !(wear_neck.item_flags & ABSTRACT)) ? wear_neck : "Empty"]
                Eyes:Obscured
                Eyes:[(glasses && !(glasses.item_flags & ABSTRACT)) ? glasses : "Empty"]
                Ears:Obscured
                Ears:[(ears && !(ears.item_flags & ABSTRACT)) ? ears : "Empty"]
                 
                Exosuit:[(wear_suit && !(wear_suit.item_flags & ABSTRACT)) ? wear_suit : "Empty"]
                 ↳Suit Storage:[(s_store && !(s_store.item_flags & ABSTRACT)) ? s_store : "Empty"]" + if(has_breathable_mask && istype(s_store, /obj/item/tank)) + dat += " [internal ? "Disable Internals" : "Set Internals"]" + dat += "
                 ↳Suit Storage:
                Shoes:Obscured
                Shoes:[(shoes && !(shoes.item_flags & ABSTRACT)) ? shoes : "Empty"]
                Gloves:Obscured
                Gloves:[(gloves && !(gloves.item_flags & ABSTRACT)) ? gloves : "Empty"]
                Uniform:Obscured
                Uniform:[(w_uniform && !(w_uniform.item_flags & ABSTRACT)) ? w_uniform : "Empty"]
                 ↳Pockets:
                 ↳ID:
                 ↳Belt:
                 ↳Belt:[(belt && !(belt.item_flags & ABSTRACT)) ? belt : "Empty"]" + if(has_breathable_mask && istype(belt, /obj/item/tank)) + dat += " [internal ? "Disable Internals" : "Set Internals"]" + dat += "
                 ↳Pockets:[(l_store && !(l_store.item_flags & ABSTRACT)) ? "Left (Full)" : "Left (Empty)"]" + dat += " [(r_store && !(r_store.item_flags & ABSTRACT)) ? "Right (Full)" : "Right (Empty)"]
                 ↳ID:[(wear_id && !(wear_id.item_flags & ABSTRACT)) ? wear_id : "Empty"]
                Handcuffed: Remove
                Legcuffed
                + Close + "} + + var/datum/browser/popup = new(user, "mob[REF(src)]", "[src]", 440, 510) + popup.set_content(dat.Join()) + popup.open() + +// called when something steps onto a human +// this could be made more general, but for now just handle mulebot +/mob/living/carbon/human/Crossed(atom/movable/AM) + var/mob/living/simple_animal/bot/mulebot/MB = AM + if(istype(MB)) + MB.RunOver(src) + + spreadFire(AM) + +/mob/living/carbon/human/Topic(href, href_list) + if(usr.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + if(href_list["embedded_object"]) + var/obj/item/bodypart/L = locate(href_list["embedded_limb"]) in bodyparts + if(!L) + return + var/obj/item/I = locate(href_list["embedded_object"]) in L.embedded_objects + if(!I || I.loc != src) //no item, no limb, or item is not in limb or in the person anymore + return + var/time_taken = I.embedding.embedded_unsafe_removal_time/I.w_class //Citadel Change from * to / + usr.visible_message("[usr] attempts to remove [I] from [usr.p_their()] [L.name].","You attempt to remove [I] from your [L.name]... (It will take [DisplayTimeText(time_taken)].)") + if(do_after(usr, time_taken, needhand = 1, target = src)) + remove_embedded_unsafe(L, I, usr) + /* CITADEL EDIT: remove_embedded_unsafe replaces this code + if(!I || !L || I.loc != src || !(I in L.embedded_objects)) + return + L.embedded_objects -= I + L.receive_damage(I.embedding.embedded_unsafe_removal_pain_multiplier*I.w_class)//It hurts to rip it out, get surgery you dingus. + I.forceMove(get_turf(src)) + usr.put_in_hands(I) + usr.emote("scream") + usr.visible_message("[usr] successfully rips [I] out of [usr.p_their()] [L.name]!","You successfully remove [I] from your [L.name].") + if(!has_embedded_objects()) + clear_alert("embeddedobject") + SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded") */ + return + + if(href_list["item"]) + var/slot = text2num(href_list["item"]) + if(slot in check_obscured_slots()) + to_chat(usr, "You can't reach that! Something is covering it.") + return + + if(href_list["pockets"]) + var/pocket_side = href_list["pockets"] + var/pocket_id = (pocket_side == "right" ? SLOT_R_STORE : SLOT_L_STORE) + var/obj/item/pocket_item = (pocket_id == SLOT_R_STORE ? r_store : l_store) + var/obj/item/place_item = usr.get_active_held_item() // Item to place in the pocket, if it's empty + + var/delay_denominator = 1 + if(pocket_item && !(pocket_item.item_flags & ABSTRACT)) + if(HAS_TRAIT(pocket_item, TRAIT_NODROP)) + to_chat(usr, "You try to empty [src]'s [pocket_side] pocket, it seems to be stuck!") + to_chat(usr, "You try to empty [src]'s [pocket_side] pocket.") + else if(place_item && place_item.mob_can_equip(src, usr, pocket_id, 1) && !(place_item.item_flags & ABSTRACT)) + to_chat(usr, "You try to place [place_item] into [src]'s [pocket_side] pocket.") + delay_denominator = 4 + else + return + + if(do_mob(usr, src, POCKET_STRIP_DELAY/delay_denominator, ignorehelditem = TRUE)) //placing an item into the pocket is 4 times faster + if(pocket_item) + if(pocket_item == (pocket_id == SLOT_R_STORE ? r_store : l_store)) //item still in the pocket we search + dropItemToGround(pocket_item) + if(!usr.can_hold_items() || !usr.put_in_hands(pocket_item)) + pocket_item.forceMove(drop_location()) + else + if(place_item) + if(place_item.mob_can_equip(src, usr, pocket_id, FALSE, TRUE)) + usr.temporarilyRemoveItemFromInventory(place_item, TRUE) + equip_to_slot(place_item, pocket_id, TRUE) + //do nothing otherwise + + // Update strip window + if(usr.machine == src && in_range(src, usr)) + show_inv(usr) + else + // Display a warning if the user mocks up + to_chat(src, "You feel your [pocket_side] pocket being fumbled with!") + + ..() //CITADEL CHANGE - removes a tab from behind this ..() so that flavortext can actually be examined + + +///////HUDs/////// + if(href_list["hud"]) + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + var/perpname = get_face_name(get_id_name("")) + if(istype(H.glasses, /obj/item/clothing/glasses/hud) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud)) + var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.general) + if(href_list["photo_front"] || href_list["photo_side"]) + if(R) + if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) + return + var/obj/item/photo/P = null + if(href_list["photo_front"]) + P = R.fields["photo_front"] + else if(href_list["photo_side"]) + P = R.fields["photo_side"] + if(P) + P.show(H) + + if(href_list["hud"] == "m") + if(istype(H.glasses, /obj/item/clothing/glasses/hud/health) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) + if(href_list["p_stat"]) + var/health_status = input(usr, "Specify a new physical status for this person.", "Medical HUD", R.fields["p_stat"]) in list("Active", "Physically Unfit", "*Unconscious*", "*Deceased*", "Cancel") + if(R) + if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/health) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) + return + if(health_status && health_status != "Cancel") + R.fields["p_stat"] = health_status + return + if(href_list["m_stat"]) + var/health_status = input(usr, "Specify a new mental status for this person.", "Medical HUD", R.fields["m_stat"]) in list("Stable", "*Watch*", "*Unstable*", "*Insane*", "Cancel") + if(R) + if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/health) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/medical)) + return + if(health_status && health_status != "Cancel") + R.fields["m_stat"] = health_status + return + if(href_list["evaluation"]) + if(!getBruteLoss() && !getFireLoss() && !getOxyLoss() && getToxLoss() < 20) + to_chat(usr, "No external injuries detected.
                ") + return + var/span = "notice" + var/status = "" + if(getBruteLoss()) + to_chat(usr, "Physical trauma analysis:") + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + var/brutedamage = BP.brute_dam + if(brutedamage > 0) + status = "received minor physical injuries." + span = "notice" + if(brutedamage > 20) + status = "been seriously damaged." + span = "danger" + if(brutedamage > 40) + status = "sustained major trauma!" + span = "userdanger" + if(brutedamage) + to_chat(usr, "[BP] appears to have [status]") + if(getFireLoss()) + to_chat(usr, "Analysis of skin burns:") + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + var/burndamage = BP.burn_dam + if(burndamage > 0) + status = "signs of minor burns." + span = "notice" + if(burndamage > 20) + status = "serious burns." + span = "danger" + if(burndamage > 40) + status = "major burns!" + span = "userdanger" + if(burndamage) + to_chat(usr, "[BP] appears to have [status]") + if(getOxyLoss()) + to_chat(usr, "Patient has signs of suffocation, emergency treatment may be required!") + if(getToxLoss() > 20) + to_chat(usr, "Gathered data is inconsistent with the analysis, possible cause: poisoning.") + + if(href_list["hud"] == "s") + if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + if(usr.stat || usr == src) //|| !usr.canmove || usr.restrained()) Fluff: Sechuds have eye-tracking technology and sets 'arrest' to people that the wearer looks and blinks at. + return //Non-fluff: This allows sec to set people to arrest as they get disarmed or beaten + // Checks the user has security clearence before allowing them to change arrest status via hud, comment out to enable all access + var/allowed_access = null + var/obj/item/clothing/glasses/G = H.glasses + if (!(G.obj_flags |= EMAGGED)) + if(H.wear_id) + var/list/access = H.wear_id.GetAccess() + if(ACCESS_SEC_DOORS in access) + allowed_access = H.get_authentification_name() + else + allowed_access = "@%&ERROR_%$*" + + + if(!allowed_access) + to_chat(H, "ERROR: Invalid Access") + return + + if(perpname) + R = find_record("name", perpname, GLOB.data_core.security) + if(R) + if(href_list["status"]) + var/setcriminal = input(usr, "Specify a new criminal status for this person.", "Security HUD", R.fields["criminal"]) in list("None", "*Arrest*", "Incarcerated", "Paroled", "Discharged", "Cancel") + if(setcriminal != "Cancel") + if(R) + if(H.canUseHUD()) + if(istype(H.glasses, /obj/item/clothing/glasses/hud/security) || istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + investigate_log("[key_name(src)] has been set from [R.fields["criminal"]] to [setcriminal] by [key_name(usr)].", INVESTIGATE_RECORDS) + R.fields["criminal"] = setcriminal + sec_hud_set_security_status() + return + + if(href_list["view"]) + if(R) + if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + return + to_chat(usr, "Name: [R.fields["name"]] Criminal Status: [R.fields["criminal"]]") + to_chat(usr, "Minor Crimes:") + for(var/datum/data/crime/c in R.fields["mi_crim"]) + to_chat(usr, "Crime: [c.crimeName]") + to_chat(usr, "Details: [c.crimeDetails]") + to_chat(usr, "Added by [c.author] at [c.time]") + to_chat(usr, "----------") + to_chat(usr, "Major Crimes:") + for(var/datum/data/crime/c in R.fields["ma_crim"]) + to_chat(usr, "Crime: [c.crimeName]") + to_chat(usr, "Details: [c.crimeDetails]") + to_chat(usr, "Added by [c.author] at [c.time]") + to_chat(usr, "----------") + to_chat(usr, "Notes: [R.fields["notes"]]") + return + + if(href_list["add_crime"]) + switch(alert("What crime would you like to add?","Security HUD","Minor Crime","Major Crime","Cancel")) + if("Minor Crime") + if(R) + var/t1 = stripped_input("Please input minor crime names:", "Security HUD", "", null) + var/t2 = stripped_multiline_input("Please input minor crime details:", "Security HUD", "", null) + if(R) + if (!t1 || !t2 || !allowed_access) + return + else if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + return + var/crime = GLOB.data_core.createCrimeEntry(t1, t2, allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss")) + GLOB.data_core.addMinorCrime(R.fields["id"], crime) + investigate_log("New Minor Crime: [t1]: [t2] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) + to_chat(usr, "Successfully added a minor crime.") + return + if("Major Crime") + if(R) + var/t1 = stripped_input("Please input major crime names:", "Security HUD", "", null) + var/t2 = stripped_multiline_input("Please input major crime details:", "Security HUD", "", null) + if(R) + if (!t1 || !t2 || !allowed_access) + return + else if (!H.canUseHUD()) + return + else if (!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + return + var/crime = GLOB.data_core.createCrimeEntry(t1, t2, allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss")) + GLOB.data_core.addMajorCrime(R.fields["id"], crime) + investigate_log("New Major Crime: [t1]: [t2] | Added to [R.fields["name"]] by [key_name(usr)]", INVESTIGATE_RECORDS) + to_chat(usr, "Successfully added a major crime.") + return + + if(href_list["view_comment"]) + if(R) + if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + return + to_chat(usr, "Comments/Log:") + var/counter = 1 + while(R.fields[text("com_[]", counter)]) + to_chat(usr, R.fields[text("com_[]", counter)]) + to_chat(usr, "----------") + counter++ + return + + if(href_list["add_comment"]) + if(R) + var/t1 = stripped_multiline_input("Add Comment:", "Secure. records", null, null) + if(R) + if (!t1 || !allowed_access) + return + else if(!H.canUseHUD()) + return + else if(!istype(H.glasses, /obj/item/clothing/glasses/hud/security) && !istype(H.getorganslot(ORGAN_SLOT_HUD), /obj/item/organ/cyberimp/eyes/hud/security)) + return + var/counter = 1 + while(R.fields[text("com_[]", counter)]) + counter++ + R.fields[text("com_[]", counter)] = text("Made by [] on [] [], []
                []", allowed_access, STATION_TIME_TIMESTAMP("hh:mm:ss"), time2text(world.realtime, "MMM DD"), GLOB.year_integer, t1) + to_chat(usr, "Successfully added comment.") + return + to_chat(usr, "Unable to locate a data core entry for this person.") + +/mob/living/carbon/human/proc/canUseHUD() + return !(src.stat || IsKnockdown() || IsStun() || src.restrained()) + +/mob/living/carbon/human/can_inject(mob/user, error_msg, target_zone, penetrate_thick = FALSE, bypass_immunity = FALSE) + . = 1 // Default to returning true. + if(user && !target_zone) + target_zone = user.zone_selected + if(HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && !bypass_immunity) + . = 0 + // If targeting the head, see if the head item is thin enough. + // If targeting anything else, see if the wear suit is thin enough. + if (!penetrate_thick) + if(above_neck(target_zone)) + if(head && istype(head, /obj/item/clothing)) + var/obj/item/clothing/CH = head + if (CH.clothing_flags & THICKMATERIAL) + . = 0 + else + if(wear_suit && istype(wear_suit, /obj/item/clothing)) + var/obj/item/clothing/CS = wear_suit + if (CS.clothing_flags & THICKMATERIAL) + . = 0 + if(!. && error_msg && user) + // Might need re-wording. + to_chat(user, "There is no exposed flesh or thin material [above_neck(target_zone) ? "on [p_their()] head" : "on [p_their()] body"].") + +/mob/living/carbon/human/proc/check_obscured_slots() + var/list/obscured = list() + + if(wear_suit) + if(wear_suit.flags_inv & HIDEGLOVES) + obscured |= SLOT_GLOVES + if(wear_suit.flags_inv & HIDEJUMPSUIT) + obscured |= SLOT_W_UNIFORM + if(wear_suit.flags_inv & HIDESHOES) + obscured |= SLOT_SHOES + + if(head) + if(head.flags_inv & HIDEMASK) + obscured |= SLOT_WEAR_MASK + if(head.flags_inv & HIDEEYES) + obscured |= SLOT_GLASSES + if(head.flags_inv & HIDEEARS) + obscured |= SLOT_EARS + + if(wear_mask) + if(wear_mask.flags_inv & HIDEEYES) + obscured |= SLOT_GLASSES + + if(obscured.len) + return obscured + else + return null + +/mob/living/carbon/human/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) + if(judgement_criteria & JUDGE_EMAGGED) + return 10 //Everyone is a criminal! + + var/threatcount = 0 + + //Lasertag bullshit + if(lasercolor) + if(lasercolor == "b")//Lasertag turrets target the opposing team, how great is that? -Sieve + if(istype(wear_suit, /obj/item/clothing/suit/redtag)) + threatcount += 4 + if(is_holding_item_of_type(/obj/item/gun/energy/laser/redtag)) + threatcount += 4 + if(istype(belt, /obj/item/gun/energy/laser/redtag)) + threatcount += 2 + + if(lasercolor == "r") + if(istype(wear_suit, /obj/item/clothing/suit/bluetag)) + threatcount += 4 + if(is_holding_item_of_type(/obj/item/gun/energy/laser/bluetag)) + threatcount += 4 + if(istype(belt, /obj/item/gun/energy/laser/bluetag)) + threatcount += 2 + + return threatcount + + //Check for ID + var/obj/item/card/id/idcard = get_idcard(FALSE) + if( (judgement_criteria & JUDGE_IDCHECK) && !idcard && name=="Unknown") + threatcount += 4 + + //Check for weapons + if( (judgement_criteria & JUDGE_WEAPONCHECK) && weaponcheck) + if(!idcard || !(ACCESS_WEAPONS in idcard.access)) + for(var/obj/item/I in held_items) //if they're holding a gun + if(weaponcheck.Invoke(I)) + threatcount += 4 + if(weaponcheck.Invoke(belt) || weaponcheck.Invoke(back)) //if a weapon is present in the belt or back slot + threatcount += 2 //not enough to trigger look_for_perp() on it's own unless they also have criminal status. + + //Check for arrest warrant + if(judgement_criteria & JUDGE_RECORDCHECK) + var/perpname = get_face_name(get_id_name()) + var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security) + if(R && R.fields["criminal"]) + switch(R.fields["criminal"]) + if("*Arrest*") + threatcount += 5 + if("Incarcerated") + threatcount += 2 + if("Paroled") + threatcount += 2 + + //Check for dresscode violations + if(istype(head, /obj/item/clothing/head/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/syndi) || istype(head, /obj/item/clothing/head/helmet/space/hardsuit/shielded/syndi)) + threatcount += 4 //fuk u antags <3 //no you + + //mindshield implants imply trustworthyness + if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) + threatcount -= 1 + + //Agent cards lower threatlevel. + if(istype(idcard, /obj/item/card/id/syndicate)) + threatcount -= 2 + + return threatcount + + +//Used for new human mobs created by cloning/goleming/podding +/mob/living/carbon/human/proc/set_cloned_appearance() + if(gender == MALE) + facial_hair_style = "Full Beard" + else + facial_hair_style = "Shaved" + hair_style = pick("Bedhead", "Bedhead 2", "Bedhead 3") + underwear = "Nude" + undershirt = "Nude" + update_body() + update_hair() + update_genitals() + +/mob/living/carbon/human/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_THREE) + for(var/obj/item/hand in held_items) + if(prob(current_size * 5) && hand.w_class >= ((11-current_size)/2) && dropItemToGround(hand)) + step_towards(hand, src) + to_chat(src, "\The [S] pulls \the [hand] from your grip!") + rad_act(current_size * 3) + if(mob_negates_gravity()) + return + +/mob/living/carbon/human/proc/do_cpr(mob/living/carbon/C) + CHECK_DNA_AND_SPECIES(C) + + if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) + to_chat(src, "[C.name] is dead!") + return + if(is_mouth_covered()) + to_chat(src, "Remove your mask first!") + return 0 + if(C.is_mouth_covered()) + to_chat(src, "Remove [p_their()] mask first!") + return 0 + + if(C.cpr_time < world.time + 30) + visible_message("[src] is trying to perform CPR on [C.name]!", \ + "You try to perform CPR on [C.name]... Hold still!") + if(!do_mob(src, C)) + to_chat(src, "You fail to perform CPR on [C]!") + return 0 + + var/they_breathe = !HAS_TRAIT(C, TRAIT_NOBREATH) + var/they_lung = C.getorganslot(ORGAN_SLOT_LUNGS) + + if(C.health > C.crit_threshold) + return + + src.visible_message("[src] performs CPR on [C.name]!", "You perform CPR on [C.name].") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "perform_cpr", /datum/mood_event/perform_cpr) + C.cpr_time = world.time + log_combat(src, C, "CPRed") + + if(they_breathe && they_lung) + var/suff = min(C.getOxyLoss(), 7) + C.adjustOxyLoss(-suff) + C.updatehealth() + to_chat(C, "You feel a breath of fresh air enter your lungs... It feels good...") + else if(they_breathe && !they_lung) + to_chat(C, "You feel a breath of fresh air... but you don't feel any better...") + else + to_chat(C, "You feel a breath of fresh air... which is a sensation you don't recognise...") + +/mob/living/carbon/human/cuff_resist(obj/item/I) + if(dna && dna.check_mutation(HULK)) + say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") + if(..(I, cuff_break = FAST_CUFFBREAK)) + dropItemToGround(I) + else + if(..()) + dropItemToGround(I) + +/mob/living/carbon/human/clean_blood() + var/mob/living/carbon/human/H = src + if(H.gloves) + if(H.gloves.clean_blood()) + H.update_inv_gloves() + else + ..() // Clear the Blood_DNA list + if(H.bloody_hands) + H.bloody_hands = 0 + H.update_inv_gloves() + update_icons() //apply the now updated overlays to the mob + +/mob/living/carbon/human/wash_cream() + if(creamed) //clean both to prevent a rare bug + cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_lizard")) + cut_overlay(mutable_appearance('icons/effects/creampie.dmi', "creampie_human")) + creamed = FALSE + +//Turns a mob black, flashes a skeleton overlay +//Just like a cartoon! +/mob/living/carbon/human/proc/electrocution_animation(anim_duration) + //Handle mutant parts if possible + if(dna && dna.species) + add_atom_colour("#000000", TEMPORARY_COLOUR_PRIORITY) + var/static/mutable_appearance/electrocution_skeleton_anim + if(!electrocution_skeleton_anim) + electrocution_skeleton_anim = mutable_appearance(icon, "electrocuted_base") + electrocution_skeleton_anim.appearance_flags |= RESET_COLOR|KEEP_APART + add_overlay(electrocution_skeleton_anim) + addtimer(CALLBACK(src, .proc/end_electrocution_animation, electrocution_skeleton_anim), anim_duration) + + else //or just do a generic animation + flick_overlay_view(image(icon,src,"electrocuted_generic",ABOVE_MOB_LAYER), src, anim_duration) + +/mob/living/carbon/human/proc/end_electrocution_animation(mutable_appearance/MA) + remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, "#000000") + cut_overlay(MA) + +/mob/living/carbon/human/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) + if(incapacitated() || lying ) + to_chat(src, "You can't do that right now!") + return FALSE + if(!Adjacent(M) && (M.loc != src)) + if((be_close == 0) || (!no_tk && (dna.check_mutation(TK) && tkMaxRangeCheck(src, M)))) + return TRUE + to_chat(src, "You are too far away!") + return FALSE + return TRUE + +/mob/living/carbon/human/resist_restraints() + if(wear_suit && wear_suit.breakouttime) + changeNext_move(CLICK_CD_BREAKOUT) + last_special = world.time + CLICK_CD_BREAKOUT + cuff_resist(wear_suit) + else + ..() + +/mob/living/carbon/human/replace_records_name(oldname,newname) // Only humans have records right now, move this up if changed. + for(var/list/L in list(GLOB.data_core.general,GLOB.data_core.medical,GLOB.data_core.security,GLOB.data_core.locked)) + var/datum/data/record/R = find_record("name", oldname, L) + if(R) + R.fields["name"] = newname + +/mob/living/carbon/human/get_total_tint() + . = ..() + if(glasses) + . += glasses.tint + +/mob/living/carbon/human/update_health_hud() + if(!client || !hud_used) + return + if(dna.species.update_health_hud()) + return + else + if(hud_used.healths) + var/health_amount = health - CLAMP(getStaminaLoss()-50, 0, 80)//CIT CHANGE - makes staminaloss have less of an impact on the health hud + if(..(health_amount)) //not dead + switch(hal_screwyhud) + if(SCREWYHUD_CRIT) + hud_used.healths.icon_state = "health6" + if(SCREWYHUD_DEAD) + hud_used.healths.icon_state = "health7" + if(SCREWYHUD_HEALTHY) + hud_used.healths.icon_state = "health0" + if(hud_used.healthdoll) + hud_used.healthdoll.cut_overlays() + if(stat != DEAD) + hud_used.healthdoll.icon_state = "healthdoll_OVERLAY" + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + var/damage = BP.burn_dam + BP.brute_dam + var/comparison = (BP.max_damage/5) + var/icon_num = 0 + if(damage) + icon_num = 1 + if(damage > (comparison)) + icon_num = 2 + if(damage > (comparison*2)) + icon_num = 3 + if(damage > (comparison*3)) + icon_num = 4 + if(damage > (comparison*4)) + icon_num = 5 + if(hal_screwyhud == SCREWYHUD_HEALTHY) + icon_num = 0 + if(icon_num) + hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[BP.body_zone][icon_num]")) + for(var/t in get_missing_limbs()) //Missing limbs + hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[t]6")) + for(var/t in get_disabled_limbs()) //Disabled limbs + hud_used.healthdoll.add_overlay(mutable_appearance('icons/mob/screen_gen.dmi', "[t]7")) + else + hud_used.healthdoll.icon_state = "healthdoll_DEAD" + + hud_used.staminas?.update_icon_state() + hud_used.staminabuffer?.update_icon_state() + +/mob/living/carbon/human/fully_heal(admin_revive = 0) + if(admin_revive) + regenerate_limbs() + regenerate_organs() + remove_all_embedded_objects() + set_heartattack(FALSE) + drunkenness = 0 + for(var/datum/mutation/human/HM in dna.mutations) + if(HM.quality != POSITIVE) + dna.remove_mutation(HM.name) + if(blood_volume < (BLOOD_VOLUME_NORMAL*blood_ratio)) + blood_volume = (BLOOD_VOLUME_NORMAL*blood_ratio) + ..() + +/mob/living/carbon/human/check_weakness(obj/item/weapon, mob/living/attacker) + . = ..() + if (dna && dna.species) + . += dna.species.check_weakness(weapon, attacker) + +/mob/living/carbon/human/is_literate() + return 1 + +/mob/living/carbon/human/can_hold_items() + return TRUE + +/mob/living/carbon/human/update_gravity(has_gravity,override = 0) + if(dna && dna.species) //prevents a runtime while a human is being monkeyfied + override = dna.species.override_float + ..() + +/mob/living/carbon/human/vomit(lost_nutrition = 10, blood = 0, stun = 1, distance = 0, message = 1, toxic = 0) + if(blood && (NOBLOOD in dna.species.species_traits)) + if(message) + visible_message("[src] dry heaves!", \ + "You try to throw up, but there's nothing in your stomach!") + if(stun) + Knockdown(200) + return 1 + ..() + +/mob/living/carbon/human/vv_get_dropdown() + . = ..() + . += "---" + .["Make monkey"] = "?_src_=vars;[HrefToken()];makemonkey=[REF(src)]" + .["Set Species"] = "?_src_=vars;[HrefToken()];setspecies=[REF(src)]" + .["Make cyborg"] = "?_src_=vars;[HrefToken()];makerobot=[REF(src)]" + .["Make alien"] = "?_src_=vars;[HrefToken()];makealien=[REF(src)]" + .["Make slime"] = "?_src_=vars;[HrefToken()];makeslime=[REF(src)]" + .["Toggle Purrbation"] = "?_src_=vars;[HrefToken()];purrbation=[REF(src)]" + .["Copy outfit"] = "?_src_=vars;[HrefToken()];copyoutfit=[REF(src)]" + .["Add/Remove Quirks"] = "?_src_=vars;[HrefToken()];modquirks=[REF(src)]" + +/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user) + if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS) + //If they dragged themselves and we're currently aggressively grabbing them try to piggyback + if(user == target && can_piggyback(target)) + piggyback(target) + return + //If you dragged them to you and you're aggressively grabbing try to fireman carry them + else if(user != target) + if(user.a_intent == INTENT_GRAB) + fireman_carry(target) + return + . = ..() + +//src is the user that will be carrying, target is the mob to be carried +/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/target) + return (istype(target) && target.stat == CONSCIOUS) + +/mob/living/carbon/human/proc/can_be_firemanned(mob/living/carbon/target) + return (ishuman(target) && target.lying) + +/mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target) + if(can_be_firemanned(target)) + visible_message("[src] starts lifting [target] onto their back...", + "You start lifting [target] onto your back...") + if(do_after(src, 30, TRUE, target)) + //Second check to make sure they're still valid to be carried + if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE)) + target.resting = FALSE + buckle_mob(target, TRUE, TRUE, 90, 1, 0) + return + visible_message("[src] fails to fireman carry [target]!") + else + if (ishuman(target)) + to_chat(src, "You can't fireman carry [target] while they're standing!") + else + to_chat(src, "You can't seem to fireman carry that kind of species.") + +/mob/living/carbon/human/proc/piggyback(mob/living/carbon/target) + if(can_piggyback(target)) + visible_message("[target] starts to climb onto [src]...") + if(do_after(target, 15, target = src)) + if(can_piggyback(target)) + if(target.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE)) + target.visible_message("[target] can't hang onto [src]!") + return + buckle_mob(target, TRUE, TRUE, FALSE, 0, 2) + else + visible_message("[target] fails to climb onto [src]!") + else + to_chat(target, "You can't piggyback ride [src] right now!") + +/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = FALSE, hands_needed = 0, target_hands_needed = 0) + if(!force)//humans are only meant to be ridden through piggybacking and special cases + return + if(!is_type_in_typecache(target, can_ride_typecache)) + target.visible_message("[target] really can't seem to mount [src]...") + return + buckle_lying = lying_buckle + var/datum/component/riding/human/riding_datum = LoadComponent(/datum/component/riding/human) + if(target_hands_needed) + riding_datum.ride_check_rider_restrained = TRUE + if(buckled_mobs && ((target in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled) + return + var/equipped_hands_self + var/equipped_hands_target + if(hands_needed) + equipped_hands_self = riding_datum.equip_buckle_inhands(src, hands_needed, target) + if(target_hands_needed) + equipped_hands_target = riding_datum.equip_buckle_inhands(target, target_hands_needed) + + if(hands_needed || target_hands_needed) + if(hands_needed && !equipped_hands_self) + src.visible_message("[src] can't get a grip on [target] because their hands are full!", + "You can't get a grip on [target] because your hands are full!") + return + else if(target_hands_needed && !equipped_hands_target) + target.visible_message("[target] can't get a grip on [src] because their hands are full!", + "You can't get a grip on [src] because your hands are full!") + return + + stop_pulling() + riding_datum.handle_vehicle_layer() + . = ..(target, force, check_loc) + +/mob/living/carbon/human/proc/is_shove_knockdown_blocked() //If you want to add more things that block shove knockdown, extend this + for(var/obj/item/clothing/C in get_equipped_items()) //doesn't include pockets + if(C.blocks_shove_knockdown) + return TRUE + return FALSE + +/mob/living/carbon/human/proc/clear_shove_slowdown() + remove_movespeed_modifier(MOVESPEED_ID_SHOVE) + var/active_item = get_active_held_item() + if(is_type_in_typecache(active_item, GLOB.shove_disarming_types)) + visible_message("[src.name] regains their grip on \the [active_item]!", "You regain your grip on \the [active_item]", null, COMBAT_MESSAGE_RANGE) + +/mob/living/carbon/human/do_after_coefficent() + . = ..() + . *= physiology.do_after_speed + +/mob/living/carbon/human/species + var/race = null + +/mob/living/carbon/human/species/Initialize() + . = ..() + set_species(race) + +/mob/living/carbon/human/species/abductor + race = /datum/species/abductor + +/mob/living/carbon/human/species/android + race = /datum/species/android + +/mob/living/carbon/human/species/angel + race = /datum/species/angel + +/mob/living/carbon/human/species/corporate + race = /datum/species/corporate + +/mob/living/carbon/human/species/dullahan + race = /datum/species/dullahan + +/mob/living/carbon/human/species/felinid + race = /datum/species/human/felinid + +/mob/living/carbon/human/species/fly + race = /datum/species/fly + +/mob/living/carbon/human/species/golem + race = /datum/species/golem + +/mob/living/carbon/human/species/golem/random + race = /datum/species/golem/random + +/mob/living/carbon/human/species/golem/adamantine + race = /datum/species/golem/adamantine + +/mob/living/carbon/human/species/golem/plasma + race = /datum/species/golem/plasma + +/mob/living/carbon/human/species/golem/diamond + race = /datum/species/golem/diamond + +/mob/living/carbon/human/species/golem/gold + race = /datum/species/golem/gold + +/mob/living/carbon/human/species/golem/silver + race = /datum/species/golem/silver + +/mob/living/carbon/human/species/golem/plasteel + race = /datum/species/golem/plasteel + +/mob/living/carbon/human/species/golem/titanium + race = /datum/species/golem/titanium + +/mob/living/carbon/human/species/golem/plastitanium + race = /datum/species/golem/plastitanium + +/mob/living/carbon/human/species/golem/alien_alloy + race = /datum/species/golem/alloy + +/mob/living/carbon/human/species/golem/wood + race = /datum/species/golem/wood + +/mob/living/carbon/human/species/golem/uranium + race = /datum/species/golem/uranium + +/mob/living/carbon/human/species/golem/sand + race = /datum/species/golem/sand + +/mob/living/carbon/human/species/golem/glass + race = /datum/species/golem/glass + +/mob/living/carbon/human/species/golem/bluespace + race = /datum/species/golem/bluespace + +/mob/living/carbon/human/species/golem/bananium + race = /datum/species/golem/bananium + +/mob/living/carbon/human/species/golem/blood_cult + race = /datum/species/golem/runic + +/mob/living/carbon/human/species/golem/cloth + race = /datum/species/golem/cloth + +/mob/living/carbon/human/species/golem/plastic + race = /datum/species/golem/plastic + +/mob/living/carbon/human/species/golem/bronze + race = /datum/species/golem/bronze + +/mob/living/carbon/human/species/golem/cardboard + race = /datum/species/golem/cardboard + +/mob/living/carbon/human/species/golem/leather + race = /datum/species/golem/leather + +/mob/living/carbon/human/species/golem/bone + race = /datum/species/golem/bone + +/mob/living/carbon/human/species/golem/durathread + race = /datum/species/golem/durathread + +/mob/living/carbon/human/species/golem/clockwork + race = /datum/species/golem/clockwork + +/mob/living/carbon/human/species/golem/clockwork/no_scrap + race = /datum/species/golem/clockwork/no_scrap + +/mob/living/carbon/human/species/jelly + race = /datum/species/jelly + +/mob/living/carbon/human/species/jelly/slime + race = /datum/species/jelly/slime + +/mob/living/carbon/human/species/jelly/stargazer + race = /datum/species/jelly/stargazer + +/mob/living/carbon/human/species/jelly/luminescent + race = /datum/species/jelly/luminescent + +/mob/living/carbon/human/species/lizard + race = /datum/species/lizard + +/mob/living/carbon/human/species/lizard/ashwalker + race = /datum/species/lizard/ashwalker + +/mob/living/carbon/human/species/insect + race = /datum/species/insect + +/mob/living/carbon/human/species/mush + race = /datum/species/mush + +/mob/living/carbon/human/species/plasma + race = /datum/species/plasmaman + +/mob/living/carbon/human/species/pod + race = /datum/species/pod + +/mob/living/carbon/human/species/shadow + race = /datum/species/shadow + +/mob/living/carbon/human/species/shadow/nightmare + race = /datum/species/shadow/nightmare + +/mob/living/carbon/human/species/skeleton + race = /datum/species/skeleton + +/mob/living/carbon/human/species/synth + race = /datum/species/synth + +/mob/living/carbon/human/species/synth/military + race = /datum/species/synth/military + +/mob/living/carbon/human/species/vampire + race = /datum/species/vampire + +/mob/living/carbon/human/species/zombie + race = /datum/species/zombie + +/mob/living/carbon/human/species/zombie/infectious + race = /datum/species/zombie/infectious + +/mob/living/carbon/human/species/zombie/krokodil_addict + race = /datum/species/krokodil_addict + +/mob/living/carbon/human/species/mammal + race = /datum/species/mammal + +/mob/living/carbon/human/species/insect + race = /datum/species/insect + +/mob/living/carbon/human/species/xeno + race = /datum/species/xeno + +/mob/living/carbon/human/species/ipc + race = /datum/species/ipc + +/mob/living/carbon/human/species/roundstartslime + race = /datum/species/jelly/roundstartslime diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 94bc2ea2e3..c5d3ec18f9 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -1,819 +1,819 @@ -/mob/living/carbon/human/getarmor(def_zone, type) - var/armorval = 0 - var/organnum = 0 - - if(def_zone) - if(isbodypart(def_zone)) - var/obj/item/bodypart/bp = def_zone - if(bp.body_part) - return checkarmor(def_zone, type) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(def_zone)) - return checkarmor(affecting, type) - //If a specific bodypart is targetted, check how that bodypart is protected and return the value. - - //If you don't specify a bodypart, it checks ALL your bodyparts for protection, and averages out the values - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - armorval += checkarmor(BP, type) - organnum++ - return (armorval/max(organnum, 1)) - - -/mob/living/carbon/human/proc/checkarmor(obj/item/bodypart/def_zone, d_type) - if(!d_type) - return 0 - var/protection = 0 - var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, ears, wear_id, wear_neck) //Everything but pockets. Pockets are l_store and r_store. (if pockets were allowed, putting something armored, gloves or hats for example, would double up on the armor) - for(var/bp in body_parts) - if(!bp) - continue - if(istype(bp, /obj/item/clothing)) - var/obj/item/clothing/C = bp - if(C.body_parts_covered & def_zone.body_part) - protection += C.armor.getRating(d_type) - protection += physiology.armor.getRating(d_type) - return protection - -/mob/living/carbon/human/on_hit(obj/item/projectile/P) - if(dna && dna.species) - dna.species.on_hit(P, src) - - -/mob/living/carbon/human/bullet_act(obj/item/projectile/P, def_zone) - if(dna && dna.species) - var/spec_return = dna.species.bullet_act(P, src) - if(spec_return) - return spec_return - - if(mind) - if (mind.martial_art && mind.martial_art.dodge_chance) - if(!lying && dna && !dna.check_mutation(HULK)) - if(prob(mind.martial_art.dodge_chance)) - var/dodgemessage = pick("dodges under the projectile!","dodges to the right of the projectile!","jumps over the projectile!") - visible_message("[src] [dodgemessage]", "You dodge the projectile!") - return -1 - if(mind.martial_art && !incapacitated(FALSE, TRUE) && mind.martial_art.can_use(src) && mind.martial_art.deflection_chance) //Some martial arts users can deflect projectiles! - if(prob(mind.martial_art.deflection_chance)) - if(!lying && dna && !dna.check_mutation(HULK)) //But only if they're not lying down, and hulks can't do it - if(mind.martial_art.deflection_chance >= 100) //if they can NEVER be hit, lets clue sec in ;) - visible_message("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons!", "You deflect the projectile!") - else - visible_message("[src] deflects the projectile!", "You deflect the projectile!") - playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, 1) - if(!mind.martial_art.reroute_deflection) - return FALSE - else - P.firer = src - P.setAngle(rand(0, 360))//SHING - return FALSE - - return ..() - -/mob/living/carbon/human/check_reflect(def_zone) - if(wear_suit?.IsReflect(def_zone)) - return TRUE - return ..() - -/mob/living/carbon/human/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) - . = ..() - if(.) - return - var/block_chance_modifier = round(damage / -3) - if(wear_suit) - var/final_block_chance = wear_suit.block_chance - (CLAMP((armour_penetration-wear_suit.armour_penetration)/2,0,100)) + block_chance_modifier - if(wear_suit.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return TRUE - if(w_uniform) - var/final_block_chance = w_uniform.block_chance - (CLAMP((armour_penetration-w_uniform.armour_penetration)/2,0,100)) + block_chance_modifier - if(w_uniform.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return TRUE - if(wear_neck) - var/final_block_chance = wear_neck.block_chance - (CLAMP((armour_penetration-wear_neck.armour_penetration)/2,0,100)) + block_chance_modifier - if(wear_neck.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return TRUE - return FALSE - -/mob/living/carbon/human/can_embed(obj/item/I) - if(I.get_sharpness() || is_pointed(I) || is_type_in_typecache(I, GLOB.can_embed_types)) - return TRUE - return FALSE - -/mob/living/carbon/human/proc/check_block() - if(mind) - if(mind.martial_art && prob(mind.martial_art.block_chance) && mind.martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) - return TRUE - return FALSE - -/mob/living/carbon/human/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE) - return dna?.species?.spec_hitby(AM, src) || ..() - -/mob/living/carbon/human/grabbedby(mob/living/carbon/user, supress_message = 0) - if(user == src && pulling && !pulling.anchored && grab_state >= GRAB_AGGRESSIVE && (HAS_TRAIT(src, TRAIT_FAT)) && ismonkey(pulling)) - devour_mob(pulling) - else - ..() - -/mob/living/carbon/human/grippedby(mob/living/user, instant = FALSE) - if(w_uniform) - w_uniform.add_fingerprint(user) - ..() - - -/mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user) - if(!I || !user) - return 0 - - var/obj/item/bodypart/affecting - if(user == src) - affecting = get_bodypart(check_zone(user.zone_selected)) //stabbing yourself always hits the right target - else - affecting = get_bodypart(ran_zone(user.zone_selected)) - var/target_area = parse_zone(check_zone(user.zone_selected)) //our intended target - - SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting) - - SSblackbox.record_feedback("nested tally", "item_used_for_combat", 1, list("[I.force]", "[I.type]")) - SSblackbox.record_feedback("tally", "zone_targeted", 1, target_area) - - // the attacked_by code varies among species - return dna.species.spec_attacked_by(I, user, affecting, a_intent, src) - - -/mob/living/carbon/human/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) - if(user.a_intent == INTENT_HARM) - . = ..(user, TRUE) - if(.) - return - var/hulk_verb = pick("smash","pummel") - playsound(loc, user.dna.species.attack_sound, 25, 1, -1) - var/message = "[user] has [hulk_verb]ed [src]!" - visible_message("[message]", \ - "[message]") - adjustBruteLoss(15) - return 1 - -/mob/living/carbon/human/attack_hand(mob/user) - . = ..() - if(.) //To allow surgery to return properly. - return - if(ishuman(user)) - var/mob/living/carbon/human/H = user - dna.species.spec_attack_hand(H, src) - -/mob/living/carbon/human/attack_paw(mob/living/carbon/monkey/M) - 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 = get_bodypart(ran_zone(dam_zone)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - if(M.a_intent == INTENT_HELP) - return ..() //shaking - - if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stunned instead. - var/obj/item/I = get_active_held_item() - if(I && dropItemToGround(I)) - playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) - visible_message("[M] disarmed [src]!", \ - "[M] disarmed [src]!") - else if(!M.client || prob(5)) // only natural monkeys get to stun reliably, (they only do it occasionaly) - playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) - Knockdown(100) - log_combat(M, src, "tackled") - visible_message("[M] has tackled down [src]!", \ - "[M] has tackled down [src]!") - - if(M.limb_destroyer) - dismembering_strike(M, affecting.body_zone) - - if(can_inject(M, 1, affecting))//Thick suits can stop monkey bites. - if(..()) //successful monkey bite, this handles disease contraction. - var/damage = rand(1, 3) - apply_damage(damage, BRUTE, affecting, run_armor_check(affecting, "melee")) - return 1 - -/mob/living/carbon/human/attack_alien(mob/living/carbon/alien/humanoid/M) - . = ..() - if(!.) - return - if(M.a_intent == INTENT_HARM) - if (w_uniform) - w_uniform.add_fingerprint(M) - var/damage = prob(90) ? 20 : 0 - if(!damage) - playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1) - visible_message("[M] has lunged at [src]!", \ - "[M] has lunged at [src]!") - return 0 - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armor_block = run_armor_check(affecting, "melee", null, null,10) - - playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) - visible_message("[M] has slashed at [src]!", \ - "[M] has slashed at [src]!") - log_combat(M, src, "attacked") - if(!dismembering_strike(M, M.zone_selected)) //Dismemberment successful - return 1 - apply_damage(damage, BRUTE, affecting, armor_block) - - if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stun instead. - var/obj/item/I = get_active_held_item() - if(I && dropItemToGround(I)) - playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) - visible_message("[M] disarmed [src]!", \ - "[M] disarmed [src]!") - else - playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) - if(!lying) //CITADEL EDIT - Knockdown(100, TRUE, FALSE, 30, 25) - else - Knockdown(100) - log_combat(M, src, "tackled") - visible_message("[M] has tackled down [src]!", \ - "[M] has tackled down [src]!") - -/mob/living/carbon/human/attack_larva(mob/living/carbon/alien/larva/L) - . = ..() - if(!.) //unsuccessful larva bite. - return - var/damage = rand(1, 3) - if(stat != DEAD) - L.amount_grown = min(L.amount_grown + damage, L.max_grown) - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(L.zone_selected)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armor_block = run_armor_check(affecting, "melee") - apply_damage(damage, BRUTE, affecting, armor_block) - - -/mob/living/carbon/human/attack_animal(mob/living/simple_animal/M) - . = ..() - if(.) - var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) - var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - if(!dam_zone) //Dismemberment successful - return TRUE - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armor = run_armor_check(affecting, "melee", armour_penetration = M.armour_penetration) - apply_damage(damage, M.melee_damage_type, affecting, armor) - - -/mob/living/carbon/human/attack_slime(mob/living/simple_animal/slime/M) - . = ..() - if(!.) //unsuccessful slime attack - return - var/damage = rand(5, 25) - if(M.is_adult) - damage = rand(10, 35) - - var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) - if(!dam_zone) //Dismemberment successful - return 1 - - var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) - if(!affecting) - affecting = get_bodypart(BODY_ZONE_CHEST) - var/armor_block = run_armor_check(affecting, "melee") - apply_damage(damage, BRUTE, affecting, armor_block) - -/mob/living/carbon/human/mech_melee_attack(obj/mecha/M) - if(M.occupant.a_intent == INTENT_HARM) - if(HAS_TRAIT(M.occupant, TRAIT_PACIFISM)) - to_chat(M.occupant, "You don't want to harm other living beings!") - return - M.do_attack_animation(src) - if(M.damtype == "brute") - step_away(src,M,15) - var/obj/item/bodypart/temp = get_bodypart(pick(BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_HEAD)) - if(temp) - var/update = 0 - var/dmg = rand(M.force/2, M.force) - var/atom/throw_target = get_edge_target_turf(src, M.dir) - switch(M.damtype) - if("brute") - if(M.force > 35) // durand and other heavy mechas - Knockdown(50) - src.throw_at(throw_target, rand(1,5), 7) - else if(M.force >= 20 && !IsKnockdown()) // lightweight mechas like gygax - Knockdown(30) - src.throw_at(throw_target, rand(1,3), 7) - update |= temp.receive_damage(dmg, 0) - playsound(src, 'sound/weapons/punch4.ogg', 50, 1) - if("fire") - update |= temp.receive_damage(0, dmg) - playsound(src, 'sound/items/welder.ogg', 50, 1) - if("tox") - M.mech_toxin_damage(src) - else - return - if(update) - update_damage_overlays() - updatehealth() - - visible_message("[M.name] has hit [src]!", \ - "[M.name] has hit [src]!", null, COMBAT_MESSAGE_RANGE) - log_combat(M.occupant, src, "attacked", M, "(INTENT: [uppertext(M.occupant.a_intent)]) (DAMTYPE: [uppertext(M.damtype)])") - - else - ..() - - -/mob/living/carbon/human/ex_act(severity, target, origin) - if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) - return - ..() - if (!severity) - return - var/brute_loss = 0 - var/burn_loss = 0 - var/bomb_armor = getarmor(null, "bomb") - - //200 max knockdown for EXPLODE_HEAVY - //160 max knockdown for EXPLODE_LIGHT - - switch (severity) - if (EXPLODE_DEVASTATE) - if(bomb_armor < EXPLODE_GIB_THRESHOLD) //gibs the mob if their bomb armor is lower than EXPLODE_GIB_THRESHOLD - for(var/I in contents) - var/atom/A = I - A.ex_act(severity) - gib() - return - else - brute_loss = 500 - var/atom/throw_target = get_edge_target_turf(src, get_dir(src, get_step_away(src, src))) - throw_at(throw_target, 200, 4) - damage_clothes(400 - bomb_armor, BRUTE, "bomb") - - if (EXPLODE_HEAVY) - brute_loss = 60 - burn_loss = 60 - if(bomb_armor) - brute_loss = 30*(2 - round(bomb_armor*0.01, 0.05)) - burn_loss = brute_loss //damage gets reduced from 120 to up to 60 combined brute+burn - damage_clothes(200 - bomb_armor, BRUTE, "bomb") - if (!istype(ears, /obj/item/clothing/ears/earmuffs)) - adjustEarDamage(30, 120) - Unconscious(20) //short amount of time for follow up attacks against elusive enemies like wizards - Knockdown(200 - (bomb_armor * 1.6)) //between ~4 and ~20 seconds of knockdown depending on bomb armor - - if(EXPLODE_LIGHT) - brute_loss = 30 - if(bomb_armor) - brute_loss = 15*(2 - round(bomb_armor*0.01, 0.05)) - damage_clothes(max(50 - bomb_armor, 0), BRUTE, "bomb") - if (!istype(ears, /obj/item/clothing/ears/earmuffs)) - adjustEarDamage(15,60) - Knockdown(160 - (bomb_armor * 1.6)) //100 bomb armor will prevent knockdown altogether - - take_overall_damage(brute_loss,burn_loss) - - //attempt to dismember bodyparts - if(severity <= 2 || !bomb_armor) - var/max_limb_loss = round(4/severity) //so you don't lose four limbs at severity 3. - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - if(prob(50/severity) && !prob(getarmor(BP, "bomb")) && BP.body_zone != BODY_ZONE_HEAD && BP.body_zone != BODY_ZONE_CHEST) - BP.brute_dam = BP.max_damage - BP.dismember() - max_limb_loss-- - if(!max_limb_loss) - break - - -/mob/living/carbon/human/blob_act(obj/structure/blob/B) - if(stat == DEAD) - return - show_message("The blob attacks you!") - 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 = get_bodypart(ran_zone(dam_zone)) - apply_damage(5, BRUTE, affecting, run_armor_check(affecting, "melee")) - - -//Added a safety check in case you want to shock a human mob directly through electrocute_act. -/mob/living/carbon/human/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = 0, override = 0, tesla_shock = 0, illusion = 0, stun = TRUE) - if(tesla_shock) - var/total_coeff = 1 - if(gloves) - var/obj/item/clothing/gloves/G = gloves - if(G.siemens_coefficient <= 0) - total_coeff -= 0.5 - if(wear_suit) - var/obj/item/clothing/suit/S = wear_suit - if(S.siemens_coefficient <= 0) - total_coeff -= 0.95 - else if(S.siemens_coefficient == (-1)) - total_coeff -= 1 - siemens_coeff = total_coeff - if(flags_1 & TESLA_IGNORE_1) - siemens_coeff = 0 - else if(!safety) - var/gloves_siemens_coeff = 1 - if(gloves) - var/obj/item/clothing/gloves/G = gloves - gloves_siemens_coeff = G.siemens_coefficient - siemens_coeff = gloves_siemens_coeff - if(undergoing_cardiac_arrest() && !illusion) - if(shock_damage * siemens_coeff >= 1 && prob(25)) - var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART) - heart.beating = TRUE - if(stat == CONSCIOUS) - to_chat(src, "You feel your heart beating again!") - siemens_coeff *= physiology.siemens_coeff - . = ..(shock_damage,source,siemens_coeff,safety,override,tesla_shock, illusion, stun) - if(.) - electrocution_animation(40) - - -/mob/living/carbon/human/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_CONTENTS) - return - var/informed = FALSE - for(var/obj/item/bodypart/L in src.bodyparts) - if(L.status == BODYPART_ROBOTIC) - if(!informed) - to_chat(src, "You feel a sharp pain as your robotic limbs overload.") - informed = TRUE - switch(severity) - if(1) - L.receive_damage(0,10) - Stun(200) - if(2) - L.receive_damage(0,5) - Stun(100) - -/mob/living/carbon/human/acid_act(acidpwr, acid_volume, bodyzone_hit) - var/list/damaged = list() - var/list/inventory_items_to_kill = list() - var/acidity = acidpwr * min(acid_volume*0.005, 0.1) - //HEAD// - if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_HEAD) //only if we didn't specify a zone or if that zone is the head. - var/obj/item/clothing/head_clothes = null - if(glasses) - head_clothes = glasses - if(wear_mask) - head_clothes = wear_mask - if(wear_neck) - head_clothes = wear_neck - if(head) - head_clothes = head - if(head_clothes) - if(!(head_clothes.resistance_flags & UNACIDABLE)) - head_clothes.acid_act(acidpwr, acid_volume) - update_inv_glasses() - update_inv_wear_mask() - update_inv_neck() - update_inv_head() - else - to_chat(src, "Your [head_clothes.name] protects your head and face from the acid!") - else - . = get_bodypart(BODY_ZONE_HEAD) - if(.) - damaged += . - if(ears) - inventory_items_to_kill += ears - - //CHEST// - if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_CHEST) - var/obj/item/clothing/chest_clothes = null - if(w_uniform) - chest_clothes = w_uniform - if(wear_suit) - chest_clothes = wear_suit - if(chest_clothes) - if(!(chest_clothes.resistance_flags & UNACIDABLE)) - chest_clothes.acid_act(acidpwr, acid_volume) - update_inv_w_uniform() - update_inv_wear_suit() - else - to_chat(src, "Your [chest_clothes.name] protects your body from the acid!") - else - . = get_bodypart(BODY_ZONE_CHEST) - if(.) - damaged += . - if(wear_id) - inventory_items_to_kill += wear_id - if(r_store) - inventory_items_to_kill += r_store - if(l_store) - inventory_items_to_kill += l_store - if(s_store) - inventory_items_to_kill += s_store - - - //ARMS & HANDS// - if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_L_ARM || bodyzone_hit == BODY_ZONE_R_ARM) - var/obj/item/clothing/arm_clothes = null - if(gloves) - arm_clothes = gloves - if(w_uniform && ((w_uniform.body_parts_covered & HANDS) || (w_uniform.body_parts_covered & ARMS))) - arm_clothes = w_uniform - if(wear_suit && ((wear_suit.body_parts_covered & HANDS) || (wear_suit.body_parts_covered & ARMS))) - arm_clothes = wear_suit - - if(arm_clothes) - if(!(arm_clothes.resistance_flags & UNACIDABLE)) - arm_clothes.acid_act(acidpwr, acid_volume) - update_inv_gloves() - update_inv_w_uniform() - update_inv_wear_suit() - else - to_chat(src, "Your [arm_clothes.name] protects your arms and hands from the acid!") - else - . = get_bodypart(BODY_ZONE_R_ARM) - if(.) - damaged += . - . = get_bodypart(BODY_ZONE_L_ARM) - if(.) - damaged += . - - - //LEGS & FEET// - if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_L_LEG || bodyzone_hit == BODY_ZONE_R_LEG || bodyzone_hit == "feet") - var/obj/item/clothing/leg_clothes = null - if(shoes) - leg_clothes = shoes - if(w_uniform && ((w_uniform.body_parts_covered & FEET) || (bodyzone_hit != "feet" && (w_uniform.body_parts_covered & LEGS)))) - leg_clothes = w_uniform - if(wear_suit && ((wear_suit.body_parts_covered & FEET) || (bodyzone_hit != "feet" && (wear_suit.body_parts_covered & LEGS)))) - leg_clothes = wear_suit - if(leg_clothes) - if(!(leg_clothes.resistance_flags & UNACIDABLE)) - leg_clothes.acid_act(acidpwr, acid_volume) - update_inv_shoes() - update_inv_w_uniform() - update_inv_wear_suit() - else - to_chat(src, "Your [leg_clothes.name] protects your legs and feet from the acid!") - else - . = get_bodypart(BODY_ZONE_R_LEG) - if(.) - damaged += . - . = get_bodypart(BODY_ZONE_L_LEG) - if(.) - damaged += . - - - //DAMAGE// - for(var/obj/item/bodypart/affecting in damaged) - affecting.receive_damage(acidity, 2*acidity) - - if(affecting.name == BODY_ZONE_HEAD) - if(prob(min(acidpwr*acid_volume/10, 90))) //Applies disfigurement - affecting.receive_damage(acidity, 2*acidity) - emote("scream") - facial_hair_style = "Shaved" - hair_style = "Bald" - update_hair() - ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) - - update_damage_overlays() - - //MELTING INVENTORY ITEMS// - //these items are all outside of armour visually, so melt regardless. - if(!bodyzone_hit) - if(back) - inventory_items_to_kill += back - if(belt) - inventory_items_to_kill += belt - - inventory_items_to_kill += held_items - - for(var/obj/item/I in inventory_items_to_kill) - I.acid_act(acidpwr, acid_volume) - return 1 - -/mob/living/carbon/human/singularity_act() - var/gain = 20 - if(mind) - if((mind.assigned_role == "Station Engineer") || (mind.assigned_role == "Chief Engineer") ) - gain = 100 - if(HAS_TRAIT(mind, TRAIT_CLOWN_MENTALITY)) - gain = rand(-300, 300) - investigate_log("([key_name(src)]) has been consumed by the singularity.", INVESTIGATE_SINGULO) //Oh that's where the clown ended up! - gib() - return(gain) - -/mob/living/carbon/human/help_shake_act(mob/living/carbon/M) - if(!istype(M)) - return - - if(health >= 0) - if(src == M) - if(has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) - to_chat(src, "You attempt to remove the durathread strand from around your neck.") - if(do_after(src, 35, null, src)) - to_chat(src, "You succesfuly remove the durathread strand.") - remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) - return - var/to_send = "" - visible_message("[src] examines [p_them()]self.", \ - "You check yourself for injuries.") - - var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) - for(var/X in bodyparts) - var/obj/item/bodypart/LB = X - missing -= LB.body_zone - if(LB.is_pseudopart) //don't show injury text for fake bodyparts; ie chainsaw arms or synthetic armblades - continue - var/limb_max_damage = LB.max_damage - var/status = "" - var/brutedamage = LB.brute_dam - var/burndamage = LB.burn_dam - if(hallucination) - if(prob(30)) - brutedamage += rand(30,40) - if(prob(30)) - burndamage += rand(30,40) - - if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) - status = "[brutedamage] brute damage and [burndamage] burn damage" - if(!brutedamage && !burndamage) - status = "no damage" - - else - if(brutedamage > 0) - status = LB.light_brute_msg - if(brutedamage > (limb_max_damage*0.4)) - status = LB.medium_brute_msg - if(brutedamage > (limb_max_damage*0.8)) - status = LB.heavy_brute_msg - if(brutedamage > 0 && burndamage > 0) - status += " and " - - if(burndamage > (limb_max_damage*0.8)) - status += LB.heavy_burn_msg - else if(burndamage > (limb_max_damage*0.2)) - status += LB.medium_burn_msg - else if(burndamage > 0) - status += LB.light_burn_msg - - if(status == "") - status = "OK" - var/no_damage - if(status == "OK" || status == "no damage") - no_damage = TRUE - to_send += "\t Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].\n" - - for(var/obj/item/I in LB.embedded_objects) - to_send += "\t There is \a [I] embedded in your [LB.name]!\n" - - for(var/t in missing) - to_send += "Your [parse_zone(t)] is missing!\n" - - if(bleed_rate) - to_send += "You are bleeding!\n" - if(getStaminaLoss()) - if(getStaminaLoss() > 30) - to_send += "You're completely exhausted.\n" - else - to_send += "You feel fatigued.\n" - if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) - if(toxloss) - if(toxloss > 10) - to_send += "You feel sick.\n" - else if(toxloss > 20) - to_send += "You feel nauseated.\n" - else if(toxloss > 40) - to_send += "You feel very unwell!\n" - if(oxyloss) - if(oxyloss > 10) - to_send += "You feel lightheaded.\n" - else if(oxyloss > 20) - to_send += "Your thinking is clouded and distant.\n" - else if(oxyloss > 30) - to_send += "You're choking!\n" - - switch(nutrition) - if(NUTRITION_LEVEL_FULL to INFINITY) - to_send += "You're completely stuffed!\n" - if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL) - to_send += "You're well fed!\n" - if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED) - to_send += "You're not hungry.\n" - if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) - to_send += "You could use a bite to eat.\n" - if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) - to_send += "You feel quite hungry.\n" - if(0 to NUTRITION_LEVEL_STARVING) - to_send += "You're starving!\n" - - - //TODO: Convert these messages into vague messages, thereby encouraging actual dignosis. - //Compiles then shows the list of damaged organs and broken organs - var/list/broken = list() - var/list/damaged = list() - var/broken_message - var/damaged_message - var/broken_plural - var/damaged_plural - //Sets organs into their proper list - for(var/O in internal_organs) - var/obj/item/organ/organ = O - if(organ.organ_flags & ORGAN_FAILING) - if(broken.len) - broken += ", " - broken += organ.name - else if(organ.damage > organ.low_threshold) - if(damaged.len) - damaged += ", " - damaged += organ.name - //Checks to enforce proper grammar, inserts words as necessary into the list - if(broken.len) - if(broken.len > 1) - broken.Insert(broken.len, "and ") - broken_plural = TRUE - else - var/holder = broken[1] //our one and only element - if(holder[length(holder)] == "s") - broken_plural = TRUE - //Put the items in that list into a string of text - for(var/B in broken) - broken_message += B - to_chat(src, " Your [broken_message] [broken_plural ? "are" : "is"] non-functional!") - if(damaged.len) - if(damaged.len > 1) - damaged.Insert(damaged.len, "and ") - damaged_plural = TRUE - else - var/holder = damaged[1] - if(holder[length(holder)] == "s") - damaged_plural = TRUE - for(var/D in damaged) - damaged_message += D - to_chat(src, "Your [damaged_message] [damaged_plural ? "are" : "is"] hurt.") - - if(roundstart_quirks.len) - to_send += "You have these quirks: [get_trait_string()].\n" - - to_chat(src, to_send) - else - if(wear_suit) - wear_suit.add_fingerprint(M) - else if(w_uniform) - w_uniform.add_fingerprint(M) - - ..() - - -/mob/living/carbon/human/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone) - if(damage_type != BRUTE && damage_type != BURN) - return - damage_amount *= 0.5 //0.5 multiplier for balance reason, we don't want clothes to be too easily destroyed - var/list/torn_items = list() - - //HEAD// - if(!def_zone || def_zone == BODY_ZONE_HEAD) - var/obj/item/clothing/head_clothes = null - if(glasses) - head_clothes = glasses - if(wear_mask) - head_clothes = wear_mask - if(wear_neck) - head_clothes = wear_neck - if(head) - head_clothes = head - if(head_clothes) - torn_items += head_clothes - else if(ears) - torn_items += ears - - //CHEST// - if(!def_zone || def_zone == BODY_ZONE_CHEST) - var/obj/item/clothing/chest_clothes = null - if(w_uniform) - chest_clothes = w_uniform - if(wear_suit) - chest_clothes = wear_suit - if(chest_clothes) - torn_items += chest_clothes - - //ARMS & HANDS// - if(!def_zone || def_zone == BODY_ZONE_L_ARM || def_zone == BODY_ZONE_R_ARM) - var/obj/item/clothing/arm_clothes = null - if(gloves) - arm_clothes = gloves - if(w_uniform && ((w_uniform.body_parts_covered & HANDS) || (w_uniform.body_parts_covered & ARMS))) - arm_clothes = w_uniform - if(wear_suit && ((wear_suit.body_parts_covered & HANDS) || (wear_suit.body_parts_covered & ARMS))) - arm_clothes = wear_suit - if(arm_clothes) - torn_items |= arm_clothes - - //LEGS & FEET// - if(!def_zone || def_zone == BODY_ZONE_L_LEG || def_zone == BODY_ZONE_R_LEG) - var/obj/item/clothing/leg_clothes = null - if(shoes) - leg_clothes = shoes - if(w_uniform && ((w_uniform.body_parts_covered & FEET) || (w_uniform.body_parts_covered & LEGS))) - leg_clothes = w_uniform - if(wear_suit && ((wear_suit.body_parts_covered & FEET) || (wear_suit.body_parts_covered & LEGS))) - leg_clothes = wear_suit - if(leg_clothes) - torn_items |= leg_clothes - - for(var/obj/item/I in torn_items) - I.take_damage(damage_amount, damage_type, damage_flag, 0) +/mob/living/carbon/human/getarmor(def_zone, type) + var/armorval = 0 + var/organnum = 0 + + if(def_zone) + if(isbodypart(def_zone)) + var/obj/item/bodypart/bp = def_zone + if(bp.body_part) + return checkarmor(def_zone, type) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(def_zone)) + return checkarmor(affecting, type) + //If a specific bodypart is targetted, check how that bodypart is protected and return the value. + + //If you don't specify a bodypart, it checks ALL your bodyparts for protection, and averages out the values + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + armorval += checkarmor(BP, type) + organnum++ + return (armorval/max(organnum, 1)) + + +/mob/living/carbon/human/proc/checkarmor(obj/item/bodypart/def_zone, d_type) + if(!d_type) + return 0 + var/protection = 0 + var/list/body_parts = list(head, wear_mask, wear_suit, w_uniform, back, gloves, shoes, belt, s_store, glasses, ears, wear_id, wear_neck) //Everything but pockets. Pockets are l_store and r_store. (if pockets were allowed, putting something armored, gloves or hats for example, would double up on the armor) + for(var/bp in body_parts) + if(!bp) + continue + if(istype(bp, /obj/item/clothing)) + var/obj/item/clothing/C = bp + if(C.body_parts_covered & def_zone.body_part) + protection += C.armor.getRating(d_type) + protection += physiology.armor.getRating(d_type) + return protection + +/mob/living/carbon/human/on_hit(obj/item/projectile/P) + if(dna && dna.species) + dna.species.on_hit(P, src) + + +/mob/living/carbon/human/bullet_act(obj/item/projectile/P, def_zone) + if(dna && dna.species) + var/spec_return = dna.species.bullet_act(P, src) + if(spec_return) + return spec_return + + if(mind) + if (mind.martial_art && mind.martial_art.dodge_chance) + if(!lying && dna && !dna.check_mutation(HULK)) + if(prob(mind.martial_art.dodge_chance)) + var/dodgemessage = pick("dodges under the projectile!","dodges to the right of the projectile!","jumps over the projectile!") + visible_message("[src] [dodgemessage]", "You dodge the projectile!") + return -1 + if(mind.martial_art && !incapacitated(FALSE, TRUE) && mind.martial_art.can_use(src) && mind.martial_art.deflection_chance) //Some martial arts users can deflect projectiles! + if(prob(mind.martial_art.deflection_chance)) + if(!lying && dna && !dna.check_mutation(HULK)) //But only if they're not lying down, and hulks can't do it + if(mind.martial_art.deflection_chance >= 100) //if they can NEVER be hit, lets clue sec in ;) + visible_message("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons!", "You deflect the projectile!") + else + visible_message("[src] deflects the projectile!", "You deflect the projectile!") + playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, 1) + if(!mind.martial_art.reroute_deflection) + return FALSE + else + P.firer = src + P.setAngle(rand(0, 360))//SHING + return FALSE + + return ..() + +/mob/living/carbon/human/check_reflect(def_zone) + if(wear_suit?.IsReflect(def_zone)) + return TRUE + return ..() + +/mob/living/carbon/human/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) + . = ..() + if(.) + return + var/block_chance_modifier = round(damage / -3) + if(wear_suit) + var/final_block_chance = wear_suit.block_chance - (CLAMP((armour_penetration-wear_suit.armour_penetration)/2,0,100)) + block_chance_modifier + if(wear_suit.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) + return TRUE + if(w_uniform) + var/final_block_chance = w_uniform.block_chance - (CLAMP((armour_penetration-w_uniform.armour_penetration)/2,0,100)) + block_chance_modifier + if(w_uniform.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) + return TRUE + if(wear_neck) + var/final_block_chance = wear_neck.block_chance - (CLAMP((armour_penetration-wear_neck.armour_penetration)/2,0,100)) + block_chance_modifier + if(wear_neck.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) + return TRUE + return FALSE + +/mob/living/carbon/human/can_embed(obj/item/I) + if(I.get_sharpness() || is_pointed(I) || is_type_in_typecache(I, GLOB.can_embed_types)) + return TRUE + return FALSE + +/mob/living/carbon/human/proc/check_block() + if(mind) + if(mind.martial_art && prob(mind.martial_art.block_chance) && mind.martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) + return TRUE + return FALSE + +/mob/living/carbon/human/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE) + return dna?.species?.spec_hitby(AM, src) || ..() + +/mob/living/carbon/human/grabbedby(mob/living/carbon/user, supress_message = 0) + if(user == src && pulling && !pulling.anchored && grab_state >= GRAB_AGGRESSIVE && (HAS_TRAIT(src, TRAIT_FAT)) && ismonkey(pulling)) + devour_mob(pulling) + else + ..() + +/mob/living/carbon/human/grippedby(mob/living/user, instant = FALSE) + if(w_uniform) + w_uniform.add_fingerprint(user) + ..() + + +/mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user) + if(!I || !user) + return 0 + + var/obj/item/bodypart/affecting + if(user == src) + affecting = get_bodypart(check_zone(user.zone_selected)) //stabbing yourself always hits the right target + else + affecting = get_bodypart(ran_zone(user.zone_selected)) + var/target_area = parse_zone(check_zone(user.zone_selected)) //our intended target + + SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting) + + SSblackbox.record_feedback("nested tally", "item_used_for_combat", 1, list("[I.force]", "[I.type]")) + SSblackbox.record_feedback("tally", "zone_targeted", 1, target_area) + + // the attacked_by code varies among species + return dna.species.spec_attacked_by(I, user, affecting, a_intent, src) + + +/mob/living/carbon/human/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + if(user.a_intent == INTENT_HARM) + . = ..(user, TRUE) + if(.) + return + var/hulk_verb = pick("smash","pummel") + playsound(loc, user.dna.species.attack_sound, 25, 1, -1) + var/message = "[user] has [hulk_verb]ed [src]!" + visible_message("[message]", \ + "[message]") + adjustBruteLoss(15) + return 1 + +/mob/living/carbon/human/attack_hand(mob/user) + . = ..() + if(.) //To allow surgery to return properly. + return + if(ishuman(user)) + var/mob/living/carbon/human/H = user + dna.species.spec_attack_hand(H, src) + +/mob/living/carbon/human/attack_paw(mob/living/carbon/monkey/M) + 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 = get_bodypart(ran_zone(dam_zone)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + if(M.a_intent == INTENT_HELP) + return ..() //shaking + + if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stunned instead. + var/obj/item/I = get_active_held_item() + if(I && dropItemToGround(I)) + playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) + visible_message("[M] disarmed [src]!", \ + "[M] disarmed [src]!") + else if(!M.client || prob(5)) // only natural monkeys get to stun reliably, (they only do it occasionaly) + playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) + Knockdown(100) + log_combat(M, src, "tackled") + visible_message("[M] has tackled down [src]!", \ + "[M] has tackled down [src]!") + + if(M.limb_destroyer) + dismembering_strike(M, affecting.body_zone) + + if(can_inject(M, 1, affecting))//Thick suits can stop monkey bites. + if(..()) //successful monkey bite, this handles disease contraction. + var/damage = rand(1, 3) + apply_damage(damage, BRUTE, affecting, run_armor_check(affecting, "melee")) + return 1 + +/mob/living/carbon/human/attack_alien(mob/living/carbon/alien/humanoid/M) + . = ..() + if(!.) + return + if(M.a_intent == INTENT_HARM) + if (w_uniform) + w_uniform.add_fingerprint(M) + var/damage = prob(90) ? 20 : 0 + if(!damage) + playsound(loc, 'sound/weapons/slashmiss.ogg', 50, 1, -1) + visible_message("[M] has lunged at [src]!", \ + "[M] has lunged at [src]!") + return 0 + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(M.zone_selected)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor_block = run_armor_check(affecting, "melee", null, null,10) + + playsound(loc, 'sound/weapons/slice.ogg', 25, 1, -1) + visible_message("[M] has slashed at [src]!", \ + "[M] has slashed at [src]!") + log_combat(M, src, "attacked") + if(!dismembering_strike(M, M.zone_selected)) //Dismemberment successful + return 1 + apply_damage(damage, BRUTE, affecting, armor_block) + + if(M.a_intent == INTENT_DISARM) //Always drop item in hand, if no item, get stun instead. + var/obj/item/I = get_active_held_item() + if(I && dropItemToGround(I)) + playsound(loc, 'sound/weapons/slash.ogg', 25, 1, -1) + visible_message("[M] disarmed [src]!", \ + "[M] disarmed [src]!") + else + playsound(loc, 'sound/weapons/pierce.ogg', 25, 1, -1) + if(!lying) //CITADEL EDIT + Knockdown(100, TRUE, FALSE, 30, 25) + else + Knockdown(100) + log_combat(M, src, "tackled") + visible_message("[M] has tackled down [src]!", \ + "[M] has tackled down [src]!") + +/mob/living/carbon/human/attack_larva(mob/living/carbon/alien/larva/L) + . = ..() + if(!.) //unsuccessful larva bite. + return + var/damage = rand(1, 3) + if(stat != DEAD) + L.amount_grown = min(L.amount_grown + damage, L.max_grown) + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(L.zone_selected)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor_block = run_armor_check(affecting, "melee") + apply_damage(damage, BRUTE, affecting, armor_block) + + +/mob/living/carbon/human/attack_animal(mob/living/simple_animal/M) + . = ..() + if(.) + var/damage = rand(M.melee_damage_lower, M.melee_damage_upper) + var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + if(!dam_zone) //Dismemberment successful + return TRUE + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor = run_armor_check(affecting, "melee", armour_penetration = M.armour_penetration) + apply_damage(damage, M.melee_damage_type, affecting, armor) + + +/mob/living/carbon/human/attack_slime(mob/living/simple_animal/slime/M) + . = ..() + if(!.) //unsuccessful slime attack + return + var/damage = rand(5, 25) + if(M.is_adult) + damage = rand(10, 35) + + var/dam_zone = dismembering_strike(M, pick(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)) + if(!dam_zone) //Dismemberment successful + return 1 + + var/obj/item/bodypart/affecting = get_bodypart(ran_zone(dam_zone)) + if(!affecting) + affecting = get_bodypart(BODY_ZONE_CHEST) + var/armor_block = run_armor_check(affecting, "melee") + apply_damage(damage, BRUTE, affecting, armor_block) + +/mob/living/carbon/human/mech_melee_attack(obj/mecha/M) + if(M.occupant.a_intent == INTENT_HARM) + if(HAS_TRAIT(M.occupant, TRAIT_PACIFISM)) + to_chat(M.occupant, "You don't want to harm other living beings!") + return + M.do_attack_animation(src) + if(M.damtype == "brute") + step_away(src,M,15) + var/obj/item/bodypart/temp = get_bodypart(pick(BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_CHEST, BODY_ZONE_HEAD)) + if(temp) + var/update = 0 + var/dmg = rand(M.force/2, M.force) + var/atom/throw_target = get_edge_target_turf(src, M.dir) + switch(M.damtype) + if("brute") + if(M.force > 35) // durand and other heavy mechas + Knockdown(50) + src.throw_at(throw_target, rand(1,5), 7) + else if(M.force >= 20 && !IsKnockdown()) // lightweight mechas like gygax + Knockdown(30) + src.throw_at(throw_target, rand(1,3), 7) + update |= temp.receive_damage(dmg, 0) + playsound(src, 'sound/weapons/punch4.ogg', 50, 1) + if("fire") + update |= temp.receive_damage(0, dmg) + playsound(src, 'sound/items/welder.ogg', 50, 1) + if("tox") + M.mech_toxin_damage(src) + else + return + if(update) + update_damage_overlays() + updatehealth() + + visible_message("[M.name] has hit [src]!", \ + "[M.name] has hit [src]!", null, COMBAT_MESSAGE_RANGE) + log_combat(M.occupant, src, "attacked", M, "(INTENT: [uppertext(M.occupant.a_intent)]) (DAMTYPE: [uppertext(M.damtype)])") + + else + ..() + + +/mob/living/carbon/human/ex_act(severity, target, origin) + if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) + return + ..() + if (!severity) + return + var/brute_loss = 0 + var/burn_loss = 0 + var/bomb_armor = getarmor(null, "bomb") + + //200 max knockdown for EXPLODE_HEAVY + //160 max knockdown for EXPLODE_LIGHT + + switch (severity) + if (EXPLODE_DEVASTATE) + if(bomb_armor < EXPLODE_GIB_THRESHOLD) //gibs the mob if their bomb armor is lower than EXPLODE_GIB_THRESHOLD + for(var/I in contents) + var/atom/A = I + A.ex_act(severity) + gib() + return + else + brute_loss = 500 + var/atom/throw_target = get_edge_target_turf(src, get_dir(src, get_step_away(src, src))) + throw_at(throw_target, 200, 4) + damage_clothes(400 - bomb_armor, BRUTE, "bomb") + + if (EXPLODE_HEAVY) + brute_loss = 60 + burn_loss = 60 + if(bomb_armor) + brute_loss = 30*(2 - round(bomb_armor*0.01, 0.05)) + burn_loss = brute_loss //damage gets reduced from 120 to up to 60 combined brute+burn + damage_clothes(200 - bomb_armor, BRUTE, "bomb") + if (!istype(ears, /obj/item/clothing/ears/earmuffs)) + adjustEarDamage(30, 120) + Unconscious(20) //short amount of time for follow up attacks against elusive enemies like wizards + Knockdown(200 - (bomb_armor * 1.6)) //between ~4 and ~20 seconds of knockdown depending on bomb armor + + if(EXPLODE_LIGHT) + brute_loss = 30 + if(bomb_armor) + brute_loss = 15*(2 - round(bomb_armor*0.01, 0.05)) + damage_clothes(max(50 - bomb_armor, 0), BRUTE, "bomb") + if (!istype(ears, /obj/item/clothing/ears/earmuffs)) + adjustEarDamage(15,60) + Knockdown(160 - (bomb_armor * 1.6)) //100 bomb armor will prevent knockdown altogether + + take_overall_damage(brute_loss,burn_loss) + + //attempt to dismember bodyparts + if(severity <= 2 || !bomb_armor) + var/max_limb_loss = round(4/severity) //so you don't lose four limbs at severity 3. + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + if(prob(50/severity) && !prob(getarmor(BP, "bomb")) && BP.body_zone != BODY_ZONE_HEAD && BP.body_zone != BODY_ZONE_CHEST) + BP.brute_dam = BP.max_damage + BP.dismember() + max_limb_loss-- + if(!max_limb_loss) + break + + +/mob/living/carbon/human/blob_act(obj/structure/blob/B) + if(stat == DEAD) + return + show_message("The blob attacks you!") + 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 = get_bodypart(ran_zone(dam_zone)) + apply_damage(5, BRUTE, affecting, run_armor_check(affecting, "melee")) + + +//Added a safety check in case you want to shock a human mob directly through electrocute_act. +/mob/living/carbon/human/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = 0, override = 0, tesla_shock = 0, illusion = 0, stun = TRUE) + if(tesla_shock) + var/total_coeff = 1 + if(gloves) + var/obj/item/clothing/gloves/G = gloves + if(G.siemens_coefficient <= 0) + total_coeff -= 0.5 + if(wear_suit) + var/obj/item/clothing/suit/S = wear_suit + if(S.siemens_coefficient <= 0) + total_coeff -= 0.95 + else if(S.siemens_coefficient == (-1)) + total_coeff -= 1 + siemens_coeff = total_coeff + if(flags_1 & TESLA_IGNORE_1) + siemens_coeff = 0 + else if(!safety) + var/gloves_siemens_coeff = 1 + if(gloves) + var/obj/item/clothing/gloves/G = gloves + gloves_siemens_coeff = G.siemens_coefficient + siemens_coeff = gloves_siemens_coeff + if(undergoing_cardiac_arrest() && !illusion) + if(shock_damage * siemens_coeff >= 1 && prob(25)) + var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART) + heart.beating = TRUE + if(stat == CONSCIOUS) + to_chat(src, "You feel your heart beating again!") + siemens_coeff *= physiology.siemens_coeff + . = ..(shock_damage,source,siemens_coeff,safety,override,tesla_shock, illusion, stun) + if(.) + electrocution_animation(40) + + +/mob/living/carbon/human/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_CONTENTS) + return + var/informed = FALSE + for(var/obj/item/bodypart/L in src.bodyparts) + if(L.status == BODYPART_ROBOTIC) + if(!informed) + to_chat(src, "You feel a sharp pain as your robotic limbs overload.") + informed = TRUE + switch(severity) + if(1) + L.receive_damage(0,10) + Stun(200) + if(2) + L.receive_damage(0,5) + Stun(100) + +/mob/living/carbon/human/acid_act(acidpwr, acid_volume, bodyzone_hit) + var/list/damaged = list() + var/list/inventory_items_to_kill = list() + var/acidity = acidpwr * min(acid_volume*0.005, 0.1) + //HEAD// + if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_HEAD) //only if we didn't specify a zone or if that zone is the head. + var/obj/item/clothing/head_clothes = null + if(glasses) + head_clothes = glasses + if(wear_mask) + head_clothes = wear_mask + if(wear_neck) + head_clothes = wear_neck + if(head) + head_clothes = head + if(head_clothes) + if(!(head_clothes.resistance_flags & UNACIDABLE)) + head_clothes.acid_act(acidpwr, acid_volume) + update_inv_glasses() + update_inv_wear_mask() + update_inv_neck() + update_inv_head() + else + to_chat(src, "Your [head_clothes.name] protects your head and face from the acid!") + else + . = get_bodypart(BODY_ZONE_HEAD) + if(.) + damaged += . + if(ears) + inventory_items_to_kill += ears + + //CHEST// + if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_CHEST) + var/obj/item/clothing/chest_clothes = null + if(w_uniform) + chest_clothes = w_uniform + if(wear_suit) + chest_clothes = wear_suit + if(chest_clothes) + if(!(chest_clothes.resistance_flags & UNACIDABLE)) + chest_clothes.acid_act(acidpwr, acid_volume) + update_inv_w_uniform() + update_inv_wear_suit() + else + to_chat(src, "Your [chest_clothes.name] protects your body from the acid!") + else + . = get_bodypart(BODY_ZONE_CHEST) + if(.) + damaged += . + if(wear_id) + inventory_items_to_kill += wear_id + if(r_store) + inventory_items_to_kill += r_store + if(l_store) + inventory_items_to_kill += l_store + if(s_store) + inventory_items_to_kill += s_store + + + //ARMS & HANDS// + if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_L_ARM || bodyzone_hit == BODY_ZONE_R_ARM) + var/obj/item/clothing/arm_clothes = null + if(gloves) + arm_clothes = gloves + if(w_uniform && ((w_uniform.body_parts_covered & HANDS) || (w_uniform.body_parts_covered & ARMS))) + arm_clothes = w_uniform + if(wear_suit && ((wear_suit.body_parts_covered & HANDS) || (wear_suit.body_parts_covered & ARMS))) + arm_clothes = wear_suit + + if(arm_clothes) + if(!(arm_clothes.resistance_flags & UNACIDABLE)) + arm_clothes.acid_act(acidpwr, acid_volume) + update_inv_gloves() + update_inv_w_uniform() + update_inv_wear_suit() + else + to_chat(src, "Your [arm_clothes.name] protects your arms and hands from the acid!") + else + . = get_bodypart(BODY_ZONE_R_ARM) + if(.) + damaged += . + . = get_bodypart(BODY_ZONE_L_ARM) + if(.) + damaged += . + + + //LEGS & FEET// + if(!bodyzone_hit || bodyzone_hit == BODY_ZONE_L_LEG || bodyzone_hit == BODY_ZONE_R_LEG || bodyzone_hit == "feet") + var/obj/item/clothing/leg_clothes = null + if(shoes) + leg_clothes = shoes + if(w_uniform && ((w_uniform.body_parts_covered & FEET) || (bodyzone_hit != "feet" && (w_uniform.body_parts_covered & LEGS)))) + leg_clothes = w_uniform + if(wear_suit && ((wear_suit.body_parts_covered & FEET) || (bodyzone_hit != "feet" && (wear_suit.body_parts_covered & LEGS)))) + leg_clothes = wear_suit + if(leg_clothes) + if(!(leg_clothes.resistance_flags & UNACIDABLE)) + leg_clothes.acid_act(acidpwr, acid_volume) + update_inv_shoes() + update_inv_w_uniform() + update_inv_wear_suit() + else + to_chat(src, "Your [leg_clothes.name] protects your legs and feet from the acid!") + else + . = get_bodypart(BODY_ZONE_R_LEG) + if(.) + damaged += . + . = get_bodypart(BODY_ZONE_L_LEG) + if(.) + damaged += . + + + //DAMAGE// + for(var/obj/item/bodypart/affecting in damaged) + affecting.receive_damage(acidity, 2*acidity) + + if(affecting.name == BODY_ZONE_HEAD) + if(prob(min(acidpwr*acid_volume/10, 90))) //Applies disfigurement + affecting.receive_damage(acidity, 2*acidity) + emote("scream") + facial_hair_style = "Shaved" + hair_style = "Bald" + update_hair() + ADD_TRAIT(src, TRAIT_DISFIGURED, TRAIT_GENERIC) + + update_damage_overlays() + + //MELTING INVENTORY ITEMS// + //these items are all outside of armour visually, so melt regardless. + if(!bodyzone_hit) + if(back) + inventory_items_to_kill += back + if(belt) + inventory_items_to_kill += belt + + inventory_items_to_kill += held_items + + for(var/obj/item/I in inventory_items_to_kill) + I.acid_act(acidpwr, acid_volume) + return 1 + +/mob/living/carbon/human/singularity_act() + var/gain = 20 + if(mind) + if((mind.assigned_role == "Station Engineer") || (mind.assigned_role == "Chief Engineer") ) + gain = 100 + if(HAS_TRAIT(mind, TRAIT_CLOWN_MENTALITY)) + gain = rand(-300, 300) + investigate_log("([key_name(src)]) has been consumed by the singularity.", INVESTIGATE_SINGULO) //Oh that's where the clown ended up! + gib() + return(gain) + +/mob/living/carbon/human/help_shake_act(mob/living/carbon/M) + if(!istype(M)) + return + + if(health >= 0) + if(src == M) + if(has_status_effect(STATUS_EFFECT_CHOKINGSTRAND)) + to_chat(src, "You attempt to remove the durathread strand from around your neck.") + if(do_after(src, 35, null, src)) + to_chat(src, "You succesfuly remove the durathread strand.") + remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND) + return + var/to_send = "" + visible_message("[src] examines [p_them()]self.", \ + "You check yourself for injuries.") + + var/list/missing = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + for(var/X in bodyparts) + var/obj/item/bodypart/LB = X + missing -= LB.body_zone + if(LB.is_pseudopart) //don't show injury text for fake bodyparts; ie chainsaw arms or synthetic armblades + continue + var/limb_max_damage = LB.max_damage + var/status = "" + var/brutedamage = LB.brute_dam + var/burndamage = LB.burn_dam + if(hallucination) + if(prob(30)) + brutedamage += rand(30,40) + if(prob(30)) + burndamage += rand(30,40) + + if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) + status = "[brutedamage] brute damage and [burndamage] burn damage" + if(!brutedamage && !burndamage) + status = "no damage" + + else + if(brutedamage > 0) + status = LB.light_brute_msg + if(brutedamage > (limb_max_damage*0.4)) + status = LB.medium_brute_msg + if(brutedamage > (limb_max_damage*0.8)) + status = LB.heavy_brute_msg + if(brutedamage > 0 && burndamage > 0) + status += " and " + + if(burndamage > (limb_max_damage*0.8)) + status += LB.heavy_burn_msg + else if(burndamage > (limb_max_damage*0.2)) + status += LB.medium_burn_msg + else if(burndamage > 0) + status += LB.light_burn_msg + + if(status == "") + status = "OK" + var/no_damage + if(status == "OK" || status == "no damage") + no_damage = TRUE + to_send += "\t Your [LB.name] [HAS_TRAIT(src, TRAIT_SELF_AWARE) ? "has" : "is"] [status].\n" + + for(var/obj/item/I in LB.embedded_objects) + to_send += "\t There is \a [I] embedded in your [LB.name]!\n" + + for(var/t in missing) + to_send += "Your [parse_zone(t)] is missing!\n" + + if(bleed_rate) + to_send += "You are bleeding!\n" + if(getStaminaLoss()) + if(getStaminaLoss() > 30) + to_send += "You're completely exhausted.\n" + else + to_send += "You feel fatigued.\n" + if(HAS_TRAIT(src, TRAIT_SELF_AWARE)) + if(toxloss) + if(toxloss > 10) + to_send += "You feel sick.\n" + else if(toxloss > 20) + to_send += "You feel nauseated.\n" + else if(toxloss > 40) + to_send += "You feel very unwell!\n" + if(oxyloss) + if(oxyloss > 10) + to_send += "You feel lightheaded.\n" + else if(oxyloss > 20) + to_send += "Your thinking is clouded and distant.\n" + else if(oxyloss > 30) + to_send += "You're choking!\n" + + switch(nutrition) + if(NUTRITION_LEVEL_FULL to INFINITY) + to_send += "You're completely stuffed!\n" + if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL) + to_send += "You're well fed!\n" + if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED) + to_send += "You're not hungry.\n" + if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED) + to_send += "You could use a bite to eat.\n" + if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY) + to_send += "You feel quite hungry.\n" + if(0 to NUTRITION_LEVEL_STARVING) + to_send += "You're starving!\n" + + + //TODO: Convert these messages into vague messages, thereby encouraging actual dignosis. + //Compiles then shows the list of damaged organs and broken organs + var/list/broken = list() + var/list/damaged = list() + var/broken_message + var/damaged_message + var/broken_plural + var/damaged_plural + //Sets organs into their proper list + for(var/O in internal_organs) + var/obj/item/organ/organ = O + if(organ.organ_flags & ORGAN_FAILING) + if(broken.len) + broken += ", " + broken += organ.name + else if(organ.damage > organ.low_threshold) + if(damaged.len) + damaged += ", " + damaged += organ.name + //Checks to enforce proper grammar, inserts words as necessary into the list + if(broken.len) + if(broken.len > 1) + broken.Insert(broken.len, "and ") + broken_plural = TRUE + else + var/holder = broken[1] //our one and only element + if(holder[length(holder)] == "s") + broken_plural = TRUE + //Put the items in that list into a string of text + for(var/B in broken) + broken_message += B + to_chat(src, " Your [broken_message] [broken_plural ? "are" : "is"] non-functional!") + if(damaged.len) + if(damaged.len > 1) + damaged.Insert(damaged.len, "and ") + damaged_plural = TRUE + else + var/holder = damaged[1] + if(holder[length(holder)] == "s") + damaged_plural = TRUE + for(var/D in damaged) + damaged_message += D + to_chat(src, "Your [damaged_message] [damaged_plural ? "are" : "is"] hurt.") + + if(roundstart_quirks.len) + to_send += "You have these quirks: [get_trait_string()].\n" + + to_chat(src, to_send) + else + if(wear_suit) + wear_suit.add_fingerprint(M) + else if(w_uniform) + w_uniform.add_fingerprint(M) + + ..() + + +/mob/living/carbon/human/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone) + if(damage_type != BRUTE && damage_type != BURN) + return + damage_amount *= 0.5 //0.5 multiplier for balance reason, we don't want clothes to be too easily destroyed + var/list/torn_items = list() + + //HEAD// + if(!def_zone || def_zone == BODY_ZONE_HEAD) + var/obj/item/clothing/head_clothes = null + if(glasses) + head_clothes = glasses + if(wear_mask) + head_clothes = wear_mask + if(wear_neck) + head_clothes = wear_neck + if(head) + head_clothes = head + if(head_clothes) + torn_items += head_clothes + else if(ears) + torn_items += ears + + //CHEST// + if(!def_zone || def_zone == BODY_ZONE_CHEST) + var/obj/item/clothing/chest_clothes = null + if(w_uniform) + chest_clothes = w_uniform + if(wear_suit) + chest_clothes = wear_suit + if(chest_clothes) + torn_items += chest_clothes + + //ARMS & HANDS// + if(!def_zone || def_zone == BODY_ZONE_L_ARM || def_zone == BODY_ZONE_R_ARM) + var/obj/item/clothing/arm_clothes = null + if(gloves) + arm_clothes = gloves + if(w_uniform && ((w_uniform.body_parts_covered & HANDS) || (w_uniform.body_parts_covered & ARMS))) + arm_clothes = w_uniform + if(wear_suit && ((wear_suit.body_parts_covered & HANDS) || (wear_suit.body_parts_covered & ARMS))) + arm_clothes = wear_suit + if(arm_clothes) + torn_items |= arm_clothes + + //LEGS & FEET// + if(!def_zone || def_zone == BODY_ZONE_L_LEG || def_zone == BODY_ZONE_R_LEG) + var/obj/item/clothing/leg_clothes = null + if(shoes) + leg_clothes = shoes + if(w_uniform && ((w_uniform.body_parts_covered & FEET) || (w_uniform.body_parts_covered & LEGS))) + leg_clothes = w_uniform + if(wear_suit && ((wear_suit.body_parts_covered & FEET) || (wear_suit.body_parts_covered & LEGS))) + leg_clothes = wear_suit + if(leg_clothes) + torn_items |= leg_clothes + + for(var/obj/item/I in torn_items) + I.take_damage(damage_amount, damage_type, damage_flag, 0) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 9ffa994066..595d60004c 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -1,70 +1,70 @@ -/mob/living/carbon/human - hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPCHEM_HUD,IMPTRACK_HUD, NANITE_HUD, DIAG_NANITE_FULL_HUD,ANTAG_HUD,GLAND_HUD,SENTIENT_DISEASE_HUD,RAD_HUD) - hud_type = /datum/hud/human - possible_a_intents = list(INTENT_HELP, INTENT_DISARM, INTENT_GRAB, INTENT_HARM) - pressure_resistance = 25 - can_buckle = TRUE - buckle_lying = FALSE - mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) - //Hair colour and style - var/hair_color = "000" - var/hair_style = "Bald" - - //Facial hair colour and style - var/facial_hair_color = "000" - var/facial_hair_style = "Shaved" - - //Eye colour - var/eye_color = "000" - - var/horn_color = "85615a" //specific horn colors, because why not? - - var/wing_color = "fff" //wings too - - var/skin_tone = "caucasian1" //Skin tone - - var/lip_style = null //no lipstick by default- arguably misleading, as it could be used for general makeup - var/lip_color = "white" - - var/age = 30 //Player's age - - var/underwear = "Nude" //Which underwear the player wants - var/undie_color = "FFFFFF" - var/undershirt = "Nude" //Which undershirt the player wants - var/shirt_color = "FFFFFF" - var/socks = "Nude" //Which socks the player wants - var/socks_color = "FFFFFF" - - //Equipment slots - var/obj/item/wear_suit = null - var/obj/item/w_uniform = null - var/obj/item/belt = null - var/obj/item/wear_id = null - var/obj/item/r_store = null - var/obj/item/l_store = null - var/obj/item/s_store = null - - var/special_voice = "" // For changing our voice. Used by a symptom. - - var/bleed_rate = 0 //how much are we bleeding - var/bleedsuppress = 0 //for stopping bloodloss, eventually this will be limb-based like bleeding - - var/blood_state = BLOOD_STATE_NOT_BLOODY - var/list/blood_smear = list(BLOOD_STATE_BLOOD = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) - - var/name_override //For temporary visible name changes - var/genital_override = FALSE //Force genitals on things incase of chems - - var/nameless = FALSE //For drones of both the insectoid and robotic kind. And other types of nameless critters. - - var/custom_species = null - - var/datum/personal_crafting/handcrafting - var/datum/physiology/physiology - - var/list/datum/bioware = list() - - var/creamed = FALSE //to use with creampie overlays - var/static/list/can_ride_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/simple_animal/slime, /mob/living/simple_animal/parrot)) - var/lastpuke = 0 - var/last_fire_update +/mob/living/carbon/human + hud_possible = list(HEALTH_HUD,STATUS_HUD,ID_HUD,WANTED_HUD,IMPLOYAL_HUD,IMPCHEM_HUD,IMPTRACK_HUD, NANITE_HUD, DIAG_NANITE_FULL_HUD,ANTAG_HUD,GLAND_HUD,SENTIENT_DISEASE_HUD,RAD_HUD) + hud_type = /datum/hud/human + possible_a_intents = list(INTENT_HELP, INTENT_DISARM, INTENT_GRAB, INTENT_HARM) + pressure_resistance = 25 + can_buckle = TRUE + buckle_lying = FALSE + mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) + //Hair colour and style + var/hair_color = "000" + var/hair_style = "Bald" + + //Facial hair colour and style + var/facial_hair_color = "000" + var/facial_hair_style = "Shaved" + + //Eye colour + var/eye_color = "000" + + var/horn_color = "85615a" //specific horn colors, because why not? + + var/wing_color = "fff" //wings too + + var/skin_tone = "caucasian1" //Skin tone + + var/lip_style = null //no lipstick by default- arguably misleading, as it could be used for general makeup + var/lip_color = "white" + + var/age = 30 //Player's age + + var/underwear = "Nude" //Which underwear the player wants + var/undie_color = "FFFFFF" + var/undershirt = "Nude" //Which undershirt the player wants + var/shirt_color = "FFFFFF" + var/socks = "Nude" //Which socks the player wants + var/socks_color = "FFFFFF" + + //Equipment slots + var/obj/item/wear_suit = null + var/obj/item/w_uniform = null + var/obj/item/belt = null + var/obj/item/wear_id = null + var/obj/item/r_store = null + var/obj/item/l_store = null + var/obj/item/s_store = null + + var/special_voice = "" // For changing our voice. Used by a symptom. + + var/bleed_rate = 0 //how much are we bleeding + var/bleedsuppress = 0 //for stopping bloodloss, eventually this will be limb-based like bleeding + + var/blood_state = BLOOD_STATE_NOT_BLOODY + var/list/blood_smear = list(BLOOD_STATE_BLOOD = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0) + + var/name_override //For temporary visible name changes + var/genital_override = FALSE //Force genitals on things incase of chems + + var/nameless = FALSE //For drones of both the insectoid and robotic kind. And other types of nameless critters. + + var/custom_species = null + + var/datum/personal_crafting/handcrafting + var/datum/physiology/physiology + + var/list/datum/bioware = list() + + var/creamed = FALSE //to use with creampie overlays + var/static/list/can_ride_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/simple_animal/slime, /mob/living/simple_animal/parrot)) + var/lastpuke = 0 + var/last_fire_update diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index e5e546fdf8..7b3953e40e 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -1,142 +1,142 @@ - -/mob/living/carbon/human/restrained(ignore_grab) - . = ((wear_suit && wear_suit.breakouttime) || ..()) - - -/mob/living/carbon/human/canBeHandcuffed() - if(get_num_arms(FALSE) >= 2) - return TRUE - else - return FALSE - -//gets assignment from ID or ID inside PDA or PDA itself -//Useful when player do something with computers -/mob/living/carbon/human/proc/get_assignment(if_no_id = "No id", if_no_job = "No job", hand_first = TRUE) - var/obj/item/card/id/id = get_idcard(hand_first) - if(id) - . = id.assignment - else - var/obj/item/pda/pda = wear_id - if(istype(pda)) - . = pda.ownjob - else - return if_no_id - if(!.) - return if_no_job - -//gets name from ID or ID inside PDA or PDA itself -//Useful when player do something with computers -/mob/living/carbon/human/proc/get_authentification_name(if_no_id = "Unknown") - var/obj/item/card/id/id = get_idcard(FALSE) - if(id) - return id.registered_name - var/obj/item/pda/pda = wear_id - if(istype(pda)) - return pda.owner - return if_no_id - -//repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a separate proc as it'll be useful elsewhere -/mob/living/carbon/human/get_visible_name() - var/face_name = get_face_name("") - var/id_name = get_id_name("") - if(name_override) - return name_override - if(face_name) - if(id_name && (id_name != face_name)) - return "[face_name] (as [id_name])" - return face_name - if(id_name) - return id_name - return "Unknown" - -//Returns "Unknown" if facially disfigured and real_name if not. Useful for setting name when Fluacided or when updating a human's name variable -/mob/living/carbon/human/proc/get_face_name(if_no_face="Unknown") - if( wear_mask && (wear_mask.flags_inv&HIDEFACE) ) //Wearing a mask which hides our face, use id-name if possible - return if_no_face - if( head && (head.flags_inv&HIDEFACE) ) - return if_no_face //Likewise for hats - var/obj/item/bodypart/O = get_bodypart(BODY_ZONE_HEAD) - if( !O || (HAS_TRAIT(src, TRAIT_DISFIGURED)) || (O.brutestate+O.burnstate)>2 || cloneloss>50 || !real_name || nameless) //disfigured. use id-name if possible - return if_no_face - return real_name - -//gets name from ID or PDA itself, ID inside PDA doesn't matter -//Useful when player is being seen by other mobs -/mob/living/carbon/human/proc/get_id_name(if_no_id = "Unknown") - var/obj/item/storage/wallet/wallet = wear_id - var/obj/item/pda/pda = wear_id - var/obj/item/card/id/id = wear_id - var/obj/item/modular_computer/tablet/tablet = wear_id - if(istype(wallet)) - id = wallet.front_id - if(istype(id)) - . = id.registered_name - else if(istype(pda)) - . = pda.owner - else if(istype(tablet)) - var/obj/item/computer_hardware/card_slot/card_slot = tablet.all_components[MC_CARD] - if(card_slot && (card_slot.stored_card2 || card_slot.stored_card)) - if(card_slot.stored_card2) //The second card is the one used for authorization in the ID changing program, so we prioritize it here for consistency - . = card_slot.stored_card2.registered_name - else - if(card_slot.stored_card) - . = card_slot.stored_card.registered_name - if(!.) - . = if_no_id //to prevent null-names making the mob unclickable - return - -//gets ID card object from special clothes slot or null. -/mob/living/carbon/human/get_idcard(hand_first = TRUE) - . = ..() - if(. && hand_first) - return - //Check inventory slots - var/obj/item/card/id/id_card = wear_id?.GetID() - if(!id_card) - id_card = belt?.GetID() - return id_card || . - -/mob/living/carbon/human/IsAdvancedToolUser() - if(HAS_TRAIT(src, TRAIT_MONKEYLIKE)) - return FALSE - return TRUE//Humans can use guns and such - -/mob/living/carbon/human/reagent_check(datum/reagent/R) - return dna.species.handle_chemicals(R,src) - // if it returns 0, it will run the usual on_mob_life for that reagent. otherwise, it will stop after running handle_chemicals for the species. - -/mob/living/carbon/human/can_track(mob/living/user) - if(wear_id && istype(wear_id.GetID(), /obj/item/card/id/syndicate)) - return 0 - if(istype(head, /obj/item/clothing/head)) - var/obj/item/clothing/head/hat = head - if(hat.blockTracking) - return 0 - - return ..() - -/mob/living/carbon/human/can_use_guns(obj/item/G) - . = ..() - if(!.) - return - if(G.trigger_guard == TRIGGER_GUARD_NORMAL) - if(HAS_TRAIT(src, TRAIT_CHUNKYFINGERS)) - to_chat(src, "Your meaty finger is much too large for the trigger guard!") - return FALSE - if(HAS_TRAIT(src, TRAIT_NOGUNS)) - to_chat(src, "Your fingers don't fit in the trigger guard!") - return FALSE - -/mob/living/carbon/human/can_see_reagents() - . = ..() - if(.) //No need to run through all of this if it's already true. - return - if(isclothing(glasses) && (glasses.clothing_flags & SCAN_REAGENTS)) - return TRUE - -/* -/mob/living/carbon/human/transfer_blood_dna(list/blood_dna) - ..() - if(blood_dna.len) - last_bloodtype = blood_dna[blood_dna[blood_dna.len]]//trust me this works - last_blood_DNA = blood_dna[blood_dna.len]*/ + +/mob/living/carbon/human/restrained(ignore_grab) + . = ((wear_suit && wear_suit.breakouttime) || ..()) + + +/mob/living/carbon/human/canBeHandcuffed() + if(get_num_arms(FALSE) >= 2) + return TRUE + else + return FALSE + +//gets assignment from ID or ID inside PDA or PDA itself +//Useful when player do something with computers +/mob/living/carbon/human/proc/get_assignment(if_no_id = "No id", if_no_job = "No job", hand_first = TRUE) + var/obj/item/card/id/id = get_idcard(hand_first) + if(id) + . = id.assignment + else + var/obj/item/pda/pda = wear_id + if(istype(pda)) + . = pda.ownjob + else + return if_no_id + if(!.) + return if_no_job + +//gets name from ID or ID inside PDA or PDA itself +//Useful when player do something with computers +/mob/living/carbon/human/proc/get_authentification_name(if_no_id = "Unknown") + var/obj/item/card/id/id = get_idcard(FALSE) + if(id) + return id.registered_name + var/obj/item/pda/pda = wear_id + if(istype(pda)) + return pda.owner + return if_no_id + +//repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a separate proc as it'll be useful elsewhere +/mob/living/carbon/human/get_visible_name() + var/face_name = get_face_name("") + var/id_name = get_id_name("") + if(name_override) + return name_override + if(face_name) + if(id_name && (id_name != face_name)) + return "[face_name] (as [id_name])" + return face_name + if(id_name) + return id_name + return "Unknown" + +//Returns "Unknown" if facially disfigured and real_name if not. Useful for setting name when Fluacided or when updating a human's name variable +/mob/living/carbon/human/proc/get_face_name(if_no_face="Unknown") + if( wear_mask && (wear_mask.flags_inv&HIDEFACE) ) //Wearing a mask which hides our face, use id-name if possible + return if_no_face + if( head && (head.flags_inv&HIDEFACE) ) + return if_no_face //Likewise for hats + var/obj/item/bodypart/O = get_bodypart(BODY_ZONE_HEAD) + if( !O || (HAS_TRAIT(src, TRAIT_DISFIGURED)) || (O.brutestate+O.burnstate)>2 || cloneloss>50 || !real_name || nameless) //disfigured. use id-name if possible + return if_no_face + return real_name + +//gets name from ID or PDA itself, ID inside PDA doesn't matter +//Useful when player is being seen by other mobs +/mob/living/carbon/human/proc/get_id_name(if_no_id = "Unknown") + var/obj/item/storage/wallet/wallet = wear_id + var/obj/item/pda/pda = wear_id + var/obj/item/card/id/id = wear_id + var/obj/item/modular_computer/tablet/tablet = wear_id + if(istype(wallet)) + id = wallet.front_id + if(istype(id)) + . = id.registered_name + else if(istype(pda)) + . = pda.owner + else if(istype(tablet)) + var/obj/item/computer_hardware/card_slot/card_slot = tablet.all_components[MC_CARD] + if(card_slot && (card_slot.stored_card2 || card_slot.stored_card)) + if(card_slot.stored_card2) //The second card is the one used for authorization in the ID changing program, so we prioritize it here for consistency + . = card_slot.stored_card2.registered_name + else + if(card_slot.stored_card) + . = card_slot.stored_card.registered_name + if(!.) + . = if_no_id //to prevent null-names making the mob unclickable + return + +//gets ID card object from special clothes slot or null. +/mob/living/carbon/human/get_idcard(hand_first = TRUE) + . = ..() + if(. && hand_first) + return + //Check inventory slots + var/obj/item/card/id/id_card = wear_id?.GetID() + if(!id_card) + id_card = belt?.GetID() + return id_card || . + +/mob/living/carbon/human/IsAdvancedToolUser() + if(HAS_TRAIT(src, TRAIT_MONKEYLIKE)) + return FALSE + return TRUE//Humans can use guns and such + +/mob/living/carbon/human/reagent_check(datum/reagent/R) + return dna.species.handle_chemicals(R,src) + // if it returns 0, it will run the usual on_mob_life for that reagent. otherwise, it will stop after running handle_chemicals for the species. + +/mob/living/carbon/human/can_track(mob/living/user) + if(wear_id && istype(wear_id.GetID(), /obj/item/card/id/syndicate)) + return 0 + if(istype(head, /obj/item/clothing/head)) + var/obj/item/clothing/head/hat = head + if(hat.blockTracking) + return 0 + + return ..() + +/mob/living/carbon/human/can_use_guns(obj/item/G) + . = ..() + if(!.) + return + if(G.trigger_guard == TRIGGER_GUARD_NORMAL) + if(HAS_TRAIT(src, TRAIT_CHUNKYFINGERS)) + to_chat(src, "Your meaty finger is much too large for the trigger guard!") + return FALSE + if(HAS_TRAIT(src, TRAIT_NOGUNS)) + to_chat(src, "Your fingers don't fit in the trigger guard!") + return FALSE + +/mob/living/carbon/human/can_see_reagents() + . = ..() + if(.) //No need to run through all of this if it's already true. + return + if(isclothing(glasses) && (glasses.clothing_flags & SCAN_REAGENTS)) + return TRUE + +/* +/mob/living/carbon/human/transfer_blood_dna(list/blood_dna) + ..() + if(blood_dna.len) + last_bloodtype = blood_dna[blood_dna[blood_dna.len]]//trust me this works + last_blood_DNA = blood_dna[blood_dna.len]*/ diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index 4e6f9e0eb9..460dd6ea51 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -1,78 +1,78 @@ -/mob/living/carbon/human/get_movespeed_modifiers() - var/list/considering = ..() - . = considering - if(HAS_TRAIT(src, TRAIT_IGNORESLOWDOWN)) - for(var/id in .) - var/list/data = .[id] - if(data[MOVESPEED_DATA_INDEX_FLAGS] & IGNORE_NOSLOW) - .[id] = data - -/mob/living/carbon/human/movement_delay() - . = ..() - if(dna && dna.species) - . += dna.species.movement_delay(src) - -/mob/living/carbon/human/slip(knockdown_amount, obj/O, lube) - if(HAS_TRAIT(src, TRAIT_NOSLIPALL)) - return 0 - if (!(lube&GALOSHES_DONT_HELP)) - if(HAS_TRAIT(src, TRAIT_NOSLIPWATER)) - return 0 - if(shoes && istype(shoes, /obj/item/clothing)) - var/obj/item/clothing/CS = shoes - if (CS.clothing_flags & NOSLIP) - return 0 - return ..() - -/mob/living/carbon/human/experience_pressure_difference() - playsound(src, 'sound/effects/space_wind.ogg', 50, 1) - if(shoes && istype(shoes, /obj/item/clothing)) - var/obj/item/clothing/S = shoes - if (S.clothing_flags & NOSLIP) - return 0 - return ..() - -/mob/living/carbon/human/mob_has_gravity() - . = ..() - if(!.) - if(mob_negates_gravity()) - . = 1 - -/mob/living/carbon/human/mob_negates_gravity() - return ((shoes && shoes.negates_gravity()) || (dna.species.negates_gravity(src))) - -/mob/living/carbon/human/Move(NewLoc, direct) - . = ..() - for(var/datum/mutation/human/HM in dna.mutations) - HM.on_move(src, NewLoc) - - if(shoes) - if(!lying && !buckled) - if(loc == NewLoc) - if(!has_gravity(loc)) - return - var/obj/item/clothing/shoes/S = shoes - - //Bloody footprints - var/turf/T = get_turf(src) - if(S.bloody_shoes && S.bloody_shoes[S.blood_state]) - var/obj/effect/decal/cleanable/blood/footprints/oldFP = locate(/obj/effect/decal/cleanable/blood/footprints) in T - if(oldFP && (oldFP.blood_state == S.blood_state && oldFP.color == bloodtype_to_color(S.last_bloodtype))) - return - S.bloody_shoes[S.blood_state] = max(0, S.bloody_shoes[S.blood_state]-BLOOD_LOSS_PER_STEP) - var/obj/effect/decal/cleanable/blood/footprints/FP = new /obj/effect/decal/cleanable/blood/footprints(T) - FP.blood_state = S.blood_state - FP.entered_dirs |= dir - FP.bloodiness = S.bloody_shoes[S.blood_state] - if(S.last_bloodtype) - FP.blood_DNA += list(S.last_blood_DNA = S.last_bloodtype) - FP.update_icon() - update_inv_shoes() - //End bloody footprints - - S.step_action() - -/mob/living/carbon/human/Process_Spacemove(movement_dir = 0) //Temporary laziness thing. Will change to handles by species reee. - if(dna.species.space_move(src)) - return TRUE - return ..() +/mob/living/carbon/human/get_movespeed_modifiers() + var/list/considering = ..() + . = considering + if(HAS_TRAIT(src, TRAIT_IGNORESLOWDOWN)) + for(var/id in .) + var/list/data = .[id] + if(data[MOVESPEED_DATA_INDEX_FLAGS] & IGNORE_NOSLOW) + .[id] = data + +/mob/living/carbon/human/movement_delay() + . = ..() + if(dna && dna.species) + . += dna.species.movement_delay(src) + +/mob/living/carbon/human/slip(knockdown_amount, obj/O, lube) + if(HAS_TRAIT(src, TRAIT_NOSLIPALL)) + return 0 + if (!(lube&GALOSHES_DONT_HELP)) + if(HAS_TRAIT(src, TRAIT_NOSLIPWATER)) + return 0 + if(shoes && istype(shoes, /obj/item/clothing)) + var/obj/item/clothing/CS = shoes + if (CS.clothing_flags & NOSLIP) + return 0 + return ..() + +/mob/living/carbon/human/experience_pressure_difference() + playsound(src, 'sound/effects/space_wind.ogg', 50, 1) + if(shoes && istype(shoes, /obj/item/clothing)) + var/obj/item/clothing/S = shoes + if (S.clothing_flags & NOSLIP) + return 0 + return ..() + +/mob/living/carbon/human/mob_has_gravity() + . = ..() + if(!.) + if(mob_negates_gravity()) + . = 1 + +/mob/living/carbon/human/mob_negates_gravity() + return ((shoes && shoes.negates_gravity()) || (dna.species.negates_gravity(src))) + +/mob/living/carbon/human/Move(NewLoc, direct) + . = ..() + for(var/datum/mutation/human/HM in dna.mutations) + HM.on_move(src, NewLoc) + + if(shoes) + if(!lying && !buckled) + if(loc == NewLoc) + if(!has_gravity(loc)) + return + var/obj/item/clothing/shoes/S = shoes + + //Bloody footprints + var/turf/T = get_turf(src) + if(S.bloody_shoes && S.bloody_shoes[S.blood_state]) + var/obj/effect/decal/cleanable/blood/footprints/oldFP = locate(/obj/effect/decal/cleanable/blood/footprints) in T + if(oldFP && (oldFP.blood_state == S.blood_state && oldFP.color == bloodtype_to_color(S.last_bloodtype))) + return + S.bloody_shoes[S.blood_state] = max(0, S.bloody_shoes[S.blood_state]-BLOOD_LOSS_PER_STEP) + var/obj/effect/decal/cleanable/blood/footprints/FP = new /obj/effect/decal/cleanable/blood/footprints(T) + FP.blood_state = S.blood_state + FP.entered_dirs |= dir + FP.bloodiness = S.bloody_shoes[S.blood_state] + if(S.last_bloodtype) + FP.blood_DNA += list(S.last_blood_DNA = S.last_bloodtype) + FP.update_icon() + update_inv_shoes() + //End bloody footprints + + S.step_action() + +/mob/living/carbon/human/Process_Spacemove(movement_dir = 0) //Temporary laziness thing. Will change to handles by species reee. + if(dna.species.space_move(src)) + return TRUE + return ..() diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 69ec619e5d..ec0eda846c 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -1,273 +1,273 @@ -/mob/living/carbon/human/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) - return dna.species.can_equip(I, slot, disable_warning, src, bypass_equip_delay_self) - -// Return the item currently in the slot ID -/mob/living/carbon/human/get_item_by_slot(slot_id) - switch(slot_id) - if(SLOT_BACK) - return back - if(SLOT_WEAR_MASK) - return wear_mask - if(SLOT_NECK) - return wear_neck - if(SLOT_HANDCUFFED) - return handcuffed - if(SLOT_LEGCUFFED) - return legcuffed - if(SLOT_BELT) - return belt - if(SLOT_WEAR_ID) - return wear_id - if(SLOT_EARS) - return ears - if(SLOT_GLASSES) - return glasses - if(SLOT_GLOVES) - return gloves - if(SLOT_HEAD) - return head - if(SLOT_SHOES) - return shoes - if(SLOT_WEAR_SUIT) - return wear_suit - if(SLOT_W_UNIFORM) - return w_uniform - if(SLOT_L_STORE) - return l_store - if(SLOT_R_STORE) - return r_store - if(SLOT_S_STORE) - return s_store - return null - -/mob/living/carbon/human/proc/get_all_slots() - . = get_head_slots() | get_body_slots() - -/mob/living/carbon/human/proc/get_body_slots() - return list( - back, - s_store, - handcuffed, - legcuffed, - wear_suit, - gloves, - shoes, - belt, - wear_id, - l_store, - r_store, - w_uniform - ) - -/mob/living/carbon/human/proc/get_head_slots() - return list( - head, - wear_mask, - wear_neck, - glasses, - ears, - ) - -/mob/living/carbon/human/proc/get_storage_slots() - return list( - back, - belt, - l_store, - r_store, - s_store, - ) - -//This is an UNSAFE proc. Use mob_can_equip() before calling this one! Or rather use equip_to_slot_if_possible() or advanced_equip_to_slot_if_possible() -/mob/living/carbon/human/equip_to_slot(obj/item/I, slot) - . = ..() - if(!.) //a check failed or the item has already found its slot - return - - var/not_handled = FALSE //Added in case we make this type path deeper one day - switch(slot) - if(SLOT_BELT) - belt = I - update_inv_belt() - if(SLOT_WEAR_ID) - wear_id = I - sec_hud_set_ID() - update_inv_wear_id() - if(SLOT_EARS) - ears = I - update_inv_ears() - if(SLOT_GLASSES) - glasses = I - var/obj/item/clothing/glasses/G = I - if(G.glass_colour_type) - update_glasses_color(G, 1) - if(G.tint) - update_tint() - if(G.vision_correction) - clear_fullscreen("nearsighted") - clear_fullscreen("eye_damage") - if(G.vision_flags || G.darkness_view || G.invis_override || G.invis_view || !isnull(G.lighting_alpha)) - update_sight() - update_inv_glasses() - if(SLOT_GLOVES) - gloves = I - update_inv_gloves() - if(SLOT_SHOES) - shoes = I - update_inv_shoes() - if(SLOT_WEAR_SUIT) - wear_suit = I - if(I.flags_inv & HIDEJUMPSUIT) - update_inv_w_uniform() - if(wear_suit.breakouttime) //when equipping a straightjacket - stop_pulling() //can't pull if restrained - update_action_buttons_icon() //certain action buttons will no longer be usable. - update_inv_wear_suit() - if(SLOT_W_UNIFORM) - w_uniform = I - update_suit_sensors() - update_inv_w_uniform() - if(SLOT_L_STORE) - l_store = I - update_inv_pockets() - if(SLOT_R_STORE) - r_store = I - update_inv_pockets() - if(SLOT_S_STORE) - s_store = I - update_inv_s_store() - else - to_chat(src, "You are trying to equip this item to an unsupported inventory slot. Report this to a coder!") - not_handled = TRUE - - //Item is handled and in slot, valid to call callback, for this proc should always be true - if(!not_handled) - I.equipped(src, slot) - - return not_handled //For future deeper overrides - -/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) - var/index = get_held_index_of_item(I) - . = ..() //See mob.dm for an explanation on this and some rage about people copypasting instead of calling ..() like they should. - if(!. || !I) - return - if(index && !QDELETED(src) && dna.species.mutanthands) //hand freed, fill with claws, skip if we're getting deleted. - put_in_hand(new dna.species.mutanthands(), index) - if(I == wear_suit) - if(s_store && invdrop) - dropItemToGround(s_store, TRUE) //It makes no sense for your suit storage to stay on you if you drop your suit. - if(wear_suit.breakouttime) //when unequipping a straightjacket - drop_all_held_items() //suit is restraining - update_action_buttons_icon() //certain action buttons may be usable again. - wear_suit = null - if(!QDELETED(src)) //no need to update we're getting deleted anyway - if(I.flags_inv & HIDEJUMPSUIT) - update_inv_w_uniform() - update_inv_wear_suit() - else if(I == w_uniform) - if(invdrop) - if(r_store) - dropItemToGround(r_store, TRUE) //Again, makes sense for pockets to drop. - if(l_store) - dropItemToGround(l_store, TRUE) - if(wear_id && !CHECK_BITFIELD(wear_id.item_flags, NO_UNIFORM_REQUIRED)) - dropItemToGround(wear_id) - if(belt && !CHECK_BITFIELD(belt.item_flags, NO_UNIFORM_REQUIRED)) - dropItemToGround(belt) - w_uniform = null - update_suit_sensors() - if(!QDELETED(src)) - update_inv_w_uniform() - else if(I == gloves) - gloves = null - if(!QDELETED(src)) - update_inv_gloves() - else if(I == glasses) - glasses = null - var/obj/item/clothing/glasses/G = I - if(G.glass_colour_type) - update_glasses_color(G, 0) - if(G.tint) - update_tint() - if(G.vision_correction) - if(HAS_TRAIT(src, TRAIT_NEARSIGHT)) - overlay_fullscreen("nearsighted", /obj/screen/fullscreen/impaired, 1) - if(G.vision_flags || G.darkness_view || G.invis_override || G.invis_view || !isnull(G.lighting_alpha)) - update_sight() - if(!QDELETED(src)) - update_inv_glasses() - else if(I == ears) - ears = null - if(!QDELETED(src)) - update_inv_ears() - else if(I == shoes) - shoes = null - if(!QDELETED(src)) - update_inv_shoes() - else if(I == belt) - belt = null - if(!QDELETED(src)) - update_inv_belt() - else if(I == wear_id) - wear_id = null - sec_hud_set_ID() - if(!QDELETED(src)) - update_inv_wear_id() - else if(I == r_store) - r_store = null - if(!QDELETED(src)) - update_inv_pockets() - else if(I == l_store) - l_store = null - if(!QDELETED(src)) - update_inv_pockets() - else if(I == s_store) - s_store = null - if(!QDELETED(src)) - update_inv_s_store() - -/mob/living/carbon/human/wear_mask_update(obj/item/clothing/C, toggle_off = 1) - if((C.flags_inv & (HIDEHAIR|HIDEFACIALHAIR)) || (initial(C.flags_inv) & (HIDEHAIR|HIDEFACIALHAIR))) - update_hair() - if(toggle_off && internal && !getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - update_internals_hud_icon(0) - internal = null - if(C.flags_inv & HIDEEYES) - update_inv_glasses() - sec_hud_set_security_status() - ..() - -/mob/living/carbon/human/head_update(obj/item/I, forced) - if((I.flags_inv & (HIDEHAIR|HIDEFACIALHAIR)) || forced) - update_hair() - else - var/obj/item/clothing/C = I - if(istype(C) && C.dynamic_hair_suffix) - update_hair() - if(I.flags_inv & HIDEEYES || forced) - update_inv_glasses() - if(I.flags_inv & HIDEEARS || forced) - update_body() - sec_hud_set_security_status() - ..() - -/mob/living/carbon/human/proc/equipOutfit(outfit, visualsOnly = FALSE, client/preference_source) - var/datum/outfit/O = null - - if(ispath(outfit)) - O = new outfit - else - O = outfit - if(!istype(O)) - return 0 - if(!O) - return 0 - - return O.equip(src, visualsOnly, preference_source) - - -//delete all equipment without dropping anything -/mob/living/carbon/human/proc/delete_equipment() - for(var/slot in get_all_slots())//order matters, dependant slots go first - qdel(slot) - for(var/obj/item/I in held_items) - qdel(I) +/mob/living/carbon/human/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) + return dna.species.can_equip(I, slot, disable_warning, src, bypass_equip_delay_self) + +// Return the item currently in the slot ID +/mob/living/carbon/human/get_item_by_slot(slot_id) + switch(slot_id) + if(SLOT_BACK) + return back + if(SLOT_WEAR_MASK) + return wear_mask + if(SLOT_NECK) + return wear_neck + if(SLOT_HANDCUFFED) + return handcuffed + if(SLOT_LEGCUFFED) + return legcuffed + if(SLOT_BELT) + return belt + if(SLOT_WEAR_ID) + return wear_id + if(SLOT_EARS) + return ears + if(SLOT_GLASSES) + return glasses + if(SLOT_GLOVES) + return gloves + if(SLOT_HEAD) + return head + if(SLOT_SHOES) + return shoes + if(SLOT_WEAR_SUIT) + return wear_suit + if(SLOT_W_UNIFORM) + return w_uniform + if(SLOT_L_STORE) + return l_store + if(SLOT_R_STORE) + return r_store + if(SLOT_S_STORE) + return s_store + return null + +/mob/living/carbon/human/proc/get_all_slots() + . = get_head_slots() | get_body_slots() + +/mob/living/carbon/human/proc/get_body_slots() + return list( + back, + s_store, + handcuffed, + legcuffed, + wear_suit, + gloves, + shoes, + belt, + wear_id, + l_store, + r_store, + w_uniform + ) + +/mob/living/carbon/human/proc/get_head_slots() + return list( + head, + wear_mask, + wear_neck, + glasses, + ears, + ) + +/mob/living/carbon/human/proc/get_storage_slots() + return list( + back, + belt, + l_store, + r_store, + s_store, + ) + +//This is an UNSAFE proc. Use mob_can_equip() before calling this one! Or rather use equip_to_slot_if_possible() or advanced_equip_to_slot_if_possible() +/mob/living/carbon/human/equip_to_slot(obj/item/I, slot) + . = ..() + if(!.) //a check failed or the item has already found its slot + return + + var/not_handled = FALSE //Added in case we make this type path deeper one day + switch(slot) + if(SLOT_BELT) + belt = I + update_inv_belt() + if(SLOT_WEAR_ID) + wear_id = I + sec_hud_set_ID() + update_inv_wear_id() + if(SLOT_EARS) + ears = I + update_inv_ears() + if(SLOT_GLASSES) + glasses = I + var/obj/item/clothing/glasses/G = I + if(G.glass_colour_type) + update_glasses_color(G, 1) + if(G.tint) + update_tint() + if(G.vision_correction) + clear_fullscreen("nearsighted") + clear_fullscreen("eye_damage") + if(G.vision_flags || G.darkness_view || G.invis_override || G.invis_view || !isnull(G.lighting_alpha)) + update_sight() + update_inv_glasses() + if(SLOT_GLOVES) + gloves = I + update_inv_gloves() + if(SLOT_SHOES) + shoes = I + update_inv_shoes() + if(SLOT_WEAR_SUIT) + wear_suit = I + if(I.flags_inv & HIDEJUMPSUIT) + update_inv_w_uniform() + if(wear_suit.breakouttime) //when equipping a straightjacket + stop_pulling() //can't pull if restrained + update_action_buttons_icon() //certain action buttons will no longer be usable. + update_inv_wear_suit() + if(SLOT_W_UNIFORM) + w_uniform = I + update_suit_sensors() + update_inv_w_uniform() + if(SLOT_L_STORE) + l_store = I + update_inv_pockets() + if(SLOT_R_STORE) + r_store = I + update_inv_pockets() + if(SLOT_S_STORE) + s_store = I + update_inv_s_store() + else + to_chat(src, "You are trying to equip this item to an unsupported inventory slot. Report this to a coder!") + not_handled = TRUE + + //Item is handled and in slot, valid to call callback, for this proc should always be true + if(!not_handled) + I.equipped(src, slot) + + return not_handled //For future deeper overrides + +/mob/living/carbon/human/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) + var/index = get_held_index_of_item(I) + . = ..() //See mob.dm for an explanation on this and some rage about people copypasting instead of calling ..() like they should. + if(!. || !I) + return + if(index && !QDELETED(src) && dna.species.mutanthands) //hand freed, fill with claws, skip if we're getting deleted. + put_in_hand(new dna.species.mutanthands(), index) + if(I == wear_suit) + if(s_store && invdrop) + dropItemToGround(s_store, TRUE) //It makes no sense for your suit storage to stay on you if you drop your suit. + if(wear_suit.breakouttime) //when unequipping a straightjacket + drop_all_held_items() //suit is restraining + update_action_buttons_icon() //certain action buttons may be usable again. + wear_suit = null + if(!QDELETED(src)) //no need to update we're getting deleted anyway + if(I.flags_inv & HIDEJUMPSUIT) + update_inv_w_uniform() + update_inv_wear_suit() + else if(I == w_uniform) + if(invdrop) + if(r_store) + dropItemToGround(r_store, TRUE) //Again, makes sense for pockets to drop. + if(l_store) + dropItemToGround(l_store, TRUE) + if(wear_id && !CHECK_BITFIELD(wear_id.item_flags, NO_UNIFORM_REQUIRED)) + dropItemToGround(wear_id) + if(belt && !CHECK_BITFIELD(belt.item_flags, NO_UNIFORM_REQUIRED)) + dropItemToGround(belt) + w_uniform = null + update_suit_sensors() + if(!QDELETED(src)) + update_inv_w_uniform() + else if(I == gloves) + gloves = null + if(!QDELETED(src)) + update_inv_gloves() + else if(I == glasses) + glasses = null + var/obj/item/clothing/glasses/G = I + if(G.glass_colour_type) + update_glasses_color(G, 0) + if(G.tint) + update_tint() + if(G.vision_correction) + if(HAS_TRAIT(src, TRAIT_NEARSIGHT)) + overlay_fullscreen("nearsighted", /obj/screen/fullscreen/impaired, 1) + if(G.vision_flags || G.darkness_view || G.invis_override || G.invis_view || !isnull(G.lighting_alpha)) + update_sight() + if(!QDELETED(src)) + update_inv_glasses() + else if(I == ears) + ears = null + if(!QDELETED(src)) + update_inv_ears() + else if(I == shoes) + shoes = null + if(!QDELETED(src)) + update_inv_shoes() + else if(I == belt) + belt = null + if(!QDELETED(src)) + update_inv_belt() + else if(I == wear_id) + wear_id = null + sec_hud_set_ID() + if(!QDELETED(src)) + update_inv_wear_id() + else if(I == r_store) + r_store = null + if(!QDELETED(src)) + update_inv_pockets() + else if(I == l_store) + l_store = null + if(!QDELETED(src)) + update_inv_pockets() + else if(I == s_store) + s_store = null + if(!QDELETED(src)) + update_inv_s_store() + +/mob/living/carbon/human/wear_mask_update(obj/item/clothing/C, toggle_off = 1) + if((C.flags_inv & (HIDEHAIR|HIDEFACIALHAIR)) || (initial(C.flags_inv) & (HIDEHAIR|HIDEFACIALHAIR))) + update_hair() + if(toggle_off && internal && !getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + update_internals_hud_icon(0) + internal = null + if(C.flags_inv & HIDEEYES) + update_inv_glasses() + sec_hud_set_security_status() + ..() + +/mob/living/carbon/human/head_update(obj/item/I, forced) + if((I.flags_inv & (HIDEHAIR|HIDEFACIALHAIR)) || forced) + update_hair() + else + var/obj/item/clothing/C = I + if(istype(C) && C.dynamic_hair_suffix) + update_hair() + if(I.flags_inv & HIDEEYES || forced) + update_inv_glasses() + if(I.flags_inv & HIDEEARS || forced) + update_body() + sec_hud_set_security_status() + ..() + +/mob/living/carbon/human/proc/equipOutfit(outfit, visualsOnly = FALSE, client/preference_source) + var/datum/outfit/O = null + + if(ispath(outfit)) + O = new outfit + else + O = outfit + if(!istype(O)) + return 0 + if(!O) + return 0 + + return O.equip(src, visualsOnly, preference_source) + + +//delete all equipment without dropping anything +/mob/living/carbon/human/proc/delete_equipment() + for(var/slot in get_all_slots())//order matters, dependant slots go first + qdel(slot) + for(var/obj/item/I in held_items) + qdel(I) diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm index c54f41017a..8e12ac97d3 100644 --- a/code/modules/mob/living/carbon/human/say.dm +++ b/code/modules/mob/living/carbon/human/say.dm @@ -1,116 +1,116 @@ -/mob/living/carbon/human/say_mod(input, message_mode) - verb_say = dna.species.say_mod - . = ..() - if(message_mode != MODE_CUSTOM_SAY && message_mode != MODE_WHISPER_CRIT) - switch(slurring) - if(10 to 25) - return "jumbles" - if(25 to 50) - return "slurs" - if(50 to INFINITY) - return "garbles" - -/mob/living/carbon/human/GetVoice() - if(istype(wear_mask, /obj/item/clothing/mask/chameleon)) - var/obj/item/clothing/mask/chameleon/V = wear_mask - if(V.vchange && wear_id) - var/obj/item/card/id/idcard = wear_id.GetID() - if(istype(idcard)) - return idcard.registered_name - else - return real_name - else - return real_name - if(mind) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling && changeling.mimicing ) - return changeling.mimicing - if(GetSpecialVoice()) - return GetSpecialVoice() - return real_name - -/mob/living/carbon/human/IsVocal() - // how do species that don't breathe talk? magic, that's what. - if(!HAS_TRAIT_FROM(src, TRAIT_NOBREATH, SPECIES_TRAIT) && !getorganslot(ORGAN_SLOT_LUNGS)) - return FALSE - if(mind) - return !mind.miming - return TRUE - -/mob/living/carbon/human/proc/SetSpecialVoice(new_voice) - if(new_voice) - special_voice = new_voice - return - -/mob/living/carbon/human/proc/UnsetSpecialVoice() - special_voice = "" - return - -/mob/living/carbon/human/proc/GetSpecialVoice() - return special_voice - -/mob/living/carbon/human/binarycheck() - if(ears) - var/obj/item/radio/headset/dongle = ears - if(!istype(dongle)) - return FALSE - if(dongle.translate_binary) - return TRUE - -/mob/living/carbon/human/radio(message, message_mode, list/spans, language) - . = ..() - if(.) - return - - switch(message_mode) - if(MODE_HEADSET) - if (ears) - ears.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - - if(MODE_DEPARTMENT) - if (ears) - ears.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - if(message_mode in GLOB.radiochannels) - if(ears) - ears.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - return 0 - -/mob/living/carbon/human/get_alt_name() - if(name != GetVoice()) - return " (as [get_id_name("Unknown")])" - -/mob/living/carbon/human/proc/forcesay(list/append) //this proc is at the bottom of the file because quote fuckery makes notepad++ cri - if(stat == CONSCIOUS) - if(client) - var/virgin = 1 //has the text been modified yet? - var/temp = winget(client, "input", "text") - if(findtextEx(temp, "Say \"", 1, 7) && length(temp) > 5) //"case sensitive means - - temp = replacetext(temp, ";", "") //general radio - - if(findtext(trim_left(temp), ":", 6, 7)) //dept radio - temp = copytext(trim_left(temp), 8) - virgin = 0 - - if(virgin) - temp = copytext(trim_left(temp), 6) //normal speech - virgin = 0 - - while(findtext(trim_left(temp), ":", 1, 2)) //dept radio again (necessary) - temp = copytext(trim_left(temp), 3) - - if(findtext(temp, "*", 1, 2)) //emotes - return - - var/trimmed = trim_left(temp) - if(length(trimmed)) - if(append) - temp += pick(append) - - say(temp) - winset(client, "input", "text=[null]") +/mob/living/carbon/human/say_mod(input, message_mode) + verb_say = dna.species.say_mod + . = ..() + if(message_mode != MODE_CUSTOM_SAY && message_mode != MODE_WHISPER_CRIT) + switch(slurring) + if(10 to 25) + return "jumbles" + if(25 to 50) + return "slurs" + if(50 to INFINITY) + return "garbles" + +/mob/living/carbon/human/GetVoice() + if(istype(wear_mask, /obj/item/clothing/mask/chameleon)) + var/obj/item/clothing/mask/chameleon/V = wear_mask + if(V.vchange && wear_id) + var/obj/item/card/id/idcard = wear_id.GetID() + if(istype(idcard)) + return idcard.registered_name + else + return real_name + else + return real_name + if(mind) + var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling && changeling.mimicing ) + return changeling.mimicing + if(GetSpecialVoice()) + return GetSpecialVoice() + return real_name + +/mob/living/carbon/human/IsVocal() + // how do species that don't breathe talk? magic, that's what. + if(!HAS_TRAIT_FROM(src, TRAIT_NOBREATH, SPECIES_TRAIT) && !getorganslot(ORGAN_SLOT_LUNGS)) + return FALSE + if(mind) + return !mind.miming + return TRUE + +/mob/living/carbon/human/proc/SetSpecialVoice(new_voice) + if(new_voice) + special_voice = new_voice + return + +/mob/living/carbon/human/proc/UnsetSpecialVoice() + special_voice = "" + return + +/mob/living/carbon/human/proc/GetSpecialVoice() + return special_voice + +/mob/living/carbon/human/binarycheck() + if(ears) + var/obj/item/radio/headset/dongle = ears + if(!istype(dongle)) + return FALSE + if(dongle.translate_binary) + return TRUE + +/mob/living/carbon/human/radio(message, message_mode, list/spans, language) + . = ..() + if(.) + return + + switch(message_mode) + if(MODE_HEADSET) + if (ears) + ears.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + + if(MODE_DEPARTMENT) + if (ears) + ears.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + if(message_mode in GLOB.radiochannels) + if(ears) + ears.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + return 0 + +/mob/living/carbon/human/get_alt_name() + if(name != GetVoice()) + return " (as [get_id_name("Unknown")])" + +/mob/living/carbon/human/proc/forcesay(list/append) //this proc is at the bottom of the file because quote fuckery makes notepad++ cri + if(stat == CONSCIOUS) + if(client) + var/virgin = 1 //has the text been modified yet? + var/temp = winget(client, "input", "text") + if(findtextEx(temp, "Say \"", 1, 7) && length(temp) > 5) //"case sensitive means + + temp = replacetext(temp, ";", "") //general radio + + if(findtext(trim_left(temp), ":", 6, 7)) //dept radio + temp = copytext(trim_left(temp), 8) + virgin = 0 + + if(virgin) + temp = copytext(trim_left(temp), 6) //normal speech + virgin = 0 + + while(findtext(trim_left(temp), ":", 1, 2)) //dept radio again (necessary) + temp = copytext(trim_left(temp), 3) + + if(findtext(temp, "*", 1, 2)) //emotes + return + + var/trimmed = trim_left(temp) + if(length(trimmed)) + if(append) + temp += pick(append) + + say(temp) + winset(client, "input", "text=[null]") diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm index 6e54e320ff..0899038da4 100644 --- a/code/modules/mob/living/carbon/human/species_types/abductors.dm +++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm @@ -1,18 +1,18 @@ -/datum/species/abductor - name = "Abductor" - id = "abductor" - say_mod = "gibbers" - sexes = FALSE - species_traits = list(NOBLOOD,NOEYES,NOGENITALS,NOAROUSAL) - inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_CHUNKYFINGERS,TRAIT_NOHUNGER,TRAIT_NOBREATH) - mutanttongue = /obj/item/organ/tongue/abductor - -/datum/species/abductor/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] - abductor_hud.add_hud_to(C) - -/datum/species/abductor/on_species_loss(mob/living/carbon/C) - . = ..() - var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] - abductor_hud.remove_hud_from(C) +/datum/species/abductor + name = "Abductor" + id = "abductor" + say_mod = "gibbers" + sexes = FALSE + species_traits = list(NOBLOOD,NOEYES,NOGENITALS,NOAROUSAL) + inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_CHUNKYFINGERS,TRAIT_NOHUNGER,TRAIT_NOBREATH) + mutanttongue = /obj/item/organ/tongue/abductor + +/datum/species/abductor/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] + abductor_hud.add_hud_to(C) + +/datum/species/abductor/on_species_loss(mob/living/carbon/C) + . = ..() + var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] + abductor_hud.remove_hud_from(C) diff --git a/code/modules/mob/living/carbon/human/species_types/android.dm b/code/modules/mob/living/carbon/human/species_types/android.dm index a36835fbb3..ad9ba83771 100644 --- a/code/modules/mob/living/carbon/human/species_types/android.dm +++ b/code/modules/mob/living/carbon/human/species_types/android.dm @@ -1,24 +1,24 @@ -/datum/species/android - name = "Android" - id = "android" - say_mod = "states" - species_traits = list(NOBLOOD,NOGENITALS,NOAROUSAL) - inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_NOFIRE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_LIMBATTACHMENT) - inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID) - meat = null - gib_types = /obj/effect/gibspawner/robot - damage_overlay_type = "synth" - mutanttongue = /obj/item/organ/tongue/robot - limbs_id = "synth" - -/datum/species/android/on_species_gain(mob/living/carbon/C) - . = ..() - for(var/X in C.bodyparts) - var/obj/item/bodypart/O = X - O.change_bodypart_status(BODYPART_ROBOTIC, FALSE, TRUE) - -/datum/species/android/on_species_loss(mob/living/carbon/C) - . = ..() - for(var/X in C.bodyparts) - var/obj/item/bodypart/O = X - O.change_bodypart_status(BODYPART_ORGANIC,FALSE, TRUE) +/datum/species/android + name = "Android" + id = "android" + say_mod = "states" + species_traits = list(NOBLOOD,NOGENITALS,NOAROUSAL) + inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_NOFIRE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_LIMBATTACHMENT) + inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID) + meat = null + gib_types = /obj/effect/gibspawner/robot + damage_overlay_type = "synth" + mutanttongue = /obj/item/organ/tongue/robot + limbs_id = "synth" + +/datum/species/android/on_species_gain(mob/living/carbon/C) + . = ..() + for(var/X in C.bodyparts) + var/obj/item/bodypart/O = X + O.change_bodypart_status(BODYPART_ROBOTIC, FALSE, TRUE) + +/datum/species/android/on_species_loss(mob/living/carbon/C) + . = ..() + for(var/X in C.bodyparts) + var/obj/item/bodypart/O = X + O.change_bodypart_status(BODYPART_ORGANIC,FALSE, TRUE) diff --git a/code/modules/mob/living/carbon/human/species_types/angel.dm b/code/modules/mob/living/carbon/human/species_types/angel.dm index 7669a8c740..18c6a7bab4 100644 --- a/code/modules/mob/living/carbon/human/species_types/angel.dm +++ b/code/modules/mob/living/carbon/human/species_types/angel.dm @@ -1,145 +1,145 @@ -/datum/species/angel - name = "Angel" - id = "angel" - default_color = "FFFFFF" - species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS) - mutant_bodyparts = list("wings") - default_features = list("mcolor" = "FFF", "tail_human" = "None", "ears" = "None", "wings" = "Angel") - use_skintones = 1 - no_equip = list(SLOT_BACK) - blacklisted = 1 - limbs_id = "human" - skinned_type = /obj/item/stack/sheet/animalhide/human - - var/datum/action/innate/flight/fly - -/datum/species/angel/on_species_gain(mob/living/carbon/human/H, datum/species/old_species) - ..() - if(H.dna && H.dna.species && (H.dna.features["wings"] != "Angel")) - if(!("wings" in H.dna.species.mutant_bodyparts)) - H.dna.species.mutant_bodyparts |= "wings" - H.dna.features["wings"] = "Angel" - H.update_body() - if(ishuman(H) && !fly) - fly = new - fly.Grant(H) - ADD_TRAIT(H, TRAIT_HOLY, SPECIES_TRAIT) - -/datum/species/angel/on_species_loss(mob/living/carbon/human/H) - if(fly) - fly.Remove(H) - if(H.movement_type & FLYING) - H.setMovetype(H.movement_type & ~FLYING) - ToggleFlight(H,0) - if(H.dna && H.dna.species && (H.dna.features["wings"] == "Angel")) - if("wings" in H.dna.species.mutant_bodyparts) - H.dna.species.mutant_bodyparts -= "wings" - H.dna.features["wings"] = "None" - H.update_body() - REMOVE_TRAIT(H, TRAIT_HOLY, SPECIES_TRAIT) - ..() - -/datum/species/angel/spec_life(mob/living/carbon/human/H) - HandleFlight(H) - -/datum/species/angel/proc/HandleFlight(mob/living/carbon/human/H) - if(H.movement_type & FLYING) - if(!CanFly(H)) - ToggleFlight(H,0) - return 0 - return 1 - else - return 0 - -/datum/species/angel/proc/CanFly(mob/living/carbon/human/H) - if(H.stat || H.IsStun() || H.IsKnockdown()) - return 0 - if(H.wear_suit && ((H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception)))) //Jumpsuits have tail holes, so it makes sense they have wing holes too - to_chat(H, "Your suit blocks your wings from extending!") - return 0 - var/turf/T = get_turf(H) - if(!T) - return 0 - - var/datum/gas_mixture/environment = T.return_air() - if(environment && !(environment.return_pressure() > 30)) - to_chat(H, "The atmosphere is too thin for you to fly!") - return 0 - else - return 1 - -/datum/action/innate/flight - name = "Toggle Flight" - check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_STUN - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "flight" - -/datum/action/innate/flight/Activate() - var/mob/living/carbon/human/H = owner - var/datum/species/angel/A = H.dna.species - if(A.CanFly(H)) - if(H.movement_type & FLYING) - to_chat(H, "You settle gently back onto the ground...") - A.ToggleFlight(H,0) - H.update_canmove() - else - to_chat(H, "You beat your wings and begin to hover gently above the ground...") - H.resting = 0 - A.ToggleFlight(H,1) - H.update_canmove() - -/datum/species/angel/proc/flyslip(mob/living/carbon/human/H) - var/obj/buckled_obj - if(H.buckled) - buckled_obj = H.buckled - - to_chat(H, "Your wings spazz out and launch you!") - - playsound(H.loc, 'sound/misc/slip.ogg', 50, 1, -3) - - for(var/obj/item/I in H.held_items) - H.accident(I) - - var/olddir = H.dir - - H.stop_pulling() - if(buckled_obj) - buckled_obj.unbuckle_mob(H) - step(buckled_obj, olddir) - else - for(var/i=1, i<5, i++) - spawn (i) - step(H, olddir) - H.spin(1,1) - return 1 - - -/datum/species/angel/spec_stun(mob/living/carbon/human/H,amount) - if(H.movement_type & FLYING) - ToggleFlight(H,0) - flyslip(H) - . = ..() - -/datum/species/angel/negates_gravity(mob/living/carbon/human/H) - if(H.movement_type & FLYING) - return 1 - -/datum/species/angel/space_move(mob/living/carbon/human/H) - if(H.movement_type & FLYING) - return 1 - -/datum/species/angel/proc/ToggleFlight(mob/living/carbon/human/H,flight) - if(flight && CanFly(H)) - stunmod = 2 - speedmod = -0.35 - H.setMovetype(H.movement_type | FLYING) - override_float = TRUE - H.pass_flags |= PASSTABLE - H.OpenWings() - else - stunmod = 1 - speedmod = 0 - H.setMovetype(H.movement_type & ~FLYING) - override_float = FALSE - H.pass_flags &= ~PASSTABLE +/datum/species/angel + name = "Angel" + id = "angel" + default_color = "FFFFFF" + species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS) + mutant_bodyparts = list("wings") + default_features = list("mcolor" = "FFF", "tail_human" = "None", "ears" = "None", "wings" = "Angel") + use_skintones = 1 + no_equip = list(SLOT_BACK) + blacklisted = 1 + limbs_id = "human" + skinned_type = /obj/item/stack/sheet/animalhide/human + + var/datum/action/innate/flight/fly + +/datum/species/angel/on_species_gain(mob/living/carbon/human/H, datum/species/old_species) + ..() + if(H.dna && H.dna.species && (H.dna.features["wings"] != "Angel")) + if(!("wings" in H.dna.species.mutant_bodyparts)) + H.dna.species.mutant_bodyparts |= "wings" + H.dna.features["wings"] = "Angel" + H.update_body() + if(ishuman(H) && !fly) + fly = new + fly.Grant(H) + ADD_TRAIT(H, TRAIT_HOLY, SPECIES_TRAIT) + +/datum/species/angel/on_species_loss(mob/living/carbon/human/H) + if(fly) + fly.Remove(H) + if(H.movement_type & FLYING) + H.setMovetype(H.movement_type & ~FLYING) + ToggleFlight(H,0) + if(H.dna && H.dna.species && (H.dna.features["wings"] == "Angel")) + if("wings" in H.dna.species.mutant_bodyparts) + H.dna.species.mutant_bodyparts -= "wings" + H.dna.features["wings"] = "None" + H.update_body() + REMOVE_TRAIT(H, TRAIT_HOLY, SPECIES_TRAIT) + ..() + +/datum/species/angel/spec_life(mob/living/carbon/human/H) + HandleFlight(H) + +/datum/species/angel/proc/HandleFlight(mob/living/carbon/human/H) + if(H.movement_type & FLYING) + if(!CanFly(H)) + ToggleFlight(H,0) + return 0 + return 1 + else + return 0 + +/datum/species/angel/proc/CanFly(mob/living/carbon/human/H) + if(H.stat || H.IsStun() || H.IsKnockdown()) + return 0 + if(H.wear_suit && ((H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception)))) //Jumpsuits have tail holes, so it makes sense they have wing holes too + to_chat(H, "Your suit blocks your wings from extending!") + return 0 + var/turf/T = get_turf(H) + if(!T) + return 0 + + var/datum/gas_mixture/environment = T.return_air() + if(environment && !(environment.return_pressure() > 30)) + to_chat(H, "The atmosphere is too thin for you to fly!") + return 0 + else + return 1 + +/datum/action/innate/flight + name = "Toggle Flight" + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_STUN + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "flight" + +/datum/action/innate/flight/Activate() + var/mob/living/carbon/human/H = owner + var/datum/species/angel/A = H.dna.species + if(A.CanFly(H)) + if(H.movement_type & FLYING) + to_chat(H, "You settle gently back onto the ground...") + A.ToggleFlight(H,0) + H.update_canmove() + else + to_chat(H, "You beat your wings and begin to hover gently above the ground...") + H.resting = 0 + A.ToggleFlight(H,1) + H.update_canmove() + +/datum/species/angel/proc/flyslip(mob/living/carbon/human/H) + var/obj/buckled_obj + if(H.buckled) + buckled_obj = H.buckled + + to_chat(H, "Your wings spazz out and launch you!") + + playsound(H.loc, 'sound/misc/slip.ogg', 50, 1, -3) + + for(var/obj/item/I in H.held_items) + H.accident(I) + + var/olddir = H.dir + + H.stop_pulling() + if(buckled_obj) + buckled_obj.unbuckle_mob(H) + step(buckled_obj, olddir) + else + for(var/i=1, i<5, i++) + spawn (i) + step(H, olddir) + H.spin(1,1) + return 1 + + +/datum/species/angel/spec_stun(mob/living/carbon/human/H,amount) + if(H.movement_type & FLYING) + ToggleFlight(H,0) + flyslip(H) + . = ..() + +/datum/species/angel/negates_gravity(mob/living/carbon/human/H) + if(H.movement_type & FLYING) + return 1 + +/datum/species/angel/space_move(mob/living/carbon/human/H) + if(H.movement_type & FLYING) + return 1 + +/datum/species/angel/proc/ToggleFlight(mob/living/carbon/human/H,flight) + if(flight && CanFly(H)) + stunmod = 2 + speedmod = -0.35 + H.setMovetype(H.movement_type | FLYING) + override_float = TRUE + H.pass_flags |= PASSTABLE + H.OpenWings() + else + stunmod = 1 + speedmod = 0 + H.setMovetype(H.movement_type & ~FLYING) + override_float = FALSE + H.pass_flags &= ~PASSTABLE H.CloseWings() \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm index 426213161e..4697e6aa3c 100644 --- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm @@ -1,36 +1,36 @@ -/datum/species/fly - name = "Anthromorphic Fly" - id = "fly" - say_mod = "buzzes" - species_traits = list(NOEYES) - inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_BUG) - mutanttongue = /obj/item/organ/tongue/fly - mutantliver = /obj/item/organ/liver/fly - mutantstomach = /obj/item/organ/stomach/fly - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/fly - disliked_food = null - liked_food = GROSS - exotic_bloodtype = "BUG" - -/datum/species/fly/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) - if(chem.type == /datum/reagent/toxin/pestkiller) - H.adjustToxLoss(3) - H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM) - return 1 - - -/datum/species/fly/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) - if(istype(chem, /datum/reagent/consumable)) - var/datum/reagent/consumable/nutri_check = chem - if(nutri_check.nutriment_factor > 0) - var/turf/pos = get_turf(H) - H.vomit(0, FALSE, FALSE, 2, TRUE) - playsound(pos, 'sound/effects/splat.ogg', 50, 1) - H.visible_message("[H] vomits on the floor!", \ - "You throw up on the floor!") - ..() - -/datum/species/fly/check_weakness(obj/item/weapon, mob/living/attacker) - if(istype(weapon, /obj/item/melee/flyswatter)) - return 29 //Flyswatters deal 30x damage to flypeople. - return 0 +/datum/species/fly + name = "Anthromorphic Fly" + id = "fly" + say_mod = "buzzes" + species_traits = list(NOEYES) + inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_BUG) + mutanttongue = /obj/item/organ/tongue/fly + mutantliver = /obj/item/organ/liver/fly + mutantstomach = /obj/item/organ/stomach/fly + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/fly + disliked_food = null + liked_food = GROSS + exotic_bloodtype = "BUG" + +/datum/species/fly/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) + if(chem.type == /datum/reagent/toxin/pestkiller) + H.adjustToxLoss(3) + H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM) + return 1 + + +/datum/species/fly/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) + if(istype(chem, /datum/reagent/consumable)) + var/datum/reagent/consumable/nutri_check = chem + if(nutri_check.nutriment_factor > 0) + var/turf/pos = get_turf(H) + H.vomit(0, FALSE, FALSE, 2, TRUE) + playsound(pos, 'sound/effects/splat.ogg', 50, 1) + H.visible_message("[H] vomits on the floor!", \ + "You throw up on the floor!") + ..() + +/datum/species/fly/check_weakness(obj/item/weapon, mob/living/attacker) + if(istype(weapon, /obj/item/melee/flyswatter)) + return 29 //Flyswatters deal 30x damage to flypeople. + return 0 diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index c247d14589..3dad8449df 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -1,1025 +1,1025 @@ -/datum/species/golem - // Animated beings of stone. They have increased defenses, and do not need to breathe. They're also slow as fuuuck. - name = "Golem" - id = "iron golem" - species_traits = list(NOBLOOD,MUTCOLORS,NO_UNDERWEAR,NOGENITALS,NOAROUSAL) - inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) - inherent_biotypes = list(MOB_INORGANIC, MOB_HUMANOID) - mutant_organs = list(/obj/item/organ/adamantine_resonator) - speedmod = 2 - armor = 55 - siemens_coeff = 0 - punchdamagelow = 5 - punchdamagehigh = 14 - punchstunthreshold = 11 //about 40% chance to stun - no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE) - nojumpsuit = 1 - sexes = 1 - damage_overlay_type = "" - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/golem - // To prevent golem subtypes from overwhelming the odds when random species - // changes, only the Random Golem type can be chosen - blacklisted = TRUE - dangerous_existence = TRUE - limbs_id = "golem" - fixed_mut_color = "aaa" - var/info_text = "As an Iron Golem, you don't have any special traits." - var/random_eligible = TRUE //If false, the golem subtype can't be made through golem mutation toxin - - var/prefix = "Iron" - var/list/special_names = list("Tarkus") - var/human_surname_chance = 3 - var/special_name_chance = 5 - var/owner //dobby is a free golem - -/datum/species/golem/random_name(gender,unique,lastname) - var/golem_surname = pick(GLOB.golem_names) - // 3% chance that our golem has a human surname, because - // cultural contamination - if(prob(human_surname_chance)) - golem_surname = pick(GLOB.last_names) - else if(special_names && special_names.len && prob(special_name_chance)) - golem_surname = pick(special_names) - - var/golem_name = "[prefix] [golem_surname]" - return golem_name - -/datum/species/golem/random - name = "Golem Mutant" - blacklisted = FALSE - dangerous_existence = FALSE - var/static/list/random_golem_types - -/datum/species/golem/random/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - if(!random_golem_types) - random_golem_types = subtypesof(/datum/species/golem) - type - for(var/V in random_golem_types) - var/datum/species/golem/G = V - if(!initial(G.random_eligible)) - random_golem_types -= G - var/datum/species/golem/golem_type = pick(random_golem_types) - var/mob/living/carbon/human/H = C - H.set_species(golem_type) - to_chat(H, "[initial(golem_type.info_text)]") - -/datum/species/golem/adamantine - name = "Adamantine Golem" - id = "adamantine golem" - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/golem/adamantine - mutant_organs = list(/obj/item/organ/adamantine_resonator, /obj/item/organ/vocal_cords/adamantine) - fixed_mut_color = "4ed" - info_text = "As an Adamantine Golem, you possess special vocal cords allowing you to \"resonate\" messages to all golems. Your unique mineral makeup makes you immune to most types of magic." - prefix = "Adamantine" - special_names = null - -/datum/species/golem/adamantine/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - ADD_TRAIT(C, TRAIT_ANTIMAGIC, SPECIES_TRAIT) - -/datum/species/golem/adamantine/on_species_loss(mob/living/carbon/C) - REMOVE_TRAIT(C, TRAIT_ANTIMAGIC, SPECIES_TRAIT) - ..() - -//The suicide bombers of golemkind -/datum/species/golem/plasma - name = "Plasma Golem" - id = "plasma golem" - fixed_mut_color = "a3d" - meat = /obj/item/stack/ore/plasma - //Can burn and takes damage from heat - inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) //no RESISTHEAT, NOFIRE - info_text = "As a Plasma Golem, you burn easily. Be careful, if you get hot enough while burning, you'll blow up!" - heatmod = 0 //fine until they blow up - prefix = "Plasma" - special_names = list("Flood","Fire","Bar","Man") - var/boom_warning = FALSE - var/datum/action/innate/ignite/ignite - -/datum/species/golem/plasma/spec_life(mob/living/carbon/human/H) - if(H.bodytemperature > 750) - if(!boom_warning && H.on_fire) - to_chat(H, "You feel like you could blow up at any moment!") - boom_warning = TRUE - else - if(boom_warning) - to_chat(H, "You feel more stable.") - boom_warning = FALSE - - if(H.bodytemperature > 850 && H.on_fire && prob(25)) - explosion(get_turf(H),1,2,4,flame_range = 5) - if(H) - H.gib() - if(H.fire_stacks < 2) //flammable - H.adjust_fire_stacks(1) - ..() - -/datum/species/golem/plasma/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - if(ishuman(C)) - ignite = new - ignite.Grant(C) - -/datum/species/golem/plasma/on_species_loss(mob/living/carbon/C) - if(ignite) - ignite.Remove(C) - ..() - -/datum/action/innate/ignite - name = "Ignite" - desc = "Set yourself aflame, bringing yourself closer to exploding!" - check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "sacredflame" - -/datum/action/innate/ignite/Activate() - if(ishuman(owner)) - var/mob/living/carbon/human/H = owner - if(H.fire_stacks) - to_chat(owner, "You ignite yourself!") - else - to_chat(owner, "You try to ignite yourself, but fail!") - H.IgniteMob() //firestacks are already there passively - -//Harder to hurt -/datum/species/golem/diamond - name = "Diamond Golem" - id = "diamond golem" - fixed_mut_color = "0ff" - armor = 70 //up from 55 - meat = /obj/item/stack/ore/diamond - info_text = "As a Diamond Golem, you are more resistant than the average golem." - prefix = "Diamond" - special_names = list("Back") - -//Faster but softer and less armoured -/datum/species/golem/gold - name = "Gold Golem" - id = "gold golem" - fixed_mut_color = "cc0" - speedmod = 1 - armor = 25 //down from 55 - meat = /obj/item/stack/ore/gold - info_text = "As a Gold Golem, you are faster but less resistant than the average golem." - prefix = "Golden" - special_names = list("Boy") - -//Heavier, thus higher chance of stunning when punching -/datum/species/golem/silver - name = "Silver Golem" - id = "silver golem" - fixed_mut_color = "ddd" - punchstunthreshold = 9 //60% chance, from 40% - meat = /obj/item/stack/ore/silver - info_text = "As a Silver Golem, your attacks have a higher chance of stunning. Being made of silver, your body is immune to most types of magic." - prefix = "Silver" - special_names = list("Surfer", "Chariot", "Lining") - -/datum/species/golem/silver/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - ADD_TRAIT(C, TRAIT_HOLY, SPECIES_TRAIT) - -/datum/species/golem/silver/on_species_loss(mob/living/carbon/C) - REMOVE_TRAIT(C, TRAIT_HOLY, SPECIES_TRAIT) - ..() - -//Harder to stun, deals more damage, but it's even slower -/datum/species/golem/plasteel - name = "Plasteel Golem" - id = "plasteel golem" - fixed_mut_color = "bbb" - stunmod = 0.4 - punchdamagelow = 12 - punchdamagehigh = 21 - punchstunthreshold = 18 //still 40% stun chance - speedmod = 4 //pretty fucking slow - meat = /obj/item/stack/ore/iron - info_text = "As a Plasteel Golem, you are slower, but harder to stun, and hit very hard when punching." - attack_verb = "smash" - attack_sound = 'sound/effects/meteorimpact.ogg' //hits pretty hard - prefix = "Plasteel" - special_names = null - -//Immune to ash storms -/datum/species/golem/titanium - name = "Titanium Golem" - id = "titanium golem" - fixed_mut_color = "fff" - meat = /obj/item/stack/ore/titanium - info_text = "As a Titanium Golem, you are immune to ash storms, and slightly more resistant to burn damage." - burnmod = 0.9 - prefix = "Titanium" - special_names = list("Dioxide") - -/datum/species/golem/titanium/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - C.weather_immunities |= "ash" - -/datum/species/golem/titanium/on_species_loss(mob/living/carbon/C) - . = ..() - C.weather_immunities -= "ash" - -//Immune to ash storms and lava -/datum/species/golem/plastitanium - name = "Plastitanium Golem" - id = "plastitanium golem" - fixed_mut_color = "888" - meat = /obj/item/stack/ore/titanium - info_text = "As a Plastitanium Golem, you are immune to both ash storms and lava, and slightly more resistant to burn damage." - burnmod = 0.8 - prefix = "Plastitanium" - special_names = null - -/datum/species/golem/plastitanium/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - C.weather_immunities |= "lava" - C.weather_immunities |= "ash" - -/datum/species/golem/plastitanium/on_species_loss(mob/living/carbon/C) - . = ..() - C.weather_immunities -= "ash" - C.weather_immunities -= "lava" - -//Fast and regenerates... but can only speak like an abductor -/datum/species/golem/alloy - name = "Alien Alloy Golem" - id = "alloy golem" - fixed_mut_color = "333" - meat = /obj/item/stack/sheet/mineral/abductor - mutanttongue = /obj/item/organ/tongue/abductor - speedmod = 1 //faster - info_text = "As an Alloy Golem, you are made of advanced alien materials: you are faster and regenerate over time. You are, however, only able to be heard by other alloy golems." - prefix = "Alien" - special_names = list("Outsider", "Technology", "Watcher", "Stranger") //ominous and unknown - -//Regenerates because self-repairing super-advanced alien tech -/datum/species/golem/alloy/spec_life(mob/living/carbon/human/H) - if(H.stat == DEAD) - return - H.heal_overall_damage(2,2) - H.adjustToxLoss(-2) - H.adjustOxyLoss(-2) - -//Since this will usually be created from a collaboration between podpeople and free golems, wood golems are a mix between the two races -/datum/species/golem/wood - name = "Wood Golem" - id = "wood golem" - fixed_mut_color = "9E704B" - meat = /obj/item/stack/sheet/mineral/wood - //Can burn and take damage from heat - inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) - armor = 30 - burnmod = 1.25 - heatmod = 1.5 - info_text = "As a Wooden Golem, you have plant-like traits: you take damage from extreme temperatures, can be set on fire, and have lower armor than a normal golem. You regenerate when in the light and wither in the darkness." - prefix = "Wooden" - special_names = list("Bark", "Willow", "Catalpa", "Woody", "Oak", "Sap", "Twig", "Branch", "Maple", "Birch", "Elm", "Basswood", "Cottonwood", "Larch", "Aspen", "Ash", "Beech", "Buckeye", "Cedar", "Chestnut", "Cypress", "Fir", "Hawthorn", "Hazel", "Hickory", "Ironwood", "Juniper", "Leaf", "Mangrove", "Palm", "Pawpaw", "Pine", "Poplar", "Redwood", "Redbud", "Sassafras", "Spruce", "Sumac", "Trunk", "Walnut", "Yew") - human_surname_chance = 0 - special_name_chance = 100 - -/datum/species/golem/wood/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - C.faction |= "plants" - C.faction |= "vines" - -/datum/species/golem/wood/on_species_loss(mob/living/carbon/C) - . = ..() - C.faction -= "plants" - C.faction -= "vines" - -/datum/species/golem/wood/spec_life(mob/living/carbon/human/H) - if(H.stat == DEAD) - return - var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing - if(isturf(H.loc)) //else, there's considered to be no light - var/turf/T = H.loc - light_amount = min(1,T.get_lumcount()) - 0.5 - H.nutrition += light_amount * 10 - if(H.nutrition > NUTRITION_LEVEL_FULL) - H.nutrition = NUTRITION_LEVEL_FULL - if(light_amount > 0.2) //if there's enough light, heal - H.heal_overall_damage(1,1) - H.adjustToxLoss(-1) - H.adjustOxyLoss(-1) - - if(H.nutrition < NUTRITION_LEVEL_STARVING + 50) - H.take_overall_damage(2,0) - -/datum/species/golem/wood/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) - if(chem.type == /datum/reagent/toxin/plantbgone) - H.adjustToxLoss(3) - H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM) - return 1 - -//Radioactive -/datum/species/golem/uranium - name = "Uranium Golem" - id = "uranium golem" - fixed_mut_color = "7f0" - meat = /obj/item/stack/ore/uranium - info_text = "As an Uranium Golem, you emit radiation pulses every once in a while. It won't harm fellow golems, but organic lifeforms will be affected." - - var/last_event = 0 - var/active = null - prefix = "Uranium" - special_names = list("Oxide", "Rod", "Meltdown") - -/datum/species/golem/uranium/spec_life(mob/living/carbon/human/H) - if(!active) - if(world.time > last_event+30) - active = 1 - radiation_pulse(H, 50) - last_event = world.time - active = null - ..() - -//Immune to physical bullets and resistant to brute, but very vulnerable to burn damage. Dusts on death. -/datum/species/golem/sand - name = "Sand Golem" - id = "sand golem" - fixed_mut_color = "ffdc8f" - meat = /obj/item/stack/ore/glass //this is sand - armor = 0 - burnmod = 3 //melts easily - brutemod = 0.25 - info_text = "As a Sand Golem, you are immune to physical bullets and take very little brute damage, but are extremely vulnerable to burn damage and energy weapons. You will also turn to sand when dying, preventing any form of recovery." - attack_sound = 'sound/effects/shovel_dig.ogg' - prefix = "Sand" - special_names = list("Castle", "Bag", "Dune", "Worm", "Storm") - -/datum/species/golem/sand/spec_death(gibbed, mob/living/carbon/human/H) - H.visible_message("[H] turns into a pile of sand!") - for(var/obj/item/W in H) - H.dropItemToGround(W) - for(var/i=1, i <= rand(3,5), i++) - new /obj/item/stack/ore/glass(get_turf(H)) - qdel(H) - -/datum/species/golem/sand/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H) - if(!(P.original == H && P.firer == H)) - if(P.flag == "bullet" || P.flag == "bomb") - playsound(H, 'sound/effects/shovel_dig.ogg', 70, 1) - H.visible_message("The [P.name] sinks harmlessly in [H]'s sandy body!", \ - "The [P.name] sinks harmlessly in [H]'s sandy body!") - return 2 - return 0 - -//Reflects lasers and resistant to burn damage, but very vulnerable to brute damage. Shatters on death. -/datum/species/golem/glass - name = "Glass Golem" - id = "glass golem" - fixed_mut_color = "5a96b4aa" //transparent body - meat = /obj/item/shard - armor = 0 - brutemod = 3 //very fragile - burnmod = 0.25 - info_text = "As a Glass Golem, you reflect lasers and energy weapons, and are very resistant to burn damage. However, you are extremely vulnerable to brute damage. On death, you'll shatter beyond any hope of recovery." - attack_sound = 'sound/effects/glassbr2.ogg' - prefix = "Glass" - special_names = list("Lens", "Prism", "Fiber", "Bead") - -/datum/species/golem/glass/spec_death(gibbed, mob/living/carbon/human/H) - playsound(H, "shatter", 70, 1) - H.visible_message("[H] shatters!") - for(var/obj/item/W in H) - H.dropItemToGround(W) - for(var/i=1, i <= rand(3,5), i++) - new /obj/item/shard(get_turf(H)) - qdel(H) - -/datum/species/golem/glass/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H) - if(!(P.original == H && P.firer == H)) //self-shots don't reflect - if(P.flag == "laser" || P.flag == "energy") - H.visible_message("The [P.name] gets reflected by [H]'s glass skin!", \ - "The [P.name] gets reflected by [H]'s glass skin!") - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/turf/target = get_turf(P.starting) - // redirect the projectile - P.preparePixelProjectile(locate(CLAMP(target.x + new_x, 1, world.maxx), CLAMP(target.y + new_y, 1, world.maxy), H.z), H) - return -1 - return 0 - -//Teleports when hit or when it wants to -/datum/species/golem/bluespace - name = "Bluespace Golem" - id = "bluespace golem" - fixed_mut_color = "33f" - meat = /obj/item/stack/ore/bluespace_crystal - info_text = "As a Bluespace Golem, you are spatially unstable: You will teleport when hit, and you can teleport manually at a long distance." - attack_verb = "bluespace punch" - attack_sound = 'sound/effects/phasein.ogg' - prefix = "Bluespace" - special_names = list("Crystal", "Polycrystal") - - var/datum/action/innate/unstable_teleport/unstable_teleport - var/teleport_cooldown = 100 - var/last_teleport = 0 - -/datum/species/golem/bluespace/proc/reactive_teleport(mob/living/carbon/human/H) - H.visible_message("[H] teleports!", "You destabilize and teleport!") - new /obj/effect/particle_effect/sparks(get_turf(H)) - playsound(get_turf(H), "sparks", 50, 1) - do_teleport(H, get_turf(H), 6, asoundin = 'sound/weapons/emitter2.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) - last_teleport = world.time - -/datum/species/golem/bluespace/spec_hitby(atom/movable/AM, mob/living/carbon/human/H) - ..() - var/obj/item/I - if(istype(AM, /obj/item)) - I = AM - if(I.thrownby == H) //No throwing stuff at yourself to trigger the teleport - return 0 - else - reactive_teleport(H) - -/datum/species/golem/bluespace/spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style) - ..() - if(world.time > last_teleport + teleport_cooldown && M != H && M.a_intent != INTENT_HELP) - reactive_teleport(H) - -/datum/species/golem/bluespace/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H) - ..() - if(world.time > last_teleport + teleport_cooldown && user != H) - reactive_teleport(H) - -/datum/species/golem/bluespace/on_hit(obj/item/projectile/P, mob/living/carbon/human/H) - ..() - if(world.time > last_teleport + teleport_cooldown) - reactive_teleport(H) - -/datum/species/golem/bluespace/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - if(ishuman(C)) - unstable_teleport = new - unstable_teleport.Grant(C) - last_teleport = world.time - -/datum/species/golem/bluespace/on_species_loss(mob/living/carbon/C) - if(unstable_teleport) - unstable_teleport.Remove(C) - ..() - -/datum/action/innate/unstable_teleport - name = "Unstable Teleport" - check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "jaunt" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - var/cooldown = 150 - var/last_teleport = 0 - -/datum/action/innate/unstable_teleport/IsAvailable() - if(..()) - if(world.time > last_teleport + cooldown) - return 1 - return 0 - -/datum/action/innate/unstable_teleport/Activate() - var/mob/living/carbon/human/H = owner - H.visible_message("[H] starts vibrating!", "You start charging your bluespace core...") - playsound(get_turf(H), 'sound/weapons/flash.ogg', 25, 1) - addtimer(CALLBACK(src, .proc/teleport, H), 15) - -/datum/action/innate/unstable_teleport/proc/teleport(mob/living/carbon/human/H) - H.visible_message("[H] disappears in a shower of sparks!", "You teleport!") - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread - spark_system.set_up(10, 0, src) - spark_system.attach(H) - spark_system.start() - do_teleport(H, get_turf(H), 12, asoundin = 'sound/weapons/emitter2.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) - last_teleport = world.time - UpdateButtonIcon() //action icon looks unavailable - sleep(cooldown + 5) - UpdateButtonIcon() //action icon looks available again - - -//honk -/datum/species/golem/bananium - name = "Bananium Golem" - id = "bananium golem" - fixed_mut_color = "ff0" - say_mod = "honks" - punchdamagelow = 0 - punchdamagehigh = 1 - punchstunthreshold = 2 //Harmless and can't stun - meat = /obj/item/stack/ore/bananium - info_text = "As a Bananium Golem, you are made for pranking. Your body emits natural honks, and you can barely even hurt people when punching them. Your skin also bleeds banana peels when damaged." - attack_verb = "honk" - attack_sound = 'sound/items/airhorn2.ogg' - prefix = "Bananium" - special_names = null - - var/last_honk = 0 - var/honkooldown = 0 - var/last_banana = 0 - var/banana_cooldown = 100 - var/active = null - -/datum/species/golem/bananium/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - last_banana = world.time - last_honk = world.time - RegisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) - -/datum/species/golem/bananium/on_species_loss(mob/living/carbon/C) - . = ..() - UnregisterSignal(C, COMSIG_MOB_SAY) - -/datum/species/golem/bananium/random_name(gender,unique,lastname) - var/clown_name = pick(GLOB.clown_names) - var/golem_name = "[uppertext(clown_name)]" - return golem_name - -/datum/species/golem/bananium/spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style) - ..() - if(world.time > last_banana + banana_cooldown && M != H && M.a_intent != INTENT_HELP) - new/obj/item/grown/bananapeel/specialpeel(get_turf(H)) - last_banana = world.time - -/datum/species/golem/bananium/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H) - ..() - if(world.time > last_banana + banana_cooldown && user != H) - new/obj/item/grown/bananapeel/specialpeel(get_turf(H)) - last_banana = world.time - -/datum/species/golem/bananium/on_hit(obj/item/projectile/P, mob/living/carbon/human/H) - ..() - if(world.time > last_banana + banana_cooldown) - new/obj/item/grown/bananapeel/specialpeel(get_turf(H)) - last_banana = world.time - -/datum/species/golem/bananium/spec_hitby(atom/movable/AM, mob/living/carbon/human/H) - ..() - var/obj/item/I - if(istype(AM, /obj/item)) - I = AM - if(I.thrownby == H) //No throwing stuff at yourself to make bananas - return 0 - else - new/obj/item/grown/bananapeel/specialpeel(get_turf(H)) - last_banana = world.time - -/datum/species/golem/bananium/spec_life(mob/living/carbon/human/H) - if(!active) - if(world.time > last_honk + honkooldown) - active = 1 - playsound(get_turf(H), 'sound/items/bikehorn.ogg', 50, 1) - last_honk = world.time - honkooldown = rand(20, 80) - active = null - ..() - -/datum/species/golem/bananium/spec_death(gibbed, mob/living/carbon/human/H) - playsound(get_turf(H), 'sound/misc/sadtrombone.ogg', 70, 0) - -/datum/species/golem/bananium/proc/handle_speech(datum/source, list/speech_args) - speech_args[SPEECH_SPANS] |= SPAN_CLOWN - -/datum/species/golem/runic - name = "Runic Golem" - id = "runic golem" - limbs_id = "cultgolem" - sexes = FALSE - info_text = "As a Runic Golem, you possess eldritch powers granted by the Elder Goddess Nar'Sie." - species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES,NOGENITALS,NOAROUSAL) //no mutcolors - prefix = "Runic" - special_names = null - - var/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/golem/phase_shift - var/obj/effect/proc_holder/spell/targeted/abyssal_gaze/abyssal_gaze - var/obj/effect/proc_holder/spell/targeted/dominate/dominate - -/datum/species/golem/runic/random_name(gender,unique,lastname) - var/edgy_first_name = pick("Razor","Blood","Dark","Evil","Cold","Pale","Black","Silent","Chaos","Deadly","Coldsteel") - var/edgy_last_name = pick("Edge","Night","Death","Razor","Blade","Steel","Calamity","Twilight","Shadow","Nightmare") //dammit Razor Razor - var/golem_name = "[edgy_first_name] [edgy_last_name]" - return golem_name - -/datum/species/golem/runic/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - C.faction |= "cult" - phase_shift = new - phase_shift.charge_counter = 0 - C.AddSpell(phase_shift) - abyssal_gaze = new - abyssal_gaze.charge_counter = 0 - C.AddSpell(abyssal_gaze) - dominate = new - dominate.charge_counter = 0 - C.AddSpell(dominate) - -/datum/species/golem/runic/on_species_loss(mob/living/carbon/C) - . = ..() - C.faction -= "cult" - if(phase_shift) - C.RemoveSpell(phase_shift) - if(abyssal_gaze) - C.RemoveSpell(abyssal_gaze) - if(dominate) - C.RemoveSpell(dominate) - -/datum/species/golem/runic/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) - if(chem.type == /datum/reagent/water/holywater) - H.adjustFireLoss(4) - H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM) - - if(chem.type == /datum/reagent/fuel/unholywater) - H.adjustBruteLoss(-4) - H.adjustFireLoss(-4) - H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM) - - -/datum/species/golem/clockwork - name = "Clockwork Golem" - id = "clockwork golem" - say_mod = "clicks" - limbs_id = "clockgolem" - info_text = "As a Clockwork Golem, you are faster than other types of golems, and are capable of using guns. On death, you will break down into scrap." - species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES,NOGENITALS,NOAROUSAL) - inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID) - inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) - armor = 20 //Reinforced, but much less so to allow for fast movement - attack_verb = "smash" - attack_sound = 'sound/magic/clockwork/anima_fragment_attack.ogg' - sexes = FALSE - speedmod = 0 - damage_overlay_type = "synth" - prefix = "Clockwork" - special_names = list("Remnant", "Relic", "Scrap", "Vestige") //RIP Ratvar - var/has_corpse - -/datum/species/golem/clockwork/on_species_gain(mob/living/carbon/human/H) - . = ..() - H.faction |= "ratvar" - RegisterSignal(H, COMSIG_MOB_SAY, .proc/handle_speech) - -/datum/species/golem/clockwork/on_species_loss(mob/living/carbon/human/H) - if(!is_servant_of_ratvar(H)) - H.faction -= "ratvar" - UnregisterSignal(H, COMSIG_MOB_SAY) - . = ..() - -/datum/species/golem/clockwork/proc/handle_speech(datum/source, list/speech_args) - speech_args[SPEECH_SPANS] |= SPAN_ROBOT //beep - -/datum/species/golem/clockwork/spec_death(gibbed, mob/living/carbon/human/H) - gibbed = !has_corpse ? FALSE : gibbed - . = ..() - if(!has_corpse) - var/turf/T = get_turf(H) - H.visible_message("[H]'s exoskeleton shatters, collapsing into a heap of scrap!") - playsound(H, 'sound/magic/clockwork/anima_fragment_death.ogg', 62, TRUE) - for(var/i in 1 to rand(3, 5)) - new/obj/item/clockwork/alloy_shards/small(T) - new/obj/item/clockwork/alloy_shards/clockgolem_remains(T) - qdel(H) - -/datum/species/golem/clockwork/no_scrap //These golems are created through the herald's beacon and leave normal corpses on death. - id = "clockwork golem servant" - armor = 15 //Balance reasons make this armor weak - no_equip = list() - nojumpsuit = FALSE - has_corpse = TRUE - blacklisted = TRUE - dangerous_existence = TRUE - random_eligible = FALSE - info_text = "As a Clockwork Golem Servant, you are faster than other types of golems, and are capable of using guns." //warcult golems leave a corpse - -/datum/species/golem/cloth - name = "Cloth Golem" - id = "cloth golem" - limbs_id = "clothgolem" - sexes = FALSE - info_text = "As a Cloth Golem, you are able to reform yourself after death, provided your remains aren't burned or destroyed. You are, of course, very flammable. \ - Being made of cloth, your body is magic resistant and faster than that of other golems, but weaker and less resilient." - species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL) //no mutcolors, and can burn - inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_CHUNKYFINGERS) - inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID) - armor = 15 //feels no pain, but not too resistant - burnmod = 2 // don't get burned - speedmod = 1 // not as heavy as stone - punchdamagelow = 4 - punchstunthreshold = 7 - punchdamagehigh = 8 // not as heavy as stone - prefix = "Cloth" - special_names = null - -/datum/species/golem/cloth/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - ADD_TRAIT(C, TRAIT_HOLY, SPECIES_TRAIT) - -/datum/species/golem/cloth/on_species_loss(mob/living/carbon/C) - REMOVE_TRAIT(C, TRAIT_HOLY, SPECIES_TRAIT) - ..() - -/datum/species/golem/cloth/check_roundstart_eligible() - if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) - return TRUE - return ..() - -/datum/species/golem/cloth/random_name(gender,unique,lastname) - var/pharaoh_name = pick("Neferkare", "Hudjefa", "Khufu", "Mentuhotep", "Ahmose", "Amenhotep", "Thutmose", "Hatshepsut", "Tutankhamun", "Ramses", "Seti", \ - "Merenptah", "Djer", "Semerkhet", "Nynetjer", "Khafre", "Pepi", "Intef", "Ay") //yes, Ay was an actual pharaoh - var/golem_name = "[pharaoh_name] \Roman[rand(1,99)]" - return golem_name - -/datum/species/golem/cloth/spec_life(mob/living/carbon/human/H) - if(H.fire_stacks < 1) - H.adjust_fire_stacks(1) //always prone to burning - ..() - -/datum/species/golem/cloth/spec_death(gibbed, mob/living/carbon/human/H) - if(gibbed) - return - if(H.on_fire) - H.visible_message("[H] burns into ash!") - H.dust(just_ash = TRUE) - return - - H.visible_message("[H] falls apart into a pile of bandages!") - new /obj/structure/cloth_pile(get_turf(H), H) - ..() - -/obj/structure/cloth_pile - name = "pile of bandages" - desc = "It emits a strange aura, as if there was still life within it..." - max_integrity = 50 - armor = list("melee" = 90, "bullet" = 90, "laser" = 25, "energy" = 80, "bomb" = 50, "bio" = 100, "fire" = -50, "acid" = -50) - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "pile_bandages" - resistance_flags = FLAMMABLE - - var/revive_time = 900 - var/mob/living/carbon/human/cloth_golem - -/obj/structure/cloth_pile/Initialize(mapload, mob/living/carbon/human/H) - . = ..() - if(!QDELETED(H) && is_species(H, /datum/species/golem/cloth)) - H.unequip_everything() - H.forceMove(src) - cloth_golem = H - to_chat(cloth_golem, "You start gathering your life energy, preparing to rise again...") - addtimer(CALLBACK(src, .proc/revive), revive_time) - else - return INITIALIZE_HINT_QDEL - -/obj/structure/cloth_pile/Destroy() - if(cloth_golem) - QDEL_NULL(cloth_golem) - return ..() - -/obj/structure/cloth_pile/burn() - visible_message("[src] burns into ash!") - new /obj/effect/decal/cleanable/ash(get_turf(src)) - ..() - -/obj/structure/cloth_pile/proc/revive() - if(QDELETED(src) || QDELETED(cloth_golem)) //QDELETED also checks for null, so if no cloth golem is set this won't runtime - return - if(cloth_golem.suiciding || HAS_TRAIT(cloth_golem, TRAIT_NOCLONE)) - QDEL_NULL(cloth_golem) - return - - invisibility = INVISIBILITY_MAXIMUM //disappear before the animation - new /obj/effect/temp_visual/mummy_animation(get_turf(src)) - if(cloth_golem.revive(full_heal = TRUE, admin_revive = TRUE)) - cloth_golem.grab_ghost() //won't pull if it's a suicide - sleep(20) - cloth_golem.forceMove(get_turf(src)) - cloth_golem.visible_message("[src] rises and reforms into [cloth_golem]!","You reform into yourself!") - cloth_golem = null - qdel(src) - -/obj/structure/cloth_pile/attackby(obj/item/P, mob/living/carbon/human/user, params) - . = ..() - - if(resistance_flags & ON_FIRE) - return - - if(P.get_temperature()) - visible_message("[src] bursts into flames!") - fire_act() - -/datum/species/golem/plastic - name = "Plastic Golem" - id = "plastic golem" - prefix = "Plastic" - special_names = null - fixed_mut_color = "fff" - info_text = "As a Plastic Golem, you are capable of ventcrawling and passing through plastic flaps as long as you are naked." - -/datum/species/golem/plastic/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - C.ventcrawler = VENTCRAWLER_NUDE - -/datum/species/golem/plastic/on_species_loss(mob/living/carbon/C) - . = ..() - C.ventcrawler = initial(C.ventcrawler) - -/datum/species/golem/bronze - name = "Bronze Golem" - id = "bronze golem" - prefix = "Bronze" - special_names = list("Bell") - fixed_mut_color = "cd7f32" - info_text = "As a Bronze Golem, you are very resistant to loud noises, and make loud noises if something hard hits you, however this ability does hurt your hearing." - special_step_sounds = list('sound/machines/clockcult/integration_cog_install.ogg', 'sound/magic/clockwork/fellowship_armory.ogg' ) - attack_verb = "bonk" - mutantears = /obj/item/organ/ears/bronze - var/last_gong_time = 0 - var/gong_cooldown = 150 - -/datum/species/golem/bronze/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H) - if(!(world.time > last_gong_time + gong_cooldown)) - return ..() - if(P.flag == "bullet" || P.flag == "bomb") - gong(H) - return ..() - -/datum/species/golem/bronze/spec_hitby(atom/movable/AM, mob/living/carbon/human/H) - ..() - if(world.time > last_gong_time + gong_cooldown) - gong(H) - -/datum/species/golem/bronze/spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style) - ..() - if(world.time > last_gong_time + gong_cooldown && M.a_intent != INTENT_HELP) - gong(H) - -/datum/species/golem/bronze/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H) - ..() - if(world.time > last_gong_time + gong_cooldown) - gong(H) - -/datum/species/golem/bronze/on_hit(obj/item/projectile/P, mob/living/carbon/human/H) - ..() - if(world.time > last_gong_time + gong_cooldown) - gong(H) - -/datum/species/golem/bronze/proc/gong(mob/living/carbon/human/H) - last_gong_time = world.time - for(var/mob/living/M in get_hearers_in_view(7,H)) - if(M.stat == DEAD) //F - return - if(M == H) - H.show_message("You cringe with pain as your body rings around you!", MSG_AUDIBLE) - H.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE) - H.soundbang_act(2, 0, 100, 1) - H.jitteriness += 7 - var/distance = max(0,get_dist(get_turf(H),get_turf(M))) - switch(distance) - if(0 to 1) - M.show_message("GONG!", MSG_AUDIBLE) - M.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE) - M.soundbang_act(1, 0, 30, 3) - M.confused += 10 - M.jitteriness += 4 - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong) - if(2 to 3) - M.show_message("GONG!", MSG_AUDIBLE) - M.playsound_local(H, 'sound/effects/gong.ogg', 75, TRUE) - M.soundbang_act(1, 0, 15, 2) - M.jitteriness += 3 - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong) - else - M.show_message("GONG!", MSG_AUDIBLE) - M.playsound_local(H, 'sound/effects/gong.ogg', 50, TRUE) - - -/datum/species/golem/cardboard //Faster but weaker, can also make new shells on its own - name = "Cardboard Golem" - id = "cardboard golem" - prefix = "Cardboard" - special_names = list("Box") - info_text = "As a Cardboard Golem, you aren't very strong, but you are a bit quicker and can easily create more brethren by using cardboard on yourself." - species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL,MUTCOLORS) - inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) - fixed_mut_color = "ffffff" - limbs_id = "c_golem" //special sprites - attack_verb = "bash" - armor = 25 - burnmod = 1.25 - heatmod = 2 - speedmod = 1.5 - punchdamagelow = 4 - punchstunthreshold = 7 - punchdamagehigh = 8 - var/last_creation = 0 - var/brother_creation_cooldown = 300 - -/datum/species/golem/cardboard/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H) - . = ..() - if(user != H) - return FALSE //forced reproduction is rape. - if(istype(I, /obj/item/stack/sheet/cardboard)) - var/obj/item/stack/sheet/cardboard/C = I - if(last_creation + brother_creation_cooldown > world.time) //no cheesing dork - return - if(C.amount < 10) - to_chat(H, "You do not have enough cardboard!") - return FALSE - to_chat(H, "You attempt to create a new cardboard brother.") - if(do_after(user, 30, target = user)) - if(last_creation + brother_creation_cooldown > world.time) //no cheesing dork - return - if(!C.use(10)) - to_chat(H, "You do not have enough cardboard!") - return FALSE - to_chat(H, "You create a new cardboard golem shell.") - create_brother(H.loc) - -/datum/species/golem/cardboard/proc/create_brother(var/location) - new /obj/effect/mob_spawn/human/golem/servant(location, /datum/species/golem/cardboard, owner) - last_creation = world.time - -/datum/species/golem/leather - name = "Leather Golem" - id = "leather golem" - special_names = list("Face", "Man", "Belt") //Ah dude 4 strength 4 stam leather belt AHHH - inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER, TRAIT_STRONG_GRABBER) - prefix = "Leather" - fixed_mut_color = "624a2e" - info_text = "As a Leather Golem, you are flammable, but you can grab things with incredible ease, allowing all your grabs to start at a strong level." - attack_verb = "whipp" - grab_sound = 'sound/weapons/whipgrab.ogg' - attack_sound = 'sound/weapons/whip.ogg' - -/datum/species/golem/durathread - name = "Durathread Golem" - id = "durathread golem" - prefix = "Durathread" - limbs_id = "d_golem" - special_names = list("Boll","Weave") - species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES) - fixed_mut_color = null - inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) - info_text = "As a Durathread Golem, your strikes will cause those your targets to start choking, but your woven body won't withstand fire as well." - -/datum/species/golem/durathread/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target) - . = ..() - target.apply_status_effect(STATUS_EFFECT_CHOKINGSTRAND) - -/datum/species/golem/bone - name = "Bone Golem" - id = "bone golem" - say_mod = "rattles" - prefix = "Bone" - limbs_id = "b_golem" - special_names = list("Head", "Broth", "Fracture", "Rattler", "Appetit") - liked_food = GROSS | MEAT | RAW - toxic_food = null - inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID) - mutanttongue = /obj/item/organ/tongue/bone - sexes = FALSE - fixed_mut_color = "ffffff" - attack_verb = "rattl" - species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL,MUTCOLORS) - inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_FAKEDEATH,TRAIT_CALCIUM_HEALER) - info_text = "As a Bone Golem, You have a powerful spell that lets you chill your enemies with fear, and milk heals you! Just make sure to watch our for bone-hurting juice." - var/datum/action/innate/bonechill/bonechill - -/datum/species/golem/bone/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - if(ishuman(C)) - bonechill = new - bonechill.Grant(C) - -/datum/species/golem/bone/on_species_loss(mob/living/carbon/C) - if(bonechill) - bonechill.Remove(C) - ..() - -/datum/action/innate/bonechill - name = "Bone Chill" - desc = "Rattle your bones and strike fear into your enemies!" - check_flags = AB_CHECK_CONSCIOUS - icon_icon = 'icons/mob/actions/actions_spells.dmi' - button_icon_state = "bonechill" - var/cooldown = 600 - var/last_use - var/snas_chance = 3 - -/datum/action/innate/bonechill/Activate() - if(world.time < last_use + cooldown) - to_chat("You aren't ready yet to rattle your bones again") - return - owner.visible_message("[owner] rattles [owner.p_their()] bones harrowingly.", "You rattle your bones") - last_use = world.time - if(prob(snas_chance)) - playsound(get_turf(owner),'sound/magic/RATTLEMEBONES2.ogg', 100) - if(ishuman(owner)) - var/mob/living/carbon/human/H = owner - var/mutable_appearance/badtime = mutable_appearance('icons/mob/human_parts.dmi', "b_golem_eyes", -FIRE_LAYER-0.5) - badtime.appearance_flags = RESET_COLOR - H.overlays_standing[FIRE_LAYER+0.5] = badtime - H.apply_overlay(FIRE_LAYER+0.5) - addtimer(CALLBACK(H, /mob/living/carbon/.proc/remove_overlay, FIRE_LAYER+0.5), 25) - else - playsound(get_turf(owner),'sound/magic/RATTLEMEBONES.ogg', 100) - for(var/mob/living/L in orange(7, get_turf(owner))) - if((MOB_UNDEAD in L.mob_biotypes) || isgolem(L) || HAS_TRAIT(L, TRAIT_RESISTCOLD)) - return //Do not affect our brothers - - to_chat(L, "A spine-chilling sound chills you to the bone!") - L.apply_status_effect(/datum/status_effect/bonechill) - SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "spooked", /datum/mood_event/spooked) +/datum/species/golem + // Animated beings of stone. They have increased defenses, and do not need to breathe. They're also slow as fuuuck. + name = "Golem" + id = "iron golem" + species_traits = list(NOBLOOD,MUTCOLORS,NO_UNDERWEAR,NOGENITALS,NOAROUSAL) + inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) + inherent_biotypes = list(MOB_INORGANIC, MOB_HUMANOID) + mutant_organs = list(/obj/item/organ/adamantine_resonator) + speedmod = 2 + armor = 55 + siemens_coeff = 0 + punchdamagelow = 5 + punchdamagehigh = 14 + punchstunthreshold = 11 //about 40% chance to stun + no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE) + nojumpsuit = 1 + sexes = 1 + damage_overlay_type = "" + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/golem + // To prevent golem subtypes from overwhelming the odds when random species + // changes, only the Random Golem type can be chosen + blacklisted = TRUE + dangerous_existence = TRUE + limbs_id = "golem" + fixed_mut_color = "aaa" + var/info_text = "As an Iron Golem, you don't have any special traits." + var/random_eligible = TRUE //If false, the golem subtype can't be made through golem mutation toxin + + var/prefix = "Iron" + var/list/special_names = list("Tarkus") + var/human_surname_chance = 3 + var/special_name_chance = 5 + var/owner //dobby is a free golem + +/datum/species/golem/random_name(gender,unique,lastname) + var/golem_surname = pick(GLOB.golem_names) + // 3% chance that our golem has a human surname, because + // cultural contamination + if(prob(human_surname_chance)) + golem_surname = pick(GLOB.last_names) + else if(special_names && special_names.len && prob(special_name_chance)) + golem_surname = pick(special_names) + + var/golem_name = "[prefix] [golem_surname]" + return golem_name + +/datum/species/golem/random + name = "Golem Mutant" + blacklisted = FALSE + dangerous_existence = FALSE + var/static/list/random_golem_types + +/datum/species/golem/random/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + if(!random_golem_types) + random_golem_types = subtypesof(/datum/species/golem) - type + for(var/V in random_golem_types) + var/datum/species/golem/G = V + if(!initial(G.random_eligible)) + random_golem_types -= G + var/datum/species/golem/golem_type = pick(random_golem_types) + var/mob/living/carbon/human/H = C + H.set_species(golem_type) + to_chat(H, "[initial(golem_type.info_text)]") + +/datum/species/golem/adamantine + name = "Adamantine Golem" + id = "adamantine golem" + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/golem/adamantine + mutant_organs = list(/obj/item/organ/adamantine_resonator, /obj/item/organ/vocal_cords/adamantine) + fixed_mut_color = "4ed" + info_text = "As an Adamantine Golem, you possess special vocal cords allowing you to \"resonate\" messages to all golems. Your unique mineral makeup makes you immune to most types of magic." + prefix = "Adamantine" + special_names = null + +/datum/species/golem/adamantine/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + ADD_TRAIT(C, TRAIT_ANTIMAGIC, SPECIES_TRAIT) + +/datum/species/golem/adamantine/on_species_loss(mob/living/carbon/C) + REMOVE_TRAIT(C, TRAIT_ANTIMAGIC, SPECIES_TRAIT) + ..() + +//The suicide bombers of golemkind +/datum/species/golem/plasma + name = "Plasma Golem" + id = "plasma golem" + fixed_mut_color = "a3d" + meat = /obj/item/stack/ore/plasma + //Can burn and takes damage from heat + inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) //no RESISTHEAT, NOFIRE + info_text = "As a Plasma Golem, you burn easily. Be careful, if you get hot enough while burning, you'll blow up!" + heatmod = 0 //fine until they blow up + prefix = "Plasma" + special_names = list("Flood","Fire","Bar","Man") + var/boom_warning = FALSE + var/datum/action/innate/ignite/ignite + +/datum/species/golem/plasma/spec_life(mob/living/carbon/human/H) + if(H.bodytemperature > 750) + if(!boom_warning && H.on_fire) + to_chat(H, "You feel like you could blow up at any moment!") + boom_warning = TRUE + else + if(boom_warning) + to_chat(H, "You feel more stable.") + boom_warning = FALSE + + if(H.bodytemperature > 850 && H.on_fire && prob(25)) + explosion(get_turf(H),1,2,4,flame_range = 5) + if(H) + H.gib() + if(H.fire_stacks < 2) //flammable + H.adjust_fire_stacks(1) + ..() + +/datum/species/golem/plasma/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + if(ishuman(C)) + ignite = new + ignite.Grant(C) + +/datum/species/golem/plasma/on_species_loss(mob/living/carbon/C) + if(ignite) + ignite.Remove(C) + ..() + +/datum/action/innate/ignite + name = "Ignite" + desc = "Set yourself aflame, bringing yourself closer to exploding!" + check_flags = AB_CHECK_CONSCIOUS + button_icon_state = "sacredflame" + +/datum/action/innate/ignite/Activate() + if(ishuman(owner)) + var/mob/living/carbon/human/H = owner + if(H.fire_stacks) + to_chat(owner, "You ignite yourself!") + else + to_chat(owner, "You try to ignite yourself, but fail!") + H.IgniteMob() //firestacks are already there passively + +//Harder to hurt +/datum/species/golem/diamond + name = "Diamond Golem" + id = "diamond golem" + fixed_mut_color = "0ff" + armor = 70 //up from 55 + meat = /obj/item/stack/ore/diamond + info_text = "As a Diamond Golem, you are more resistant than the average golem." + prefix = "Diamond" + special_names = list("Back") + +//Faster but softer and less armoured +/datum/species/golem/gold + name = "Gold Golem" + id = "gold golem" + fixed_mut_color = "cc0" + speedmod = 1 + armor = 25 //down from 55 + meat = /obj/item/stack/ore/gold + info_text = "As a Gold Golem, you are faster but less resistant than the average golem." + prefix = "Golden" + special_names = list("Boy") + +//Heavier, thus higher chance of stunning when punching +/datum/species/golem/silver + name = "Silver Golem" + id = "silver golem" + fixed_mut_color = "ddd" + punchstunthreshold = 9 //60% chance, from 40% + meat = /obj/item/stack/ore/silver + info_text = "As a Silver Golem, your attacks have a higher chance of stunning. Being made of silver, your body is immune to most types of magic." + prefix = "Silver" + special_names = list("Surfer", "Chariot", "Lining") + +/datum/species/golem/silver/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + ADD_TRAIT(C, TRAIT_HOLY, SPECIES_TRAIT) + +/datum/species/golem/silver/on_species_loss(mob/living/carbon/C) + REMOVE_TRAIT(C, TRAIT_HOLY, SPECIES_TRAIT) + ..() + +//Harder to stun, deals more damage, but it's even slower +/datum/species/golem/plasteel + name = "Plasteel Golem" + id = "plasteel golem" + fixed_mut_color = "bbb" + stunmod = 0.4 + punchdamagelow = 12 + punchdamagehigh = 21 + punchstunthreshold = 18 //still 40% stun chance + speedmod = 4 //pretty fucking slow + meat = /obj/item/stack/ore/iron + info_text = "As a Plasteel Golem, you are slower, but harder to stun, and hit very hard when punching." + attack_verb = "smash" + attack_sound = 'sound/effects/meteorimpact.ogg' //hits pretty hard + prefix = "Plasteel" + special_names = null + +//Immune to ash storms +/datum/species/golem/titanium + name = "Titanium Golem" + id = "titanium golem" + fixed_mut_color = "fff" + meat = /obj/item/stack/ore/titanium + info_text = "As a Titanium Golem, you are immune to ash storms, and slightly more resistant to burn damage." + burnmod = 0.9 + prefix = "Titanium" + special_names = list("Dioxide") + +/datum/species/golem/titanium/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.weather_immunities |= "ash" + +/datum/species/golem/titanium/on_species_loss(mob/living/carbon/C) + . = ..() + C.weather_immunities -= "ash" + +//Immune to ash storms and lava +/datum/species/golem/plastitanium + name = "Plastitanium Golem" + id = "plastitanium golem" + fixed_mut_color = "888" + meat = /obj/item/stack/ore/titanium + info_text = "As a Plastitanium Golem, you are immune to both ash storms and lava, and slightly more resistant to burn damage." + burnmod = 0.8 + prefix = "Plastitanium" + special_names = null + +/datum/species/golem/plastitanium/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.weather_immunities |= "lava" + C.weather_immunities |= "ash" + +/datum/species/golem/plastitanium/on_species_loss(mob/living/carbon/C) + . = ..() + C.weather_immunities -= "ash" + C.weather_immunities -= "lava" + +//Fast and regenerates... but can only speak like an abductor +/datum/species/golem/alloy + name = "Alien Alloy Golem" + id = "alloy golem" + fixed_mut_color = "333" + meat = /obj/item/stack/sheet/mineral/abductor + mutanttongue = /obj/item/organ/tongue/abductor + speedmod = 1 //faster + info_text = "As an Alloy Golem, you are made of advanced alien materials: you are faster and regenerate over time. You are, however, only able to be heard by other alloy golems." + prefix = "Alien" + special_names = list("Outsider", "Technology", "Watcher", "Stranger") //ominous and unknown + +//Regenerates because self-repairing super-advanced alien tech +/datum/species/golem/alloy/spec_life(mob/living/carbon/human/H) + if(H.stat == DEAD) + return + H.heal_overall_damage(2,2) + H.adjustToxLoss(-2) + H.adjustOxyLoss(-2) + +//Since this will usually be created from a collaboration between podpeople and free golems, wood golems are a mix between the two races +/datum/species/golem/wood + name = "Wood Golem" + id = "wood golem" + fixed_mut_color = "9E704B" + meat = /obj/item/stack/sheet/mineral/wood + //Can burn and take damage from heat + inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) + armor = 30 + burnmod = 1.25 + heatmod = 1.5 + info_text = "As a Wooden Golem, you have plant-like traits: you take damage from extreme temperatures, can be set on fire, and have lower armor than a normal golem. You regenerate when in the light and wither in the darkness." + prefix = "Wooden" + special_names = list("Bark", "Willow", "Catalpa", "Woody", "Oak", "Sap", "Twig", "Branch", "Maple", "Birch", "Elm", "Basswood", "Cottonwood", "Larch", "Aspen", "Ash", "Beech", "Buckeye", "Cedar", "Chestnut", "Cypress", "Fir", "Hawthorn", "Hazel", "Hickory", "Ironwood", "Juniper", "Leaf", "Mangrove", "Palm", "Pawpaw", "Pine", "Poplar", "Redwood", "Redbud", "Sassafras", "Spruce", "Sumac", "Trunk", "Walnut", "Yew") + human_surname_chance = 0 + special_name_chance = 100 + +/datum/species/golem/wood/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.faction |= "plants" + C.faction |= "vines" + +/datum/species/golem/wood/on_species_loss(mob/living/carbon/C) + . = ..() + C.faction -= "plants" + C.faction -= "vines" + +/datum/species/golem/wood/spec_life(mob/living/carbon/human/H) + if(H.stat == DEAD) + return + var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing + if(isturf(H.loc)) //else, there's considered to be no light + var/turf/T = H.loc + light_amount = min(1,T.get_lumcount()) - 0.5 + H.nutrition += light_amount * 10 + if(H.nutrition > NUTRITION_LEVEL_FULL) + H.nutrition = NUTRITION_LEVEL_FULL + if(light_amount > 0.2) //if there's enough light, heal + H.heal_overall_damage(1,1) + H.adjustToxLoss(-1) + H.adjustOxyLoss(-1) + + if(H.nutrition < NUTRITION_LEVEL_STARVING + 50) + H.take_overall_damage(2,0) + +/datum/species/golem/wood/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) + if(chem.type == /datum/reagent/toxin/plantbgone) + H.adjustToxLoss(3) + H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM) + return 1 + +//Radioactive +/datum/species/golem/uranium + name = "Uranium Golem" + id = "uranium golem" + fixed_mut_color = "7f0" + meat = /obj/item/stack/ore/uranium + info_text = "As an Uranium Golem, you emit radiation pulses every once in a while. It won't harm fellow golems, but organic lifeforms will be affected." + + var/last_event = 0 + var/active = null + prefix = "Uranium" + special_names = list("Oxide", "Rod", "Meltdown") + +/datum/species/golem/uranium/spec_life(mob/living/carbon/human/H) + if(!active) + if(world.time > last_event+30) + active = 1 + radiation_pulse(H, 50) + last_event = world.time + active = null + ..() + +//Immune to physical bullets and resistant to brute, but very vulnerable to burn damage. Dusts on death. +/datum/species/golem/sand + name = "Sand Golem" + id = "sand golem" + fixed_mut_color = "ffdc8f" + meat = /obj/item/stack/ore/glass //this is sand + armor = 0 + burnmod = 3 //melts easily + brutemod = 0.25 + info_text = "As a Sand Golem, you are immune to physical bullets and take very little brute damage, but are extremely vulnerable to burn damage and energy weapons. You will also turn to sand when dying, preventing any form of recovery." + attack_sound = 'sound/effects/shovel_dig.ogg' + prefix = "Sand" + special_names = list("Castle", "Bag", "Dune", "Worm", "Storm") + +/datum/species/golem/sand/spec_death(gibbed, mob/living/carbon/human/H) + H.visible_message("[H] turns into a pile of sand!") + for(var/obj/item/W in H) + H.dropItemToGround(W) + for(var/i=1, i <= rand(3,5), i++) + new /obj/item/stack/ore/glass(get_turf(H)) + qdel(H) + +/datum/species/golem/sand/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H) + if(!(P.original == H && P.firer == H)) + if(P.flag == "bullet" || P.flag == "bomb") + playsound(H, 'sound/effects/shovel_dig.ogg', 70, 1) + H.visible_message("The [P.name] sinks harmlessly in [H]'s sandy body!", \ + "The [P.name] sinks harmlessly in [H]'s sandy body!") + return 2 + return 0 + +//Reflects lasers and resistant to burn damage, but very vulnerable to brute damage. Shatters on death. +/datum/species/golem/glass + name = "Glass Golem" + id = "glass golem" + fixed_mut_color = "5a96b4aa" //transparent body + meat = /obj/item/shard + armor = 0 + brutemod = 3 //very fragile + burnmod = 0.25 + info_text = "As a Glass Golem, you reflect lasers and energy weapons, and are very resistant to burn damage. However, you are extremely vulnerable to brute damage. On death, you'll shatter beyond any hope of recovery." + attack_sound = 'sound/effects/glassbr2.ogg' + prefix = "Glass" + special_names = list("Lens", "Prism", "Fiber", "Bead") + +/datum/species/golem/glass/spec_death(gibbed, mob/living/carbon/human/H) + playsound(H, "shatter", 70, 1) + H.visible_message("[H] shatters!") + for(var/obj/item/W in H) + H.dropItemToGround(W) + for(var/i=1, i <= rand(3,5), i++) + new /obj/item/shard(get_turf(H)) + qdel(H) + +/datum/species/golem/glass/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H) + if(!(P.original == H && P.firer == H)) //self-shots don't reflect + if(P.flag == "laser" || P.flag == "energy") + H.visible_message("The [P.name] gets reflected by [H]'s glass skin!", \ + "The [P.name] gets reflected by [H]'s glass skin!") + if(P.starting) + var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/turf/target = get_turf(P.starting) + // redirect the projectile + P.preparePixelProjectile(locate(CLAMP(target.x + new_x, 1, world.maxx), CLAMP(target.y + new_y, 1, world.maxy), H.z), H) + return -1 + return 0 + +//Teleports when hit or when it wants to +/datum/species/golem/bluespace + name = "Bluespace Golem" + id = "bluespace golem" + fixed_mut_color = "33f" + meat = /obj/item/stack/ore/bluespace_crystal + info_text = "As a Bluespace Golem, you are spatially unstable: You will teleport when hit, and you can teleport manually at a long distance." + attack_verb = "bluespace punch" + attack_sound = 'sound/effects/phasein.ogg' + prefix = "Bluespace" + special_names = list("Crystal", "Polycrystal") + + var/datum/action/innate/unstable_teleport/unstable_teleport + var/teleport_cooldown = 100 + var/last_teleport = 0 + +/datum/species/golem/bluespace/proc/reactive_teleport(mob/living/carbon/human/H) + H.visible_message("[H] teleports!", "You destabilize and teleport!") + new /obj/effect/particle_effect/sparks(get_turf(H)) + playsound(get_turf(H), "sparks", 50, 1) + do_teleport(H, get_turf(H), 6, asoundin = 'sound/weapons/emitter2.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) + last_teleport = world.time + +/datum/species/golem/bluespace/spec_hitby(atom/movable/AM, mob/living/carbon/human/H) + ..() + var/obj/item/I + if(istype(AM, /obj/item)) + I = AM + if(I.thrownby == H) //No throwing stuff at yourself to trigger the teleport + return 0 + else + reactive_teleport(H) + +/datum/species/golem/bluespace/spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style) + ..() + if(world.time > last_teleport + teleport_cooldown && M != H && M.a_intent != INTENT_HELP) + reactive_teleport(H) + +/datum/species/golem/bluespace/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H) + ..() + if(world.time > last_teleport + teleport_cooldown && user != H) + reactive_teleport(H) + +/datum/species/golem/bluespace/on_hit(obj/item/projectile/P, mob/living/carbon/human/H) + ..() + if(world.time > last_teleport + teleport_cooldown) + reactive_teleport(H) + +/datum/species/golem/bluespace/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + if(ishuman(C)) + unstable_teleport = new + unstable_teleport.Grant(C) + last_teleport = world.time + +/datum/species/golem/bluespace/on_species_loss(mob/living/carbon/C) + if(unstable_teleport) + unstable_teleport.Remove(C) + ..() + +/datum/action/innate/unstable_teleport + name = "Unstable Teleport" + check_flags = AB_CHECK_CONSCIOUS + button_icon_state = "jaunt" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + var/cooldown = 150 + var/last_teleport = 0 + +/datum/action/innate/unstable_teleport/IsAvailable() + if(..()) + if(world.time > last_teleport + cooldown) + return 1 + return 0 + +/datum/action/innate/unstable_teleport/Activate() + var/mob/living/carbon/human/H = owner + H.visible_message("[H] starts vibrating!", "You start charging your bluespace core...") + playsound(get_turf(H), 'sound/weapons/flash.ogg', 25, 1) + addtimer(CALLBACK(src, .proc/teleport, H), 15) + +/datum/action/innate/unstable_teleport/proc/teleport(mob/living/carbon/human/H) + H.visible_message("[H] disappears in a shower of sparks!", "You teleport!") + var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread + spark_system.set_up(10, 0, src) + spark_system.attach(H) + spark_system.start() + do_teleport(H, get_turf(H), 12, asoundin = 'sound/weapons/emitter2.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) + last_teleport = world.time + UpdateButtonIcon() //action icon looks unavailable + sleep(cooldown + 5) + UpdateButtonIcon() //action icon looks available again + + +//honk +/datum/species/golem/bananium + name = "Bananium Golem" + id = "bananium golem" + fixed_mut_color = "ff0" + say_mod = "honks" + punchdamagelow = 0 + punchdamagehigh = 1 + punchstunthreshold = 2 //Harmless and can't stun + meat = /obj/item/stack/ore/bananium + info_text = "As a Bananium Golem, you are made for pranking. Your body emits natural honks, and you can barely even hurt people when punching them. Your skin also bleeds banana peels when damaged." + attack_verb = "honk" + attack_sound = 'sound/items/airhorn2.ogg' + prefix = "Bananium" + special_names = null + + var/last_honk = 0 + var/honkooldown = 0 + var/last_banana = 0 + var/banana_cooldown = 100 + var/active = null + +/datum/species/golem/bananium/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + last_banana = world.time + last_honk = world.time + RegisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) + +/datum/species/golem/bananium/on_species_loss(mob/living/carbon/C) + . = ..() + UnregisterSignal(C, COMSIG_MOB_SAY) + +/datum/species/golem/bananium/random_name(gender,unique,lastname) + var/clown_name = pick(GLOB.clown_names) + var/golem_name = "[uppertext(clown_name)]" + return golem_name + +/datum/species/golem/bananium/spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style) + ..() + if(world.time > last_banana + banana_cooldown && M != H && M.a_intent != INTENT_HELP) + new/obj/item/grown/bananapeel/specialpeel(get_turf(H)) + last_banana = world.time + +/datum/species/golem/bananium/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H) + ..() + if(world.time > last_banana + banana_cooldown && user != H) + new/obj/item/grown/bananapeel/specialpeel(get_turf(H)) + last_banana = world.time + +/datum/species/golem/bananium/on_hit(obj/item/projectile/P, mob/living/carbon/human/H) + ..() + if(world.time > last_banana + banana_cooldown) + new/obj/item/grown/bananapeel/specialpeel(get_turf(H)) + last_banana = world.time + +/datum/species/golem/bananium/spec_hitby(atom/movable/AM, mob/living/carbon/human/H) + ..() + var/obj/item/I + if(istype(AM, /obj/item)) + I = AM + if(I.thrownby == H) //No throwing stuff at yourself to make bananas + return 0 + else + new/obj/item/grown/bananapeel/specialpeel(get_turf(H)) + last_banana = world.time + +/datum/species/golem/bananium/spec_life(mob/living/carbon/human/H) + if(!active) + if(world.time > last_honk + honkooldown) + active = 1 + playsound(get_turf(H), 'sound/items/bikehorn.ogg', 50, 1) + last_honk = world.time + honkooldown = rand(20, 80) + active = null + ..() + +/datum/species/golem/bananium/spec_death(gibbed, mob/living/carbon/human/H) + playsound(get_turf(H), 'sound/misc/sadtrombone.ogg', 70, 0) + +/datum/species/golem/bananium/proc/handle_speech(datum/source, list/speech_args) + speech_args[SPEECH_SPANS] |= SPAN_CLOWN + +/datum/species/golem/runic + name = "Runic Golem" + id = "runic golem" + limbs_id = "cultgolem" + sexes = FALSE + info_text = "As a Runic Golem, you possess eldritch powers granted by the Elder Goddess Nar'Sie." + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES,NOGENITALS,NOAROUSAL) //no mutcolors + prefix = "Runic" + special_names = null + + var/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/golem/phase_shift + var/obj/effect/proc_holder/spell/targeted/abyssal_gaze/abyssal_gaze + var/obj/effect/proc_holder/spell/targeted/dominate/dominate + +/datum/species/golem/runic/random_name(gender,unique,lastname) + var/edgy_first_name = pick("Razor","Blood","Dark","Evil","Cold","Pale","Black","Silent","Chaos","Deadly","Coldsteel") + var/edgy_last_name = pick("Edge","Night","Death","Razor","Blade","Steel","Calamity","Twilight","Shadow","Nightmare") //dammit Razor Razor + var/golem_name = "[edgy_first_name] [edgy_last_name]" + return golem_name + +/datum/species/golem/runic/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.faction |= "cult" + phase_shift = new + phase_shift.charge_counter = 0 + C.AddSpell(phase_shift) + abyssal_gaze = new + abyssal_gaze.charge_counter = 0 + C.AddSpell(abyssal_gaze) + dominate = new + dominate.charge_counter = 0 + C.AddSpell(dominate) + +/datum/species/golem/runic/on_species_loss(mob/living/carbon/C) + . = ..() + C.faction -= "cult" + if(phase_shift) + C.RemoveSpell(phase_shift) + if(abyssal_gaze) + C.RemoveSpell(abyssal_gaze) + if(dominate) + C.RemoveSpell(dominate) + +/datum/species/golem/runic/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) + if(chem.type == /datum/reagent/water/holywater) + H.adjustFireLoss(4) + H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM) + + if(chem.type == /datum/reagent/fuel/unholywater) + H.adjustBruteLoss(-4) + H.adjustFireLoss(-4) + H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM) + + +/datum/species/golem/clockwork + name = "Clockwork Golem" + id = "clockwork golem" + say_mod = "clicks" + limbs_id = "clockgolem" + info_text = "As a Clockwork Golem, you are faster than other types of golems, and are capable of using guns. On death, you will break down into scrap." + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES,NOGENITALS,NOAROUSAL) + inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID) + inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) + armor = 20 //Reinforced, but much less so to allow for fast movement + attack_verb = "smash" + attack_sound = 'sound/magic/clockwork/anima_fragment_attack.ogg' + sexes = FALSE + speedmod = 0 + damage_overlay_type = "synth" + prefix = "Clockwork" + special_names = list("Remnant", "Relic", "Scrap", "Vestige") //RIP Ratvar + var/has_corpse + +/datum/species/golem/clockwork/on_species_gain(mob/living/carbon/human/H) + . = ..() + H.faction |= "ratvar" + RegisterSignal(H, COMSIG_MOB_SAY, .proc/handle_speech) + +/datum/species/golem/clockwork/on_species_loss(mob/living/carbon/human/H) + if(!is_servant_of_ratvar(H)) + H.faction -= "ratvar" + UnregisterSignal(H, COMSIG_MOB_SAY) + . = ..() + +/datum/species/golem/clockwork/proc/handle_speech(datum/source, list/speech_args) + speech_args[SPEECH_SPANS] |= SPAN_ROBOT //beep + +/datum/species/golem/clockwork/spec_death(gibbed, mob/living/carbon/human/H) + gibbed = !has_corpse ? FALSE : gibbed + . = ..() + if(!has_corpse) + var/turf/T = get_turf(H) + H.visible_message("[H]'s exoskeleton shatters, collapsing into a heap of scrap!") + playsound(H, 'sound/magic/clockwork/anima_fragment_death.ogg', 62, TRUE) + for(var/i in 1 to rand(3, 5)) + new/obj/item/clockwork/alloy_shards/small(T) + new/obj/item/clockwork/alloy_shards/clockgolem_remains(T) + qdel(H) + +/datum/species/golem/clockwork/no_scrap //These golems are created through the herald's beacon and leave normal corpses on death. + id = "clockwork golem servant" + armor = 15 //Balance reasons make this armor weak + no_equip = list() + nojumpsuit = FALSE + has_corpse = TRUE + blacklisted = TRUE + dangerous_existence = TRUE + random_eligible = FALSE + info_text = "As a Clockwork Golem Servant, you are faster than other types of golems, and are capable of using guns." //warcult golems leave a corpse + +/datum/species/golem/cloth + name = "Cloth Golem" + id = "cloth golem" + limbs_id = "clothgolem" + sexes = FALSE + info_text = "As a Cloth Golem, you are able to reform yourself after death, provided your remains aren't burned or destroyed. You are, of course, very flammable. \ + Being made of cloth, your body is magic resistant and faster than that of other golems, but weaker and less resilient." + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL) //no mutcolors, and can burn + inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_CHUNKYFINGERS) + inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID) + armor = 15 //feels no pain, but not too resistant + burnmod = 2 // don't get burned + speedmod = 1 // not as heavy as stone + punchdamagelow = 4 + punchstunthreshold = 7 + punchdamagehigh = 8 // not as heavy as stone + prefix = "Cloth" + special_names = null + +/datum/species/golem/cloth/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + ADD_TRAIT(C, TRAIT_HOLY, SPECIES_TRAIT) + +/datum/species/golem/cloth/on_species_loss(mob/living/carbon/C) + REMOVE_TRAIT(C, TRAIT_HOLY, SPECIES_TRAIT) + ..() + +/datum/species/golem/cloth/check_roundstart_eligible() + if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) + return TRUE + return ..() + +/datum/species/golem/cloth/random_name(gender,unique,lastname) + var/pharaoh_name = pick("Neferkare", "Hudjefa", "Khufu", "Mentuhotep", "Ahmose", "Amenhotep", "Thutmose", "Hatshepsut", "Tutankhamun", "Ramses", "Seti", \ + "Merenptah", "Djer", "Semerkhet", "Nynetjer", "Khafre", "Pepi", "Intef", "Ay") //yes, Ay was an actual pharaoh + var/golem_name = "[pharaoh_name] \Roman[rand(1,99)]" + return golem_name + +/datum/species/golem/cloth/spec_life(mob/living/carbon/human/H) + if(H.fire_stacks < 1) + H.adjust_fire_stacks(1) //always prone to burning + ..() + +/datum/species/golem/cloth/spec_death(gibbed, mob/living/carbon/human/H) + if(gibbed) + return + if(H.on_fire) + H.visible_message("[H] burns into ash!") + H.dust(just_ash = TRUE) + return + + H.visible_message("[H] falls apart into a pile of bandages!") + new /obj/structure/cloth_pile(get_turf(H), H) + ..() + +/obj/structure/cloth_pile + name = "pile of bandages" + desc = "It emits a strange aura, as if there was still life within it..." + max_integrity = 50 + armor = list("melee" = 90, "bullet" = 90, "laser" = 25, "energy" = 80, "bomb" = 50, "bio" = 100, "fire" = -50, "acid" = -50) + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "pile_bandages" + resistance_flags = FLAMMABLE + + var/revive_time = 900 + var/mob/living/carbon/human/cloth_golem + +/obj/structure/cloth_pile/Initialize(mapload, mob/living/carbon/human/H) + . = ..() + if(!QDELETED(H) && is_species(H, /datum/species/golem/cloth)) + H.unequip_everything() + H.forceMove(src) + cloth_golem = H + to_chat(cloth_golem, "You start gathering your life energy, preparing to rise again...") + addtimer(CALLBACK(src, .proc/revive), revive_time) + else + return INITIALIZE_HINT_QDEL + +/obj/structure/cloth_pile/Destroy() + if(cloth_golem) + QDEL_NULL(cloth_golem) + return ..() + +/obj/structure/cloth_pile/burn() + visible_message("[src] burns into ash!") + new /obj/effect/decal/cleanable/ash(get_turf(src)) + ..() + +/obj/structure/cloth_pile/proc/revive() + if(QDELETED(src) || QDELETED(cloth_golem)) //QDELETED also checks for null, so if no cloth golem is set this won't runtime + return + if(cloth_golem.suiciding || HAS_TRAIT(cloth_golem, TRAIT_NOCLONE)) + QDEL_NULL(cloth_golem) + return + + invisibility = INVISIBILITY_MAXIMUM //disappear before the animation + new /obj/effect/temp_visual/mummy_animation(get_turf(src)) + if(cloth_golem.revive(full_heal = TRUE, admin_revive = TRUE)) + cloth_golem.grab_ghost() //won't pull if it's a suicide + sleep(20) + cloth_golem.forceMove(get_turf(src)) + cloth_golem.visible_message("[src] rises and reforms into [cloth_golem]!","You reform into yourself!") + cloth_golem = null + qdel(src) + +/obj/structure/cloth_pile/attackby(obj/item/P, mob/living/carbon/human/user, params) + . = ..() + + if(resistance_flags & ON_FIRE) + return + + if(P.get_temperature()) + visible_message("[src] bursts into flames!") + fire_act() + +/datum/species/golem/plastic + name = "Plastic Golem" + id = "plastic golem" + prefix = "Plastic" + special_names = null + fixed_mut_color = "fff" + info_text = "As a Plastic Golem, you are capable of ventcrawling and passing through plastic flaps as long as you are naked." + +/datum/species/golem/plastic/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + C.ventcrawler = VENTCRAWLER_NUDE + +/datum/species/golem/plastic/on_species_loss(mob/living/carbon/C) + . = ..() + C.ventcrawler = initial(C.ventcrawler) + +/datum/species/golem/bronze + name = "Bronze Golem" + id = "bronze golem" + prefix = "Bronze" + special_names = list("Bell") + fixed_mut_color = "cd7f32" + info_text = "As a Bronze Golem, you are very resistant to loud noises, and make loud noises if something hard hits you, however this ability does hurt your hearing." + special_step_sounds = list('sound/machines/clockcult/integration_cog_install.ogg', 'sound/magic/clockwork/fellowship_armory.ogg' ) + attack_verb = "bonk" + mutantears = /obj/item/organ/ears/bronze + var/last_gong_time = 0 + var/gong_cooldown = 150 + +/datum/species/golem/bronze/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H) + if(!(world.time > last_gong_time + gong_cooldown)) + return ..() + if(P.flag == "bullet" || P.flag == "bomb") + gong(H) + return ..() + +/datum/species/golem/bronze/spec_hitby(atom/movable/AM, mob/living/carbon/human/H) + ..() + if(world.time > last_gong_time + gong_cooldown) + gong(H) + +/datum/species/golem/bronze/spec_attack_hand(mob/living/carbon/human/M, mob/living/carbon/human/H, datum/martial_art/attacker_style) + ..() + if(world.time > last_gong_time + gong_cooldown && M.a_intent != INTENT_HELP) + gong(H) + +/datum/species/golem/bronze/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H) + ..() + if(world.time > last_gong_time + gong_cooldown) + gong(H) + +/datum/species/golem/bronze/on_hit(obj/item/projectile/P, mob/living/carbon/human/H) + ..() + if(world.time > last_gong_time + gong_cooldown) + gong(H) + +/datum/species/golem/bronze/proc/gong(mob/living/carbon/human/H) + last_gong_time = world.time + for(var/mob/living/M in get_hearers_in_view(7,H)) + if(M.stat == DEAD) //F + return + if(M == H) + H.show_message("You cringe with pain as your body rings around you!", MSG_AUDIBLE) + H.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE) + H.soundbang_act(2, 0, 100, 1) + H.jitteriness += 7 + var/distance = max(0,get_dist(get_turf(H),get_turf(M))) + switch(distance) + if(0 to 1) + M.show_message("GONG!", MSG_AUDIBLE) + M.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE) + M.soundbang_act(1, 0, 30, 3) + M.confused += 10 + M.jitteriness += 4 + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong) + if(2 to 3) + M.show_message("GONG!", MSG_AUDIBLE) + M.playsound_local(H, 'sound/effects/gong.ogg', 75, TRUE) + M.soundbang_act(1, 0, 15, 2) + M.jitteriness += 3 + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong) + else + M.show_message("GONG!", MSG_AUDIBLE) + M.playsound_local(H, 'sound/effects/gong.ogg', 50, TRUE) + + +/datum/species/golem/cardboard //Faster but weaker, can also make new shells on its own + name = "Cardboard Golem" + id = "cardboard golem" + prefix = "Cardboard" + special_names = list("Box") + info_text = "As a Cardboard Golem, you aren't very strong, but you are a bit quicker and can easily create more brethren by using cardboard on yourself." + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL,MUTCOLORS) + inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) + fixed_mut_color = "ffffff" + limbs_id = "c_golem" //special sprites + attack_verb = "bash" + armor = 25 + burnmod = 1.25 + heatmod = 2 + speedmod = 1.5 + punchdamagelow = 4 + punchstunthreshold = 7 + punchdamagehigh = 8 + var/last_creation = 0 + var/brother_creation_cooldown = 300 + +/datum/species/golem/cardboard/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H) + . = ..() + if(user != H) + return FALSE //forced reproduction is rape. + if(istype(I, /obj/item/stack/sheet/cardboard)) + var/obj/item/stack/sheet/cardboard/C = I + if(last_creation + brother_creation_cooldown > world.time) //no cheesing dork + return + if(C.amount < 10) + to_chat(H, "You do not have enough cardboard!") + return FALSE + to_chat(H, "You attempt to create a new cardboard brother.") + if(do_after(user, 30, target = user)) + if(last_creation + brother_creation_cooldown > world.time) //no cheesing dork + return + if(!C.use(10)) + to_chat(H, "You do not have enough cardboard!") + return FALSE + to_chat(H, "You create a new cardboard golem shell.") + create_brother(H.loc) + +/datum/species/golem/cardboard/proc/create_brother(var/location) + new /obj/effect/mob_spawn/human/golem/servant(location, /datum/species/golem/cardboard, owner) + last_creation = world.time + +/datum/species/golem/leather + name = "Leather Golem" + id = "leather golem" + special_names = list("Face", "Man", "Belt") //Ah dude 4 strength 4 stam leather belt AHHH + inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER, TRAIT_STRONG_GRABBER) + prefix = "Leather" + fixed_mut_color = "624a2e" + info_text = "As a Leather Golem, you are flammable, but you can grab things with incredible ease, allowing all your grabs to start at a strong level." + attack_verb = "whipp" + grab_sound = 'sound/weapons/whipgrab.ogg' + attack_sound = 'sound/weapons/whip.ogg' + +/datum/species/golem/durathread + name = "Durathread Golem" + id = "durathread golem" + prefix = "Durathread" + limbs_id = "d_golem" + special_names = list("Boll","Weave") + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYES) + fixed_mut_color = null + inherent_traits = list(TRAIT_NOBREATH, TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) + info_text = "As a Durathread Golem, your strikes will cause those your targets to start choking, but your woven body won't withstand fire as well." + +/datum/species/golem/durathread/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target) + . = ..() + target.apply_status_effect(STATUS_EFFECT_CHOKINGSTRAND) + +/datum/species/golem/bone + name = "Bone Golem" + id = "bone golem" + say_mod = "rattles" + prefix = "Bone" + limbs_id = "b_golem" + special_names = list("Head", "Broth", "Fracture", "Rattler", "Appetit") + liked_food = GROSS | MEAT | RAW + toxic_food = null + inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID) + mutanttongue = /obj/item/organ/tongue/bone + sexes = FALSE + fixed_mut_color = "ffffff" + attack_verb = "rattl" + species_traits = list(NOBLOOD,NO_UNDERWEAR,NOGENITALS,NOAROUSAL,MUTCOLORS) + inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_FAKEDEATH,TRAIT_CALCIUM_HEALER) + info_text = "As a Bone Golem, You have a powerful spell that lets you chill your enemies with fear, and milk heals you! Just make sure to watch our for bone-hurting juice." + var/datum/action/innate/bonechill/bonechill + +/datum/species/golem/bone/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + if(ishuman(C)) + bonechill = new + bonechill.Grant(C) + +/datum/species/golem/bone/on_species_loss(mob/living/carbon/C) + if(bonechill) + bonechill.Remove(C) + ..() + +/datum/action/innate/bonechill + name = "Bone Chill" + desc = "Rattle your bones and strike fear into your enemies!" + check_flags = AB_CHECK_CONSCIOUS + icon_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "bonechill" + var/cooldown = 600 + var/last_use + var/snas_chance = 3 + +/datum/action/innate/bonechill/Activate() + if(world.time < last_use + cooldown) + to_chat("You aren't ready yet to rattle your bones again") + return + owner.visible_message("[owner] rattles [owner.p_their()] bones harrowingly.", "You rattle your bones") + last_use = world.time + if(prob(snas_chance)) + playsound(get_turf(owner),'sound/magic/RATTLEMEBONES2.ogg', 100) + if(ishuman(owner)) + var/mob/living/carbon/human/H = owner + var/mutable_appearance/badtime = mutable_appearance('icons/mob/human_parts.dmi', "b_golem_eyes", -FIRE_LAYER-0.5) + badtime.appearance_flags = RESET_COLOR + H.overlays_standing[FIRE_LAYER+0.5] = badtime + H.apply_overlay(FIRE_LAYER+0.5) + addtimer(CALLBACK(H, /mob/living/carbon/.proc/remove_overlay, FIRE_LAYER+0.5), 25) + else + playsound(get_turf(owner),'sound/magic/RATTLEMEBONES.ogg', 100) + for(var/mob/living/L in orange(7, get_turf(owner))) + if((MOB_UNDEAD in L.mob_biotypes) || isgolem(L) || HAS_TRAIT(L, TRAIT_RESISTCOLD)) + return //Do not affect our brothers + + to_chat(L, "A spine-chilling sound chills you to the bone!") + L.apply_status_effect(/datum/status_effect/bonechill) + SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "spooked", /datum/mood_event/spooked) diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index b63cd59173..2a5ec51b55 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -1,984 +1,984 @@ -/datum/species/jelly - // Entirely alien beings that seem to be made entirely out of gel. They have three eyes and a skeleton visible within them. - name = "Xenobiological Jelly Entity" - id = "jelly" - default_color = "00FF90" - say_mod = "chirps" - species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,WINGCOLOR) - mutantlungs = /obj/item/organ/lungs/slime - mutant_heart = /obj/item/organ/heart/slime - mutant_bodyparts = list("mam_tail", "mam_ears", "mam_snouts", "taur", "deco_wings") //CIT CHANGE - default_features = list("mcolor" = "FFF", "mam_tail" = "None", "mam_ears" = "None", "mam_snouts" = "None", "taur" = "None", "deco_wings" = "None") //CIT CHANGE - inherent_traits = list(TRAIT_TOXINLOVER) - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime - gib_types = list(/obj/effect/gibspawner/slime, /obj/effect/gibspawner/slime/bodypartless) - exotic_blood = /datum/reagent/blood/jellyblood - exotic_bloodtype = "GEL" - damage_overlay_type = "" - var/datum/action/innate/regenerate_limbs/regenerate_limbs - var/datum/action/innate/slime_change/slime_change //CIT CHANGE - liked_food = TOXIC | MEAT - toxic_food = null - coldmod = 6 // = 3x cold damage - heatmod = 0.5 // = 1/4x heat damage - burnmod = 0.5 // = 1/2x generic burn damage - -/datum/species/jelly/on_species_loss(mob/living/carbon/C) - if(regenerate_limbs) - regenerate_limbs.Remove(C) - if(slime_change) //CIT CHANGE - slime_change.Remove(C) //CIT CHANGE - C.remove_language(/datum/language/slime) - C.faction -= "slime" - ..() - C.faction -= "slime" - -/datum/species/jelly/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - C.grant_language(/datum/language/slime) - if(ishuman(C)) - regenerate_limbs = new - regenerate_limbs.Grant(C) - slime_change = new //CIT CHANGE - slime_change.Grant(C) //CIT CHANGE - C.faction |= "slime" - -/datum/species/jelly/spec_life(mob/living/carbon/human/H) - if(H.stat == DEAD || HAS_TRAIT(H, TRAIT_NOMARROW)) //can't farm slime jelly from a dead slime/jelly person indefinitely, and no regeneration for vampires - return - if(!H.blood_volume) - H.blood_volume += 5 - H.adjustBruteLoss(5) - to_chat(H, "You feel empty!") - - if(H.blood_volume < (BLOOD_VOLUME_NORMAL * H.blood_ratio)) - if(H.nutrition >= NUTRITION_LEVEL_STARVING) - H.blood_volume += 3 - H.nutrition -= 2.5 - if(H.blood_volume < (BLOOD_VOLUME_OKAY*H.blood_ratio)) - if(prob(5)) - to_chat(H, "You feel drained!") - if(H.blood_volume < (BLOOD_VOLUME_BAD*H.blood_ratio)) - Cannibalize_Body(H) - if(regenerate_limbs) - regenerate_limbs.UpdateButtonIcon() - -/datum/species/jelly/proc/Cannibalize_Body(mob/living/carbon/human/H) - var/list/limbs_to_consume = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - H.get_missing_limbs() - var/obj/item/bodypart/consumed_limb - if(!limbs_to_consume.len) - H.losebreath++ - return - if(H.get_num_legs(FALSE)) //Legs go before arms - limbs_to_consume -= list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM) - consumed_limb = H.get_bodypart(pick(limbs_to_consume)) - consumed_limb.drop_limb() - to_chat(H, "Your [consumed_limb] is drawn back into your body, unable to maintain its shape!") - qdel(consumed_limb) - H.blood_volume += 20 - -/datum/action/innate/regenerate_limbs - name = "Regenerate Limbs" - check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "slimeheal" - icon_icon = 'icons/mob/actions/actions_slime.dmi' - background_icon_state = "bg_alien" - -/datum/action/innate/regenerate_limbs/IsAvailable() - if(..()) - var/mob/living/carbon/human/H = owner - var/list/limbs_to_heal = H.get_missing_limbs() - if(limbs_to_heal.len < 1) - return 0 - if(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40) - return 1 - return 0 - -/datum/action/innate/regenerate_limbs/Activate() - var/mob/living/carbon/human/H = owner - var/list/limbs_to_heal = H.get_missing_limbs() - if(limbs_to_heal.len < 1) - to_chat(H, "You feel intact enough as it is.") - return - to_chat(H, "You focus intently on your missing [limbs_to_heal.len >= 2 ? "limbs" : "limb"]...") - if(H.blood_volume >= 40*limbs_to_heal.len+(BLOOD_VOLUME_OKAY*H.blood_ratio)) - H.regenerate_limbs() - H.blood_volume -= 40*limbs_to_heal.len - to_chat(H, "...and after a moment you finish reforming!") - return - else if(H.blood_volume >= 40)//We can partially heal some limbs - while(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40) - var/healed_limb = pick(limbs_to_heal) - H.regenerate_limb(healed_limb) - limbs_to_heal -= healed_limb - H.blood_volume -= 40 - to_chat(H, "...but there is not enough of you to fix everything! You must attain more mass to heal completely!") - return - to_chat(H, "...but there is not enough of you to go around! You must attain more mass to heal!") - -/datum/species/jelly/spec_death(gibbed, mob/living/carbon/human/H) - if(H) - stop_wagging_tail(H) - -/datum/species/jelly/spec_stun(mob/living/carbon/human/H,amount) - if(H) - stop_wagging_tail(H) - . = ..() - -/datum/species/jelly/can_wag_tail(mob/living/carbon/human/H) - return ("mam_tail" in mutant_bodyparts) || ("mam_waggingtail" in mutant_bodyparts) - -/datum/species/jelly/is_wagging_tail(mob/living/carbon/human/H) - return ("mam_waggingtail" in mutant_bodyparts) - -/datum/species/jelly/start_wagging_tail(mob/living/carbon/human/H) - if("mam_tail" in mutant_bodyparts) - mutant_bodyparts -= "mam_tail" - mutant_bodyparts |= "mam_waggingtail" - H.update_body() - -/datum/species/jelly/stop_wagging_tail(mob/living/carbon/human/H) - if("mam_waggingtail" in mutant_bodyparts) - mutant_bodyparts -= "mam_waggingtail" - mutant_bodyparts |= "mam_tail" - H.update_body() - -////////////////////////////////////////////////////////SLIMEPEOPLE/////////////////////////////////////////////////////////////////// - -//Slime people are able to split like slimes, retaining a single mind that can swap between bodies at will, even after death. - -/datum/species/jelly/slime - name = "Xenobiological Slime Entity" - id = "slime" - default_color = "00FFFF" - species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR) - say_mod = "says" - hair_color = "mutcolor" - hair_alpha = 150 - ignored_by = list(/mob/living/simple_animal/slime) - var/datum/action/innate/split_body/slime_split - var/list/mob/living/carbon/bodies - var/datum/action/innate/swap_body/swap_body - -/datum/species/jelly/slime/on_species_loss(mob/living/carbon/C) - if(slime_split) - slime_split.Remove(C) - if(swap_body) - swap_body.Remove(C) - bodies -= C // This means that the other bodies maintain a link - // so if someone mindswapped into them, they'd still be shared. - bodies = null - C.blood_volume = min(C.blood_volume, (BLOOD_VOLUME_NORMAL*C.blood_ratio)) - ..() - -/datum/species/jelly/slime/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - if(ishuman(C)) - slime_split = new - slime_split.Grant(C) - swap_body = new - swap_body.Grant(C) - - if(!bodies || !bodies.len) - bodies = list(C) - else - bodies |= C - -/datum/species/jelly/slime/spec_death(gibbed, mob/living/carbon/human/H) - if(slime_split) - if(!H.mind || !H.mind.active) - return - - var/list/available_bodies = (bodies - H) - for(var/mob/living/L in available_bodies) - if(!swap_body.can_swap(L)) - available_bodies -= L - - if(!LAZYLEN(available_bodies)) - return - - swap_body.swap_to_dupe(H.mind, pick(available_bodies)) - -//If you're cloned you get your body pool back -/datum/species/jelly/slime/copy_properties_from(datum/species/jelly/slime/old_species) - bodies = old_species.bodies - -/datum/species/jelly/slime/spec_life(mob/living/carbon/human/H) - if((HAS_TRAIT(H, TRAIT_NOMARROW))) - return - if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT) - if(prob(5)) - to_chat(H, "You feel very bloated!") - else if(H.nutrition >= NUTRITION_LEVEL_WELL_FED) - H.blood_volume += 3 - H.nutrition -= 2.5 - - ..() - -/datum/action/innate/split_body - name = "Split Body" - check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "slimesplit" - icon_icon = 'icons/mob/actions/actions_slime.dmi' - background_icon_state = "bg_alien" - -/datum/action/innate/split_body/IsAvailable() - if(..()) - var/mob/living/carbon/human/H = owner - if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT) - return 1 - return 0 - -/datum/action/innate/split_body/Activate() - var/mob/living/carbon/human/H = owner - if(!isslimeperson(H)) - return - CHECK_DNA_AND_SPECIES(H) - H.visible_message("[owner] gains a look of \ - concentration while standing perfectly still.", - "You focus intently on moving your body while \ - standing perfectly still...") - - H.notransform = TRUE - - if(do_after(owner, delay=60, needhand=FALSE, target=owner, progress=TRUE)) - if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT) - make_dupe() - else - to_chat(H, "...but there is not enough of you to go around! You must attain more mass to split!") - else - to_chat(H, "...but fail to stand perfectly still!") - - H.notransform = FALSE - -/datum/action/innate/split_body/proc/make_dupe() - var/mob/living/carbon/human/H = owner - CHECK_DNA_AND_SPECIES(H) - - var/mob/living/carbon/human/spare = new /mob/living/carbon/human(H.loc) - - spare.underwear = "Nude" - H.dna.transfer_identity(spare, transfer_SE=1) - spare.dna.features["mcolor"] = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F") - spare.real_name = spare.dna.real_name - spare.name = spare.dna.real_name - spare.updateappearance(mutcolor_update=1) - spare.domutcheck() - spare.Move(get_step(H.loc, pick(NORTH,SOUTH,EAST,WEST))) - - H.blood_volume *= 0.45 - H.notransform = 0 - - var/datum/species/jelly/slime/origin_datum = H.dna.species - origin_datum.bodies |= spare - - var/datum/species/jelly/slime/spare_datum = spare.dna.species - spare_datum.bodies = origin_datum.bodies - - H.transfer_trait_datums(spare) - H.mind.transfer_to(spare) - spare.visible_message("[H] distorts as a new body \ - \"steps out\" of [H.p_them()].", - "...and after a moment of disorentation, \ - you're besides yourself!") - - -/datum/action/innate/swap_body - name = "Swap Body" - check_flags = NONE - button_icon_state = "slimeswap" - icon_icon = 'icons/mob/actions/actions_slime.dmi' - background_icon_state = "bg_alien" - -/datum/action/innate/swap_body/Activate() - if(!isslimeperson(owner)) - to_chat(owner, "You are not a slimeperson.") - Remove(owner) - else - ui_interact(owner) - -/datum/action/innate/swap_body/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state) - - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "slime_swap_body", name, 400, 400, master_ui, state) - ui.open() - -/datum/action/innate/swap_body/ui_data(mob/user) - var/mob/living/carbon/human/H = owner - if(!isslimeperson(H)) - return - - var/datum/species/jelly/slime/SS = H.dna.species - - var/list/data = list() - data["bodies"] = list() - for(var/b in SS.bodies) - var/mob/living/carbon/human/body = b - if(!body || QDELETED(body) || !isslimeperson(body)) - SS.bodies -= b - continue - - var/list/L = list() - // HTML colors need a # prefix - L["htmlcolor"] = "#[body.dna.features["mcolor"]]" - L["area"] = get_area_name(body, TRUE) - var/stat = "error" - switch(body.stat) - if(CONSCIOUS) - stat = "Conscious" - if(UNCONSCIOUS) - stat = "Unconscious" - if(DEAD) - stat = "Dead" - var/occupied - if(body == H) - occupied = "owner" - else if(body.mind && body.mind.active) - occupied = "stranger" - else - occupied = "available" - - L["status"] = stat - L["exoticblood"] = body.blood_volume - L["name"] = body.name - L["ref"] = "[REF(body)]" - L["occupied"] = occupied - var/button - if(occupied == "owner") - button = "selected" - else if(occupied == "stranger") - button = "danger" - else if(can_swap(body)) - button = null - else - button = "disabled" - - L["swap_button_state"] = button - L["swappable"] = (occupied == "available") && can_swap(body) - - data["bodies"] += list(L) - - return data - -/datum/action/innate/swap_body/ui_act(action, params) - if(..()) - return - var/mob/living/carbon/human/H = owner - if(!isslimeperson(owner)) - return - if(!H.mind || !H.mind.active) - return - switch(action) - if("swap") - var/mob/living/carbon/human/selected = locate(params["ref"]) - if(!can_swap(selected)) - return - SStgui.close_uis(src) - swap_to_dupe(H.mind, selected) - -/datum/action/innate/swap_body/proc/can_swap(mob/living/carbon/human/dupe) - var/mob/living/carbon/human/H = owner - if(!isslimeperson(H)) - return FALSE - var/datum/species/jelly/slime/SS = H.dna.species - - if(QDELETED(dupe)) //Is there a body? - SS.bodies -= dupe - return FALSE - - if(!isslimeperson(dupe)) //Is it a slimeperson? - SS.bodies -= dupe - return FALSE - - if(dupe.stat == DEAD) //Is it alive? - return FALSE - - if(dupe.stat != CONSCIOUS) //Is it awake? - return FALSE - - if(dupe.mind && dupe.mind.active) //Is it unoccupied? - return FALSE - - if(!(dupe in SS.bodies)) //Do we actually own it? - return FALSE - - return TRUE - -/datum/action/innate/swap_body/proc/swap_to_dupe(datum/mind/M, mob/living/carbon/human/dupe) - if(!can_swap(dupe)) //sanity check - return - if(M.current.stat == CONSCIOUS) - M.current.visible_message("[M.current] \ - stops moving and starts staring vacantly into space.", - "You stop moving this body...") - else - to_chat(M.current, "You abandon this body...") - M.current.transfer_trait_datums(dupe) - M.transfer_to(dupe) - dupe.visible_message("[dupe] blinks and looks \ - around.", - "...and move this one instead.") - - -////////////////////////////////////////////////////////Round Start Slimes/////////////////////////////////////////////////////////////////// - -/datum/species/jelly/roundstartslime - name = "Xenobiological Slime Hybrid" - id = "slimeperson" - limbs_id = "slime" - default_color = "00FFFF" - species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR) - inherent_traits = list(TRAIT_TOXINLOVER) - mutant_bodyparts = list("mam_tail", "mam_ears", "mam_body_markings", "mam_snouts", "taur") - default_features = list("mcolor" = "FFF", "mcolor2" = "FFF","mcolor3" = "FFF", "mam_tail" = "None", "mam_ears" = "None", "mam_body_markings" = "Plain", "mam_snouts" = "None", "taur" = "None") - say_mod = "says" - hair_color = "mutcolor" - hair_alpha = 160 //a notch brighter so it blends better. - coldmod = 3 - heatmod = 1 - burnmod = 1 - -/datum/action/innate/slime_change - name = "Alter Form" - check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "alter_form" //placeholder - icon_icon = 'modular_citadel/icons/mob/actions/actions_slime.dmi' - background_icon_state = "bg_alien" - -/datum/action/innate/slime_change/Activate() - var/mob/living/carbon/human/H = owner - if(!isjellyperson(H)) - return - else - H.visible_message("[owner] gains a look of \ - concentration while standing perfectly still.\ - Their body seems to shift and starts getting more goo-like.", - "You focus intently on altering your body while \ - standing perfectly still...") - change_form() - -/datum/action/innate/slime_change/proc/change_form() - var/mob/living/carbon/human/H = owner - var/select_alteration = input(owner, "Select what part of your form to alter", "Form Alteration", "cancel") in list("Hair Style", "Genitals", "Tail", "Snout", "Markings", "Ears", "Taur body", "Penis", "Vagina", "Penis Length", "Breast Size", "Breast Shape", "Cancel") - if(select_alteration == "Hair Style") - if(H.gender == MALE) - var/new_style = input(owner, "Select a facial hair style", "Hair Alterations") as null|anything in GLOB.facial_hair_styles_list - if(new_style) - H.facial_hair_style = new_style - else - H.facial_hair_style = "Shaved" - //handle normal hair - var/new_style = input(owner, "Select a hair style", "Hair Alterations") as null|anything in GLOB.hair_styles_list - if(new_style) - H.hair_style = new_style - H.update_hair() - else if (select_alteration == "Genitals") - var/operation = input("Select organ operation.", "Organ Manipulation", "cancel") in list("add sexual organ", "remove sexual organ", "cancel") - switch(operation) - if("add sexual organ") - var/new_organ = input("Select sexual organ:", "Organ Manipulation") as null|anything in GLOB.genitals_list - if(!new_organ) - return - H.give_genital(GLOB.genitals_list[new_organ]) - - if("remove sexual organ") - var/list/organs = list() - for(var/obj/item/organ/genital/X in H.internal_organs) - var/obj/item/organ/I = X - organs["[I.name] ([I.type])"] = I - var/obj/item/O = input("Select sexual organ:", "Organ Manipulation", null) as null|anything in organs - var/obj/item/organ/genital/G = organs[O] - if(!G) - return - G.forceMove(get_turf(H)) - qdel(G) - H.update_genitals() - - else if (select_alteration == "Ears") - var/list/snowflake_ears_list = list("Normal" = null) - for(var/path in GLOB.mam_ears_list) - var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[path] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey))) - snowflake_ears_list[S.name] = path - var/new_ears - new_ears = input(owner, "Choose your character's ears:", "Ear Alteration") as null|anything in snowflake_ears_list - if(new_ears) - H.dna.features["mam_ears"] = new_ears - H.update_body() - - else if (select_alteration == "Snout") - var/list/snowflake_snouts_list = list("Normal" = null) - for(var/path in GLOB.mam_snouts_list) - var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[path] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey))) - snowflake_snouts_list[S.name] = path - var/new_snout - new_snout = input(owner, "Choose your character's face:", "Face Alteration") as null|anything in snowflake_snouts_list - if(new_snout) - H.dna.features["mam_snouts"] = new_snout - H.update_body() - - else if (select_alteration == "Markings") - var/list/snowflake_markings_list = list() - for(var/path in GLOB.mam_body_markings_list) - var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[path] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey))) - snowflake_markings_list[S.name] = path - var/new_mam_body_markings - new_mam_body_markings = input(H, "Choose your character's body markings:", "Marking Alteration") as null|anything in snowflake_markings_list - if(new_mam_body_markings) - H.dna.features["mam_body_markings"] = new_mam_body_markings - if(new_mam_body_markings == "None") - H.dna.features["mam_body_markings"] = "Plain" - for(var/X in H.bodyparts) //propagates the markings changes - var/obj/item/bodypart/BP = X - BP.update_limb(FALSE, H) - H.update_body() - - else if (select_alteration == "Tail") - var/list/snowflake_tails_list = list("Normal" = null) - for(var/path in GLOB.mam_tails_list) - var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[path] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey))) - snowflake_tails_list[S.name] = path - var/new_tail - new_tail = input(owner, "Choose your character's Tail(s):", "Tail Alteration") as null|anything in snowflake_tails_list - if(new_tail) - H.dna.features["mam_tail"] = new_tail - if(new_tail != "None") - H.dna.features["taur"] = "None" - H.update_body() - - else if (select_alteration == "Taur body") - var/list/snowflake_taur_list = list("Normal" = null) - for(var/path in GLOB.taur_list) - var/datum/sprite_accessory/taur/instance = GLOB.taur_list[path] - if(istype(instance, /datum/sprite_accessory)) - var/datum/sprite_accessory/S = instance - if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey))) - snowflake_taur_list[S.name] = path - var/new_taur - new_taur = input(owner, "Choose your character's tauric body:", "Tauric Alteration") as null|anything in snowflake_taur_list - if(new_taur) - H.dna.features["taur"] = new_taur - if(new_taur != "None") - H.dna.features["mam_tail"] = "None" - H.update_body() - - else if (select_alteration == "Penis") - for(var/obj/item/organ/genital/penis/X in H.internal_organs) - qdel(X) - var/new_shape - new_shape = input(owner, "Choose your character's dong", "Genital Alteration") as null|anything in GLOB.cock_shapes_list - if(new_shape) - H.dna.features["cock_shape"] = new_shape - H.update_genitals() - H.give_genital(/obj/item/organ/genital/testicles) - H.give_genital(/obj/item/organ/genital/penis) - H.apply_overlay() - - - else if (select_alteration == "Vagina") - for(var/obj/item/organ/genital/vagina/X in H.internal_organs) - qdel(X) - var/new_shape - new_shape = input(owner, "Choose your character's pussy", "Genital Alteration") as null|anything in GLOB.vagina_shapes_list - if(new_shape) - H.dna.features["vag_shape"] = new_shape - H.update_genitals() - H.give_genital(/obj/item/organ/genital/womb) - H.give_genital(/obj/item/organ/genital/vagina) - H.apply_overlay() - - else if (select_alteration == "Penis Length") - for(var/obj/item/organ/genital/penis/X in H.internal_organs) - qdel(X) - var/new_length - new_length = input(owner, "Penis length in inches:\n([COCK_SIZE_MIN]-[COCK_SIZE_MAX])", "Genital Alteration") as num|null - if(new_length) - H.dna.features["cock_length"] = max(min( round(text2num(new_length)), COCK_SIZE_MAX),COCK_SIZE_MIN) - H.update_genitals() - H.apply_overlay() - H.give_genital(/obj/item/organ/genital/testicles) - H.give_genital(/obj/item/organ/genital/penis) - - else if (select_alteration == "Breast Size") - for(var/obj/item/organ/genital/breasts/X in H.internal_organs) - qdel(X) - var/new_size - new_size = input(owner, "Breast Size", "Genital Alteration") as null|anything in GLOB.breasts_size_list - if(new_size) - H.dna.features["breasts_size"] = new_size - H.update_genitals() - H.apply_overlay() - H.give_genital(/obj/item/organ/genital/breasts) - - else if (select_alteration == "Breast Shape") - for(var/obj/item/organ/genital/breasts/X in H.internal_organs) - qdel(X) - var/new_shape - new_shape = input(owner, "Breast Shape", "Genital Alteration") as null|anything in GLOB.breasts_shapes_list - if(new_shape) - H.dna.features["breasts_shape"] = new_shape - H.update_genitals() - H.apply_overlay() - H.give_genital(/obj/item/organ/genital/breasts) - - else - return - - -///////////////////////////////////LUMINESCENTS////////////////////////////////////////// - -//Luminescents are able to consume and use slime extracts, without them decaying. - -/datum/species/jelly/luminescent - name = "Luminescent Slime Entity" - id = "lum" - say_mod = "says" - var/glow_intensity = LUMINESCENT_DEFAULT_GLOW - var/obj/effect/dummy/luminescent_glow/glow - var/obj/item/slime_extract/current_extract - var/datum/action/innate/integrate_extract/integrate_extract - var/datum/action/innate/use_extract/extract_minor - var/datum/action/innate/use_extract/major/extract_major - var/extract_cooldown = 0 - -/datum/species/jelly/luminescent/on_species_loss(mob/living/carbon/C) - ..() - if(current_extract) - current_extract.forceMove(C.drop_location()) - current_extract = null - qdel(glow) - if(integrate_extract) - integrate_extract.Remove(C) - if(extract_minor) - extract_minor.Remove(C) - if(extract_major) - extract_major.Remove(C) - -/datum/species/jelly/luminescent/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - glow = new(C) - update_glow(C) - integrate_extract = new(src) - integrate_extract.Grant(C) - extract_minor = new(src) - extract_minor.Grant(C) - extract_major = new(src) - extract_major.Grant(C) - -/datum/species/jelly/luminescent/proc/update_slime_actions() - integrate_extract.update_name() - integrate_extract.UpdateButtonIcon() - extract_minor.UpdateButtonIcon() - extract_major.UpdateButtonIcon() - -/datum/species/jelly/luminescent/proc/update_glow(mob/living/carbon/C, intensity) - if(intensity) - glow_intensity = intensity - glow.set_light(glow_intensity, glow_intensity, C.dna.features["mcolor"]) - -/obj/effect/dummy/luminescent_glow - name = "luminescent glow" - desc = "Tell a coder if you're seeing this." - icon_state = "nothing" - light_color = "#FFFFFF" - light_range = LUMINESCENT_DEFAULT_GLOW - -/obj/effect/dummy/luminescent_glow/Initialize() - . = ..() - if(!isliving(loc)) - return INITIALIZE_HINT_QDEL - -/datum/action/innate/integrate_extract - name = "Integrate Extract" - desc = "Eat a slime extract to use its properties." - check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "slimeconsume" - icon_icon = 'icons/mob/actions/actions_slime.dmi' - background_icon_state = "bg_alien" - var/datum/species/jelly/luminescent/species - -/datum/action/innate/integrate_extract/New(_species) - ..() - species = _species - -/datum/action/innate/integrate_extract/proc/update_name() - if(!species || !species.current_extract) - name = "Integrate Extract" - desc = "Eat a slime extract to use its properties." - else - name = "Eject Extract" - desc = "Eject your current slime extract." - -/datum/action/innate/integrate_extract/UpdateButtonIcon(status_only, force) - if(!species || !species.current_extract) - button_icon_state = "slimeconsume" - else - button_icon_state = "slimeeject" - ..() - -/datum/action/innate/integrate_extract/ApplyIcon(obj/screen/movable/action_button/current_button, force) - ..(current_button, TRUE) - if(species && species.current_extract) - current_button.add_overlay(mutable_appearance(species.current_extract.icon, species.current_extract.icon_state)) - -/datum/action/innate/integrate_extract/Activate() - var/mob/living/carbon/human/H = owner - if(!is_species(H, /datum/species/jelly/luminescent) || !species) - return - CHECK_DNA_AND_SPECIES(H) - - if(species.current_extract) - var/obj/item/slime_extract/S = species.current_extract - if(!H.put_in_active_hand(S)) - S.forceMove(H.drop_location()) - species.current_extract = null - to_chat(H, "You eject [S].") - species.update_slime_actions() - else - var/obj/item/I = H.get_active_held_item() - if(istype(I, /obj/item/slime_extract)) - var/obj/item/slime_extract/S = I - if(!S.Uses) - to_chat(H, "[I] is spent! You cannot integrate it.") - return - if(!H.temporarilyRemoveItemFromInventory(S)) - return - S.forceMove(H) - species.current_extract = S - to_chat(H, "You consume [I], and you feel it pulse within you...") - species.update_slime_actions() - else - to_chat(H, "You need to hold an unused slime extract in your active hand!") - -/datum/action/innate/use_extract - name = "Extract Minor Activation" - desc = "Pulse the slime extract with energized jelly to activate it." - check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "slimeuse1" - icon_icon = 'icons/mob/actions/actions_slime.dmi' - background_icon_state = "bg_alien" - var/activation_type = SLIME_ACTIVATE_MINOR - var/datum/species/jelly/luminescent/species - -/datum/action/innate/use_extract/New(_species) - ..() - species = _species - -/datum/action/innate/use_extract/IsAvailable() - if(..()) - if(species && species.current_extract && (world.time > species.extract_cooldown)) - return TRUE - return FALSE - -/datum/action/innate/use_extract/ApplyIcon(obj/screen/movable/action_button/current_button, force) - ..(current_button, TRUE) - if(species && species.current_extract) - current_button.add_overlay(mutable_appearance(species.current_extract.icon, species.current_extract.icon_state)) - -/datum/action/innate/use_extract/Activate() - var/mob/living/carbon/human/H = owner - if(!is_species(H, /datum/species/jelly/luminescent) || !species) - return - CHECK_DNA_AND_SPECIES(H) - - if(species.current_extract) - species.extract_cooldown = world.time + 100 - var/cooldown = species.current_extract.activate(H, species, activation_type) - species.extract_cooldown = world.time + cooldown - -/datum/action/innate/use_extract/major - name = "Extract Major Activation" - desc = "Pulse the slime extract with plasma jelly to activate it." - button_icon_state = "slimeuse2" - activation_type = SLIME_ACTIVATE_MAJOR - -///////////////////////////////////STARGAZERS////////////////////////////////////////// - -//Stargazers are the telepathic branch of jellypeople, able to project psychic messages and to link minds with willing participants. - -/datum/species/jelly/stargazer - name = "Stargazer Slime Entity" - id = "stargazer" - var/datum/action/innate/project_thought/project_thought - var/datum/action/innate/link_minds/link_minds - var/list/mob/living/linked_mobs = list() - var/list/datum/action/innate/linked_speech/linked_actions = list() - var/mob/living/carbon/human/slimelink_owner - var/current_link_id = 0 - -/datum/species/jelly/stargazer/on_species_loss(mob/living/carbon/C) - ..() - for(var/M in linked_mobs) - unlink_mob(M) - if(project_thought) - project_thought.Remove(C) - if(link_minds) - link_minds.Remove(C) - -/datum/species/jelly/stargazer/spec_death(gibbed, mob/living/carbon/human/H) - ..() - for(var/M in linked_mobs) - unlink_mob(M) - -/datum/species/jelly/stargazer/on_species_gain(mob/living/carbon/C, datum/species/old_species) - ..() - project_thought = new(src) - project_thought.Grant(C) - link_minds = new(src) - link_minds.Grant(C) - slimelink_owner = C - link_mob(C, TRUE) - -/datum/species/jelly/stargazer/proc/link_mob(mob/living/M, selflink = FALSE) - if(QDELETED(M) || (M in linked_mobs)) - return FALSE - if(!selflink && (M.stat == DEAD || HAS_TRAIT(M, TRAIT_MINDSHIELD) || M.anti_magic_check(FALSE, FALSE, TRUE, 0))) - return FALSE - linked_mobs.Add(M) - if(!selflink) - to_chat(M, "You are now connected to [slimelink_owner.real_name]'s Slime Link.") - var/datum/action/innate/linked_speech/action = new(src) - linked_actions.Add(action) - action.Grant(M) - return TRUE - -/datum/species/jelly/stargazer/proc/unlink_mob(mob/living/M) - var/link_id = linked_mobs.Find(M) - if(!(link_id)) - return - var/datum/action/innate/linked_speech/action = linked_actions[link_id] - action.Remove(M) - to_chat(M, "You are no longer connected to [slimelink_owner.real_name]'s Slime Link.") - linked_mobs[link_id] = null - linked_actions[link_id] = null - -/datum/action/innate/linked_speech - name = "Slimelink" - desc = "Send a psychic message to everyone connected to your slime link." - button_icon_state = "link_speech" - icon_icon = 'icons/mob/actions/actions_slime.dmi' - background_icon_state = "bg_alien" - var/datum/species/jelly/stargazer/species - -/datum/action/innate/linked_speech/New(_species) - ..() - species = _species - -/datum/action/innate/linked_speech/Activate() - var/mob/living/carbon/human/H = owner - if(!species || !(H in species.linked_mobs)) - to_chat(H, "The link seems to have been severed...") - Remove(H) - return - - var/message = sanitize(input("Message:", "Slime Telepathy") as text|null) - - if(!species || !(H in species.linked_mobs)) - to_chat(H, "The link seems to have been severed...") - Remove(H) - return - - if(QDELETED(H) || H.stat == DEAD) - species.unlink_mob(H) - return - - if(message) - var/msg = "\[[species.slimelink_owner.real_name]'s Slime Link\] [H]: [message]" - log_directed_talk(H, species.slimelink_owner, msg, LOG_SAY, "slime link") - for(var/X in species.linked_mobs) - var/mob/living/M = X - if(QDELETED(M) || M.stat == DEAD) - species.unlink_mob(M) - continue - to_chat(M, msg) - - for(var/X in GLOB.dead_mob_list) - var/mob/M = X - var/link = FOLLOW_LINK(M, H) - to_chat(M, "[link] [msg]") - -/datum/action/innate/project_thought - name = "Send Thought" - desc = "Send a private psychic message to someone you can see." - button_icon_state = "send_mind" - icon_icon = 'icons/mob/actions/actions_slime.dmi' - background_icon_state = "bg_alien" - -/datum/action/innate/project_thought/Activate() - var/mob/living/carbon/human/H = owner - if(H.stat == DEAD) - return - if(!is_species(H, /datum/species/jelly/stargazer)) - return - CHECK_DNA_AND_SPECIES(H) - - var/list/options = list() - for(var/mob/living/Ms in oview(H)) - options += Ms - var/mob/living/M = input("Select who to send your message to:","Send thought to?",null) as null|mob in options - if(!M) - return - if(M.anti_magic_check(FALSE, FALSE, TRUE, 0)) - to_chat(H, "As you try to communicate with [M], you're suddenly stopped by a vision of a massive tinfoil wall that streches beyond visible range. It seems you've been foiled.") - return - var/msg = sanitize(input("Message:", "Telepathy") as text|null) - if(msg) - if(M.anti_magic_check(FALSE, FALSE, TRUE, 0)) - to_chat(H, "As you try to communicate with [M], you're suddenly stopped by a vision of a massive tinfoil wall that streches beyond visible range. It seems you've been foiled.") - return - log_directed_talk(H, M, msg, LOG_SAY, "slime telepathy") - to_chat(M, "You hear an alien voice in your head... [msg]") - to_chat(H, "You telepathically said: \"[msg]\" to [M]") - for(var/dead in GLOB.dead_mob_list) - if(!isobserver(dead)) - continue - var/follow_link_user = FOLLOW_LINK(dead, H) - var/follow_link_target = FOLLOW_LINK(dead, M) - to_chat(dead, "[follow_link_user] [H] Slime Telepathy --> [follow_link_target] [M] [msg]") - -/datum/action/innate/link_minds - name = "Link Minds" - desc = "Link someone's mind to your Slime Link, allowing them to communicate telepathically with other linked minds." - button_icon_state = "mindlink" - icon_icon = 'icons/mob/actions/actions_slime.dmi' - background_icon_state = "bg_alien" - var/datum/species/jelly/stargazer/species - -/datum/action/innate/link_minds/New(_species) - ..() - species = _species - -/datum/action/innate/link_minds/Activate() - var/mob/living/carbon/human/H = owner - if(!is_species(H, /datum/species/jelly/stargazer)) - return - CHECK_DNA_AND_SPECIES(H) - - if(!H.pulling || !isliving(H.pulling) || H.grab_state < GRAB_AGGRESSIVE) - to_chat(H, "You need to aggressively grab someone to link minds!") - return - - var/mob/living/target = H.pulling - - to_chat(H, "You begin linking [target]'s mind to yours...") - to_chat(target, "You feel a foreign presence within your mind...") - if(do_after(H, 60, target = target)) - if(H.pulling != target || H.grab_state < GRAB_AGGRESSIVE) - return - if(species.link_mob(target)) - to_chat(H, "You connect [target]'s mind to your slime link!") - else - to_chat(H, "You can't seem to link [target]'s mind...") - to_chat(target, "The foreign presence leaves your mind.") +/datum/species/jelly + // Entirely alien beings that seem to be made entirely out of gel. They have three eyes and a skeleton visible within them. + name = "Xenobiological Jelly Entity" + id = "jelly" + default_color = "00FF90" + say_mod = "chirps" + species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,WINGCOLOR) + mutantlungs = /obj/item/organ/lungs/slime + mutant_heart = /obj/item/organ/heart/slime + mutant_bodyparts = list("mam_tail", "mam_ears", "mam_snouts", "taur", "deco_wings") //CIT CHANGE + default_features = list("mcolor" = "FFF", "mam_tail" = "None", "mam_ears" = "None", "mam_snouts" = "None", "taur" = "None", "deco_wings" = "None") //CIT CHANGE + inherent_traits = list(TRAIT_TOXINLOVER) + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime + gib_types = list(/obj/effect/gibspawner/slime, /obj/effect/gibspawner/slime/bodypartless) + exotic_blood = /datum/reagent/blood/jellyblood + exotic_bloodtype = "GEL" + damage_overlay_type = "" + var/datum/action/innate/regenerate_limbs/regenerate_limbs + var/datum/action/innate/slime_change/slime_change //CIT CHANGE + liked_food = TOXIC | MEAT + toxic_food = null + coldmod = 6 // = 3x cold damage + heatmod = 0.5 // = 1/4x heat damage + burnmod = 0.5 // = 1/2x generic burn damage + +/datum/species/jelly/on_species_loss(mob/living/carbon/C) + if(regenerate_limbs) + regenerate_limbs.Remove(C) + if(slime_change) //CIT CHANGE + slime_change.Remove(C) //CIT CHANGE + C.remove_language(/datum/language/slime) + C.faction -= "slime" + ..() + C.faction -= "slime" + +/datum/species/jelly/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + C.grant_language(/datum/language/slime) + if(ishuman(C)) + regenerate_limbs = new + regenerate_limbs.Grant(C) + slime_change = new //CIT CHANGE + slime_change.Grant(C) //CIT CHANGE + C.faction |= "slime" + +/datum/species/jelly/spec_life(mob/living/carbon/human/H) + if(H.stat == DEAD || HAS_TRAIT(H, TRAIT_NOMARROW)) //can't farm slime jelly from a dead slime/jelly person indefinitely, and no regeneration for vampires + return + if(!H.blood_volume) + H.blood_volume += 5 + H.adjustBruteLoss(5) + to_chat(H, "You feel empty!") + + if(H.blood_volume < (BLOOD_VOLUME_NORMAL * H.blood_ratio)) + if(H.nutrition >= NUTRITION_LEVEL_STARVING) + H.blood_volume += 3 + H.nutrition -= 2.5 + if(H.blood_volume < (BLOOD_VOLUME_OKAY*H.blood_ratio)) + if(prob(5)) + to_chat(H, "You feel drained!") + if(H.blood_volume < (BLOOD_VOLUME_BAD*H.blood_ratio)) + Cannibalize_Body(H) + if(regenerate_limbs) + regenerate_limbs.UpdateButtonIcon() + +/datum/species/jelly/proc/Cannibalize_Body(mob/living/carbon/human/H) + var/list/limbs_to_consume = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - H.get_missing_limbs() + var/obj/item/bodypart/consumed_limb + if(!limbs_to_consume.len) + H.losebreath++ + return + if(H.get_num_legs(FALSE)) //Legs go before arms + limbs_to_consume -= list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM) + consumed_limb = H.get_bodypart(pick(limbs_to_consume)) + consumed_limb.drop_limb() + to_chat(H, "Your [consumed_limb] is drawn back into your body, unable to maintain its shape!") + qdel(consumed_limb) + H.blood_volume += 20 + +/datum/action/innate/regenerate_limbs + name = "Regenerate Limbs" + check_flags = AB_CHECK_CONSCIOUS + button_icon_state = "slimeheal" + icon_icon = 'icons/mob/actions/actions_slime.dmi' + background_icon_state = "bg_alien" + +/datum/action/innate/regenerate_limbs/IsAvailable() + if(..()) + var/mob/living/carbon/human/H = owner + var/list/limbs_to_heal = H.get_missing_limbs() + if(limbs_to_heal.len < 1) + return 0 + if(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40) + return 1 + return 0 + +/datum/action/innate/regenerate_limbs/Activate() + var/mob/living/carbon/human/H = owner + var/list/limbs_to_heal = H.get_missing_limbs() + if(limbs_to_heal.len < 1) + to_chat(H, "You feel intact enough as it is.") + return + to_chat(H, "You focus intently on your missing [limbs_to_heal.len >= 2 ? "limbs" : "limb"]...") + if(H.blood_volume >= 40*limbs_to_heal.len+(BLOOD_VOLUME_OKAY*H.blood_ratio)) + H.regenerate_limbs() + H.blood_volume -= 40*limbs_to_heal.len + to_chat(H, "...and after a moment you finish reforming!") + return + else if(H.blood_volume >= 40)//We can partially heal some limbs + while(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40) + var/healed_limb = pick(limbs_to_heal) + H.regenerate_limb(healed_limb) + limbs_to_heal -= healed_limb + H.blood_volume -= 40 + to_chat(H, "...but there is not enough of you to fix everything! You must attain more mass to heal completely!") + return + to_chat(H, "...but there is not enough of you to go around! You must attain more mass to heal!") + +/datum/species/jelly/spec_death(gibbed, mob/living/carbon/human/H) + if(H) + stop_wagging_tail(H) + +/datum/species/jelly/spec_stun(mob/living/carbon/human/H,amount) + if(H) + stop_wagging_tail(H) + . = ..() + +/datum/species/jelly/can_wag_tail(mob/living/carbon/human/H) + return ("mam_tail" in mutant_bodyparts) || ("mam_waggingtail" in mutant_bodyparts) + +/datum/species/jelly/is_wagging_tail(mob/living/carbon/human/H) + return ("mam_waggingtail" in mutant_bodyparts) + +/datum/species/jelly/start_wagging_tail(mob/living/carbon/human/H) + if("mam_tail" in mutant_bodyparts) + mutant_bodyparts -= "mam_tail" + mutant_bodyparts |= "mam_waggingtail" + H.update_body() + +/datum/species/jelly/stop_wagging_tail(mob/living/carbon/human/H) + if("mam_waggingtail" in mutant_bodyparts) + mutant_bodyparts -= "mam_waggingtail" + mutant_bodyparts |= "mam_tail" + H.update_body() + +////////////////////////////////////////////////////////SLIMEPEOPLE/////////////////////////////////////////////////////////////////// + +//Slime people are able to split like slimes, retaining a single mind that can swap between bodies at will, even after death. + +/datum/species/jelly/slime + name = "Xenobiological Slime Entity" + id = "slime" + default_color = "00FFFF" + species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR) + say_mod = "says" + hair_color = "mutcolor" + hair_alpha = 150 + ignored_by = list(/mob/living/simple_animal/slime) + var/datum/action/innate/split_body/slime_split + var/list/mob/living/carbon/bodies + var/datum/action/innate/swap_body/swap_body + +/datum/species/jelly/slime/on_species_loss(mob/living/carbon/C) + if(slime_split) + slime_split.Remove(C) + if(swap_body) + swap_body.Remove(C) + bodies -= C // This means that the other bodies maintain a link + // so if someone mindswapped into them, they'd still be shared. + bodies = null + C.blood_volume = min(C.blood_volume, (BLOOD_VOLUME_NORMAL*C.blood_ratio)) + ..() + +/datum/species/jelly/slime/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + if(ishuman(C)) + slime_split = new + slime_split.Grant(C) + swap_body = new + swap_body.Grant(C) + + if(!bodies || !bodies.len) + bodies = list(C) + else + bodies |= C + +/datum/species/jelly/slime/spec_death(gibbed, mob/living/carbon/human/H) + if(slime_split) + if(!H.mind || !H.mind.active) + return + + var/list/available_bodies = (bodies - H) + for(var/mob/living/L in available_bodies) + if(!swap_body.can_swap(L)) + available_bodies -= L + + if(!LAZYLEN(available_bodies)) + return + + swap_body.swap_to_dupe(H.mind, pick(available_bodies)) + +//If you're cloned you get your body pool back +/datum/species/jelly/slime/copy_properties_from(datum/species/jelly/slime/old_species) + bodies = old_species.bodies + +/datum/species/jelly/slime/spec_life(mob/living/carbon/human/H) + if((HAS_TRAIT(H, TRAIT_NOMARROW))) + return + if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT) + if(prob(5)) + to_chat(H, "You feel very bloated!") + else if(H.nutrition >= NUTRITION_LEVEL_WELL_FED) + H.blood_volume += 3 + H.nutrition -= 2.5 + + ..() + +/datum/action/innate/split_body + name = "Split Body" + check_flags = AB_CHECK_CONSCIOUS + button_icon_state = "slimesplit" + icon_icon = 'icons/mob/actions/actions_slime.dmi' + background_icon_state = "bg_alien" + +/datum/action/innate/split_body/IsAvailable() + if(..()) + var/mob/living/carbon/human/H = owner + if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT) + return 1 + return 0 + +/datum/action/innate/split_body/Activate() + var/mob/living/carbon/human/H = owner + if(!isslimeperson(H)) + return + CHECK_DNA_AND_SPECIES(H) + H.visible_message("[owner] gains a look of \ + concentration while standing perfectly still.", + "You focus intently on moving your body while \ + standing perfectly still...") + + H.notransform = TRUE + + if(do_after(owner, delay=60, needhand=FALSE, target=owner, progress=TRUE)) + if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT) + make_dupe() + else + to_chat(H, "...but there is not enough of you to go around! You must attain more mass to split!") + else + to_chat(H, "...but fail to stand perfectly still!") + + H.notransform = FALSE + +/datum/action/innate/split_body/proc/make_dupe() + var/mob/living/carbon/human/H = owner + CHECK_DNA_AND_SPECIES(H) + + var/mob/living/carbon/human/spare = new /mob/living/carbon/human(H.loc) + + spare.underwear = "Nude" + H.dna.transfer_identity(spare, transfer_SE=1) + spare.dna.features["mcolor"] = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F") + spare.real_name = spare.dna.real_name + spare.name = spare.dna.real_name + spare.updateappearance(mutcolor_update=1) + spare.domutcheck() + spare.Move(get_step(H.loc, pick(NORTH,SOUTH,EAST,WEST))) + + H.blood_volume *= 0.45 + H.notransform = 0 + + var/datum/species/jelly/slime/origin_datum = H.dna.species + origin_datum.bodies |= spare + + var/datum/species/jelly/slime/spare_datum = spare.dna.species + spare_datum.bodies = origin_datum.bodies + + H.transfer_trait_datums(spare) + H.mind.transfer_to(spare) + spare.visible_message("[H] distorts as a new body \ + \"steps out\" of [H.p_them()].", + "...and after a moment of disorentation, \ + you're besides yourself!") + + +/datum/action/innate/swap_body + name = "Swap Body" + check_flags = NONE + button_icon_state = "slimeswap" + icon_icon = 'icons/mob/actions/actions_slime.dmi' + background_icon_state = "bg_alien" + +/datum/action/innate/swap_body/Activate() + if(!isslimeperson(owner)) + to_chat(owner, "You are not a slimeperson.") + Remove(owner) + else + ui_interact(owner) + +/datum/action/innate/swap_body/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "slime_swap_body", name, 400, 400, master_ui, state) + ui.open() + +/datum/action/innate/swap_body/ui_data(mob/user) + var/mob/living/carbon/human/H = owner + if(!isslimeperson(H)) + return + + var/datum/species/jelly/slime/SS = H.dna.species + + var/list/data = list() + data["bodies"] = list() + for(var/b in SS.bodies) + var/mob/living/carbon/human/body = b + if(!body || QDELETED(body) || !isslimeperson(body)) + SS.bodies -= b + continue + + var/list/L = list() + // HTML colors need a # prefix + L["htmlcolor"] = "#[body.dna.features["mcolor"]]" + L["area"] = get_area_name(body, TRUE) + var/stat = "error" + switch(body.stat) + if(CONSCIOUS) + stat = "Conscious" + if(UNCONSCIOUS) + stat = "Unconscious" + if(DEAD) + stat = "Dead" + var/occupied + if(body == H) + occupied = "owner" + else if(body.mind && body.mind.active) + occupied = "stranger" + else + occupied = "available" + + L["status"] = stat + L["exoticblood"] = body.blood_volume + L["name"] = body.name + L["ref"] = "[REF(body)]" + L["occupied"] = occupied + var/button + if(occupied == "owner") + button = "selected" + else if(occupied == "stranger") + button = "danger" + else if(can_swap(body)) + button = null + else + button = "disabled" + + L["swap_button_state"] = button + L["swappable"] = (occupied == "available") && can_swap(body) + + data["bodies"] += list(L) + + return data + +/datum/action/innate/swap_body/ui_act(action, params) + if(..()) + return + var/mob/living/carbon/human/H = owner + if(!isslimeperson(owner)) + return + if(!H.mind || !H.mind.active) + return + switch(action) + if("swap") + var/mob/living/carbon/human/selected = locate(params["ref"]) + if(!can_swap(selected)) + return + SStgui.close_uis(src) + swap_to_dupe(H.mind, selected) + +/datum/action/innate/swap_body/proc/can_swap(mob/living/carbon/human/dupe) + var/mob/living/carbon/human/H = owner + if(!isslimeperson(H)) + return FALSE + var/datum/species/jelly/slime/SS = H.dna.species + + if(QDELETED(dupe)) //Is there a body? + SS.bodies -= dupe + return FALSE + + if(!isslimeperson(dupe)) //Is it a slimeperson? + SS.bodies -= dupe + return FALSE + + if(dupe.stat == DEAD) //Is it alive? + return FALSE + + if(dupe.stat != CONSCIOUS) //Is it awake? + return FALSE + + if(dupe.mind && dupe.mind.active) //Is it unoccupied? + return FALSE + + if(!(dupe in SS.bodies)) //Do we actually own it? + return FALSE + + return TRUE + +/datum/action/innate/swap_body/proc/swap_to_dupe(datum/mind/M, mob/living/carbon/human/dupe) + if(!can_swap(dupe)) //sanity check + return + if(M.current.stat == CONSCIOUS) + M.current.visible_message("[M.current] \ + stops moving and starts staring vacantly into space.", + "You stop moving this body...") + else + to_chat(M.current, "You abandon this body...") + M.current.transfer_trait_datums(dupe) + M.transfer_to(dupe) + dupe.visible_message("[dupe] blinks and looks \ + around.", + "...and move this one instead.") + + +////////////////////////////////////////////////////////Round Start Slimes/////////////////////////////////////////////////////////////////// + +/datum/species/jelly/roundstartslime + name = "Xenobiological Slime Hybrid" + id = "slimeperson" + limbs_id = "slime" + default_color = "00FFFF" + species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR) + inherent_traits = list(TRAIT_TOXINLOVER) + mutant_bodyparts = list("mam_tail", "mam_ears", "mam_body_markings", "mam_snouts", "taur") + default_features = list("mcolor" = "FFF", "mcolor2" = "FFF","mcolor3" = "FFF", "mam_tail" = "None", "mam_ears" = "None", "mam_body_markings" = "Plain", "mam_snouts" = "None", "taur" = "None") + say_mod = "says" + hair_color = "mutcolor" + hair_alpha = 160 //a notch brighter so it blends better. + coldmod = 3 + heatmod = 1 + burnmod = 1 + +/datum/action/innate/slime_change + name = "Alter Form" + check_flags = AB_CHECK_CONSCIOUS + button_icon_state = "alter_form" //placeholder + icon_icon = 'modular_citadel/icons/mob/actions/actions_slime.dmi' + background_icon_state = "bg_alien" + +/datum/action/innate/slime_change/Activate() + var/mob/living/carbon/human/H = owner + if(!isjellyperson(H)) + return + else + H.visible_message("[owner] gains a look of \ + concentration while standing perfectly still.\ + Their body seems to shift and starts getting more goo-like.", + "You focus intently on altering your body while \ + standing perfectly still...") + change_form() + +/datum/action/innate/slime_change/proc/change_form() + var/mob/living/carbon/human/H = owner + var/select_alteration = input(owner, "Select what part of your form to alter", "Form Alteration", "cancel") in list("Hair Style", "Genitals", "Tail", "Snout", "Markings", "Ears", "Taur body", "Penis", "Vagina", "Penis Length", "Breast Size", "Breast Shape", "Cancel") + if(select_alteration == "Hair Style") + if(H.gender == MALE) + var/new_style = input(owner, "Select a facial hair style", "Hair Alterations") as null|anything in GLOB.facial_hair_styles_list + if(new_style) + H.facial_hair_style = new_style + else + H.facial_hair_style = "Shaved" + //handle normal hair + var/new_style = input(owner, "Select a hair style", "Hair Alterations") as null|anything in GLOB.hair_styles_list + if(new_style) + H.hair_style = new_style + H.update_hair() + else if (select_alteration == "Genitals") + var/operation = input("Select organ operation.", "Organ Manipulation", "cancel") in list("add sexual organ", "remove sexual organ", "cancel") + switch(operation) + if("add sexual organ") + var/new_organ = input("Select sexual organ:", "Organ Manipulation") as null|anything in GLOB.genitals_list + if(!new_organ) + return + H.give_genital(GLOB.genitals_list[new_organ]) + + if("remove sexual organ") + var/list/organs = list() + for(var/obj/item/organ/genital/X in H.internal_organs) + var/obj/item/organ/I = X + organs["[I.name] ([I.type])"] = I + var/obj/item/O = input("Select sexual organ:", "Organ Manipulation", null) as null|anything in organs + var/obj/item/organ/genital/G = organs[O] + if(!G) + return + G.forceMove(get_turf(H)) + qdel(G) + H.update_genitals() + + else if (select_alteration == "Ears") + var/list/snowflake_ears_list = list("Normal" = null) + for(var/path in GLOB.mam_ears_list) + var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[path] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey))) + snowflake_ears_list[S.name] = path + var/new_ears + new_ears = input(owner, "Choose your character's ears:", "Ear Alteration") as null|anything in snowflake_ears_list + if(new_ears) + H.dna.features["mam_ears"] = new_ears + H.update_body() + + else if (select_alteration == "Snout") + var/list/snowflake_snouts_list = list("Normal" = null) + for(var/path in GLOB.mam_snouts_list) + var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[path] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey))) + snowflake_snouts_list[S.name] = path + var/new_snout + new_snout = input(owner, "Choose your character's face:", "Face Alteration") as null|anything in snowflake_snouts_list + if(new_snout) + H.dna.features["mam_snouts"] = new_snout + H.update_body() + + else if (select_alteration == "Markings") + var/list/snowflake_markings_list = list() + for(var/path in GLOB.mam_body_markings_list) + var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[path] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey))) + snowflake_markings_list[S.name] = path + var/new_mam_body_markings + new_mam_body_markings = input(H, "Choose your character's body markings:", "Marking Alteration") as null|anything in snowflake_markings_list + if(new_mam_body_markings) + H.dna.features["mam_body_markings"] = new_mam_body_markings + if(new_mam_body_markings == "None") + H.dna.features["mam_body_markings"] = "Plain" + for(var/X in H.bodyparts) //propagates the markings changes + var/obj/item/bodypart/BP = X + BP.update_limb(FALSE, H) + H.update_body() + + else if (select_alteration == "Tail") + var/list/snowflake_tails_list = list("Normal" = null) + for(var/path in GLOB.mam_tails_list) + var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[path] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey))) + snowflake_tails_list[S.name] = path + var/new_tail + new_tail = input(owner, "Choose your character's Tail(s):", "Tail Alteration") as null|anything in snowflake_tails_list + if(new_tail) + H.dna.features["mam_tail"] = new_tail + if(new_tail != "None") + H.dna.features["taur"] = "None" + H.update_body() + + else if (select_alteration == "Taur body") + var/list/snowflake_taur_list = list("Normal" = null) + for(var/path in GLOB.taur_list) + var/datum/sprite_accessory/taur/instance = GLOB.taur_list[path] + if(istype(instance, /datum/sprite_accessory)) + var/datum/sprite_accessory/S = instance + if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey))) + snowflake_taur_list[S.name] = path + var/new_taur + new_taur = input(owner, "Choose your character's tauric body:", "Tauric Alteration") as null|anything in snowflake_taur_list + if(new_taur) + H.dna.features["taur"] = new_taur + if(new_taur != "None") + H.dna.features["mam_tail"] = "None" + H.update_body() + + else if (select_alteration == "Penis") + for(var/obj/item/organ/genital/penis/X in H.internal_organs) + qdel(X) + var/new_shape + new_shape = input(owner, "Choose your character's dong", "Genital Alteration") as null|anything in GLOB.cock_shapes_list + if(new_shape) + H.dna.features["cock_shape"] = new_shape + H.update_genitals() + H.give_genital(/obj/item/organ/genital/testicles) + H.give_genital(/obj/item/organ/genital/penis) + H.apply_overlay() + + + else if (select_alteration == "Vagina") + for(var/obj/item/organ/genital/vagina/X in H.internal_organs) + qdel(X) + var/new_shape + new_shape = input(owner, "Choose your character's pussy", "Genital Alteration") as null|anything in GLOB.vagina_shapes_list + if(new_shape) + H.dna.features["vag_shape"] = new_shape + H.update_genitals() + H.give_genital(/obj/item/organ/genital/womb) + H.give_genital(/obj/item/organ/genital/vagina) + H.apply_overlay() + + else if (select_alteration == "Penis Length") + for(var/obj/item/organ/genital/penis/X in H.internal_organs) + qdel(X) + var/new_length + new_length = input(owner, "Penis length in inches:\n([COCK_SIZE_MIN]-[COCK_SIZE_MAX])", "Genital Alteration") as num|null + if(new_length) + H.dna.features["cock_length"] = max(min( round(text2num(new_length)), COCK_SIZE_MAX),COCK_SIZE_MIN) + H.update_genitals() + H.apply_overlay() + H.give_genital(/obj/item/organ/genital/testicles) + H.give_genital(/obj/item/organ/genital/penis) + + else if (select_alteration == "Breast Size") + for(var/obj/item/organ/genital/breasts/X in H.internal_organs) + qdel(X) + var/new_size + new_size = input(owner, "Breast Size", "Genital Alteration") as null|anything in GLOB.breasts_size_list + if(new_size) + H.dna.features["breasts_size"] = new_size + H.update_genitals() + H.apply_overlay() + H.give_genital(/obj/item/organ/genital/breasts) + + else if (select_alteration == "Breast Shape") + for(var/obj/item/organ/genital/breasts/X in H.internal_organs) + qdel(X) + var/new_shape + new_shape = input(owner, "Breast Shape", "Genital Alteration") as null|anything in GLOB.breasts_shapes_list + if(new_shape) + H.dna.features["breasts_shape"] = new_shape + H.update_genitals() + H.apply_overlay() + H.give_genital(/obj/item/organ/genital/breasts) + + else + return + + +///////////////////////////////////LUMINESCENTS////////////////////////////////////////// + +//Luminescents are able to consume and use slime extracts, without them decaying. + +/datum/species/jelly/luminescent + name = "Luminescent Slime Entity" + id = "lum" + say_mod = "says" + var/glow_intensity = LUMINESCENT_DEFAULT_GLOW + var/obj/effect/dummy/luminescent_glow/glow + var/obj/item/slime_extract/current_extract + var/datum/action/innate/integrate_extract/integrate_extract + var/datum/action/innate/use_extract/extract_minor + var/datum/action/innate/use_extract/major/extract_major + var/extract_cooldown = 0 + +/datum/species/jelly/luminescent/on_species_loss(mob/living/carbon/C) + ..() + if(current_extract) + current_extract.forceMove(C.drop_location()) + current_extract = null + qdel(glow) + if(integrate_extract) + integrate_extract.Remove(C) + if(extract_minor) + extract_minor.Remove(C) + if(extract_major) + extract_major.Remove(C) + +/datum/species/jelly/luminescent/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + glow = new(C) + update_glow(C) + integrate_extract = new(src) + integrate_extract.Grant(C) + extract_minor = new(src) + extract_minor.Grant(C) + extract_major = new(src) + extract_major.Grant(C) + +/datum/species/jelly/luminescent/proc/update_slime_actions() + integrate_extract.update_name() + integrate_extract.UpdateButtonIcon() + extract_minor.UpdateButtonIcon() + extract_major.UpdateButtonIcon() + +/datum/species/jelly/luminescent/proc/update_glow(mob/living/carbon/C, intensity) + if(intensity) + glow_intensity = intensity + glow.set_light(glow_intensity, glow_intensity, C.dna.features["mcolor"]) + +/obj/effect/dummy/luminescent_glow + name = "luminescent glow" + desc = "Tell a coder if you're seeing this." + icon_state = "nothing" + light_color = "#FFFFFF" + light_range = LUMINESCENT_DEFAULT_GLOW + +/obj/effect/dummy/luminescent_glow/Initialize() + . = ..() + if(!isliving(loc)) + return INITIALIZE_HINT_QDEL + +/datum/action/innate/integrate_extract + name = "Integrate Extract" + desc = "Eat a slime extract to use its properties." + check_flags = AB_CHECK_CONSCIOUS + button_icon_state = "slimeconsume" + icon_icon = 'icons/mob/actions/actions_slime.dmi' + background_icon_state = "bg_alien" + var/datum/species/jelly/luminescent/species + +/datum/action/innate/integrate_extract/New(_species) + ..() + species = _species + +/datum/action/innate/integrate_extract/proc/update_name() + if(!species || !species.current_extract) + name = "Integrate Extract" + desc = "Eat a slime extract to use its properties." + else + name = "Eject Extract" + desc = "Eject your current slime extract." + +/datum/action/innate/integrate_extract/UpdateButtonIcon(status_only, force) + if(!species || !species.current_extract) + button_icon_state = "slimeconsume" + else + button_icon_state = "slimeeject" + ..() + +/datum/action/innate/integrate_extract/ApplyIcon(obj/screen/movable/action_button/current_button, force) + ..(current_button, TRUE) + if(species && species.current_extract) + current_button.add_overlay(mutable_appearance(species.current_extract.icon, species.current_extract.icon_state)) + +/datum/action/innate/integrate_extract/Activate() + var/mob/living/carbon/human/H = owner + if(!is_species(H, /datum/species/jelly/luminescent) || !species) + return + CHECK_DNA_AND_SPECIES(H) + + if(species.current_extract) + var/obj/item/slime_extract/S = species.current_extract + if(!H.put_in_active_hand(S)) + S.forceMove(H.drop_location()) + species.current_extract = null + to_chat(H, "You eject [S].") + species.update_slime_actions() + else + var/obj/item/I = H.get_active_held_item() + if(istype(I, /obj/item/slime_extract)) + var/obj/item/slime_extract/S = I + if(!S.Uses) + to_chat(H, "[I] is spent! You cannot integrate it.") + return + if(!H.temporarilyRemoveItemFromInventory(S)) + return + S.forceMove(H) + species.current_extract = S + to_chat(H, "You consume [I], and you feel it pulse within you...") + species.update_slime_actions() + else + to_chat(H, "You need to hold an unused slime extract in your active hand!") + +/datum/action/innate/use_extract + name = "Extract Minor Activation" + desc = "Pulse the slime extract with energized jelly to activate it." + check_flags = AB_CHECK_CONSCIOUS + button_icon_state = "slimeuse1" + icon_icon = 'icons/mob/actions/actions_slime.dmi' + background_icon_state = "bg_alien" + var/activation_type = SLIME_ACTIVATE_MINOR + var/datum/species/jelly/luminescent/species + +/datum/action/innate/use_extract/New(_species) + ..() + species = _species + +/datum/action/innate/use_extract/IsAvailable() + if(..()) + if(species && species.current_extract && (world.time > species.extract_cooldown)) + return TRUE + return FALSE + +/datum/action/innate/use_extract/ApplyIcon(obj/screen/movable/action_button/current_button, force) + ..(current_button, TRUE) + if(species && species.current_extract) + current_button.add_overlay(mutable_appearance(species.current_extract.icon, species.current_extract.icon_state)) + +/datum/action/innate/use_extract/Activate() + var/mob/living/carbon/human/H = owner + if(!is_species(H, /datum/species/jelly/luminescent) || !species) + return + CHECK_DNA_AND_SPECIES(H) + + if(species.current_extract) + species.extract_cooldown = world.time + 100 + var/cooldown = species.current_extract.activate(H, species, activation_type) + species.extract_cooldown = world.time + cooldown + +/datum/action/innate/use_extract/major + name = "Extract Major Activation" + desc = "Pulse the slime extract with plasma jelly to activate it." + button_icon_state = "slimeuse2" + activation_type = SLIME_ACTIVATE_MAJOR + +///////////////////////////////////STARGAZERS////////////////////////////////////////// + +//Stargazers are the telepathic branch of jellypeople, able to project psychic messages and to link minds with willing participants. + +/datum/species/jelly/stargazer + name = "Stargazer Slime Entity" + id = "stargazer" + var/datum/action/innate/project_thought/project_thought + var/datum/action/innate/link_minds/link_minds + var/list/mob/living/linked_mobs = list() + var/list/datum/action/innate/linked_speech/linked_actions = list() + var/mob/living/carbon/human/slimelink_owner + var/current_link_id = 0 + +/datum/species/jelly/stargazer/on_species_loss(mob/living/carbon/C) + ..() + for(var/M in linked_mobs) + unlink_mob(M) + if(project_thought) + project_thought.Remove(C) + if(link_minds) + link_minds.Remove(C) + +/datum/species/jelly/stargazer/spec_death(gibbed, mob/living/carbon/human/H) + ..() + for(var/M in linked_mobs) + unlink_mob(M) + +/datum/species/jelly/stargazer/on_species_gain(mob/living/carbon/C, datum/species/old_species) + ..() + project_thought = new(src) + project_thought.Grant(C) + link_minds = new(src) + link_minds.Grant(C) + slimelink_owner = C + link_mob(C, TRUE) + +/datum/species/jelly/stargazer/proc/link_mob(mob/living/M, selflink = FALSE) + if(QDELETED(M) || (M in linked_mobs)) + return FALSE + if(!selflink && (M.stat == DEAD || HAS_TRAIT(M, TRAIT_MINDSHIELD) || M.anti_magic_check(FALSE, FALSE, TRUE, 0))) + return FALSE + linked_mobs.Add(M) + if(!selflink) + to_chat(M, "You are now connected to [slimelink_owner.real_name]'s Slime Link.") + var/datum/action/innate/linked_speech/action = new(src) + linked_actions.Add(action) + action.Grant(M) + return TRUE + +/datum/species/jelly/stargazer/proc/unlink_mob(mob/living/M) + var/link_id = linked_mobs.Find(M) + if(!(link_id)) + return + var/datum/action/innate/linked_speech/action = linked_actions[link_id] + action.Remove(M) + to_chat(M, "You are no longer connected to [slimelink_owner.real_name]'s Slime Link.") + linked_mobs[link_id] = null + linked_actions[link_id] = null + +/datum/action/innate/linked_speech + name = "Slimelink" + desc = "Send a psychic message to everyone connected to your slime link." + button_icon_state = "link_speech" + icon_icon = 'icons/mob/actions/actions_slime.dmi' + background_icon_state = "bg_alien" + var/datum/species/jelly/stargazer/species + +/datum/action/innate/linked_speech/New(_species) + ..() + species = _species + +/datum/action/innate/linked_speech/Activate() + var/mob/living/carbon/human/H = owner + if(!species || !(H in species.linked_mobs)) + to_chat(H, "The link seems to have been severed...") + Remove(H) + return + + var/message = sanitize(input("Message:", "Slime Telepathy") as text|null) + + if(!species || !(H in species.linked_mobs)) + to_chat(H, "The link seems to have been severed...") + Remove(H) + return + + if(QDELETED(H) || H.stat == DEAD) + species.unlink_mob(H) + return + + if(message) + var/msg = "\[[species.slimelink_owner.real_name]'s Slime Link\] [H]: [message]" + log_directed_talk(H, species.slimelink_owner, msg, LOG_SAY, "slime link") + for(var/X in species.linked_mobs) + var/mob/living/M = X + if(QDELETED(M) || M.stat == DEAD) + species.unlink_mob(M) + continue + to_chat(M, msg) + + for(var/X in GLOB.dead_mob_list) + var/mob/M = X + var/link = FOLLOW_LINK(M, H) + to_chat(M, "[link] [msg]") + +/datum/action/innate/project_thought + name = "Send Thought" + desc = "Send a private psychic message to someone you can see." + button_icon_state = "send_mind" + icon_icon = 'icons/mob/actions/actions_slime.dmi' + background_icon_state = "bg_alien" + +/datum/action/innate/project_thought/Activate() + var/mob/living/carbon/human/H = owner + if(H.stat == DEAD) + return + if(!is_species(H, /datum/species/jelly/stargazer)) + return + CHECK_DNA_AND_SPECIES(H) + + var/list/options = list() + for(var/mob/living/Ms in oview(H)) + options += Ms + var/mob/living/M = input("Select who to send your message to:","Send thought to?",null) as null|mob in options + if(!M) + return + if(M.anti_magic_check(FALSE, FALSE, TRUE, 0)) + to_chat(H, "As you try to communicate with [M], you're suddenly stopped by a vision of a massive tinfoil wall that streches beyond visible range. It seems you've been foiled.") + return + var/msg = sanitize(input("Message:", "Telepathy") as text|null) + if(msg) + if(M.anti_magic_check(FALSE, FALSE, TRUE, 0)) + to_chat(H, "As you try to communicate with [M], you're suddenly stopped by a vision of a massive tinfoil wall that streches beyond visible range. It seems you've been foiled.") + return + log_directed_talk(H, M, msg, LOG_SAY, "slime telepathy") + to_chat(M, "You hear an alien voice in your head... [msg]") + to_chat(H, "You telepathically said: \"[msg]\" to [M]") + for(var/dead in GLOB.dead_mob_list) + if(!isobserver(dead)) + continue + var/follow_link_user = FOLLOW_LINK(dead, H) + var/follow_link_target = FOLLOW_LINK(dead, M) + to_chat(dead, "[follow_link_user] [H] Slime Telepathy --> [follow_link_target] [M] [msg]") + +/datum/action/innate/link_minds + name = "Link Minds" + desc = "Link someone's mind to your Slime Link, allowing them to communicate telepathically with other linked minds." + button_icon_state = "mindlink" + icon_icon = 'icons/mob/actions/actions_slime.dmi' + background_icon_state = "bg_alien" + var/datum/species/jelly/stargazer/species + +/datum/action/innate/link_minds/New(_species) + ..() + species = _species + +/datum/action/innate/link_minds/Activate() + var/mob/living/carbon/human/H = owner + if(!is_species(H, /datum/species/jelly/stargazer)) + return + CHECK_DNA_AND_SPECIES(H) + + if(!H.pulling || !isliving(H.pulling) || H.grab_state < GRAB_AGGRESSIVE) + to_chat(H, "You need to aggressively grab someone to link minds!") + return + + var/mob/living/target = H.pulling + + to_chat(H, "You begin linking [target]'s mind to yours...") + to_chat(target, "You feel a foreign presence within your mind...") + if(do_after(H, 60, target = target)) + if(H.pulling != target || H.grab_state < GRAB_AGGRESSIVE) + return + if(species.link_mob(target)) + to_chat(H, "You connect [target]'s mind to your slime link!") + else + to_chat(H, "You can't seem to link [target]'s mind...") + to_chat(target, "The foreign presence leaves your mind.") diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index df8d4d727a..5cdb051026 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -1,93 +1,93 @@ -/datum/species/lizard - // Reptilian humanoids with scaled skin and tails. - name = "Anthromorphic Lizard" - id = "lizard" - say_mod = "hisses" - default_color = "00FF00" - species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,LIPS,HORNCOLOR,WINGCOLOR) - inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_REPTILE) - mutant_bodyparts = list("tail_lizard", "snout", "spines", "horns", "frills", "body_markings", "legs", "taur", "deco_wings") - mutanttongue = /obj/item/organ/tongue/lizard - mutanttail = /obj/item/organ/tail/lizard - coldmod = 1.5 - heatmod = 0.67 - default_features = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round", - "horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", - "legs" = "Digitigrade", "taur" = "None", "deco_wings" = "None") - attack_verb = "slash" - attack_sound = 'sound/weapons/slash.ogg' - miss_sound = 'sound/weapons/slashmiss.ogg' - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/lizard - gib_types = list(/obj/effect/gibspawner/lizard, /obj/effect/gibspawner/lizard/bodypartless) - skinned_type = /obj/item/stack/sheet/animalhide/lizard - exotic_bloodtype = "L" - disliked_food = GRAIN | DAIRY - liked_food = GROSS | MEAT - -/datum/species/lizard/after_equip_job(datum/job/J, mob/living/carbon/human/H) - H.grant_language(/datum/language/draconic) - -/datum/species/lizard/random_name(gender,unique,lastname) - if(unique) - return random_unique_lizard_name(gender) - - var/randname = lizard_name(gender) - - if(lastname) - randname += " [lastname]" - - return randname - -/datum/species/lizard/qualifies_for_rank(rank, list/features) - return TRUE - -//I wag in death -/datum/species/lizard/spec_death(gibbed, mob/living/carbon/human/H) - if(H) - stop_wagging_tail(H) - -/datum/species/lizard/spec_stun(mob/living/carbon/human/H,amount) - if(H) - stop_wagging_tail(H) - . = ..() - -/datum/species/lizard/can_wag_tail(mob/living/carbon/human/H) - return ("tail_lizard" in mutant_bodyparts) || ("waggingtail_lizard" in mutant_bodyparts) - -/datum/species/lizard/is_wagging_tail(mob/living/carbon/human/H) - return ("waggingtail_lizard" in mutant_bodyparts) - -/datum/species/lizard/start_wagging_tail(mob/living/carbon/human/H) - if("tail_lizard" in mutant_bodyparts) - mutant_bodyparts -= "tail_lizard" - mutant_bodyparts -= "spines" - mutant_bodyparts |= "waggingtail_lizard" - mutant_bodyparts |= "waggingspines" - H.update_body() - -/datum/species/lizard/stop_wagging_tail(mob/living/carbon/human/H) - if("waggingtail_lizard" in mutant_bodyparts) - mutant_bodyparts -= "waggingtail_lizard" - mutant_bodyparts -= "waggingspines" - mutant_bodyparts |= "tail_lizard" - mutant_bodyparts |= "spines" - H.update_body() - -/* - Lizard subspecies: ASHWALKERS -*/ -/datum/species/lizard/ashwalker - name = "Ash Walker" - id = "ashlizard" - limbs_id = "lizard" - species_traits = list(MUTCOLORS,EYECOLOR,LIPS,DIGITIGRADE) - inherent_traits = list(TRAIT_CHUNKYFINGERS) - mutantlungs = /obj/item/organ/lungs/ashwalker - burnmod = 0.9 - brutemod = 0.9 - -/datum/species/lizard/ashwalker/on_species_gain(mob/living/carbon/human/C, datum/species/old_species) - if((C.dna.features["spines"] != "None" ) && (C.dna.features["tail_lizard"] == "None")) //tbh, it's kinda ugly for them not to have a tail yet have floating spines - C.dna.features["tail_lizard"] = "Smooth" - C.update_body() - return ..() +/datum/species/lizard + // Reptilian humanoids with scaled skin and tails. + name = "Anthromorphic Lizard" + id = "lizard" + say_mod = "hisses" + default_color = "00FF00" + species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,LIPS,HORNCOLOR,WINGCOLOR) + inherent_biotypes = list(MOB_ORGANIC, MOB_HUMANOID, MOB_REPTILE) + mutant_bodyparts = list("tail_lizard", "snout", "spines", "horns", "frills", "body_markings", "legs", "taur", "deco_wings") + mutanttongue = /obj/item/organ/tongue/lizard + mutanttail = /obj/item/organ/tail/lizard + coldmod = 1.5 + heatmod = 0.67 + default_features = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round", + "horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None", + "legs" = "Digitigrade", "taur" = "None", "deco_wings" = "None") + attack_verb = "slash" + attack_sound = 'sound/weapons/slash.ogg' + miss_sound = 'sound/weapons/slashmiss.ogg' + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/lizard + gib_types = list(/obj/effect/gibspawner/lizard, /obj/effect/gibspawner/lizard/bodypartless) + skinned_type = /obj/item/stack/sheet/animalhide/lizard + exotic_bloodtype = "L" + disliked_food = GRAIN | DAIRY + liked_food = GROSS | MEAT + +/datum/species/lizard/after_equip_job(datum/job/J, mob/living/carbon/human/H) + H.grant_language(/datum/language/draconic) + +/datum/species/lizard/random_name(gender,unique,lastname) + if(unique) + return random_unique_lizard_name(gender) + + var/randname = lizard_name(gender) + + if(lastname) + randname += " [lastname]" + + return randname + +/datum/species/lizard/qualifies_for_rank(rank, list/features) + return TRUE + +//I wag in death +/datum/species/lizard/spec_death(gibbed, mob/living/carbon/human/H) + if(H) + stop_wagging_tail(H) + +/datum/species/lizard/spec_stun(mob/living/carbon/human/H,amount) + if(H) + stop_wagging_tail(H) + . = ..() + +/datum/species/lizard/can_wag_tail(mob/living/carbon/human/H) + return ("tail_lizard" in mutant_bodyparts) || ("waggingtail_lizard" in mutant_bodyparts) + +/datum/species/lizard/is_wagging_tail(mob/living/carbon/human/H) + return ("waggingtail_lizard" in mutant_bodyparts) + +/datum/species/lizard/start_wagging_tail(mob/living/carbon/human/H) + if("tail_lizard" in mutant_bodyparts) + mutant_bodyparts -= "tail_lizard" + mutant_bodyparts -= "spines" + mutant_bodyparts |= "waggingtail_lizard" + mutant_bodyparts |= "waggingspines" + H.update_body() + +/datum/species/lizard/stop_wagging_tail(mob/living/carbon/human/H) + if("waggingtail_lizard" in mutant_bodyparts) + mutant_bodyparts -= "waggingtail_lizard" + mutant_bodyparts -= "waggingspines" + mutant_bodyparts |= "tail_lizard" + mutant_bodyparts |= "spines" + H.update_body() + +/* + Lizard subspecies: ASHWALKERS +*/ +/datum/species/lizard/ashwalker + name = "Ash Walker" + id = "ashlizard" + limbs_id = "lizard" + species_traits = list(MUTCOLORS,EYECOLOR,LIPS,DIGITIGRADE) + inherent_traits = list(TRAIT_CHUNKYFINGERS) + mutantlungs = /obj/item/organ/lungs/ashwalker + burnmod = 0.9 + brutemod = 0.9 + +/datum/species/lizard/ashwalker/on_species_gain(mob/living/carbon/human/C, datum/species/old_species) + if((C.dna.features["spines"] != "None" ) && (C.dna.features["tail_lizard"] == "None")) //tbh, it's kinda ugly for them not to have a tail yet have floating spines + C.dna.features["tail_lizard"] = "Smooth" + C.update_body() + return ..() diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index 06f456e004..5a7710fe29 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -1,155 +1,155 @@ -/datum/species/plasmaman - name = "Plasmaman" - id = "plasmaman" - say_mod = "rattles" - sexes = 0 - meat = /obj/item/stack/sheet/mineral/plasma - species_traits = list(NOBLOOD,NOTRANSSTING,NOGENITALS) - inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_NOHUNGER,TRAIT_CALCIUM_HEALER) - inherent_biotypes = list(MOB_INORGANIC, MOB_HUMANOID) - mutantlungs = /obj/item/organ/lungs/plasmaman - mutanttongue = /obj/item/organ/tongue/bone/plasmaman - mutantliver = /obj/item/organ/liver/plasmaman - mutantstomach = /obj/item/organ/stomach/plasmaman - dangerous_existence = 1 //So so much - blacklisted = 1 //See above - burnmod = 1.5 - heatmod = 1.5 - breathid = "tox" - damage_overlay_type = ""//let's not show bloody wounds or burns over bones. - var/internal_fire = FALSE //If the bones themselves are burning clothes won't help you much - disliked_food = FRUIT - liked_food = VEGETABLES - -/datum/species/plasmaman/spec_life(mob/living/carbon/human/H) - var/datum/gas_mixture/environment = H.loc.return_air() - var/atmos_sealed = FALSE - if (H.wear_suit && H.head && istype(H.wear_suit, /obj/item/clothing) && istype(H.head, /obj/item/clothing)) - var/obj/item/clothing/CS = H.wear_suit - var/obj/item/clothing/CH = H.head - if (CS.clothing_flags & CH.clothing_flags & STOPSPRESSUREDAMAGE) - atmos_sealed = TRUE - if((!istype(H.w_uniform, /obj/item/clothing/under/plasmaman) || !istype(H.head, /obj/item/clothing/head/helmet/space/plasmaman)) && !atmos_sealed) - if(environment) - if(environment.total_moles()) - if(environment.gases[/datum/gas/oxygen] && (environment.gases[/datum/gas/oxygen]) >= 1) //Same threshhold that extinguishes fire - H.adjust_fire_stacks(0.5) - if(!H.on_fire && H.fire_stacks > 0) - H.visible_message("[H]'s body reacts with the atmosphere and bursts into flames!","Your body reacts with the atmosphere and bursts into flame!") - H.IgniteMob() - internal_fire = TRUE - else - if(H.fire_stacks) - var/obj/item/clothing/under/plasmaman/P = H.w_uniform - if(istype(P)) - P.Extinguish(H) - internal_fire = FALSE - else - internal_fire = FALSE - H.update_fire() - -/datum/species/plasmaman/handle_fire(mob/living/carbon/human/H, no_protection) - if(internal_fire) - no_protection = TRUE - ..() - -/datum/species/plasmaman/before_equip_job(datum/job/J, mob/living/carbon/human/H, visualsOnly = FALSE) - var/current_job = J.title - var/datum/outfit/plasmaman/O = new /datum/outfit/plasmaman - switch(current_job) - if("Chaplain") - O = new /datum/outfit/plasmaman/chaplain - - if("Curator") - O = new /datum/outfit/plasmaman/curator - - if("Janitor") - O = new /datum/outfit/plasmaman/janitor - - if("Botanist") - O = new /datum/outfit/plasmaman/botany - - if("Bartender", "Lawyer") - O = new /datum/outfit/plasmaman/bar - - if("Cook") - O = new /datum/outfit/plasmaman/chef - - if("Security Officer") - O = new /datum/outfit/plasmaman/security - - if("Detective") - O = new /datum/outfit/plasmaman/detective - - if("Warden") - O = new /datum/outfit/plasmaman/warden - - if("Cargo Technician", "Quartermaster") - O = new /datum/outfit/plasmaman/cargo - - if("Shaft Miner") - O = new /datum/outfit/plasmaman/mining - - if("Medical Doctor") - O = new /datum/outfit/plasmaman/medical - - if("Chemist") - O = new /datum/outfit/plasmaman/chemist - - if("Geneticist") - O = new /datum/outfit/plasmaman/genetics - - if("Roboticist") - O = new /datum/outfit/plasmaman/robotics - - if("Virologist") - O = new /datum/outfit/plasmaman/viro - - if("Scientist") - O = new /datum/outfit/plasmaman/science - - if("Station Engineer") - O = new /datum/outfit/plasmaman/engineering - - if("Atmospheric Technician") - O = new /datum/outfit/plasmaman/atmospherics - - if("Captain") - O = new /datum/outfit/plasmaman/captain - - if("Head of Personnel") - O = new /datum/outfit/plasmaman/hop - - if("Head of Security") - O = new /datum/outfit/plasmaman/hos - - if("Chief Engineer") - O = new /datum/outfit/plasmaman/ce - - if("Chief Medical Officer") - O = new /datum/outfit/plasmaman/cmo - - if("Research Director") - O = new /datum/outfit/plasmaman/rd - - if("Mime") - O = new /datum/outfit/plasmaman/mime - - if("Clown") - O = new /datum/outfit/plasmaman/clown - - H.equipOutfit(O, visualsOnly) - H.internal = H.get_item_for_held_index(2) - H.update_internals_hud_icon(1) - return 0 - -/datum/species/plasmaman/random_name(gender,unique,lastname) - if(unique) - return random_unique_plasmaman_name() - - var/randname = plasmaman_name() - - if(lastname) - randname += " [lastname]" - - return randname +/datum/species/plasmaman + name = "Plasmaman" + id = "plasmaman" + say_mod = "rattles" + sexes = 0 + meat = /obj/item/stack/sheet/mineral/plasma + species_traits = list(NOBLOOD,NOTRANSSTING,NOGENITALS) + inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_NOHUNGER,TRAIT_CALCIUM_HEALER) + inherent_biotypes = list(MOB_INORGANIC, MOB_HUMANOID) + mutantlungs = /obj/item/organ/lungs/plasmaman + mutanttongue = /obj/item/organ/tongue/bone/plasmaman + mutantliver = /obj/item/organ/liver/plasmaman + mutantstomach = /obj/item/organ/stomach/plasmaman + dangerous_existence = 1 //So so much + blacklisted = 1 //See above + burnmod = 1.5 + heatmod = 1.5 + breathid = "tox" + damage_overlay_type = ""//let's not show bloody wounds or burns over bones. + var/internal_fire = FALSE //If the bones themselves are burning clothes won't help you much + disliked_food = FRUIT + liked_food = VEGETABLES + +/datum/species/plasmaman/spec_life(mob/living/carbon/human/H) + var/datum/gas_mixture/environment = H.loc.return_air() + var/atmos_sealed = FALSE + if (H.wear_suit && H.head && istype(H.wear_suit, /obj/item/clothing) && istype(H.head, /obj/item/clothing)) + var/obj/item/clothing/CS = H.wear_suit + var/obj/item/clothing/CH = H.head + if (CS.clothing_flags & CH.clothing_flags & STOPSPRESSUREDAMAGE) + atmos_sealed = TRUE + if((!istype(H.w_uniform, /obj/item/clothing/under/plasmaman) || !istype(H.head, /obj/item/clothing/head/helmet/space/plasmaman)) && !atmos_sealed) + if(environment) + if(environment.total_moles()) + if(environment.gases[/datum/gas/oxygen] && (environment.gases[/datum/gas/oxygen]) >= 1) //Same threshhold that extinguishes fire + H.adjust_fire_stacks(0.5) + if(!H.on_fire && H.fire_stacks > 0) + H.visible_message("[H]'s body reacts with the atmosphere and bursts into flames!","Your body reacts with the atmosphere and bursts into flame!") + H.IgniteMob() + internal_fire = TRUE + else + if(H.fire_stacks) + var/obj/item/clothing/under/plasmaman/P = H.w_uniform + if(istype(P)) + P.Extinguish(H) + internal_fire = FALSE + else + internal_fire = FALSE + H.update_fire() + +/datum/species/plasmaman/handle_fire(mob/living/carbon/human/H, no_protection) + if(internal_fire) + no_protection = TRUE + ..() + +/datum/species/plasmaman/before_equip_job(datum/job/J, mob/living/carbon/human/H, visualsOnly = FALSE) + var/current_job = J.title + var/datum/outfit/plasmaman/O = new /datum/outfit/plasmaman + switch(current_job) + if("Chaplain") + O = new /datum/outfit/plasmaman/chaplain + + if("Curator") + O = new /datum/outfit/plasmaman/curator + + if("Janitor") + O = new /datum/outfit/plasmaman/janitor + + if("Botanist") + O = new /datum/outfit/plasmaman/botany + + if("Bartender", "Lawyer") + O = new /datum/outfit/plasmaman/bar + + if("Cook") + O = new /datum/outfit/plasmaman/chef + + if("Security Officer") + O = new /datum/outfit/plasmaman/security + + if("Detective") + O = new /datum/outfit/plasmaman/detective + + if("Warden") + O = new /datum/outfit/plasmaman/warden + + if("Cargo Technician", "Quartermaster") + O = new /datum/outfit/plasmaman/cargo + + if("Shaft Miner") + O = new /datum/outfit/plasmaman/mining + + if("Medical Doctor") + O = new /datum/outfit/plasmaman/medical + + if("Chemist") + O = new /datum/outfit/plasmaman/chemist + + if("Geneticist") + O = new /datum/outfit/plasmaman/genetics + + if("Roboticist") + O = new /datum/outfit/plasmaman/robotics + + if("Virologist") + O = new /datum/outfit/plasmaman/viro + + if("Scientist") + O = new /datum/outfit/plasmaman/science + + if("Station Engineer") + O = new /datum/outfit/plasmaman/engineering + + if("Atmospheric Technician") + O = new /datum/outfit/plasmaman/atmospherics + + if("Captain") + O = new /datum/outfit/plasmaman/captain + + if("Head of Personnel") + O = new /datum/outfit/plasmaman/hop + + if("Head of Security") + O = new /datum/outfit/plasmaman/hos + + if("Chief Engineer") + O = new /datum/outfit/plasmaman/ce + + if("Chief Medical Officer") + O = new /datum/outfit/plasmaman/cmo + + if("Research Director") + O = new /datum/outfit/plasmaman/rd + + if("Mime") + O = new /datum/outfit/plasmaman/mime + + if("Clown") + O = new /datum/outfit/plasmaman/clown + + H.equipOutfit(O, visualsOnly) + H.internal = H.get_item_for_held_index(2) + H.update_internals_hud_icon(1) + return 0 + +/datum/species/plasmaman/random_name(gender,unique,lastname) + if(unique) + return random_unique_plasmaman_name() + + var/randname = plasmaman_name() + + if(lastname) + randname += " [lastname]" + + return randname diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index 52ae32b23c..fa2fd1c858 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -1,222 +1,222 @@ -#define HEART_RESPAWN_THRESHHOLD 40 -#define HEART_SPECIAL_SHADOWIFY 2 - -/datum/species/shadow - // Humans cursed to stay in the darkness, lest their life forces drain. They regain health in shadow and die in light. - name = "???" - id = "shadow" - sexes = 0 - blacklisted = 1 - ignored_by = list(/mob/living/simple_animal/hostile/faithless) - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/shadow - species_traits = list(NOBLOOD,NOEYES) - inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_NOBREATH) - - dangerous_existence = 1 - mutanteyes = /obj/item/organ/eyes/night_vision - - -/datum/species/shadow/spec_life(mob/living/carbon/human/H) - var/turf/T = H.loc - if(istype(T)) - var/light_amount = T.get_lumcount() - - if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) //if there's enough light, start dying - H.take_overall_damage(1,1) - else if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark - H.heal_overall_damage(1,1) - -/datum/species/shadow/check_roundstart_eligible() - if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) - return TRUE - return ..() - -/datum/species/shadow/nightmare - name = "Nightmare" - id = "nightmare" - limbs_id = "shadow" - burnmod = 1.5 - blacklisted = TRUE - no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE) - species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYES,NOGENITALS,NOAROUSAL) - inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER) - mutanteyes = /obj/item/organ/eyes/night_vision/nightmare - mutant_organs = list(/obj/item/organ/heart/nightmare) - mutant_brain = /obj/item/organ/brain/nightmare - - var/info_text = "You are a Nightmare. The ability shadow walk allows unlimited, unrestricted movement in the dark while activated. \ - Your light eater will destroy any light producing objects you attack, as well as destroy any lights a living creature may be holding. You will automatically dodge gunfire and melee attacks when on a dark tile. If killed, you will eventually revive if left in darkness." - -/datum/species/shadow/nightmare/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - to_chat(C, "[info_text]") - - C.fully_replace_character_name("[pick(GLOB.nightmare_names)]") - -/datum/species/shadow/nightmare/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H) - var/turf/T = H.loc - if(istype(T)) - var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - H.visible_message("[H] dances in the shadows, evading [P]!") - playsound(T, "bullet_miss", 75, 1) - return -1 - return 0 - -/datum/species/shadow/nightmare/check_roundstart_eligible() - return FALSE - -//Organs - -/obj/item/organ/brain/nightmare - name = "tumorous mass" - desc = "A fleshy growth that was dug out of the skull of a Nightmare." - icon_state = "brain-x-d" - var/obj/effect/proc_holder/spell/targeted/shadowwalk/shadowwalk - -/obj/item/organ/brain/nightmare/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE) - ..() - if(M.dna.species.id != "nightmare") - M.set_species(/datum/species/shadow/nightmare) - visible_message("[M] thrashes as [src] takes root in [M.p_their()] body!") - var/obj/effect/proc_holder/spell/targeted/shadowwalk/SW = new - M.AddSpell(SW) - shadowwalk = SW - - -/obj/item/organ/brain/nightmare/Remove(mob/living/carbon/M, special = 0) - if(shadowwalk) - M.RemoveSpell(shadowwalk) - ..() - - -/obj/item/organ/heart/nightmare - name = "heart of darkness" - desc = "An alien organ that twists and writhes when exposed to light." - icon = 'icons/obj/surgery.dmi' - icon_state = "demon_heart-on" - color = "#1C1C1C" - var/respawn_progress = 0 - var/obj/item/light_eater/blade - decay_factor = 0 - - -/obj/item/organ/heart/nightmare/attack(mob/M, mob/living/carbon/user, obj/target) - if(M != user) - return ..() - user.visible_message("[user] raises [src] to [user.p_their()] mouth and tears into it with [user.p_their()] teeth!", \ - "[src] feels unnaturally cold in your hands. You raise [src] your mouth and devour it!") - playsound(user, 'sound/magic/demon_consume.ogg', 50, 1) - - - user.visible_message("Blood erupts from [user]'s arm as it reforms into a weapon!", \ - "Icy blood pumps through your veins as your arm reforms itself!") - user.temporarilyRemoveItemFromInventory(src, TRUE) - Insert(user) - -/obj/item/organ/heart/nightmare/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE) - ..() - if(special != HEART_SPECIAL_SHADOWIFY) - blade = new/obj/item/light_eater - M.put_in_hands(blade) - -/obj/item/organ/heart/nightmare/Remove(mob/living/carbon/M, special = 0) - respawn_progress = 0 - if(blade && special != HEART_SPECIAL_SHADOWIFY) - M.visible_message("\The [blade] disintegrates!") - QDEL_NULL(blade) - ..() - -/obj/item/organ/heart/nightmare/Stop() - return 0 - -/obj/item/organ/heart/nightmare/update_icon() - return //always beating visually - -/obj/item/organ/heart/nightmare/on_death() - if(!owner) - return - var/turf/T = get_turf(owner) - if(istype(T)) - var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - respawn_progress++ - playsound(owner,'sound/effects/singlebeat.ogg',40,1) - if(respawn_progress >= HEART_RESPAWN_THRESHHOLD) - owner.revive(full_heal = TRUE) - if(!(owner.dna.species.id == "shadow" || owner.dna.species.id == "nightmare")) - var/mob/living/carbon/old_owner = owner - Remove(owner, HEART_SPECIAL_SHADOWIFY) - old_owner.set_species(/datum/species/shadow) - Insert(old_owner, HEART_SPECIAL_SHADOWIFY) - to_chat(owner, "You feel the shadows invade your skin, leaping into the center of your chest! You're alive!") - SEND_SOUND(owner, sound('sound/effects/ghost.ogg')) - owner.visible_message("[owner] staggers to [owner.p_their()] feet!") - playsound(owner, 'sound/hallucinations/far_noise.ogg', 50, 1) - respawn_progress = 0 - -//Weapon - -/obj/item/light_eater - name = "light eater" //as opposed to heavy eater - icon_state = "arm_blade" - item_state = "arm_blade" - force = 25 - armour_penetration = 35 - lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' - righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' - item_flags = ABSTRACT | DROPDEL - w_class = WEIGHT_CLASS_HUGE - sharpness = IS_SHARP - total_mass = TOTAL_MASS_HAND_REPLACEMENT - -/obj/item/light_eater/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) - AddComponent(/datum/component/butchering, 80, 70) - -/obj/item/light_eater/afterattack(atom/movable/AM, mob/user, proximity) - . = ..() - if(!proximity) - return - if(isopenturf(AM)) - var/turf/open/T = AM - if(T.light_range && !isspaceturf(T)) //no fairy grass or light tile can escape the fury of the darkness. - to_chat(user, "You scrape away [T] with your [name] and snuff out its lights.") - T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) - else if(isliving(AM)) - var/mob/living/L = AM - if(iscyborg(AM)) - var/mob/living/silicon/robot/borg = AM - if(borg.lamp_intensity) - borg.update_headlamp(TRUE, INFINITY) - to_chat(borg, "Your headlamp is fried! You'll need a human to help replace it.") - for(var/obj/item/assembly/flash/cyborg/F in borg.held_items) - if(!F.crit_fail) - F.burn_out() - else - for(var/obj/item/O in AM) - if(O.light_range && O.light_power) - disintegrate(O) - if(L.pulling && L.pulling.light_range && isitem(L.pulling)) - disintegrate(L.pulling) - else if(isitem(AM)) - var/obj/item/I = AM - if(I.light_range && I.light_power) - disintegrate(I) - -/obj/item/light_eater/proc/disintegrate(obj/item/O) - if(istype(O, /obj/item/pda)) - var/obj/item/pda/PDA = O - PDA.set_light(0) - PDA.fon = FALSE - PDA.f_lum = 0 - PDA.update_icon() - visible_message("The light in [PDA] shorts out!") - else - visible_message("[O] is disintegrated by [src]!") - O.burn() - playsound(src, 'sound/items/welder.ogg', 50, 1) - -#undef HEART_SPECIAL_SHADOWIFY -#undef HEART_RESPAWN_THRESHHOLD +#define HEART_RESPAWN_THRESHHOLD 40 +#define HEART_SPECIAL_SHADOWIFY 2 + +/datum/species/shadow + // Humans cursed to stay in the darkness, lest their life forces drain. They regain health in shadow and die in light. + name = "???" + id = "shadow" + sexes = 0 + blacklisted = 1 + ignored_by = list(/mob/living/simple_animal/hostile/faithless) + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/shadow + species_traits = list(NOBLOOD,NOEYES) + inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_NOBREATH) + + dangerous_existence = 1 + mutanteyes = /obj/item/organ/eyes/night_vision + + +/datum/species/shadow/spec_life(mob/living/carbon/human/H) + var/turf/T = H.loc + if(istype(T)) + var/light_amount = T.get_lumcount() + + if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) //if there's enough light, start dying + H.take_overall_damage(1,1) + else if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark + H.heal_overall_damage(1,1) + +/datum/species/shadow/check_roundstart_eligible() + if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) + return TRUE + return ..() + +/datum/species/shadow/nightmare + name = "Nightmare" + id = "nightmare" + limbs_id = "shadow" + burnmod = 1.5 + blacklisted = TRUE + no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE) + species_traits = list(NOBLOOD,NO_UNDERWEAR,NO_DNA_COPY,NOTRANSSTING,NOEYES,NOGENITALS,NOAROUSAL) + inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_NOBREATH,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_CHUNKYFINGERS,TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER) + mutanteyes = /obj/item/organ/eyes/night_vision/nightmare + mutant_organs = list(/obj/item/organ/heart/nightmare) + mutant_brain = /obj/item/organ/brain/nightmare + + var/info_text = "You are a Nightmare. The ability shadow walk allows unlimited, unrestricted movement in the dark while activated. \ + Your light eater will destroy any light producing objects you attack, as well as destroy any lights a living creature may be holding. You will automatically dodge gunfire and melee attacks when on a dark tile. If killed, you will eventually revive if left in darkness." + +/datum/species/shadow/nightmare/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + to_chat(C, "[info_text]") + + C.fully_replace_character_name("[pick(GLOB.nightmare_names)]") + +/datum/species/shadow/nightmare/bullet_act(obj/item/projectile/P, mob/living/carbon/human/H) + var/turf/T = H.loc + if(istype(T)) + var/light_amount = T.get_lumcount() + if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) + H.visible_message("[H] dances in the shadows, evading [P]!") + playsound(T, "bullet_miss", 75, 1) + return -1 + return 0 + +/datum/species/shadow/nightmare/check_roundstart_eligible() + return FALSE + +//Organs + +/obj/item/organ/brain/nightmare + name = "tumorous mass" + desc = "A fleshy growth that was dug out of the skull of a Nightmare." + icon_state = "brain-x-d" + var/obj/effect/proc_holder/spell/targeted/shadowwalk/shadowwalk + +/obj/item/organ/brain/nightmare/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE) + ..() + if(M.dna.species.id != "nightmare") + M.set_species(/datum/species/shadow/nightmare) + visible_message("[M] thrashes as [src] takes root in [M.p_their()] body!") + var/obj/effect/proc_holder/spell/targeted/shadowwalk/SW = new + M.AddSpell(SW) + shadowwalk = SW + + +/obj/item/organ/brain/nightmare/Remove(mob/living/carbon/M, special = 0) + if(shadowwalk) + M.RemoveSpell(shadowwalk) + ..() + + +/obj/item/organ/heart/nightmare + name = "heart of darkness" + desc = "An alien organ that twists and writhes when exposed to light." + icon = 'icons/obj/surgery.dmi' + icon_state = "demon_heart-on" + color = "#1C1C1C" + var/respawn_progress = 0 + var/obj/item/light_eater/blade + decay_factor = 0 + + +/obj/item/organ/heart/nightmare/attack(mob/M, mob/living/carbon/user, obj/target) + if(M != user) + return ..() + user.visible_message("[user] raises [src] to [user.p_their()] mouth and tears into it with [user.p_their()] teeth!", \ + "[src] feels unnaturally cold in your hands. You raise [src] your mouth and devour it!") + playsound(user, 'sound/magic/demon_consume.ogg', 50, 1) + + + user.visible_message("Blood erupts from [user]'s arm as it reforms into a weapon!", \ + "Icy blood pumps through your veins as your arm reforms itself!") + user.temporarilyRemoveItemFromInventory(src, TRUE) + Insert(user) + +/obj/item/organ/heart/nightmare/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE) + ..() + if(special != HEART_SPECIAL_SHADOWIFY) + blade = new/obj/item/light_eater + M.put_in_hands(blade) + +/obj/item/organ/heart/nightmare/Remove(mob/living/carbon/M, special = 0) + respawn_progress = 0 + if(blade && special != HEART_SPECIAL_SHADOWIFY) + M.visible_message("\The [blade] disintegrates!") + QDEL_NULL(blade) + ..() + +/obj/item/organ/heart/nightmare/Stop() + return 0 + +/obj/item/organ/heart/nightmare/update_icon() + return //always beating visually + +/obj/item/organ/heart/nightmare/on_death() + if(!owner) + return + var/turf/T = get_turf(owner) + if(istype(T)) + var/light_amount = T.get_lumcount() + if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) + respawn_progress++ + playsound(owner,'sound/effects/singlebeat.ogg',40,1) + if(respawn_progress >= HEART_RESPAWN_THRESHHOLD) + owner.revive(full_heal = TRUE) + if(!(owner.dna.species.id == "shadow" || owner.dna.species.id == "nightmare")) + var/mob/living/carbon/old_owner = owner + Remove(owner, HEART_SPECIAL_SHADOWIFY) + old_owner.set_species(/datum/species/shadow) + Insert(old_owner, HEART_SPECIAL_SHADOWIFY) + to_chat(owner, "You feel the shadows invade your skin, leaping into the center of your chest! You're alive!") + SEND_SOUND(owner, sound('sound/effects/ghost.ogg')) + owner.visible_message("[owner] staggers to [owner.p_their()] feet!") + playsound(owner, 'sound/hallucinations/far_noise.ogg', 50, 1) + respawn_progress = 0 + +//Weapon + +/obj/item/light_eater + name = "light eater" //as opposed to heavy eater + icon_state = "arm_blade" + item_state = "arm_blade" + force = 25 + armour_penetration = 35 + lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi' + righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi' + item_flags = ABSTRACT | DROPDEL + w_class = WEIGHT_CLASS_HUGE + sharpness = IS_SHARP + total_mass = TOTAL_MASS_HAND_REPLACEMENT + +/obj/item/light_eater/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT) + AddComponent(/datum/component/butchering, 80, 70) + +/obj/item/light_eater/afterattack(atom/movable/AM, mob/user, proximity) + . = ..() + if(!proximity) + return + if(isopenturf(AM)) + var/turf/open/T = AM + if(T.light_range && !isspaceturf(T)) //no fairy grass or light tile can escape the fury of the darkness. + to_chat(user, "You scrape away [T] with your [name] and snuff out its lights.") + T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) + else if(isliving(AM)) + var/mob/living/L = AM + if(iscyborg(AM)) + var/mob/living/silicon/robot/borg = AM + if(borg.lamp_intensity) + borg.update_headlamp(TRUE, INFINITY) + to_chat(borg, "Your headlamp is fried! You'll need a human to help replace it.") + for(var/obj/item/assembly/flash/cyborg/F in borg.held_items) + if(!F.crit_fail) + F.burn_out() + else + for(var/obj/item/O in AM) + if(O.light_range && O.light_power) + disintegrate(O) + if(L.pulling && L.pulling.light_range && isitem(L.pulling)) + disintegrate(L.pulling) + else if(isitem(AM)) + var/obj/item/I = AM + if(I.light_range && I.light_power) + disintegrate(I) + +/obj/item/light_eater/proc/disintegrate(obj/item/O) + if(istype(O, /obj/item/pda)) + var/obj/item/pda/PDA = O + PDA.set_light(0) + PDA.fon = FALSE + PDA.f_lum = 0 + PDA.update_icon() + visible_message("The light in [PDA] shorts out!") + else + visible_message("[O] is disintegrated by [src]!") + O.burn() + playsound(src, 'sound/items/welder.ogg', 50, 1) + +#undef HEART_SPECIAL_SHADOWIFY +#undef HEART_RESPAWN_THRESHHOLD diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm index 6a4c7a4463..0c4007555c 100644 --- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm +++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm @@ -1,30 +1,30 @@ -/datum/species/skeleton - // 2spooky - name = "Spooky Scary Skeleton" - id = "skeleton" - say_mod = "rattles" - blacklisted = 0 - sexes = 0 - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/skeleton - species_traits = list(NOBLOOD,NOGENITALS,NOAROUSAL) - inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_FAKEDEATH, TRAIT_CALCIUM_HEALER) - inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID) - mutanttongue = /obj/item/organ/tongue/bone - damage_overlay_type = ""//let's not show bloody wounds or burns over bones. - disliked_food = NONE - liked_food = GROSS | MEAT | RAW | DAIRY - -/datum/species/skeleton/check_roundstart_eligible() - if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) - return TRUE - return ..() - -/datum/species/skeleton/space - name = "Spooky Spacey Skeleton" - id = "spaceskeleton" - limbs_id = "skeleton" - blacklisted = 1 - inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_FAKEDEATH, TRAIT_CALCIUM_HEALER) - -/datum/species/skeleton/space/check_roundstart_eligible() +/datum/species/skeleton + // 2spooky + name = "Spooky Scary Skeleton" + id = "skeleton" + say_mod = "rattles" + blacklisted = 0 + sexes = 0 + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/skeleton + species_traits = list(NOBLOOD,NOGENITALS,NOAROUSAL) + inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_FAKEDEATH, TRAIT_CALCIUM_HEALER) + inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID) + mutanttongue = /obj/item/organ/tongue/bone + damage_overlay_type = ""//let's not show bloody wounds or burns over bones. + disliked_food = NONE + liked_food = GROSS | MEAT | RAW | DAIRY + +/datum/species/skeleton/check_roundstart_eligible() + if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) + return TRUE + return ..() + +/datum/species/skeleton/space + name = "Spooky Spacey Skeleton" + id = "spaceskeleton" + limbs_id = "skeleton" + blacklisted = 1 + inherent_traits = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_FAKEDEATH, TRAIT_CALCIUM_HEALER) + +/datum/species/skeleton/space/check_roundstart_eligible() return FALSE \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/species_types/synths.dm b/code/modules/mob/living/carbon/human/species_types/synths.dm index 0335e08922..0957a9e807 100644 --- a/code/modules/mob/living/carbon/human/species_types/synths.dm +++ b/code/modules/mob/living/carbon/human/species_types/synths.dm @@ -1,127 +1,127 @@ -/datum/species/synth - name = "Synthetic" //inherited from the real species, for health scanners and things - id = "synth" - say_mod = "beep boops" //inherited from a user's real species - sexes = 0 - species_traits = list(NOTRANSSTING,NOGENITALS,NOAROUSAL) //all of these + whatever we inherit from the real species - inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER,TRAIT_NOBREATH) - inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID) - dangerous_existence = 1 - blacklisted = 1 - meat = null - gib_types = /obj/effect/gibspawner/robot - damage_overlay_type = "synth" - limbs_id = "synth" - var/list/initial_species_traits = list(NOTRANSSTING) //for getting these values back for assume_disguise() - var/list/initial_inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER,TRAIT_NOBREATH) - var/disguise_fail_health = 75 //When their health gets to this level their synthflesh partially falls off - var/datum/species/fake_species = null //a species to do most of our work for us, unless we're damaged - -/datum/species/synth/military - name = "Military Synth" - id = "military_synth" - armor = 25 - punchdamagelow = 10 - punchdamagehigh = 19 - punchstunthreshold = 14 //about 50% chance to stun - disguise_fail_health = 50 - -/datum/species/synth/on_species_gain(mob/living/carbon/human/H, datum/species/old_species) - ..() - assume_disguise(old_species, H) - RegisterSignal(H, COMSIG_MOB_SAY, .proc/handle_speech) - -/datum/species/synth/on_species_loss(mob/living/carbon/human/H) - . = ..() - UnregisterSignal(H, COMSIG_MOB_SAY) - -/datum/species/synth/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) - if(chem.type == /datum/reagent/medicine/synthflesh) - chem.reaction_mob(H, TOUCH, 2 ,0) //heal a little - H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM) - return 1 - else - return ..() - - -/datum/species/synth/proc/assume_disguise(datum/species/S, mob/living/carbon/human/H) - if(S && !istype(S, type)) - name = S.name - say_mod = S.say_mod - sexes = S.sexes - species_traits = initial_species_traits.Copy() - inherent_traits = initial_inherent_traits.Copy() - species_traits |= S.species_traits - inherent_traits |= S.inherent_traits - attack_verb = S.attack_verb - attack_sound = S.attack_sound - miss_sound = S.miss_sound - meat = S.meat - mutant_bodyparts = S.mutant_bodyparts.Copy() - mutant_organs = S.mutant_organs.Copy() - default_features = S.default_features.Copy() - nojumpsuit = S.nojumpsuit - no_equip = S.no_equip.Copy() - limbs_id = S.limbs_id - use_skintones = S.use_skintones - fixed_mut_color = S.fixed_mut_color - hair_color = S.hair_color - fake_species = new S.type - else - name = initial(name) - say_mod = initial(say_mod) - species_traits = initial_species_traits.Copy() - inherent_traits = initial_inherent_traits.Copy() - attack_verb = initial(attack_verb) - attack_sound = initial(attack_sound) - miss_sound = initial(miss_sound) - mutant_bodyparts = list() - default_features = list() - nojumpsuit = initial(nojumpsuit) - no_equip = list() - qdel(fake_species) - fake_species = null - meat = initial(meat) - limbs_id = "synth" - use_skintones = 0 - sexes = 0 - fixed_mut_color = "" - hair_color = "" - - for(var/X in H.bodyparts) //propagates the damage_overlay changes - var/obj/item/bodypart/BP = X - BP.update_limb() - H.update_body_parts() //to update limb icon cache with the new damage overlays - -//Proc redirects: -//Passing procs onto the fake_species, to ensure we look as much like them as possible - -/datum/species/synth/handle_hair(mob/living/carbon/human/H, forced_colour) - if(fake_species) - fake_species.handle_hair(H, forced_colour) - else - return ..() - - -/datum/species/synth/handle_body(mob/living/carbon/human/H) - if(fake_species) - fake_species.handle_body(H) - else - return ..() - - -/datum/species/synth/handle_mutant_bodyparts(mob/living/carbon/human/H, forced_colour) - if(fake_species) - fake_species.handle_body(H,forced_colour) - else - return ..() - -/datum/species/synth/proc/handle_speech(datum/source, list/speech_args) - if (isliving(source)) // yeah it's gonna be living but just to be clean - var/mob/living/L = source - if(fake_species && L.health > disguise_fail_health) - switch (fake_species.type) - if (/datum/species/golem/bananium) - speech_args[SPEECH_SPANS] |= SPAN_CLOWN - if (/datum/species/golem/clockwork) +/datum/species/synth + name = "Synthetic" //inherited from the real species, for health scanners and things + id = "synth" + say_mod = "beep boops" //inherited from a user's real species + sexes = 0 + species_traits = list(NOTRANSSTING,NOGENITALS,NOAROUSAL) //all of these + whatever we inherit from the real species + inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER,TRAIT_NOBREATH) + inherent_biotypes = list(MOB_ROBOTIC, MOB_HUMANOID) + dangerous_existence = 1 + blacklisted = 1 + meat = null + gib_types = /obj/effect/gibspawner/robot + damage_overlay_type = "synth" + limbs_id = "synth" + var/list/initial_species_traits = list(NOTRANSSTING) //for getting these values back for assume_disguise() + var/list/initial_inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOLIMBDISABLE,TRAIT_NOHUNGER,TRAIT_NOBREATH) + var/disguise_fail_health = 75 //When their health gets to this level their synthflesh partially falls off + var/datum/species/fake_species = null //a species to do most of our work for us, unless we're damaged + +/datum/species/synth/military + name = "Military Synth" + id = "military_synth" + armor = 25 + punchdamagelow = 10 + punchdamagehigh = 19 + punchstunthreshold = 14 //about 50% chance to stun + disguise_fail_health = 50 + +/datum/species/synth/on_species_gain(mob/living/carbon/human/H, datum/species/old_species) + ..() + assume_disguise(old_species, H) + RegisterSignal(H, COMSIG_MOB_SAY, .proc/handle_speech) + +/datum/species/synth/on_species_loss(mob/living/carbon/human/H) + . = ..() + UnregisterSignal(H, COMSIG_MOB_SAY) + +/datum/species/synth/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) + if(chem.type == /datum/reagent/medicine/synthflesh) + chem.reaction_mob(H, TOUCH, 2 ,0) //heal a little + H.reagents.remove_reagent(chem.type, REAGENTS_METABOLISM) + return 1 + else + return ..() + + +/datum/species/synth/proc/assume_disguise(datum/species/S, mob/living/carbon/human/H) + if(S && !istype(S, type)) + name = S.name + say_mod = S.say_mod + sexes = S.sexes + species_traits = initial_species_traits.Copy() + inherent_traits = initial_inherent_traits.Copy() + species_traits |= S.species_traits + inherent_traits |= S.inherent_traits + attack_verb = S.attack_verb + attack_sound = S.attack_sound + miss_sound = S.miss_sound + meat = S.meat + mutant_bodyparts = S.mutant_bodyparts.Copy() + mutant_organs = S.mutant_organs.Copy() + default_features = S.default_features.Copy() + nojumpsuit = S.nojumpsuit + no_equip = S.no_equip.Copy() + limbs_id = S.limbs_id + use_skintones = S.use_skintones + fixed_mut_color = S.fixed_mut_color + hair_color = S.hair_color + fake_species = new S.type + else + name = initial(name) + say_mod = initial(say_mod) + species_traits = initial_species_traits.Copy() + inherent_traits = initial_inherent_traits.Copy() + attack_verb = initial(attack_verb) + attack_sound = initial(attack_sound) + miss_sound = initial(miss_sound) + mutant_bodyparts = list() + default_features = list() + nojumpsuit = initial(nojumpsuit) + no_equip = list() + qdel(fake_species) + fake_species = null + meat = initial(meat) + limbs_id = "synth" + use_skintones = 0 + sexes = 0 + fixed_mut_color = "" + hair_color = "" + + for(var/X in H.bodyparts) //propagates the damage_overlay changes + var/obj/item/bodypart/BP = X + BP.update_limb() + H.update_body_parts() //to update limb icon cache with the new damage overlays + +//Proc redirects: +//Passing procs onto the fake_species, to ensure we look as much like them as possible + +/datum/species/synth/handle_hair(mob/living/carbon/human/H, forced_colour) + if(fake_species) + fake_species.handle_hair(H, forced_colour) + else + return ..() + + +/datum/species/synth/handle_body(mob/living/carbon/human/H) + if(fake_species) + fake_species.handle_body(H) + else + return ..() + + +/datum/species/synth/handle_mutant_bodyparts(mob/living/carbon/human/H, forced_colour) + if(fake_species) + fake_species.handle_body(H,forced_colour) + else + return ..() + +/datum/species/synth/proc/handle_speech(datum/source, list/speech_args) + if (isliving(source)) // yeah it's gonna be living but just to be clean + var/mob/living/L = source + if(fake_species && L.health > disguise_fail_health) + switch (fake_species.type) + if (/datum/species/golem/bananium) + speech_args[SPEECH_SPANS] |= SPAN_CLOWN + if (/datum/species/golem/clockwork) speech_args[SPEECH_SPANS] |= SPAN_ROBOT \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm index c50677c54c..8255f08507 100644 --- a/code/modules/mob/living/carbon/human/species_types/zombies.dm +++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm @@ -1,98 +1,98 @@ -#define REGENERATION_DELAY 60 // After taking damage, how long it takes for automatic regeneration to begin - -/datum/species/zombie - // 1spooky - name = "High-Functioning Zombie" - id = "zombie" - say_mod = "moans" - sexes = 0 - blacklisted = 1 - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/zombie - species_traits = list(NOBLOOD,NOZOMBIE,NOTRANSSTING) - inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_FAKEDEATH) - inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID) - mutanttongue = /obj/item/organ/tongue/zombie - var/static/list/spooks = list('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg','sound/hallucinations/veryfar_noise.ogg','sound/hallucinations/wail.ogg') - disliked_food = NONE - liked_food = GROSS | MEAT | RAW - -/datum/species/zombie/notspaceproof - id = "notspaceproofzombie" - limbs_id = "zombie" - blacklisted = 0 - inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_FAKEDEATH) - -/datum/species/zombie/notspaceproof/check_roundstart_eligible() - if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) - return TRUE - return ..() - -/datum/species/zombie/infectious - name = "Infectious Zombie" - id = "memezombies" - limbs_id = "zombie" - mutanthands = /obj/item/zombie_hand - armor = 20 // 120 damage to KO a zombie, which kills it - speedmod = 1.6 - mutanteyes = /obj/item/organ/eyes/night_vision/zombie - var/heal_rate = 1 - var/regen_cooldown = 0 - -/datum/species/zombie/infectious/check_roundstart_eligible() - return FALSE - - -/datum/species/zombie/infectious/spec_stun(mob/living/carbon/human/H,amount) - . = min(20, amount) - -/datum/species/zombie/infectious/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE) - . = ..() - if(.) - regen_cooldown = world.time + REGENERATION_DELAY - -/datum/species/zombie/infectious/spec_life(mob/living/carbon/C) - . = ..() - C.a_intent = INTENT_HARM // THE SUFFERING MUST FLOW - - //Zombies never actually die, they just fall down until they regenerate enough to rise back up. - //They must be restrained, beheaded or gibbed to stop being a threat. - if(regen_cooldown < world.time) - var/heal_amt = heal_rate - if(C.InCritical()) - heal_amt *= 2 - C.heal_overall_damage(heal_amt,heal_amt) - C.adjustToxLoss(-heal_amt) - if(!C.InCritical() && prob(4)) - playsound(C, pick(spooks), 50, TRUE, 10) - -//Congrats you somehow died so hard you stopped being a zombie -/datum/species/zombie/infectious/spec_death(gibbed, mob/living/carbon/C) - . = ..() - var/obj/item/organ/zombie_infection/infection - infection = C.getorganslot(ORGAN_SLOT_ZOMBIE) - if(infection) - qdel(infection) - -/datum/species/zombie/infectious/on_species_gain(mob/living/carbon/C, datum/species/old_species) - . = ..() - - // Deal with the source of this zombie corruption - // Infection organ needs to be handled separately from mutant_organs - // because it persists through species transitions - var/obj/item/organ/zombie_infection/infection - infection = C.getorganslot(ORGAN_SLOT_ZOMBIE) - if(!infection) - infection = new() - infection.Insert(C) - - -// Your skin falls off -/datum/species/krokodil_addict - name = "Human" - id = "goofzombies" - limbs_id = "zombie" //They look like zombies - sexes = 0 - meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/zombie - mutanttongue = /obj/item/organ/tongue/zombie - -#undef REGENERATION_DELAY +#define REGENERATION_DELAY 60 // After taking damage, how long it takes for automatic regeneration to begin + +/datum/species/zombie + // 1spooky + name = "High-Functioning Zombie" + id = "zombie" + say_mod = "moans" + sexes = 0 + blacklisted = 1 + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/zombie + species_traits = list(NOBLOOD,NOZOMBIE,NOTRANSSTING) + inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_FAKEDEATH) + inherent_biotypes = list(MOB_UNDEAD, MOB_HUMANOID) + mutanttongue = /obj/item/organ/tongue/zombie + var/static/list/spooks = list('sound/hallucinations/growl1.ogg','sound/hallucinations/growl2.ogg','sound/hallucinations/growl3.ogg','sound/hallucinations/veryfar_noise.ogg','sound/hallucinations/wail.ogg') + disliked_food = NONE + liked_food = GROSS | MEAT | RAW + +/datum/species/zombie/notspaceproof + id = "notspaceproofzombie" + limbs_id = "zombie" + blacklisted = 0 + inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_FAKEDEATH) + +/datum/species/zombie/notspaceproof/check_roundstart_eligible() + if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) + return TRUE + return ..() + +/datum/species/zombie/infectious + name = "Infectious Zombie" + id = "memezombies" + limbs_id = "zombie" + mutanthands = /obj/item/zombie_hand + armor = 20 // 120 damage to KO a zombie, which kills it + speedmod = 1.6 + mutanteyes = /obj/item/organ/eyes/night_vision/zombie + var/heal_rate = 1 + var/regen_cooldown = 0 + +/datum/species/zombie/infectious/check_roundstart_eligible() + return FALSE + + +/datum/species/zombie/infectious/spec_stun(mob/living/carbon/human/H,amount) + . = min(20, amount) + +/datum/species/zombie/infectious/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE) + . = ..() + if(.) + regen_cooldown = world.time + REGENERATION_DELAY + +/datum/species/zombie/infectious/spec_life(mob/living/carbon/C) + . = ..() + C.a_intent = INTENT_HARM // THE SUFFERING MUST FLOW + + //Zombies never actually die, they just fall down until they regenerate enough to rise back up. + //They must be restrained, beheaded or gibbed to stop being a threat. + if(regen_cooldown < world.time) + var/heal_amt = heal_rate + if(C.InCritical()) + heal_amt *= 2 + C.heal_overall_damage(heal_amt,heal_amt) + C.adjustToxLoss(-heal_amt) + if(!C.InCritical() && prob(4)) + playsound(C, pick(spooks), 50, TRUE, 10) + +//Congrats you somehow died so hard you stopped being a zombie +/datum/species/zombie/infectious/spec_death(gibbed, mob/living/carbon/C) + . = ..() + var/obj/item/organ/zombie_infection/infection + infection = C.getorganslot(ORGAN_SLOT_ZOMBIE) + if(infection) + qdel(infection) + +/datum/species/zombie/infectious/on_species_gain(mob/living/carbon/C, datum/species/old_species) + . = ..() + + // Deal with the source of this zombie corruption + // Infection organ needs to be handled separately from mutant_organs + // because it persists through species transitions + var/obj/item/organ/zombie_infection/infection + infection = C.getorganslot(ORGAN_SLOT_ZOMBIE) + if(!infection) + infection = new() + infection.Insert(C) + + +// Your skin falls off +/datum/species/krokodil_addict + name = "Human" + id = "goofzombies" + limbs_id = "zombie" //They look like zombies + sexes = 0 + meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/zombie + mutanttongue = /obj/item/organ/tongue/zombie + +#undef REGENERATION_DELAY diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index eed3a17c34..bbae050bba 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -1,142 +1,142 @@ -/mob/living/carbon/get_item_by_slot(slot_id) - switch(slot_id) - if(SLOT_BACK) - return back - if(SLOT_WEAR_MASK) - return wear_mask - if(SLOT_NECK) - return wear_neck - if(SLOT_HEAD) - return head - if(SLOT_HANDCUFFED) - return handcuffed - if(SLOT_LEGCUFFED) - return legcuffed - return null - -/mob/living/carbon/proc/equip_in_one_of_slots(obj/item/I, list/slots, qdel_on_fail = 1) - for(var/slot in slots) - if(equip_to_slot_if_possible(I, slots[slot], qdel_on_fail = 0, disable_warning = TRUE)) - return slot - if(qdel_on_fail) - qdel(I) - return null - -//This is an UNSAFE proc. Use mob_can_equip() before calling this one! Or rather use equip_to_slot_if_possible() or advanced_equip_to_slot_if_possible() -/mob/living/carbon/equip_to_slot(obj/item/I, slot) - if(!slot) - return - if(!istype(I)) - return - - var/index = get_held_index_of_item(I) - if(index) - held_items[index] = null - - if(I.pulledby) - I.pulledby.stop_pulling() - - I.screen_loc = null - if(client) - client.screen -= I - if(observers && observers.len) - for(var/M in observers) - var/mob/dead/observe = M - if(observe.client) - observe.client.screen -= I - I.forceMove(src) - I.layer = ABOVE_HUD_LAYER - I.plane = ABOVE_HUD_PLANE - I.appearance_flags |= NO_CLIENT_COLOR - var/not_handled = FALSE - switch(slot) - if(SLOT_BACK) - back = I - update_inv_back() - if(SLOT_WEAR_MASK) - wear_mask = I - wear_mask_update(I, toggle_off = 0) - if(SLOT_HEAD) - head = I - head_update(I) - if(SLOT_NECK) - wear_neck = I - update_inv_neck(I) - if(SLOT_HANDCUFFED) - handcuffed = I - update_handcuffed() - if(SLOT_LEGCUFFED) - legcuffed = I - update_inv_legcuffed() - if(SLOT_HANDS) - put_in_hands(I) - update_inv_hands() - if(SLOT_IN_BACKPACK) - if(!SEND_SIGNAL(back, COMSIG_TRY_STORAGE_INSERT, I, src, TRUE)) - not_handled = TRUE - else - not_handled = TRUE - - //Item has been handled at this point and equipped callback can be safely called - //We cannot call it for items that have not been handled as they are not yet correctly - //in a slot (handled further down inheritance chain, probably living/carbon/human/equip_to_slot - if(!not_handled) - I.equipped(src, slot) - - return not_handled - -/mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) - . = ..() //Sets the default return value to what the parent returns. - if(!. || !I) //We don't want to set anything to null if the parent returned 0. - return - - if(I == head) - head = null - if(!QDELETED(src)) - head_update(I) - else if(I == back) - back = null - if(!QDELETED(src)) - update_inv_back() - else if(I == wear_mask) - wear_mask = null - if(!QDELETED(src)) - wear_mask_update(I, toggle_off = 1) - if(I == wear_neck) - wear_neck = null - if(!QDELETED(src)) - update_inv_neck(I) - else if(I == handcuffed) - handcuffed = null - if(buckled && buckled.buckle_requires_restraints) - buckled.unbuckle_mob(src) - if(!QDELETED(src)) - update_handcuffed() - else if(I == legcuffed) - legcuffed = null - if(!QDELETED(src)) - update_inv_legcuffed() - -//handle stuff to update when a mob equips/unequips a mask. -/mob/living/proc/wear_mask_update(obj/item/clothing/C, toggle_off = 1) - update_inv_wear_mask() - -/mob/living/carbon/wear_mask_update(obj/item/clothing/C, toggle_off = 1) - if(C.tint || initial(C.tint)) - update_tint() - update_inv_wear_mask() - -//handle stuff to update when a mob equips/unequips a headgear. -/mob/living/carbon/proc/head_update(obj/item/I, forced) - if(istype(I, /obj/item/clothing)) - var/obj/item/clothing/C = I - if(C.tint || initial(C.tint)) - update_tint() - update_sight() - if(I.flags_inv & HIDEMASK || forced) - update_inv_wear_mask() - update_inv_head() - -/mob/living/carbon/proc/get_holding_bodypart_of_item(obj/item/I) - var/index = get_held_index_of_item(I) - return index && hand_bodyparts[index] +/mob/living/carbon/get_item_by_slot(slot_id) + switch(slot_id) + if(SLOT_BACK) + return back + if(SLOT_WEAR_MASK) + return wear_mask + if(SLOT_NECK) + return wear_neck + if(SLOT_HEAD) + return head + if(SLOT_HANDCUFFED) + return handcuffed + if(SLOT_LEGCUFFED) + return legcuffed + return null + +/mob/living/carbon/proc/equip_in_one_of_slots(obj/item/I, list/slots, qdel_on_fail = 1) + for(var/slot in slots) + if(equip_to_slot_if_possible(I, slots[slot], qdel_on_fail = 0, disable_warning = TRUE)) + return slot + if(qdel_on_fail) + qdel(I) + return null + +//This is an UNSAFE proc. Use mob_can_equip() before calling this one! Or rather use equip_to_slot_if_possible() or advanced_equip_to_slot_if_possible() +/mob/living/carbon/equip_to_slot(obj/item/I, slot) + if(!slot) + return + if(!istype(I)) + return + + var/index = get_held_index_of_item(I) + if(index) + held_items[index] = null + + if(I.pulledby) + I.pulledby.stop_pulling() + + I.screen_loc = null + if(client) + client.screen -= I + if(observers && observers.len) + for(var/M in observers) + var/mob/dead/observe = M + if(observe.client) + observe.client.screen -= I + I.forceMove(src) + I.layer = ABOVE_HUD_LAYER + I.plane = ABOVE_HUD_PLANE + I.appearance_flags |= NO_CLIENT_COLOR + var/not_handled = FALSE + switch(slot) + if(SLOT_BACK) + back = I + update_inv_back() + if(SLOT_WEAR_MASK) + wear_mask = I + wear_mask_update(I, toggle_off = 0) + if(SLOT_HEAD) + head = I + head_update(I) + if(SLOT_NECK) + wear_neck = I + update_inv_neck(I) + if(SLOT_HANDCUFFED) + handcuffed = I + update_handcuffed() + if(SLOT_LEGCUFFED) + legcuffed = I + update_inv_legcuffed() + if(SLOT_HANDS) + put_in_hands(I) + update_inv_hands() + if(SLOT_IN_BACKPACK) + if(!SEND_SIGNAL(back, COMSIG_TRY_STORAGE_INSERT, I, src, TRUE)) + not_handled = TRUE + else + not_handled = TRUE + + //Item has been handled at this point and equipped callback can be safely called + //We cannot call it for items that have not been handled as they are not yet correctly + //in a slot (handled further down inheritance chain, probably living/carbon/human/equip_to_slot + if(!not_handled) + I.equipped(src, slot) + + return not_handled + +/mob/living/carbon/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE) + . = ..() //Sets the default return value to what the parent returns. + if(!. || !I) //We don't want to set anything to null if the parent returned 0. + return + + if(I == head) + head = null + if(!QDELETED(src)) + head_update(I) + else if(I == back) + back = null + if(!QDELETED(src)) + update_inv_back() + else if(I == wear_mask) + wear_mask = null + if(!QDELETED(src)) + wear_mask_update(I, toggle_off = 1) + if(I == wear_neck) + wear_neck = null + if(!QDELETED(src)) + update_inv_neck(I) + else if(I == handcuffed) + handcuffed = null + if(buckled && buckled.buckle_requires_restraints) + buckled.unbuckle_mob(src) + if(!QDELETED(src)) + update_handcuffed() + else if(I == legcuffed) + legcuffed = null + if(!QDELETED(src)) + update_inv_legcuffed() + +//handle stuff to update when a mob equips/unequips a mask. +/mob/living/proc/wear_mask_update(obj/item/clothing/C, toggle_off = 1) + update_inv_wear_mask() + +/mob/living/carbon/wear_mask_update(obj/item/clothing/C, toggle_off = 1) + if(C.tint || initial(C.tint)) + update_tint() + update_inv_wear_mask() + +//handle stuff to update when a mob equips/unequips a headgear. +/mob/living/carbon/proc/head_update(obj/item/I, forced) + if(istype(I, /obj/item/clothing)) + var/obj/item/clothing/C = I + if(C.tint || initial(C.tint)) + update_tint() + update_sight() + if(I.flags_inv & HIDEMASK || forced) + update_inv_wear_mask() + update_inv_head() + +/mob/living/carbon/proc/get_holding_bodypart_of_item(obj/item/I) + var/index = get_held_index_of_item(I) + return index && hand_bodyparts[index] diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 9e9d440574..61e68b50ec 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -1,759 +1,759 @@ -/mob/living/carbon/Life() - set invisibility = 0 - - if(notransform) - return - - if(damageoverlaytemp) - damageoverlaytemp = 0 - update_damage_hud() - - //Reagent processing needs to come before breathing, to prevent edge cases. - handle_organs() - - . = ..() - - if (QDELETED(src)) - return - - if(.) //not dead - handle_blood() - - if(stat != DEAD) - var/bprv = handle_bodyparts() - if(bprv & BODYPART_LIFE_UPDATE_HEALTH) - updatehealth() - update_stamina() - - if(stat != DEAD) - handle_brain_damage() - - /* BUG_PROBABLE_CAUSE - if(stat != DEAD) - handle_liver() - */ - - if(stat == DEAD) - stop_sound_channel(CHANNEL_HEARTBEAT) - handle_death() - rot() - - //Updates the number of stored chemicals for powers - handle_changeling() - - if(stat != DEAD) - return 1 - -//Procs called while dead -/mob/living/carbon/proc/handle_death() - for(var/datum/reagent/R in reagents.reagent_list) - if(R.chemical_flags & REAGENT_DEAD_PROCESS) - R.on_mob_dead(src) - -/////////////// -// BREATHING // -/////////////// - -//Start of a breath chain, calls breathe() -/mob/living/carbon/handle_breathing(times_fired) - var/next_breath = 4 - var/obj/item/organ/lungs/L = getorganslot(ORGAN_SLOT_LUNGS) - var/obj/item/organ/heart/H = getorganslot(ORGAN_SLOT_HEART) - if(L) - if(L.damage > L.high_threshold) - next_breath-- - if(H) - if(H.damage > H.high_threshold) - next_breath-- - - if((times_fired % next_breath) == 0 || failed_last_breath) - breathe() //Breathe per 4 ticks if healthy, down to 2 if our lungs or heart are damaged, unless suffocating - if(failed_last_breath) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "suffocation", /datum/mood_event/suffocation) - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "suffocation") - else - if(istype(loc, /obj/)) - var/obj/location_as_object = loc - location_as_object.handle_internal_lifeform(src,0) - -//Second link in a breath chain, calls check_breath() -/mob/living/carbon/proc/breathe() - var/obj/item/organ/lungs = getorganslot(ORGAN_SLOT_LUNGS) - if(reagents.has_reagent(/datum/reagent/toxin/lexorin)) - return - if(istype(loc, /obj/machinery/atmospherics/components/unary/cryo_cell)) - return - if(istype(loc, /obj/item/dogborg/sleeper)) - return - if(ismob(loc)) - return - if(isbelly(loc)) - return - - var/datum/gas_mixture/environment - if(loc) - environment = loc.return_air() - - var/datum/gas_mixture/breath - - if(!getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - if(health <= HEALTH_THRESHOLD_FULLCRIT || (pulledby && pulledby.grab_state >= GRAB_KILL) || HAS_TRAIT(src, TRAIT_MAGIC_CHOKE) || (lungs && lungs.organ_flags & ORGAN_FAILING)) - losebreath++ //You can't breath at all when in critical or when being choked, so you're going to miss a breath - - else if(health <= crit_threshold) - losebreath += 0.25 //You're having trouble breathing in soft crit, so you'll miss a breath one in four times - - //Suffocate - if(losebreath >= 1) //You've missed a breath, take oxy damage - losebreath-- - if(prob(10)) - emote("gasp") - if(istype(loc, /obj/)) - var/obj/loc_as_obj = loc - loc_as_obj.handle_internal_lifeform(src,0) - else - //Breathe from internal - breath = get_breath_from_internal(BREATH_VOLUME) - - if(isnull(breath)) //in case of 0 pressure internals - - if(isobj(loc)) //Breathe from loc as object - var/obj/loc_as_obj = loc - breath = loc_as_obj.handle_internal_lifeform(src, BREATH_VOLUME) - - else if(isturf(loc)) //Breathe from loc as turf - var/breath_moles = 0 - if(environment) - breath_moles = environment.total_moles()*BREATH_PERCENTAGE - - breath = loc.remove_air(breath_moles) - else //Breathe from loc as obj again - if(istype(loc, /obj/)) - var/obj/loc_as_obj = loc - loc_as_obj.handle_internal_lifeform(src,0) - - check_breath(breath) - - if(breath) - loc.assume_air(breath) - air_update_turf() - -/mob/living/carbon/proc/has_smoke_protection() - if(HAS_TRAIT(src, TRAIT_NOBREATH)) - return TRUE - return FALSE - - -//Third link in a breath chain, calls handle_breath_temperature() -/mob/living/carbon/proc/check_breath(datum/gas_mixture/breath) - if((status_flags & GODMODE)) - return - - var/obj/item/organ/lungs = getorganslot(ORGAN_SLOT_LUNGS) - if(!lungs) - adjustOxyLoss(2) - - //CRIT - if(!breath || (breath.total_moles() == 0) || !lungs) - if(reagents.has_reagent(/datum/reagent/medicine/epinephrine) && lungs) - return - adjustOxyLoss(1) - - failed_last_breath = 1 - throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) - return 0 - - var/safe_oxy_min = 16 - var/safe_oxy_max = 50 - var/safe_co2_max = 10 - var/safe_tox_max = 0.05 - var/SA_para_min = 1 - var/SA_sleep_min = 5 - var/oxygen_used = 0 - var/breath_pressure = (breath.total_moles()*R_IDEAL_GAS_EQUATION*breath.temperature)/BREATH_VOLUME - - var/list/breath_gases = breath.gases - var/O2_partialpressure = (breath_gases[/datum/gas/oxygen]/breath.total_moles())*breath_pressure - var/Toxins_partialpressure = (breath_gases[/datum/gas/plasma]/breath.total_moles())*breath_pressure - var/CO2_partialpressure = (breath_gases[/datum/gas/carbon_dioxide]/breath.total_moles())*breath_pressure - - - //OXYGEN - if(O2_partialpressure > safe_oxy_max) // Too much Oxygen - blatant CO2 effect copy/pasta - if(!o2overloadtime) - o2overloadtime = world.time - else if(world.time - o2overloadtime > 120) - Dizzy(10) // better than a minute of you're fucked KO, but certainly a wake up call. Honk. - adjustOxyLoss(3) - if(world.time - o2overloadtime > 300) - adjustOxyLoss(8) - if(prob(20)) - emote("cough") - throw_alert("too_much_oxy", /obj/screen/alert/too_much_oxy) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "suffocation", /datum/mood_event/suffocation) - - if(O2_partialpressure < safe_oxy_min) //Not enough oxygen - if(prob(20)) - emote("gasp") - if(O2_partialpressure > 0) - var/ratio = 1 - O2_partialpressure/safe_oxy_min - adjustOxyLoss(min(5*ratio, 3)) - failed_last_breath = 1 - oxygen_used = breath_gases[/datum/gas/oxygen]*ratio - else - adjustOxyLoss(3) - failed_last_breath = 1 - throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "suffocation", /datum/mood_event/suffocation) - - else //Enough oxygen - failed_last_breath = 0 - o2overloadtime = 0 //reset our counter for this too - if(health >= crit_threshold) - adjustOxyLoss(-5) - oxygen_used = breath_gases[/datum/gas/oxygen] - clear_alert("not_enough_oxy") - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "suffocation") - - breath_gases[/datum/gas/oxygen] -= oxygen_used - breath_gases[/datum/gas/carbon_dioxide] += oxygen_used - - //CARBON DIOXIDE - if(CO2_partialpressure > safe_co2_max) - if(!co2overloadtime) - co2overloadtime = world.time - else if(world.time - co2overloadtime > 120) - Unconscious(60) - adjustOxyLoss(3) - if(world.time - co2overloadtime > 300) - adjustOxyLoss(8) - if(prob(20)) - emote("cough") - - else - co2overloadtime = 0 - - //TOXINS/PLASMA - if(Toxins_partialpressure > safe_tox_max) - var/ratio = (breath_gases[/datum/gas/plasma]/safe_tox_max) * 10 - adjustToxLoss(CLAMP(ratio, MIN_TOXIC_GAS_DAMAGE, MAX_TOXIC_GAS_DAMAGE)) - throw_alert("too_much_tox", /obj/screen/alert/too_much_tox) - else - clear_alert("too_much_tox") - - //NITROUS OXIDE - if(breath_gases[/datum/gas/nitrous_oxide]) - var/SA_partialpressure = (breath_gases[/datum/gas/nitrous_oxide]/breath.total_moles())*breath_pressure - if(SA_partialpressure > SA_para_min) - Unconscious(60) - if(SA_partialpressure > SA_sleep_min) - Sleeping(max(AmountSleeping() + 40, 200)) - else if(SA_partialpressure > 0.01) - if(prob(20)) - emote(pick("giggle","laugh")) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "chemical_euphoria", /datum/mood_event/chemical_euphoria) - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "chemical_euphoria") - - //BZ (Facepunch port of their Agent B) - if(breath_gases[/datum/gas/bz]) - var/bz_partialpressure = (breath_gases[/datum/gas/bz]/breath.total_moles())*breath_pressure - if(bz_partialpressure > 1) - hallucination += 10 - else if(bz_partialpressure > 0.01) - hallucination += 5 - - //TRITIUM - if(breath_gases[/datum/gas/tritium]) - var/tritium_partialpressure = (breath_gases[/datum/gas/tritium]/breath.total_moles())*breath_pressure - radiation += tritium_partialpressure/10 - - //NITRYL - if(breath_gases[/datum/gas/nitryl]) - var/nitryl_partialpressure = (breath_gases[/datum/gas/nitryl]/breath.total_moles())*breath_pressure - adjustFireLoss(nitryl_partialpressure/4) - - //MIASMA - if(breath_gases[/datum/gas/miasma]) - var/miasma_partialpressure = (breath_gases[/datum/gas/miasma]/breath.total_moles())*breath_pressure - if(miasma_partialpressure > MINIMUM_MOLES_DELTA_TO_MOVE) - - if(prob(0.05 * miasma_partialpressure)) - var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(2,3) - miasma_disease.name = "Unknown" - ForceContractDisease(miasma_disease, TRUE, TRUE) - - //Miasma side effects - switch(miasma_partialpressure) - if(1 to 5) - // At lower pp, give out a little warning - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "smell") - if(prob(5)) - to_chat(src, "There is an unpleasant smell in the air.") - if(5 to 20) - //At somewhat higher pp, warning becomes more obvious - if(prob(15)) - to_chat(src, "You smell something horribly decayed inside this room.") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/bad_smell) - if(15 to 30) - //Small chance to vomit. By now, people have internals on anyway - if(prob(5)) - to_chat(src, "The stench of rotting carcasses is unbearable!") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/nauseating_stench) - vomit() - if(30 to INFINITY) - //Higher chance to vomit. Let the horror start - if(prob(25)) - to_chat(src, "The stench of rotting carcasses is unbearable!") - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/nauseating_stench) - vomit() - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "smell") - - - //Clear all moods if no miasma at all - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "smell") - - - - - GAS_GARBAGE_COLLECT(breath.gases) - - //BREATH TEMPERATURE - handle_breath_temperature(breath) - - return 1 - -//Fourth and final link in a breath chain -/mob/living/carbon/proc/handle_breath_temperature(datum/gas_mixture/breath) - return - -/mob/living/carbon/proc/get_breath_from_internal(volume_needed) - var/obj/item/clothing/check - var/internals = FALSE - - for(check in GET_INTERNAL_SLOTS(src)) - if(CHECK_BITFIELD(check.clothing_flags, ALLOWINTERNALS)) - internals = TRUE - if(internal) - if(internal.loc != src) - internal = null - update_internals_hud_icon(0) - else if (!internals && !getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - internal = null - update_internals_hud_icon(0) - else - update_internals_hud_icon(1) - . = internal.remove_air_volume(volume_needed) - if(!.) - return FALSE //to differentiate between no internals and active, but empty internals - -// Make corpses rot, emitting miasma -/mob/living/carbon/proc/rot() - // Properly stored corpses shouldn't create miasma - if(istype(loc, /obj/structure/closet/crate/coffin)|| istype(loc, /obj/structure/closet/body_bag) || istype(loc, /obj/structure/bodycontainer)) - return - - // No decay if formaldehyde in corpse or when the corpse is charred - if(reagents.has_reagent(/datum/reagent/toxin/formaldehyde, 15) || HAS_TRAIT(src, TRAIT_HUSK)) - return - - // Also no decay if corpse chilled or not organic/undead - if(bodytemperature <= T0C-10 || (!(MOB_ORGANIC in mob_biotypes) && !(MOB_UNDEAD in mob_biotypes))) - return - - // Wait a bit before decaying - if(world.time - timeofdeath < 1200) - return - - var/deceasedturf = get_turf(src) - - // Closed turfs don't have any air in them, so no gas building up - if(!istype(deceasedturf,/turf/open)) - return - - var/turf/open/miasma_turf = deceasedturf - - var/list/cached_gases = miasma_turf.air.gases - - cached_gases[/datum/gas/miasma] += 0.1 - -/mob/living/carbon/proc/handle_blood() - return - -/mob/living/carbon/proc/handle_bodyparts() - for(var/I in bodyparts) - var/obj/item/bodypart/BP = I - if(BP.needs_processing) - . |= BP.on_life() - -/mob/living/carbon/proc/handle_organs() - if(stat != DEAD) - for(var/V in internal_organs) - var/obj/item/organ/O = V - if(O) - O.on_life() - else - for(var/V in internal_organs) - var/obj/item/organ/O = V - if(O) - O.on_death() //Needed so organs decay while inside the body. - -/mob/living/carbon/handle_diseases() - for(var/thing in diseases) - var/datum/disease/D = thing - if(prob(D.infectivity)) - D.spread() - - if(stat != DEAD || D.process_dead) - D.stage_act() - -//todo generalize this and move hud out -/mob/living/carbon/proc/handle_changeling() - if(mind && hud_used && hud_used.lingchemdisplay) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - changeling.regenerate() - hud_used.lingchemdisplay.invisibility = 0 - hud_used.lingchemdisplay.maptext = "
                [round(changeling.chem_charges)]
                " - else - hud_used.lingchemdisplay.invisibility = INVISIBILITY_ABSTRACT - - -/mob/living/carbon/handle_mutations_and_radiation() - if(dna && dna.temporary_mutations.len) - var/datum/mutation/human/HM - for(var/mut in dna.temporary_mutations) - if(dna.temporary_mutations[mut] < world.time) - if(mut == UI_CHANGED) - if(dna.previous["UI"]) - dna.uni_identity = merge_text(dna.uni_identity,dna.previous["UI"]) - updateappearance(mutations_overlay_update=1) - dna.previous.Remove("UI") - dna.temporary_mutations.Remove(mut) - continue - if(mut == UE_CHANGED) - if(dna.previous["name"]) - real_name = dna.previous["name"] - name = real_name - dna.previous.Remove("name") - if(dna.previous["UE"]) - dna.unique_enzymes = dna.previous["UE"] - dna.previous.Remove("UE") - if(dna.previous["blood_type"]) - dna.blood_type = dna.previous["blood_type"] - dna.previous.Remove("blood_type") - dna.temporary_mutations.Remove(mut) - continue - HM = GLOB.mutations_list[mut] - HM.force_lose(src) - dna.temporary_mutations.Remove(mut) - - radiation -= min(radiation, RAD_LOSS_PER_TICK) - if(radiation > RAD_MOB_SAFE) - adjustToxLoss(log(radiation-RAD_MOB_SAFE)*RAD_TOX_COEFFICIENT) - -/mob/living/carbon/handle_stomach() - set waitfor = 0 - for(var/mob/living/M in stomach_contents) - if(M.loc != src) - stomach_contents.Remove(M) - continue - if(iscarbon(M) && stat != DEAD) - if(M.stat == DEAD) - M.death(1) - stomach_contents.Remove(M) - qdel(M) - continue - if(SSmobs.times_fired%3==1) - if(!(M.status_flags & GODMODE)) - M.adjustBruteLoss(5) - nutrition += 10 - - -/* -Alcohol Poisoning Chart -Note that all higher effects of alcohol poisoning will inherit effects for smaller amounts (i.e. light poisoning inherts from slight poisoning) -In addition, severe effects won't always trigger unless the drink is poisonously strong -All effects don't start immediately, but rather get worse over time; the rate is affected by the imbiber's alcohol tolerance - -0: Non-alcoholic -1-10: Barely classifiable as alcohol - occassional slurring -11-20: Slight alcohol content - slurring -21-30: Below average - imbiber begins to look slightly drunk -31-40: Just below average - no unique effects -41-50: Average - mild disorientation, imbiber begins to look drunk -51-60: Just above average - disorientation, vomiting, imbiber begins to look heavily drunk -61-70: Above average - small chance of blurry vision, imbiber begins to look smashed -71-80: High alcohol content - blurry vision, imbiber completely shitfaced -81-90: Extremely high alcohol content - light brain damage, passing out -91-100: Dangerously toxic - swift death -*/ -#define BALLMER_POINTS 5 -GLOBAL_LIST_INIT(ballmer_good_msg, list("Hey guys, what if we rolled out a bluespace wiring system so mice can't destroy the powergrid anymore?", - "Hear me out here. What if, and this is just a theory, we made R&D controllable from our PDAs?", - "I'm thinking we should roll out a git repository for our research under the AGPLv3 license so that we can share it among the other stations freely.", - "I dunno about you guys, but IDs and PDAs being separate is clunky as fuck. Maybe we should merge them into a chip in our arms? That way they can't be stolen easily.", - "Why the fuck aren't we just making every pair of shoes into galoshes? We have the technology.")) -GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put a webserver that's automatically turned on with default admin passwords into every PDA?", - "So like, you know how we separate our codebase from the master copy that runs on our consumer boxes? What if we merged the two and undid the separation between codebase and server?", - "Dude, radical idea: H.O.N.K mechs but with no bananium required.", - "Best idea ever: Disposal pipes instead of hallways.")) - -//this updates all special effects: stun, sleeping, knockdown, druggy, stuttering, etc.. -/mob/living/carbon/handle_status_effects() - ..() - if(getStaminaLoss() && !combatmode)//CIT CHANGE - prevents stamina regen while combat mode is active - adjustStaminaLoss(resting ? (recoveringstam ? -7.5 : -6) : -3)//CIT CHANGE - decreases adjuststaminaloss to stop stamina damage from being such a joke - - if(!recoveringstam && incomingstammult != 1) - incomingstammult = max(0.01, incomingstammult) - incomingstammult = min(1, incomingstammult*2) - - //CIT CHANGES START HERE. STAMINA BUFFER STUFF - if(bufferedstam && world.time > stambufferregentime) - var/drainrate = max((bufferedstam*(bufferedstam/(5)))*0.1,1) - bufferedstam = max(bufferedstam - drainrate, 0) - adjustStaminaLoss(drainrate*0.5) - //END OF CIT CHANGES - - var/restingpwr = 1 + 4 * resting - - //Dizziness - if(dizziness) - var/client/C = client - var/pixel_x_diff = 0 - var/pixel_y_diff = 0 - var/temp - var/saved_dizz = dizziness - if(C) - var/oldsrc = src - var/amplitude = dizziness*(sin(dizziness * world.time) + 1) // This shit is annoying at high strength - src = null - spawn(0) - if(C) - temp = amplitude * sin(saved_dizz * world.time) - pixel_x_diff += temp - C.pixel_x += temp - temp = amplitude * cos(saved_dizz * world.time) - pixel_y_diff += temp - C.pixel_y += temp - sleep(3) - if(C) - temp = amplitude * sin(saved_dizz * world.time) - pixel_x_diff += temp - C.pixel_x += temp - temp = amplitude * cos(saved_dizz * world.time) - pixel_y_diff += temp - C.pixel_y += temp - sleep(3) - if(C) - C.pixel_x -= pixel_x_diff - C.pixel_y -= pixel_y_diff - src = oldsrc - dizziness = max(dizziness - restingpwr, 0) - - if(drowsyness) - drowsyness = max(drowsyness - restingpwr, 0) - blur_eyes(2) - if(prob(5)) - AdjustSleeping(20) - Unconscious(100) - - //Jitteriness - if(jitteriness) - do_jitter_animation(jitteriness) - jitteriness = max(jitteriness - restingpwr, 0) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "jittery", /datum/mood_event/jittery) - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "jittery") - - if(stuttering) - stuttering = max(stuttering-1, 0) - - if(slurring || drunkenness) - slurring = max(slurring-1,0,drunkenness) - - if(cultslurring) - cultslurring = max(cultslurring-1, 0) - - if(silent) - silent = max(silent-1, 0) - - if(druggy) - adjust_drugginess(-1) - - if(hallucination) - handle_hallucinations() - - if(drunkenness) - drunkenness = max(drunkenness - (drunkenness * 0.04), 0) - if(drunkenness >= 6) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "drunk", /datum/mood_event/drunk) - jitteriness = max(jitteriness - 3, 0) - if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) - adjustBruteLoss(-0.12, FALSE) - adjustFireLoss(-0.06, FALSE) - - if(mind && (mind.assigned_role == "Scientist" || mind.assigned_role == "Research Director")) - if(SSresearch.science_tech) - if(drunkenness >= 12.9 && drunkenness <= 13.8) - drunkenness = round(drunkenness, 0.01) - var/ballmer_percent = 0 - if(drunkenness == 13.35) // why run math if I dont have to - ballmer_percent = 1 - else - ballmer_percent = (-abs(drunkenness - 13.35) / 0.9) + 1 - if(prob(5)) - say(pick(GLOB.ballmer_good_msg), forced = "ballmer") - SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = BALLMER_POINTS * ballmer_percent)) - if(drunkenness > 26) // by this point you're into windows ME territory - if(prob(5)) - SSresearch.science_tech.remove_point_list(list(TECHWEB_POINT_TYPE_GENERIC = BALLMER_POINTS)) - say(pick(GLOB.ballmer_windows_me_msg), forced = "ballmer") - - if(drunkenness >= 41) - if(prob(25)) - confused += 2 - Dizzy(10) - if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) // effects stack with lower tiers - adjustBruteLoss(-0.3, FALSE) - adjustFireLoss(-0.15, FALSE) - - if(drunkenness >= 51) - if(prob(5)) - confused += 10 - vomit() - Dizzy(25) - - if(drunkenness >= 61) - if(prob(50)) - blur_eyes(5) - if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) - adjustBruteLoss(-0.4, FALSE) - adjustFireLoss(-0.2, FALSE) - - if(drunkenness >= 71) - blur_eyes(5) - - if(drunkenness >= 81) - adjustToxLoss(0.2) - if(prob(5) && !stat) - to_chat(src, "Maybe you should lie down for a bit...") - - if(drunkenness >= 91) - adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.4, 60) - if(prob(20) && !stat) - if(SSshuttle.emergency.mode == SHUTTLE_DOCKED && is_station_level(z)) //QoL mainly - to_chat(src, "You're so tired... but you can't miss that shuttle...") - else - to_chat(src, "Just a quick nap...") - Sleeping(900) - - if(drunkenness >= 101) - adjustToxLoss(4) //Let's be honest you shouldn't be alive by now - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "drunk") - -//used in human and monkey handle_environment() -/mob/living/carbon/proc/natural_bodytemperature_stabilization() - if (HAS_TRAIT(src, TRAIT_COLDBLOODED)) - return 0 //Return 0 as your natural temperature. Species proc handle_environment() will adjust your temperature based on this. - - var/body_temperature_difference = BODYTEMP_NORMAL - bodytemperature - switch(bodytemperature) - if(-INFINITY to BODYTEMP_COLD_DAMAGE_LIMIT) //Cold damage limit is 50 below the default, the temperature where you start to feel effects. - return max((body_temperature_difference * metabolism_efficiency / BODYTEMP_AUTORECOVERY_DIVISOR), BODYTEMP_AUTORECOVERY_MINIMUM) - if(BODYTEMP_COLD_DAMAGE_LIMIT to BODYTEMP_NORMAL) - return max(body_temperature_difference * metabolism_efficiency / BODYTEMP_AUTORECOVERY_DIVISOR, min(body_temperature_difference, BODYTEMP_AUTORECOVERY_MINIMUM/4)) - if(BODYTEMP_NORMAL to BODYTEMP_HEAT_DAMAGE_LIMIT) // Heat damage limit is 50 above the default, the temperature where you start to feel effects. - return min(body_temperature_difference * metabolism_efficiency / BODYTEMP_AUTORECOVERY_DIVISOR, max(body_temperature_difference, -BODYTEMP_AUTORECOVERY_MINIMUM/4)) - if(BODYTEMP_HEAT_DAMAGE_LIMIT to INFINITY) - return min((body_temperature_difference / BODYTEMP_AUTORECOVERY_DIVISOR), -BODYTEMP_AUTORECOVERY_MINIMUM) //We're dealing with negative numbers -///////// -//LIVER// -///////// - -/mob/living/carbon/proc/handle_liver() - var/obj/item/organ/liver/liver = getorganslot(ORGAN_SLOT_LIVER) - if((!dna && !liver) || (NOLIVER in dna.species.species_traits)) - return - if(liver) - if(liver.damage < liver.maxHealth) - liver.organ_flags |= ORGAN_FAILING - liver_failure() - else - liver_failure() - -/mob/living/carbon/proc/undergoing_liver_failure() - var/obj/item/organ/liver/liver = getorganslot(ORGAN_SLOT_LIVER) - if(liver && liver.failing) - return TRUE - -/mob/living/carbon/proc/return_liver_damage() - var/obj/item/organ/liver/liver = getorganslot(ORGAN_SLOT_LIVER) - if(liver) - return liver.damage - -/mob/living/carbon/proc/applyLiverDamage(var/d) - var/obj/item/organ/liver/L = getorganslot(ORGAN_SLOT_LIVER) - if(L) - L.damage += d - -/mob/living/carbon/proc/liver_failure() - reagents.end_metabolization(src, keep_liverless = TRUE) //Stops trait-based effects on reagents, to prevent permanent buffs - reagents.metabolize(src, can_overdose=FALSE, liverless = TRUE) - if(HAS_TRAIT(src, TRAIT_STABLELIVER)) - return - adjustToxLoss(4, TRUE, TRUE) - if(prob(30)) - to_chat(src, "You feel a stabbing pain in your abdomen!") - - -//////////////// -//BRAIN DAMAGE// -//////////////// - -/mob/living/carbon/proc/handle_brain_damage() - for(var/T in get_traumas()) - var/datum/brain_trauma/BT = T - BT.on_life() - -///////////////////////////////////// -//MONKEYS WITH TOO MUCH CHOLOESTROL// -///////////////////////////////////// - -/mob/living/carbon/proc/can_heartattack() - if(!needs_heart()) - return FALSE - var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART) - if(!heart || (heart.organ_flags & ORGAN_SYNTHETIC)) - return FALSE - return TRUE - -/mob/living/carbon/proc/needs_heart() - if(HAS_TRAIT(src, TRAIT_STABLEHEART)) - return FALSE - if(dna && dna.species && (NOBLOOD in dna.species.species_traits)) //not all carbons have species! - return FALSE - return TRUE - -/mob/living/carbon/proc/undergoing_cardiac_arrest() - var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART) - if(istype(heart) && heart.beating) - return FALSE - else if(!needs_heart()) - return FALSE - return TRUE - -/mob/living/carbon/proc/set_heartattack(status) - if(!can_heartattack()) - return FALSE - - var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART) - if(!istype(heart)) - return - - heart.beating = !status +/mob/living/carbon/Life() + set invisibility = 0 + + if(notransform) + return + + if(damageoverlaytemp) + damageoverlaytemp = 0 + update_damage_hud() + + //Reagent processing needs to come before breathing, to prevent edge cases. + handle_organs() + + . = ..() + + if (QDELETED(src)) + return + + if(.) //not dead + handle_blood() + + if(stat != DEAD) + var/bprv = handle_bodyparts() + if(bprv & BODYPART_LIFE_UPDATE_HEALTH) + updatehealth() + update_stamina() + + if(stat != DEAD) + handle_brain_damage() + + /* BUG_PROBABLE_CAUSE + if(stat != DEAD) + handle_liver() + */ + + if(stat == DEAD) + stop_sound_channel(CHANNEL_HEARTBEAT) + handle_death() + rot() + + //Updates the number of stored chemicals for powers + handle_changeling() + + if(stat != DEAD) + return 1 + +//Procs called while dead +/mob/living/carbon/proc/handle_death() + for(var/datum/reagent/R in reagents.reagent_list) + if(R.chemical_flags & REAGENT_DEAD_PROCESS) + R.on_mob_dead(src) + +/////////////// +// BREATHING // +/////////////// + +//Start of a breath chain, calls breathe() +/mob/living/carbon/handle_breathing(times_fired) + var/next_breath = 4 + var/obj/item/organ/lungs/L = getorganslot(ORGAN_SLOT_LUNGS) + var/obj/item/organ/heart/H = getorganslot(ORGAN_SLOT_HEART) + if(L) + if(L.damage > L.high_threshold) + next_breath-- + if(H) + if(H.damage > H.high_threshold) + next_breath-- + + if((times_fired % next_breath) == 0 || failed_last_breath) + breathe() //Breathe per 4 ticks if healthy, down to 2 if our lungs or heart are damaged, unless suffocating + if(failed_last_breath) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "suffocation", /datum/mood_event/suffocation) + else + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "suffocation") + else + if(istype(loc, /obj/)) + var/obj/location_as_object = loc + location_as_object.handle_internal_lifeform(src,0) + +//Second link in a breath chain, calls check_breath() +/mob/living/carbon/proc/breathe() + var/obj/item/organ/lungs = getorganslot(ORGAN_SLOT_LUNGS) + if(reagents.has_reagent(/datum/reagent/toxin/lexorin)) + return + if(istype(loc, /obj/machinery/atmospherics/components/unary/cryo_cell)) + return + if(istype(loc, /obj/item/dogborg/sleeper)) + return + if(ismob(loc)) + return + if(isbelly(loc)) + return + + var/datum/gas_mixture/environment + if(loc) + environment = loc.return_air() + + var/datum/gas_mixture/breath + + if(!getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + if(health <= HEALTH_THRESHOLD_FULLCRIT || (pulledby && pulledby.grab_state >= GRAB_KILL) || HAS_TRAIT(src, TRAIT_MAGIC_CHOKE) || (lungs && lungs.organ_flags & ORGAN_FAILING)) + losebreath++ //You can't breath at all when in critical or when being choked, so you're going to miss a breath + + else if(health <= crit_threshold) + losebreath += 0.25 //You're having trouble breathing in soft crit, so you'll miss a breath one in four times + + //Suffocate + if(losebreath >= 1) //You've missed a breath, take oxy damage + losebreath-- + if(prob(10)) + emote("gasp") + if(istype(loc, /obj/)) + var/obj/loc_as_obj = loc + loc_as_obj.handle_internal_lifeform(src,0) + else + //Breathe from internal + breath = get_breath_from_internal(BREATH_VOLUME) + + if(isnull(breath)) //in case of 0 pressure internals + + if(isobj(loc)) //Breathe from loc as object + var/obj/loc_as_obj = loc + breath = loc_as_obj.handle_internal_lifeform(src, BREATH_VOLUME) + + else if(isturf(loc)) //Breathe from loc as turf + var/breath_moles = 0 + if(environment) + breath_moles = environment.total_moles()*BREATH_PERCENTAGE + + breath = loc.remove_air(breath_moles) + else //Breathe from loc as obj again + if(istype(loc, /obj/)) + var/obj/loc_as_obj = loc + loc_as_obj.handle_internal_lifeform(src,0) + + check_breath(breath) + + if(breath) + loc.assume_air(breath) + air_update_turf() + +/mob/living/carbon/proc/has_smoke_protection() + if(HAS_TRAIT(src, TRAIT_NOBREATH)) + return TRUE + return FALSE + + +//Third link in a breath chain, calls handle_breath_temperature() +/mob/living/carbon/proc/check_breath(datum/gas_mixture/breath) + if((status_flags & GODMODE)) + return + + var/obj/item/organ/lungs = getorganslot(ORGAN_SLOT_LUNGS) + if(!lungs) + adjustOxyLoss(2) + + //CRIT + if(!breath || (breath.total_moles() == 0) || !lungs) + if(reagents.has_reagent(/datum/reagent/medicine/epinephrine) && lungs) + return + adjustOxyLoss(1) + + failed_last_breath = 1 + throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) + return 0 + + var/safe_oxy_min = 16 + var/safe_oxy_max = 50 + var/safe_co2_max = 10 + var/safe_tox_max = 0.05 + var/SA_para_min = 1 + var/SA_sleep_min = 5 + var/oxygen_used = 0 + var/breath_pressure = (breath.total_moles()*R_IDEAL_GAS_EQUATION*breath.temperature)/BREATH_VOLUME + + var/list/breath_gases = breath.gases + var/O2_partialpressure = (breath_gases[/datum/gas/oxygen]/breath.total_moles())*breath_pressure + var/Toxins_partialpressure = (breath_gases[/datum/gas/plasma]/breath.total_moles())*breath_pressure + var/CO2_partialpressure = (breath_gases[/datum/gas/carbon_dioxide]/breath.total_moles())*breath_pressure + + + //OXYGEN + if(O2_partialpressure > safe_oxy_max) // Too much Oxygen - blatant CO2 effect copy/pasta + if(!o2overloadtime) + o2overloadtime = world.time + else if(world.time - o2overloadtime > 120) + Dizzy(10) // better than a minute of you're fucked KO, but certainly a wake up call. Honk. + adjustOxyLoss(3) + if(world.time - o2overloadtime > 300) + adjustOxyLoss(8) + if(prob(20)) + emote("cough") + throw_alert("too_much_oxy", /obj/screen/alert/too_much_oxy) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "suffocation", /datum/mood_event/suffocation) + + if(O2_partialpressure < safe_oxy_min) //Not enough oxygen + if(prob(20)) + emote("gasp") + if(O2_partialpressure > 0) + var/ratio = 1 - O2_partialpressure/safe_oxy_min + adjustOxyLoss(min(5*ratio, 3)) + failed_last_breath = 1 + oxygen_used = breath_gases[/datum/gas/oxygen]*ratio + else + adjustOxyLoss(3) + failed_last_breath = 1 + throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "suffocation", /datum/mood_event/suffocation) + + else //Enough oxygen + failed_last_breath = 0 + o2overloadtime = 0 //reset our counter for this too + if(health >= crit_threshold) + adjustOxyLoss(-5) + oxygen_used = breath_gases[/datum/gas/oxygen] + clear_alert("not_enough_oxy") + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "suffocation") + + breath_gases[/datum/gas/oxygen] -= oxygen_used + breath_gases[/datum/gas/carbon_dioxide] += oxygen_used + + //CARBON DIOXIDE + if(CO2_partialpressure > safe_co2_max) + if(!co2overloadtime) + co2overloadtime = world.time + else if(world.time - co2overloadtime > 120) + Unconscious(60) + adjustOxyLoss(3) + if(world.time - co2overloadtime > 300) + adjustOxyLoss(8) + if(prob(20)) + emote("cough") + + else + co2overloadtime = 0 + + //TOXINS/PLASMA + if(Toxins_partialpressure > safe_tox_max) + var/ratio = (breath_gases[/datum/gas/plasma]/safe_tox_max) * 10 + adjustToxLoss(CLAMP(ratio, MIN_TOXIC_GAS_DAMAGE, MAX_TOXIC_GAS_DAMAGE)) + throw_alert("too_much_tox", /obj/screen/alert/too_much_tox) + else + clear_alert("too_much_tox") + + //NITROUS OXIDE + if(breath_gases[/datum/gas/nitrous_oxide]) + var/SA_partialpressure = (breath_gases[/datum/gas/nitrous_oxide]/breath.total_moles())*breath_pressure + if(SA_partialpressure > SA_para_min) + Unconscious(60) + if(SA_partialpressure > SA_sleep_min) + Sleeping(max(AmountSleeping() + 40, 200)) + else if(SA_partialpressure > 0.01) + if(prob(20)) + emote(pick("giggle","laugh")) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "chemical_euphoria", /datum/mood_event/chemical_euphoria) + else + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "chemical_euphoria") + + //BZ (Facepunch port of their Agent B) + if(breath_gases[/datum/gas/bz]) + var/bz_partialpressure = (breath_gases[/datum/gas/bz]/breath.total_moles())*breath_pressure + if(bz_partialpressure > 1) + hallucination += 10 + else if(bz_partialpressure > 0.01) + hallucination += 5 + + //TRITIUM + if(breath_gases[/datum/gas/tritium]) + var/tritium_partialpressure = (breath_gases[/datum/gas/tritium]/breath.total_moles())*breath_pressure + radiation += tritium_partialpressure/10 + + //NITRYL + if(breath_gases[/datum/gas/nitryl]) + var/nitryl_partialpressure = (breath_gases[/datum/gas/nitryl]/breath.total_moles())*breath_pressure + adjustFireLoss(nitryl_partialpressure/4) + + //MIASMA + if(breath_gases[/datum/gas/miasma]) + var/miasma_partialpressure = (breath_gases[/datum/gas/miasma]/breath.total_moles())*breath_pressure + if(miasma_partialpressure > MINIMUM_MOLES_DELTA_TO_MOVE) + + if(prob(0.05 * miasma_partialpressure)) + var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(2,3) + miasma_disease.name = "Unknown" + ForceContractDisease(miasma_disease, TRUE, TRUE) + + //Miasma side effects + switch(miasma_partialpressure) + if(1 to 5) + // At lower pp, give out a little warning + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "smell") + if(prob(5)) + to_chat(src, "There is an unpleasant smell in the air.") + if(5 to 20) + //At somewhat higher pp, warning becomes more obvious + if(prob(15)) + to_chat(src, "You smell something horribly decayed inside this room.") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/bad_smell) + if(15 to 30) + //Small chance to vomit. By now, people have internals on anyway + if(prob(5)) + to_chat(src, "The stench of rotting carcasses is unbearable!") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/nauseating_stench) + vomit() + if(30 to INFINITY) + //Higher chance to vomit. Let the horror start + if(prob(25)) + to_chat(src, "The stench of rotting carcasses is unbearable!") + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "smell", /datum/mood_event/disgust/nauseating_stench) + vomit() + else + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "smell") + + + //Clear all moods if no miasma at all + else + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "smell") + + + + + GAS_GARBAGE_COLLECT(breath.gases) + + //BREATH TEMPERATURE + handle_breath_temperature(breath) + + return 1 + +//Fourth and final link in a breath chain +/mob/living/carbon/proc/handle_breath_temperature(datum/gas_mixture/breath) + return + +/mob/living/carbon/proc/get_breath_from_internal(volume_needed) + var/obj/item/clothing/check + var/internals = FALSE + + for(check in GET_INTERNAL_SLOTS(src)) + if(CHECK_BITFIELD(check.clothing_flags, ALLOWINTERNALS)) + internals = TRUE + if(internal) + if(internal.loc != src) + internal = null + update_internals_hud_icon(0) + else if (!internals && !getorganslot(ORGAN_SLOT_BREATHING_TUBE)) + internal = null + update_internals_hud_icon(0) + else + update_internals_hud_icon(1) + . = internal.remove_air_volume(volume_needed) + if(!.) + return FALSE //to differentiate between no internals and active, but empty internals + +// Make corpses rot, emitting miasma +/mob/living/carbon/proc/rot() + // Properly stored corpses shouldn't create miasma + if(istype(loc, /obj/structure/closet/crate/coffin)|| istype(loc, /obj/structure/closet/body_bag) || istype(loc, /obj/structure/bodycontainer)) + return + + // No decay if formaldehyde in corpse or when the corpse is charred + if(reagents.has_reagent(/datum/reagent/toxin/formaldehyde, 15) || HAS_TRAIT(src, TRAIT_HUSK)) + return + + // Also no decay if corpse chilled or not organic/undead + if(bodytemperature <= T0C-10 || (!(MOB_ORGANIC in mob_biotypes) && !(MOB_UNDEAD in mob_biotypes))) + return + + // Wait a bit before decaying + if(world.time - timeofdeath < 1200) + return + + var/deceasedturf = get_turf(src) + + // Closed turfs don't have any air in them, so no gas building up + if(!istype(deceasedturf,/turf/open)) + return + + var/turf/open/miasma_turf = deceasedturf + + var/list/cached_gases = miasma_turf.air.gases + + cached_gases[/datum/gas/miasma] += 0.1 + +/mob/living/carbon/proc/handle_blood() + return + +/mob/living/carbon/proc/handle_bodyparts() + for(var/I in bodyparts) + var/obj/item/bodypart/BP = I + if(BP.needs_processing) + . |= BP.on_life() + +/mob/living/carbon/proc/handle_organs() + if(stat != DEAD) + for(var/V in internal_organs) + var/obj/item/organ/O = V + if(O) + O.on_life() + else + for(var/V in internal_organs) + var/obj/item/organ/O = V + if(O) + O.on_death() //Needed so organs decay while inside the body. + +/mob/living/carbon/handle_diseases() + for(var/thing in diseases) + var/datum/disease/D = thing + if(prob(D.infectivity)) + D.spread() + + if(stat != DEAD || D.process_dead) + D.stage_act() + +//todo generalize this and move hud out +/mob/living/carbon/proc/handle_changeling() + if(mind && hud_used && hud_used.lingchemdisplay) + var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling) + changeling.regenerate() + hud_used.lingchemdisplay.invisibility = 0 + hud_used.lingchemdisplay.maptext = "
                [round(changeling.chem_charges)]
                " + else + hud_used.lingchemdisplay.invisibility = INVISIBILITY_ABSTRACT + + +/mob/living/carbon/handle_mutations_and_radiation() + if(dna && dna.temporary_mutations.len) + var/datum/mutation/human/HM + for(var/mut in dna.temporary_mutations) + if(dna.temporary_mutations[mut] < world.time) + if(mut == UI_CHANGED) + if(dna.previous["UI"]) + dna.uni_identity = merge_text(dna.uni_identity,dna.previous["UI"]) + updateappearance(mutations_overlay_update=1) + dna.previous.Remove("UI") + dna.temporary_mutations.Remove(mut) + continue + if(mut == UE_CHANGED) + if(dna.previous["name"]) + real_name = dna.previous["name"] + name = real_name + dna.previous.Remove("name") + if(dna.previous["UE"]) + dna.unique_enzymes = dna.previous["UE"] + dna.previous.Remove("UE") + if(dna.previous["blood_type"]) + dna.blood_type = dna.previous["blood_type"] + dna.previous.Remove("blood_type") + dna.temporary_mutations.Remove(mut) + continue + HM = GLOB.mutations_list[mut] + HM.force_lose(src) + dna.temporary_mutations.Remove(mut) + + radiation -= min(radiation, RAD_LOSS_PER_TICK) + if(radiation > RAD_MOB_SAFE) + adjustToxLoss(log(radiation-RAD_MOB_SAFE)*RAD_TOX_COEFFICIENT) + +/mob/living/carbon/handle_stomach() + set waitfor = 0 + for(var/mob/living/M in stomach_contents) + if(M.loc != src) + stomach_contents.Remove(M) + continue + if(iscarbon(M) && stat != DEAD) + if(M.stat == DEAD) + M.death(1) + stomach_contents.Remove(M) + qdel(M) + continue + if(SSmobs.times_fired%3==1) + if(!(M.status_flags & GODMODE)) + M.adjustBruteLoss(5) + nutrition += 10 + + +/* +Alcohol Poisoning Chart +Note that all higher effects of alcohol poisoning will inherit effects for smaller amounts (i.e. light poisoning inherts from slight poisoning) +In addition, severe effects won't always trigger unless the drink is poisonously strong +All effects don't start immediately, but rather get worse over time; the rate is affected by the imbiber's alcohol tolerance + +0: Non-alcoholic +1-10: Barely classifiable as alcohol - occassional slurring +11-20: Slight alcohol content - slurring +21-30: Below average - imbiber begins to look slightly drunk +31-40: Just below average - no unique effects +41-50: Average - mild disorientation, imbiber begins to look drunk +51-60: Just above average - disorientation, vomiting, imbiber begins to look heavily drunk +61-70: Above average - small chance of blurry vision, imbiber begins to look smashed +71-80: High alcohol content - blurry vision, imbiber completely shitfaced +81-90: Extremely high alcohol content - light brain damage, passing out +91-100: Dangerously toxic - swift death +*/ +#define BALLMER_POINTS 5 +GLOBAL_LIST_INIT(ballmer_good_msg, list("Hey guys, what if we rolled out a bluespace wiring system so mice can't destroy the powergrid anymore?", + "Hear me out here. What if, and this is just a theory, we made R&D controllable from our PDAs?", + "I'm thinking we should roll out a git repository for our research under the AGPLv3 license so that we can share it among the other stations freely.", + "I dunno about you guys, but IDs and PDAs being separate is clunky as fuck. Maybe we should merge them into a chip in our arms? That way they can't be stolen easily.", + "Why the fuck aren't we just making every pair of shoes into galoshes? We have the technology.")) +GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put a webserver that's automatically turned on with default admin passwords into every PDA?", + "So like, you know how we separate our codebase from the master copy that runs on our consumer boxes? What if we merged the two and undid the separation between codebase and server?", + "Dude, radical idea: H.O.N.K mechs but with no bananium required.", + "Best idea ever: Disposal pipes instead of hallways.")) + +//this updates all special effects: stun, sleeping, knockdown, druggy, stuttering, etc.. +/mob/living/carbon/handle_status_effects() + ..() + if(getStaminaLoss() && !combatmode)//CIT CHANGE - prevents stamina regen while combat mode is active + adjustStaminaLoss(resting ? (recoveringstam ? -7.5 : -6) : -3)//CIT CHANGE - decreases adjuststaminaloss to stop stamina damage from being such a joke + + if(!recoveringstam && incomingstammult != 1) + incomingstammult = max(0.01, incomingstammult) + incomingstammult = min(1, incomingstammult*2) + + //CIT CHANGES START HERE. STAMINA BUFFER STUFF + if(bufferedstam && world.time > stambufferregentime) + var/drainrate = max((bufferedstam*(bufferedstam/(5)))*0.1,1) + bufferedstam = max(bufferedstam - drainrate, 0) + adjustStaminaLoss(drainrate*0.5) + //END OF CIT CHANGES + + var/restingpwr = 1 + 4 * resting + + //Dizziness + if(dizziness) + var/client/C = client + var/pixel_x_diff = 0 + var/pixel_y_diff = 0 + var/temp + var/saved_dizz = dizziness + if(C) + var/oldsrc = src + var/amplitude = dizziness*(sin(dizziness * world.time) + 1) // This shit is annoying at high strength + src = null + spawn(0) + if(C) + temp = amplitude * sin(saved_dizz * world.time) + pixel_x_diff += temp + C.pixel_x += temp + temp = amplitude * cos(saved_dizz * world.time) + pixel_y_diff += temp + C.pixel_y += temp + sleep(3) + if(C) + temp = amplitude * sin(saved_dizz * world.time) + pixel_x_diff += temp + C.pixel_x += temp + temp = amplitude * cos(saved_dizz * world.time) + pixel_y_diff += temp + C.pixel_y += temp + sleep(3) + if(C) + C.pixel_x -= pixel_x_diff + C.pixel_y -= pixel_y_diff + src = oldsrc + dizziness = max(dizziness - restingpwr, 0) + + if(drowsyness) + drowsyness = max(drowsyness - restingpwr, 0) + blur_eyes(2) + if(prob(5)) + AdjustSleeping(20) + Unconscious(100) + + //Jitteriness + if(jitteriness) + do_jitter_animation(jitteriness) + jitteriness = max(jitteriness - restingpwr, 0) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "jittery", /datum/mood_event/jittery) + else + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "jittery") + + if(stuttering) + stuttering = max(stuttering-1, 0) + + if(slurring || drunkenness) + slurring = max(slurring-1,0,drunkenness) + + if(cultslurring) + cultslurring = max(cultslurring-1, 0) + + if(silent) + silent = max(silent-1, 0) + + if(druggy) + adjust_drugginess(-1) + + if(hallucination) + handle_hallucinations() + + if(drunkenness) + drunkenness = max(drunkenness - (drunkenness * 0.04), 0) + if(drunkenness >= 6) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "drunk", /datum/mood_event/drunk) + jitteriness = max(jitteriness - 3, 0) + if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) + adjustBruteLoss(-0.12, FALSE) + adjustFireLoss(-0.06, FALSE) + + if(mind && (mind.assigned_role == "Scientist" || mind.assigned_role == "Research Director")) + if(SSresearch.science_tech) + if(drunkenness >= 12.9 && drunkenness <= 13.8) + drunkenness = round(drunkenness, 0.01) + var/ballmer_percent = 0 + if(drunkenness == 13.35) // why run math if I dont have to + ballmer_percent = 1 + else + ballmer_percent = (-abs(drunkenness - 13.35) / 0.9) + 1 + if(prob(5)) + say(pick(GLOB.ballmer_good_msg), forced = "ballmer") + SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = BALLMER_POINTS * ballmer_percent)) + if(drunkenness > 26) // by this point you're into windows ME territory + if(prob(5)) + SSresearch.science_tech.remove_point_list(list(TECHWEB_POINT_TYPE_GENERIC = BALLMER_POINTS)) + say(pick(GLOB.ballmer_windows_me_msg), forced = "ballmer") + + if(drunkenness >= 41) + if(prob(25)) + confused += 2 + Dizzy(10) + if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) // effects stack with lower tiers + adjustBruteLoss(-0.3, FALSE) + adjustFireLoss(-0.15, FALSE) + + if(drunkenness >= 51) + if(prob(5)) + confused += 10 + vomit() + Dizzy(25) + + if(drunkenness >= 61) + if(prob(50)) + blur_eyes(5) + if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) + adjustBruteLoss(-0.4, FALSE) + adjustFireLoss(-0.2, FALSE) + + if(drunkenness >= 71) + blur_eyes(5) + + if(drunkenness >= 81) + adjustToxLoss(0.2) + if(prob(5) && !stat) + to_chat(src, "Maybe you should lie down for a bit...") + + if(drunkenness >= 91) + adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.4, 60) + if(prob(20) && !stat) + if(SSshuttle.emergency.mode == SHUTTLE_DOCKED && is_station_level(z)) //QoL mainly + to_chat(src, "You're so tired... but you can't miss that shuttle...") + else + to_chat(src, "Just a quick nap...") + Sleeping(900) + + if(drunkenness >= 101) + adjustToxLoss(4) //Let's be honest you shouldn't be alive by now + else + SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "drunk") + +//used in human and monkey handle_environment() +/mob/living/carbon/proc/natural_bodytemperature_stabilization() + if (HAS_TRAIT(src, TRAIT_COLDBLOODED)) + return 0 //Return 0 as your natural temperature. Species proc handle_environment() will adjust your temperature based on this. + + var/body_temperature_difference = BODYTEMP_NORMAL - bodytemperature + switch(bodytemperature) + if(-INFINITY to BODYTEMP_COLD_DAMAGE_LIMIT) //Cold damage limit is 50 below the default, the temperature where you start to feel effects. + return max((body_temperature_difference * metabolism_efficiency / BODYTEMP_AUTORECOVERY_DIVISOR), BODYTEMP_AUTORECOVERY_MINIMUM) + if(BODYTEMP_COLD_DAMAGE_LIMIT to BODYTEMP_NORMAL) + return max(body_temperature_difference * metabolism_efficiency / BODYTEMP_AUTORECOVERY_DIVISOR, min(body_temperature_difference, BODYTEMP_AUTORECOVERY_MINIMUM/4)) + if(BODYTEMP_NORMAL to BODYTEMP_HEAT_DAMAGE_LIMIT) // Heat damage limit is 50 above the default, the temperature where you start to feel effects. + return min(body_temperature_difference * metabolism_efficiency / BODYTEMP_AUTORECOVERY_DIVISOR, max(body_temperature_difference, -BODYTEMP_AUTORECOVERY_MINIMUM/4)) + if(BODYTEMP_HEAT_DAMAGE_LIMIT to INFINITY) + return min((body_temperature_difference / BODYTEMP_AUTORECOVERY_DIVISOR), -BODYTEMP_AUTORECOVERY_MINIMUM) //We're dealing with negative numbers +///////// +//LIVER// +///////// + +/mob/living/carbon/proc/handle_liver() + var/obj/item/organ/liver/liver = getorganslot(ORGAN_SLOT_LIVER) + if((!dna && !liver) || (NOLIVER in dna.species.species_traits)) + return + if(liver) + if(liver.damage < liver.maxHealth) + liver.organ_flags |= ORGAN_FAILING + liver_failure() + else + liver_failure() + +/mob/living/carbon/proc/undergoing_liver_failure() + var/obj/item/organ/liver/liver = getorganslot(ORGAN_SLOT_LIVER) + if(liver && liver.failing) + return TRUE + +/mob/living/carbon/proc/return_liver_damage() + var/obj/item/organ/liver/liver = getorganslot(ORGAN_SLOT_LIVER) + if(liver) + return liver.damage + +/mob/living/carbon/proc/applyLiverDamage(var/d) + var/obj/item/organ/liver/L = getorganslot(ORGAN_SLOT_LIVER) + if(L) + L.damage += d + +/mob/living/carbon/proc/liver_failure() + reagents.end_metabolization(src, keep_liverless = TRUE) //Stops trait-based effects on reagents, to prevent permanent buffs + reagents.metabolize(src, can_overdose=FALSE, liverless = TRUE) + if(HAS_TRAIT(src, TRAIT_STABLELIVER)) + return + adjustToxLoss(4, TRUE, TRUE) + if(prob(30)) + to_chat(src, "You feel a stabbing pain in your abdomen!") + + +//////////////// +//BRAIN DAMAGE// +//////////////// + +/mob/living/carbon/proc/handle_brain_damage() + for(var/T in get_traumas()) + var/datum/brain_trauma/BT = T + BT.on_life() + +///////////////////////////////////// +//MONKEYS WITH TOO MUCH CHOLOESTROL// +///////////////////////////////////// + +/mob/living/carbon/proc/can_heartattack() + if(!needs_heart()) + return FALSE + var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART) + if(!heart || (heart.organ_flags & ORGAN_SYNTHETIC)) + return FALSE + return TRUE + +/mob/living/carbon/proc/needs_heart() + if(HAS_TRAIT(src, TRAIT_STABLEHEART)) + return FALSE + if(dna && dna.species && (NOBLOOD in dna.species.species_traits)) //not all carbons have species! + return FALSE + return TRUE + +/mob/living/carbon/proc/undergoing_cardiac_arrest() + var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART) + if(istype(heart) && heart.beating) + return FALSE + else if(!needs_heart()) + return FALSE + return TRUE + +/mob/living/carbon/proc/set_heartattack(status) + if(!can_heartattack()) + return FALSE + + var/obj/item/organ/heart/heart = getorganslot(ORGAN_SLOT_HEART) + if(!istype(heart)) + return + + heart.beating = !status diff --git a/code/modules/mob/living/carbon/monkey/death.dm b/code/modules/mob/living/carbon/monkey/death.dm index 5b9125a5d1..22621a3750 100644 --- a/code/modules/mob/living/carbon/monkey/death.dm +++ b/code/modules/mob/living/carbon/monkey/death.dm @@ -1,9 +1,9 @@ -/mob/living/carbon/monkey/gib_animation() - new /obj/effect/temp_visual/gib_animation(loc, "gibbed-m") - -/mob/living/carbon/monkey/dust_animation() - new /obj/effect/temp_visual/dust_animation(loc, "dust-m") - -/mob/living/carbon/monkey/death(gibbed) - walk(src,0) // Stops dead monkeys from fleeing their attacker or climbing out from inside His Grace - . = ..() +/mob/living/carbon/monkey/gib_animation() + new /obj/effect/temp_visual/gib_animation(loc, "gibbed-m") + +/mob/living/carbon/monkey/dust_animation() + new /obj/effect/temp_visual/dust_animation(loc, "dust-m") + +/mob/living/carbon/monkey/death(gibbed) + walk(src,0) // Stops dead monkeys from fleeing their attacker or climbing out from inside His Grace + . = ..() diff --git a/code/modules/mob/living/carbon/monkey/life.dm b/code/modules/mob/living/carbon/monkey/life.dm index 906e138b0a..33b9dffaf0 100644 --- a/code/modules/mob/living/carbon/monkey/life.dm +++ b/code/modules/mob/living/carbon/monkey/life.dm @@ -1,172 +1,172 @@ - - -/mob/living/carbon/monkey - - -/mob/living/carbon/monkey/Life() - set invisibility = 0 - - if (notransform) - return - - if(..()) - - if(!client) - if(stat == CONSCIOUS) - if(on_fire || buckled || restrained() || (resting && canmove)) //CIT CHANGE - makes it so monkeys attempt to resist if they're resting) - if(!resisting && prob(MONKEY_RESIST_PROB)) - resisting = TRUE - walk_to(src,0) - resist() - else if(resisting) - resisting = FALSE - else if((mode == MONKEY_IDLE && !pickupTarget && !prob(MONKEY_SHENANIGAN_PROB)) || !handle_combat()) - if(prob(25) && canmove && isturf(loc) && !pulledby) - step(src, pick(GLOB.cardinals)) - else if(prob(1)) - emote(pick("scratch","jump","roll","tail")) - else - walk_to(src,0) - -/mob/living/carbon/monkey/handle_mutations_and_radiation() - if(radiation) - if(radiation > RAD_MOB_KNOCKDOWN && prob(RAD_MOB_KNOCKDOWN_PROB)) - if(!IsKnockdown()) - emote("collapse") - Knockdown(RAD_MOB_KNOCKDOWN_AMOUNT) - to_chat(src, "You feel weak.") - if(radiation > RAD_MOB_MUTATE) - if(prob(1)) - to_chat(src, "You mutate!") - randmutb() - emote("gasp") - domutcheck() - - if(radiation > RAD_MOB_MUTATE * 2 && prob(50)) - gorillize() - return - if(radiation > RAD_MOB_VOMIT && prob(RAD_MOB_VOMIT_PROB)) - vomit(10, TRUE) - return ..() - -/mob/living/carbon/monkey/handle_breath_temperature(datum/gas_mixture/breath) - if(abs(BODYTEMP_NORMAL - breath.temperature) > 50) - switch(breath.temperature) - if(-INFINITY to 120) - adjustFireLoss(3) - if(120 to 200) - adjustFireLoss(1.5) - if(200 to 260) - adjustFireLoss(0.5) - if(360 to 400) - adjustFireLoss(2) - if(400 to 1000) - adjustFireLoss(3) - if(1000 to INFINITY) - adjustFireLoss(8) - -/mob/living/carbon/monkey/handle_environment(datum/gas_mixture/environment) - if(!environment) - return - - var/loc_temp = get_temperature(environment) - - if(stat != DEAD) - adjust_bodytemperature(natural_bodytemperature_stabilization()) - - if(!on_fire) //If you're on fire, you do not heat up or cool down based on surrounding gases - if(loc_temp < bodytemperature) - adjust_bodytemperature(max((loc_temp - bodytemperature) / BODYTEMP_COLD_DIVISOR, BODYTEMP_COOLING_MAX)) - else - adjust_bodytemperature(min((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR, BODYTEMP_HEATING_MAX)) - - - if(bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT && !HAS_TRAIT(src, TRAIT_RESISTHEAT)) - switch(bodytemperature) - if(360 to 400) - throw_alert("temp", /obj/screen/alert/hot, 1) - apply_damage(HEAT_DAMAGE_LEVEL_1, BURN) - if(400 to 460) - throw_alert("temp", /obj/screen/alert/hot, 2) - apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) - if(460 to INFINITY) - throw_alert("temp", /obj/screen/alert/hot, 3) - if(on_fire) - apply_damage(HEAT_DAMAGE_LEVEL_3, BURN) - else - apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) - - else if(bodytemperature < BODYTEMP_COLD_DAMAGE_LIMIT && !HAS_TRAIT(src, TRAIT_RESISTCOLD)) - if(!istype(loc, /obj/machinery/atmospherics/components/unary/cryo_cell)) - switch(bodytemperature) - if(200 to 260) - throw_alert("temp", /obj/screen/alert/cold, 1) - apply_damage(COLD_DAMAGE_LEVEL_1, BURN) - if(120 to 200) - throw_alert("temp", /obj/screen/alert/cold, 2) - apply_damage(COLD_DAMAGE_LEVEL_2, BURN) - if(-INFINITY to 120) - throw_alert("temp", /obj/screen/alert/cold, 3) - apply_damage(COLD_DAMAGE_LEVEL_3, BURN) - else - clear_alert("temp") - - else - clear_alert("temp") - - //Account for massive pressure differences - - var/pressure = environment.return_pressure() - var/adjusted_pressure = calculate_affecting_pressure(pressure) //Returns how much pressure actually affects the mob. - switch(adjusted_pressure) - if(HAZARD_HIGH_PRESSURE to INFINITY) - adjustBruteLoss( min( ( (adjusted_pressure / HAZARD_HIGH_PRESSURE) -1 )*PRESSURE_DAMAGE_COEFFICIENT , MAX_HIGH_PRESSURE_DAMAGE) ) - throw_alert("pressure", /obj/screen/alert/highpressure, 2) - if(WARNING_HIGH_PRESSURE to HAZARD_HIGH_PRESSURE) - throw_alert("pressure", /obj/screen/alert/highpressure, 1) - if(WARNING_LOW_PRESSURE to WARNING_HIGH_PRESSURE) - clear_alert("pressure") - if(HAZARD_LOW_PRESSURE to WARNING_LOW_PRESSURE) - throw_alert("pressure", /obj/screen/alert/lowpressure, 1) - else - adjustBruteLoss( LOW_PRESSURE_DAMAGE ) - throw_alert("pressure", /obj/screen/alert/lowpressure, 2) - - return - -/mob/living/carbon/monkey/handle_random_events() - if (prob(1) && prob(2)) - emote("scratch") - -/mob/living/carbon/monkey/has_smoke_protection() - if(wear_mask) - if(wear_mask.clothing_flags & BLOCK_GAS_SMOKE_EFFECT) - return 1 - -/mob/living/carbon/monkey/handle_fire() - . = ..() - if(on_fire) - - //the fire tries to damage the exposed clothes and items - var/list/burning_items = list() - //HEAD// - var/obj/item/clothing/head_clothes = null - if(wear_mask) - head_clothes = wear_mask - if(wear_neck) - head_clothes = wear_neck - if(head) - head_clothes = head - if(head_clothes) - burning_items += head_clothes - - if(back) - burning_items += back - - for(var/X in burning_items) - var/obj/item/I = X - if(!(I.resistance_flags & FIRE_PROOF)) - I.take_damage(fire_stacks, BURN, "fire", 0) - - adjust_bodytemperature(BODYTEMP_HEATING_MAX) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire) + + +/mob/living/carbon/monkey + + +/mob/living/carbon/monkey/Life() + set invisibility = 0 + + if (notransform) + return + + if(..()) + + if(!client) + if(stat == CONSCIOUS) + if(on_fire || buckled || restrained() || (resting && canmove)) //CIT CHANGE - makes it so monkeys attempt to resist if they're resting) + if(!resisting && prob(MONKEY_RESIST_PROB)) + resisting = TRUE + walk_to(src,0) + resist() + else if(resisting) + resisting = FALSE + else if((mode == MONKEY_IDLE && !pickupTarget && !prob(MONKEY_SHENANIGAN_PROB)) || !handle_combat()) + if(prob(25) && canmove && isturf(loc) && !pulledby) + step(src, pick(GLOB.cardinals)) + else if(prob(1)) + emote(pick("scratch","jump","roll","tail")) + else + walk_to(src,0) + +/mob/living/carbon/monkey/handle_mutations_and_radiation() + if(radiation) + if(radiation > RAD_MOB_KNOCKDOWN && prob(RAD_MOB_KNOCKDOWN_PROB)) + if(!IsKnockdown()) + emote("collapse") + Knockdown(RAD_MOB_KNOCKDOWN_AMOUNT) + to_chat(src, "You feel weak.") + if(radiation > RAD_MOB_MUTATE) + if(prob(1)) + to_chat(src, "You mutate!") + randmutb() + emote("gasp") + domutcheck() + + if(radiation > RAD_MOB_MUTATE * 2 && prob(50)) + gorillize() + return + if(radiation > RAD_MOB_VOMIT && prob(RAD_MOB_VOMIT_PROB)) + vomit(10, TRUE) + return ..() + +/mob/living/carbon/monkey/handle_breath_temperature(datum/gas_mixture/breath) + if(abs(BODYTEMP_NORMAL - breath.temperature) > 50) + switch(breath.temperature) + if(-INFINITY to 120) + adjustFireLoss(3) + if(120 to 200) + adjustFireLoss(1.5) + if(200 to 260) + adjustFireLoss(0.5) + if(360 to 400) + adjustFireLoss(2) + if(400 to 1000) + adjustFireLoss(3) + if(1000 to INFINITY) + adjustFireLoss(8) + +/mob/living/carbon/monkey/handle_environment(datum/gas_mixture/environment) + if(!environment) + return + + var/loc_temp = get_temperature(environment) + + if(stat != DEAD) + adjust_bodytemperature(natural_bodytemperature_stabilization()) + + if(!on_fire) //If you're on fire, you do not heat up or cool down based on surrounding gases + if(loc_temp < bodytemperature) + adjust_bodytemperature(max((loc_temp - bodytemperature) / BODYTEMP_COLD_DIVISOR, BODYTEMP_COOLING_MAX)) + else + adjust_bodytemperature(min((loc_temp - bodytemperature) / BODYTEMP_HEAT_DIVISOR, BODYTEMP_HEATING_MAX)) + + + if(bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT && !HAS_TRAIT(src, TRAIT_RESISTHEAT)) + switch(bodytemperature) + if(360 to 400) + throw_alert("temp", /obj/screen/alert/hot, 1) + apply_damage(HEAT_DAMAGE_LEVEL_1, BURN) + if(400 to 460) + throw_alert("temp", /obj/screen/alert/hot, 2) + apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) + if(460 to INFINITY) + throw_alert("temp", /obj/screen/alert/hot, 3) + if(on_fire) + apply_damage(HEAT_DAMAGE_LEVEL_3, BURN) + else + apply_damage(HEAT_DAMAGE_LEVEL_2, BURN) + + else if(bodytemperature < BODYTEMP_COLD_DAMAGE_LIMIT && !HAS_TRAIT(src, TRAIT_RESISTCOLD)) + if(!istype(loc, /obj/machinery/atmospherics/components/unary/cryo_cell)) + switch(bodytemperature) + if(200 to 260) + throw_alert("temp", /obj/screen/alert/cold, 1) + apply_damage(COLD_DAMAGE_LEVEL_1, BURN) + if(120 to 200) + throw_alert("temp", /obj/screen/alert/cold, 2) + apply_damage(COLD_DAMAGE_LEVEL_2, BURN) + if(-INFINITY to 120) + throw_alert("temp", /obj/screen/alert/cold, 3) + apply_damage(COLD_DAMAGE_LEVEL_3, BURN) + else + clear_alert("temp") + + else + clear_alert("temp") + + //Account for massive pressure differences + + var/pressure = environment.return_pressure() + var/adjusted_pressure = calculate_affecting_pressure(pressure) //Returns how much pressure actually affects the mob. + switch(adjusted_pressure) + if(HAZARD_HIGH_PRESSURE to INFINITY) + adjustBruteLoss( min( ( (adjusted_pressure / HAZARD_HIGH_PRESSURE) -1 )*PRESSURE_DAMAGE_COEFFICIENT , MAX_HIGH_PRESSURE_DAMAGE) ) + throw_alert("pressure", /obj/screen/alert/highpressure, 2) + if(WARNING_HIGH_PRESSURE to HAZARD_HIGH_PRESSURE) + throw_alert("pressure", /obj/screen/alert/highpressure, 1) + if(WARNING_LOW_PRESSURE to WARNING_HIGH_PRESSURE) + clear_alert("pressure") + if(HAZARD_LOW_PRESSURE to WARNING_LOW_PRESSURE) + throw_alert("pressure", /obj/screen/alert/lowpressure, 1) + else + adjustBruteLoss( LOW_PRESSURE_DAMAGE ) + throw_alert("pressure", /obj/screen/alert/lowpressure, 2) + + return + +/mob/living/carbon/monkey/handle_random_events() + if (prob(1) && prob(2)) + emote("scratch") + +/mob/living/carbon/monkey/has_smoke_protection() + if(wear_mask) + if(wear_mask.clothing_flags & BLOCK_GAS_SMOKE_EFFECT) + return 1 + +/mob/living/carbon/monkey/handle_fire() + . = ..() + if(on_fire) + + //the fire tries to damage the exposed clothes and items + var/list/burning_items = list() + //HEAD// + var/obj/item/clothing/head_clothes = null + if(wear_mask) + head_clothes = wear_mask + if(wear_neck) + head_clothes = wear_neck + if(head) + head_clothes = head + if(head_clothes) + burning_items += head_clothes + + if(back) + burning_items += back + + for(var/X in burning_items) + var/obj/item/I = X + if(!(I.resistance_flags & FIRE_PROOF)) + I.take_damage(fire_stacks, BURN, "fire", 0) + + adjust_bodytemperature(BODYTEMP_HEATING_MAX) + SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire) diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm index 14c3a76307..ea5203778b 100644 --- a/code/modules/mob/living/carbon/monkey/monkey.dm +++ b/code/modules/mob/living/carbon/monkey/monkey.dm @@ -1,174 +1,174 @@ -/mob/living/carbon/monkey - name = "monkey" - verb_say = "chimpers" - initial_language_holder = /datum/language_holder/monkey - icon = 'icons/mob/monkey.dmi' - icon_state = "" - gender = NEUTER - pass_flags = PASSTABLE - ventcrawler = VENTCRAWLER_NUDE - mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/monkey = 5, /obj/item/stack/sheet/animalhide/monkey = 1) - type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab/monkey - gib_type = /obj/effect/decal/cleanable/blood/gibs - unique_name = TRUE - bodyparts = list(/obj/item/bodypart/chest/monkey, /obj/item/bodypart/head/monkey, /obj/item/bodypart/l_arm/monkey, - /obj/item/bodypart/r_arm/monkey, /obj/item/bodypart/r_leg/monkey, /obj/item/bodypart/l_leg/monkey) - hud_type = /datum/hud/monkey - can_be_held = "monkey" - -/mob/living/carbon/monkey/Initialize(mapload, cubespawned=FALSE, mob/spawner) - verbs += /mob/living/proc/mob_sleep - verbs += /mob/living/proc/lay_down - - if(unique_name) //used to exclude pun pun - gender = pick(MALE, FEMALE) - real_name = name - - //initialize limbs - create_bodyparts() - create_internal_organs() - - . = ..() - - if (cubespawned) - var/cap = CONFIG_GET(number/monkeycap) - if (LAZYLEN(SSmobs.cubemonkeys) > cap) - if (spawner) - to_chat(spawner, "Bluespace harmonics prevent the spawning of more than [cap] monkeys on the station at one time!") - return INITIALIZE_HINT_QDEL - SSmobs.cubemonkeys += src - - create_dna(src) - dna.initialize_dna(random_blood_type()) - -/mob/living/carbon/monkey/Destroy() - SSmobs.cubemonkeys -= src - return ..() - -/mob/living/carbon/monkey/generate_mob_holder() - var/obj/item/clothing/head/mob_holder/holder = new(get_turf(src), src, "monkey", 'icons/mob/animals_held.dmi', 'icons/mob/animals_held_lh.dmi', 'icons/mob/animals_held_rh.dmi', TRUE) - return holder - -/mob/living/carbon/monkey/create_internal_organs() - internal_organs += new /obj/item/organ/appendix - internal_organs += new /obj/item/organ/lungs - internal_organs += new /obj/item/organ/heart - internal_organs += new /obj/item/organ/brain - internal_organs += new /obj/item/organ/tongue - internal_organs += new /obj/item/organ/eyes - internal_organs += new /obj/item/organ/ears - internal_organs += new /obj/item/organ/liver - internal_organs += new /obj/item/organ/stomach - ..() - -/mob/living/carbon/monkey/on_reagent_change() - . = ..() - remove_movespeed_modifier(MOVESPEED_ID_MONKEY_REAGENT_SPEEDMOD, TRUE) - var/amount - if(reagents.has_reagent(/datum/reagent/medicine/morphine)) - amount = -1 - if(amount) - add_movespeed_modifier(MOVESPEED_ID_MONKEY_REAGENT_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = amount) - -/mob/living/carbon/monkey/updatehealth() - . = ..() - var/slow = 0 - var/health_deficiency = (100 - health) - if(health_deficiency >= 45) - slow += (health_deficiency / 25) - add_movespeed_modifier(MOVESPEED_ID_MONKEY_HEALTH_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = slow) - -/mob/living/carbon/monkey/adjust_bodytemperature(amount) - . = ..() - var/slow = 0 - if (bodytemperature < 283.222) - slow += (283.222 - bodytemperature) / 10 * 1.75 - if(slow <= 0) - return - add_movespeed_modifier(MOVESPEED_ID_MONKEY_TEMPERATURE_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = amount) - -/mob/living/carbon/monkey/Stat() - ..() - if(statpanel("Status")) - stat(null, "Intent: [a_intent]") - stat(null, "Move Mode: [m_intent]") - if(client && mind) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - stat("Chemical Storage", "[changeling.chem_charges]/[changeling.chem_storage]") - stat("Absorbed DNA", changeling.absorbedcount) - return - - -/mob/living/carbon/monkey/verb/removeinternal() - set name = "Remove Internals" - set category = "IC" - internal = null - return - - -/mob/living/carbon/monkey/IsAdvancedToolUser()//Unless its monkey mode monkeys cant use advanced tools - if(mind && is_monkey(mind)) - return TRUE - return FALSE - -/mob/living/carbon/monkey/reagent_check(datum/reagent/R) //can metabolize all reagents - return FALSE - -/mob/living/carbon/monkey/canBeHandcuffed() - return TRUE - -/mob/living/carbon/monkey/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) - if(judgement_criteria & JUDGE_EMAGGED) - return 10 //Everyone is a criminal! - - var/threatcount = 0 - - //Securitrons can't identify monkeys - if( !(judgement_criteria & JUDGE_IGNOREMONKEYS) && (judgement_criteria & JUDGE_IDCHECK) ) - threatcount += 4 - - //Lasertag bullshit - if(lasercolor) - if(lasercolor == "b")//Lasertag turrets target the opposing team, how great is that? -Sieve - if(is_holding_item_of_type(/obj/item/gun/energy/laser/redtag)) - threatcount += 4 - - if(lasercolor == "r") - if(is_holding_item_of_type(/obj/item/gun/energy/laser/bluetag)) - threatcount += 4 - - return threatcount - - //Check for weapons - if( (judgement_criteria & JUDGE_WEAPONCHECK) && weaponcheck ) - for(var/obj/item/I in held_items) //if they're holding a gun - if(weaponcheck.Invoke(I)) - threatcount += 4 - if(weaponcheck.Invoke(back)) //if a weapon is present in the back slot - threatcount += 4 //trigger look_for_perp() since they're nonhuman and very likely hostile - - //mindshield implants imply trustworthyness - if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) - threatcount -= 1 - - return threatcount - -/mob/living/carbon/monkey/IsVocal() - if(!getorganslot(ORGAN_SLOT_LUNGS)) - return 0 - return 1 - -/mob/living/carbon/monkey/can_use_guns(obj/item/G) - return TRUE - -/mob/living/carbon/monkey/angry - aggressive = TRUE - -/mob/living/carbon/monkey/angry/Initialize() - . = ..() - if(prob(10)) - var/obj/item/clothing/head/helmet/justice/escape/helmet = new(src) - equip_to_slot_or_del(helmet,SLOT_HEAD) - helmet.attack_self(src) // todo encapsulate toggle +/mob/living/carbon/monkey + name = "monkey" + verb_say = "chimpers" + initial_language_holder = /datum/language_holder/monkey + icon = 'icons/mob/monkey.dmi' + icon_state = "" + gender = NEUTER + pass_flags = PASSTABLE + ventcrawler = VENTCRAWLER_NUDE + mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/monkey = 5, /obj/item/stack/sheet/animalhide/monkey = 1) + type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab/monkey + gib_type = /obj/effect/decal/cleanable/blood/gibs + unique_name = TRUE + bodyparts = list(/obj/item/bodypart/chest/monkey, /obj/item/bodypart/head/monkey, /obj/item/bodypart/l_arm/monkey, + /obj/item/bodypart/r_arm/monkey, /obj/item/bodypart/r_leg/monkey, /obj/item/bodypart/l_leg/monkey) + hud_type = /datum/hud/monkey + can_be_held = "monkey" + +/mob/living/carbon/monkey/Initialize(mapload, cubespawned=FALSE, mob/spawner) + verbs += /mob/living/proc/mob_sleep + verbs += /mob/living/proc/lay_down + + if(unique_name) //used to exclude pun pun + gender = pick(MALE, FEMALE) + real_name = name + + //initialize limbs + create_bodyparts() + create_internal_organs() + + . = ..() + + if (cubespawned) + var/cap = CONFIG_GET(number/monkeycap) + if (LAZYLEN(SSmobs.cubemonkeys) > cap) + if (spawner) + to_chat(spawner, "Bluespace harmonics prevent the spawning of more than [cap] monkeys on the station at one time!") + return INITIALIZE_HINT_QDEL + SSmobs.cubemonkeys += src + + create_dna(src) + dna.initialize_dna(random_blood_type()) + +/mob/living/carbon/monkey/Destroy() + SSmobs.cubemonkeys -= src + return ..() + +/mob/living/carbon/monkey/generate_mob_holder() + var/obj/item/clothing/head/mob_holder/holder = new(get_turf(src), src, "monkey", 'icons/mob/animals_held.dmi', 'icons/mob/animals_held_lh.dmi', 'icons/mob/animals_held_rh.dmi', TRUE) + return holder + +/mob/living/carbon/monkey/create_internal_organs() + internal_organs += new /obj/item/organ/appendix + internal_organs += new /obj/item/organ/lungs + internal_organs += new /obj/item/organ/heart + internal_organs += new /obj/item/organ/brain + internal_organs += new /obj/item/organ/tongue + internal_organs += new /obj/item/organ/eyes + internal_organs += new /obj/item/organ/ears + internal_organs += new /obj/item/organ/liver + internal_organs += new /obj/item/organ/stomach + ..() + +/mob/living/carbon/monkey/on_reagent_change() + . = ..() + remove_movespeed_modifier(MOVESPEED_ID_MONKEY_REAGENT_SPEEDMOD, TRUE) + var/amount + if(reagents.has_reagent(/datum/reagent/medicine/morphine)) + amount = -1 + if(amount) + add_movespeed_modifier(MOVESPEED_ID_MONKEY_REAGENT_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = amount) + +/mob/living/carbon/monkey/updatehealth() + . = ..() + var/slow = 0 + var/health_deficiency = (100 - health) + if(health_deficiency >= 45) + slow += (health_deficiency / 25) + add_movespeed_modifier(MOVESPEED_ID_MONKEY_HEALTH_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = slow) + +/mob/living/carbon/monkey/adjust_bodytemperature(amount) + . = ..() + var/slow = 0 + if (bodytemperature < 283.222) + slow += (283.222 - bodytemperature) / 10 * 1.75 + if(slow <= 0) + return + add_movespeed_modifier(MOVESPEED_ID_MONKEY_TEMPERATURE_SPEEDMOD, TRUE, 100, override = TRUE, multiplicative_slowdown = amount) + +/mob/living/carbon/monkey/Stat() + ..() + if(statpanel("Status")) + stat(null, "Intent: [a_intent]") + stat(null, "Move Mode: [m_intent]") + if(client && mind) + var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling) + stat("Chemical Storage", "[changeling.chem_charges]/[changeling.chem_storage]") + stat("Absorbed DNA", changeling.absorbedcount) + return + + +/mob/living/carbon/monkey/verb/removeinternal() + set name = "Remove Internals" + set category = "IC" + internal = null + return + + +/mob/living/carbon/monkey/IsAdvancedToolUser()//Unless its monkey mode monkeys cant use advanced tools + if(mind && is_monkey(mind)) + return TRUE + return FALSE + +/mob/living/carbon/monkey/reagent_check(datum/reagent/R) //can metabolize all reagents + return FALSE + +/mob/living/carbon/monkey/canBeHandcuffed() + return TRUE + +/mob/living/carbon/monkey/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) + if(judgement_criteria & JUDGE_EMAGGED) + return 10 //Everyone is a criminal! + + var/threatcount = 0 + + //Securitrons can't identify monkeys + if( !(judgement_criteria & JUDGE_IGNOREMONKEYS) && (judgement_criteria & JUDGE_IDCHECK) ) + threatcount += 4 + + //Lasertag bullshit + if(lasercolor) + if(lasercolor == "b")//Lasertag turrets target the opposing team, how great is that? -Sieve + if(is_holding_item_of_type(/obj/item/gun/energy/laser/redtag)) + threatcount += 4 + + if(lasercolor == "r") + if(is_holding_item_of_type(/obj/item/gun/energy/laser/bluetag)) + threatcount += 4 + + return threatcount + + //Check for weapons + if( (judgement_criteria & JUDGE_WEAPONCHECK) && weaponcheck ) + for(var/obj/item/I in held_items) //if they're holding a gun + if(weaponcheck.Invoke(I)) + threatcount += 4 + if(weaponcheck.Invoke(back)) //if a weapon is present in the back slot + threatcount += 4 //trigger look_for_perp() since they're nonhuman and very likely hostile + + //mindshield implants imply trustworthyness + if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) + threatcount -= 1 + + return threatcount + +/mob/living/carbon/monkey/IsVocal() + if(!getorganslot(ORGAN_SLOT_LUNGS)) + return 0 + return 1 + +/mob/living/carbon/monkey/can_use_guns(obj/item/G) + return TRUE + +/mob/living/carbon/monkey/angry + aggressive = TRUE + +/mob/living/carbon/monkey/angry/Initialize() + . = ..() + if(prob(10)) + var/obj/item/clothing/head/helmet/justice/escape/helmet = new(src) + equip_to_slot_or_del(helmet,SLOT_HEAD) + helmet.attack_self(src) // todo encapsulate toggle diff --git a/code/modules/mob/living/carbon/monkey/update_icons.dm b/code/modules/mob/living/carbon/monkey/update_icons.dm index e9bb9fc207..ff83b00fe1 100644 --- a/code/modules/mob/living/carbon/monkey/update_icons.dm +++ b/code/modules/mob/living/carbon/monkey/update_icons.dm @@ -1,80 +1,80 @@ - -/mob/living/carbon/monkey/regenerate_icons() - if(!..()) - update_body_parts() - update_hair() - update_inv_wear_mask() - update_inv_head() - update_inv_back() - update_transform() - - -//////// - - -/mob/living/carbon/monkey/update_hair() - remove_overlay(HAIR_LAYER) - - var/obj/item/bodypart/head/HD = get_bodypart(BODY_ZONE_HEAD) - if(!HD) //Decapitated - return - - if(HAS_TRAIT(src, TRAIT_HUSK)) - return - - var/hair_hidden = 0 - - if(head) - var/obj/item/I = head - if(I.flags_inv & HIDEHAIR) - hair_hidden = 1 - if(wear_mask) - var/obj/item/clothing/mask/M = wear_mask - if(M.flags_inv & HIDEHAIR) - hair_hidden = 1 - if(!hair_hidden) - if(!getorgan(/obj/item/organ/brain)) //Applies the debrained overlay if there is no brain - overlays_standing[HAIR_LAYER] = mutable_appearance('icons/mob/human_face.dmi', "debrained", -HAIR_LAYER) - apply_overlay(HAIR_LAYER) - - -/mob/living/carbon/monkey/update_fire() - ..("Monkey_burning") - -/mob/living/carbon/monkey/update_inv_legcuffed() - remove_overlay(LEGCUFF_LAYER) - clear_alert("legcuffed") - if(legcuffed) - var/mutable_appearance/legcuffs = mutable_appearance('icons/mob/restraints.dmi', legcuffed.item_state, -LEGCUFF_LAYER) - legcuffs.color = handcuffed.color - legcuffs.pixel_y = 8 - - overlays_standing[HANDCUFF_LAYER] = legcuffs - apply_overlay(LEGCUFF_LAYER) - throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = legcuffed) - -//monkey HUD updates for items in our inventory - -//update whether our head item appears on our hud. -/mob/living/carbon/monkey/update_hud_head(obj/item/I) - if(client && hud_used && hud_used.hud_shown) - I.screen_loc = ui_monkey_head - client.screen += I - -//update whether our mask item appears on our hud. -/mob/living/carbon/monkey/update_hud_wear_mask(obj/item/I) - if(client && hud_used && hud_used.hud_shown) - I.screen_loc = ui_monkey_mask - client.screen += I - -//update whether our neck item appears on our hud. -/mob/living/carbon/monkey/update_hud_neck(obj/item/I) - if(client && hud_used && hud_used.hud_shown) - I.screen_loc = ui_monkey_neck - client.screen += I - -//update whether our back item appears on our hud. -/mob/living/carbon/monkey/update_hud_back(obj/item/I) - if(client && hud_used && hud_used.hud_shown) - I.screen_loc = ui_monkey_back + +/mob/living/carbon/monkey/regenerate_icons() + if(!..()) + update_body_parts() + update_hair() + update_inv_wear_mask() + update_inv_head() + update_inv_back() + update_transform() + + +//////// + + +/mob/living/carbon/monkey/update_hair() + remove_overlay(HAIR_LAYER) + + var/obj/item/bodypart/head/HD = get_bodypart(BODY_ZONE_HEAD) + if(!HD) //Decapitated + return + + if(HAS_TRAIT(src, TRAIT_HUSK)) + return + + var/hair_hidden = 0 + + if(head) + var/obj/item/I = head + if(I.flags_inv & HIDEHAIR) + hair_hidden = 1 + if(wear_mask) + var/obj/item/clothing/mask/M = wear_mask + if(M.flags_inv & HIDEHAIR) + hair_hidden = 1 + if(!hair_hidden) + if(!getorgan(/obj/item/organ/brain)) //Applies the debrained overlay if there is no brain + overlays_standing[HAIR_LAYER] = mutable_appearance('icons/mob/human_face.dmi', "debrained", -HAIR_LAYER) + apply_overlay(HAIR_LAYER) + + +/mob/living/carbon/monkey/update_fire() + ..("Monkey_burning") + +/mob/living/carbon/monkey/update_inv_legcuffed() + remove_overlay(LEGCUFF_LAYER) + clear_alert("legcuffed") + if(legcuffed) + var/mutable_appearance/legcuffs = mutable_appearance('icons/mob/restraints.dmi', legcuffed.item_state, -LEGCUFF_LAYER) + legcuffs.color = handcuffed.color + legcuffs.pixel_y = 8 + + overlays_standing[HANDCUFF_LAYER] = legcuffs + apply_overlay(LEGCUFF_LAYER) + throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = legcuffed) + +//monkey HUD updates for items in our inventory + +//update whether our head item appears on our hud. +/mob/living/carbon/monkey/update_hud_head(obj/item/I) + if(client && hud_used && hud_used.hud_shown) + I.screen_loc = ui_monkey_head + client.screen += I + +//update whether our mask item appears on our hud. +/mob/living/carbon/monkey/update_hud_wear_mask(obj/item/I) + if(client && hud_used && hud_used.hud_shown) + I.screen_loc = ui_monkey_mask + client.screen += I + +//update whether our neck item appears on our hud. +/mob/living/carbon/monkey/update_hud_neck(obj/item/I) + if(client && hud_used && hud_used.hud_shown) + I.screen_loc = ui_monkey_neck + client.screen += I + +//update whether our back item appears on our hud. +/mob/living/carbon/monkey/update_hud_back(obj/item/I) + if(client && hud_used && hud_used.hud_shown) + I.screen_loc = ui_monkey_back client.screen += I \ No newline at end of file diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm index ca1a946e10..e5483e8d73 100644 --- a/code/modules/mob/living/carbon/update_icons.dm +++ b/code/modules/mob/living/carbon/update_icons.dm @@ -1,281 +1,281 @@ -/mob/living/carbon - var/list/overlays_standing[TOTAL_LAYERS] - -/mob/living/carbon/proc/apply_overlay(cache_index) - if((. = overlays_standing[cache_index])) - add_overlay(.) - -/mob/living/carbon/proc/remove_overlay(cache_index) - var/I = overlays_standing[cache_index] - if(I) - cut_overlay(I) - overlays_standing[cache_index] = null - -/mob/living/carbon/regenerate_icons() - if(notransform) - return 1 - update_inv_hands() - update_inv_handcuffed() - update_inv_legcuffed() - update_fire() - - -/mob/living/carbon/update_inv_hands() - remove_overlay(HANDS_LAYER) - if (handcuffed) - drop_all_held_items() - return - - var/list/hands = list() - for(var/obj/item/I in held_items) - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - I.screen_loc = ui_hand_position(get_held_index_of_item(I)) - client.screen += I - if(observers && observers.len) - for(var/M in observers) - var/mob/dead/observe = M - if(observe.client && observe.client.eye == src) - observe.client.screen += I - else - observers -= observe - if(!observers.len) - observers = null - break - - var/t_state = I.item_state - if(!t_state) - t_state = I.icon_state - - var/icon_file = I.lefthand_file - if(get_held_index_of_item(I) % 2 == 0) - icon_file = I.righthand_file - - hands += I.build_worn_icon(state = t_state, default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE) - - overlays_standing[HANDS_LAYER] = hands - apply_overlay(HANDS_LAYER) - - -/mob/living/carbon/update_fire(var/fire_icon = "Generic_mob_burning") - remove_overlay(FIRE_LAYER) - if(on_fire) - var/mutable_appearance/new_fire_overlay = mutable_appearance('icons/mob/OnFire.dmi', fire_icon, -FIRE_LAYER) - new_fire_overlay.appearance_flags = RESET_COLOR - overlays_standing[FIRE_LAYER] = new_fire_overlay - - apply_overlay(FIRE_LAYER) - - - -/mob/living/carbon/update_damage_overlays() - remove_overlay(DAMAGE_LAYER) - var/dam_colors = "#E62525" - if(ishuman(src)) - var/mob/living/carbon/human/H = src - dam_colors = bloodtype_to_color(H.dna.blood_type) - - var/mutable_appearance/damage_overlay = mutable_appearance('icons/mob/dam_mob.dmi', "blank", -DAMAGE_LAYER, color = dam_colors) - overlays_standing[DAMAGE_LAYER] = damage_overlay - - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - if(BP.dmg_overlay_type) - if(BP.brutestate) - damage_overlay.add_overlay("[BP.dmg_overlay_type]_[BP.body_zone]_[BP.brutestate]0") //we're adding icon_states of the base image as overlays - if(BP.burnstate) - damage_overlay.add_overlay("[BP.dmg_overlay_type]_[BP.body_zone]_0[BP.burnstate]") - - apply_overlay(DAMAGE_LAYER) - - -/mob/living/carbon/update_inv_wear_mask() - remove_overlay(FACEMASK_LAYER) - - if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated - return - - if(client && hud_used && hud_used.inv_slots[SLOT_WEAR_MASK]) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_WEAR_MASK] - inv.update_icon() - - if(wear_mask) - if(!(head && (head.flags_inv & HIDEMASK))) - overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(state = wear_mask.icon_state, default_layer = FACEMASK_LAYER, default_icon_file = 'icons/mob/mask.dmi') - update_hud_wear_mask(wear_mask) - - apply_overlay(FACEMASK_LAYER) - -/mob/living/carbon/update_inv_neck() - remove_overlay(NECK_LAYER) - - if(client && hud_used && hud_used.inv_slots[SLOT_NECK]) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_NECK] - inv.update_icon() - - if(wear_neck) - if(!(head && (head.flags_inv & HIDENECK))) - overlays_standing[NECK_LAYER] = wear_neck.build_worn_icon(state = wear_neck.icon_state, default_layer = NECK_LAYER, default_icon_file = 'icons/mob/neck.dmi') - update_hud_neck(wear_neck) - - apply_overlay(NECK_LAYER) - -/mob/living/carbon/update_inv_back() - remove_overlay(BACK_LAYER) - - if(client && hud_used && hud_used.inv_slots[SLOT_BACK]) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_BACK] - inv.update_icon() - - if(back) - overlays_standing[BACK_LAYER] = back.build_worn_icon(state = back.icon_state, default_layer = BACK_LAYER, default_icon_file = 'icons/mob/back.dmi') - update_hud_back(back) - - apply_overlay(BACK_LAYER) - -/mob/living/carbon/update_inv_head() - remove_overlay(HEAD_LAYER) - - if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated - return - - if(client && hud_used && hud_used.inv_slots[SLOT_BACK]) - var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_HEAD] - inv.update_icon() - - if(head) - overlays_standing[HEAD_LAYER] = head.build_worn_icon(state = head.icon_state, default_layer = HEAD_LAYER, default_icon_file = 'icons/mob/head.dmi') - update_hud_head(head) - - apply_overlay(HEAD_LAYER) - - -/mob/living/carbon/update_inv_handcuffed() - remove_overlay(HANDCUFF_LAYER) - if(handcuffed) - var/mutable_appearance/cuffs = mutable_appearance('icons/mob/restraints.dmi', handcuffed.item_state, -HANDCUFF_LAYER) - cuffs.color = handcuffed.color - - overlays_standing[HANDCUFF_LAYER] = cuffs - apply_overlay(HANDCUFF_LAYER) - -/mob/living/carbon/update_inv_legcuffed() - remove_overlay(LEGCUFF_LAYER) - clear_alert("legcuffed") - if(legcuffed) - var/mutable_appearance/legcuffs = mutable_appearance('icons/mob/restraints.dmi', legcuffed.item_state, -LEGCUFF_LAYER) - legcuffs.color = legcuffed.color - - overlays_standing[LEGCUFF_LAYER] = legcuffs - apply_overlay(LEGCUFF_LAYER) - throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = legcuffed) - -//mob HUD updates for items in our inventory - -//update whether handcuffs appears on our hud. -/mob/living/carbon/proc/update_hud_handcuffed() - if(hud_used) - for(var/hand in hud_used.hand_slots) - var/obj/screen/inventory/hand/H = hud_used.hand_slots[hand] - if(H) - H.update_icon() - -//update whether our head item appears on our hud. -/mob/living/carbon/proc/update_hud_head(obj/item/I) - return - -//update whether our mask item appears on our hud. -/mob/living/carbon/proc/update_hud_wear_mask(obj/item/I) - return - -//update whether our neck item appears on our hud. -/mob/living/carbon/proc/update_hud_neck(obj/item/I) - return - -//update whether our back item appears on our hud. -/mob/living/carbon/proc/update_hud_back(obj/item/I) - return - - - -//Overlays for the worn overlay so you can overlay while you overlay -//eg: ammo counters, primed grenade flashing, etc. -//"icon_file" is used automatically for inhands etc. to make sure it gets the right inhand file -/obj/item/proc/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) - . = list() - - -/mob/living/carbon/update_body() - update_body_parts() - -/mob/living/carbon/proc/update_body_parts() - //CHECK FOR UPDATE - var/oldkey = icon_render_key - icon_render_key = generate_icon_render_key() - if(oldkey == icon_render_key) - return - - remove_overlay(BODYPARTS_LAYER) - - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - BP.update_limb() - - //LOAD ICONS - if(limb_icon_cache[icon_render_key]) - load_limb_from_cache() - return - - //GENERATE NEW LIMBS - var/list/new_limbs = list() - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - new_limbs += BP.get_limb_icon() - if(new_limbs.len) - overlays_standing[BODYPARTS_LAYER] = new_limbs - limb_icon_cache[icon_render_key] = new_limbs - - apply_overlay(BODYPARTS_LAYER) - update_damage_overlays() - - - -///////////////////// -// Limb Icon Cache // -///////////////////// -/* - Called from update_body_parts() these procs handle the limb icon cache. - the limb icon cache adds an icon_render_key to a human mob, it represents: - - skin_tone (if applicable) - - gender - - limbs (stores as the limb name and whether it is removed/fine, organic/robotic) - These procs only store limbs as to increase the number of matching icon_render_keys - This cache exists because drawing 6/7 icons for humans constantly is quite a waste - See RemieRichards on irc.rizon.net #coderbus -*/ - -//produces a key based on the mob's limbs - -/mob/living/carbon/proc/generate_icon_render_key() - for(var/X in bodyparts) - var/obj/item/bodypart/BP = X - . += "-[BP.body_zone]" - if(BP.use_digitigrade) - . += "-digitigrade[BP.use_digitigrade]" - if(BP.animal_origin) - . += "-[BP.animal_origin]" - if(BP.status == BODYPART_ORGANIC) - . += "-organic" - else - . += "-robotic" - - if(HAS_TRAIT(src, TRAIT_HUSK)) - . += "-husk" - - -//change the mob's icon to the one matching its key -/mob/living/carbon/proc/load_limb_from_cache() - if(limb_icon_cache[icon_render_key]) - remove_overlay(BODYPARTS_LAYER) - overlays_standing[BODYPARTS_LAYER] = limb_icon_cache[icon_render_key] - apply_overlay(BODYPARTS_LAYER) - update_damage_overlays() +/mob/living/carbon + var/list/overlays_standing[TOTAL_LAYERS] + +/mob/living/carbon/proc/apply_overlay(cache_index) + if((. = overlays_standing[cache_index])) + add_overlay(.) + +/mob/living/carbon/proc/remove_overlay(cache_index) + var/I = overlays_standing[cache_index] + if(I) + cut_overlay(I) + overlays_standing[cache_index] = null + +/mob/living/carbon/regenerate_icons() + if(notransform) + return 1 + update_inv_hands() + update_inv_handcuffed() + update_inv_legcuffed() + update_fire() + + +/mob/living/carbon/update_inv_hands() + remove_overlay(HANDS_LAYER) + if (handcuffed) + drop_all_held_items() + return + + var/list/hands = list() + for(var/obj/item/I in held_items) + if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) + I.screen_loc = ui_hand_position(get_held_index_of_item(I)) + client.screen += I + if(observers && observers.len) + for(var/M in observers) + var/mob/dead/observe = M + if(observe.client && observe.client.eye == src) + observe.client.screen += I + else + observers -= observe + if(!observers.len) + observers = null + break + + var/t_state = I.item_state + if(!t_state) + t_state = I.icon_state + + var/icon_file = I.lefthand_file + if(get_held_index_of_item(I) % 2 == 0) + icon_file = I.righthand_file + + hands += I.build_worn_icon(state = t_state, default_layer = HANDS_LAYER, default_icon_file = icon_file, isinhands = TRUE) + + overlays_standing[HANDS_LAYER] = hands + apply_overlay(HANDS_LAYER) + + +/mob/living/carbon/update_fire(var/fire_icon = "Generic_mob_burning") + remove_overlay(FIRE_LAYER) + if(on_fire) + var/mutable_appearance/new_fire_overlay = mutable_appearance('icons/mob/OnFire.dmi', fire_icon, -FIRE_LAYER) + new_fire_overlay.appearance_flags = RESET_COLOR + overlays_standing[FIRE_LAYER] = new_fire_overlay + + apply_overlay(FIRE_LAYER) + + + +/mob/living/carbon/update_damage_overlays() + remove_overlay(DAMAGE_LAYER) + var/dam_colors = "#E62525" + if(ishuman(src)) + var/mob/living/carbon/human/H = src + dam_colors = bloodtype_to_color(H.dna.blood_type) + + var/mutable_appearance/damage_overlay = mutable_appearance('icons/mob/dam_mob.dmi', "blank", -DAMAGE_LAYER, color = dam_colors) + overlays_standing[DAMAGE_LAYER] = damage_overlay + + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + if(BP.dmg_overlay_type) + if(BP.brutestate) + damage_overlay.add_overlay("[BP.dmg_overlay_type]_[BP.body_zone]_[BP.brutestate]0") //we're adding icon_states of the base image as overlays + if(BP.burnstate) + damage_overlay.add_overlay("[BP.dmg_overlay_type]_[BP.body_zone]_0[BP.burnstate]") + + apply_overlay(DAMAGE_LAYER) + + +/mob/living/carbon/update_inv_wear_mask() + remove_overlay(FACEMASK_LAYER) + + if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated + return + + if(client && hud_used && hud_used.inv_slots[SLOT_WEAR_MASK]) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_WEAR_MASK] + inv.update_icon() + + if(wear_mask) + if(!(head && (head.flags_inv & HIDEMASK))) + overlays_standing[FACEMASK_LAYER] = wear_mask.build_worn_icon(state = wear_mask.icon_state, default_layer = FACEMASK_LAYER, default_icon_file = 'icons/mob/mask.dmi') + update_hud_wear_mask(wear_mask) + + apply_overlay(FACEMASK_LAYER) + +/mob/living/carbon/update_inv_neck() + remove_overlay(NECK_LAYER) + + if(client && hud_used && hud_used.inv_slots[SLOT_NECK]) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_NECK] + inv.update_icon() + + if(wear_neck) + if(!(head && (head.flags_inv & HIDENECK))) + overlays_standing[NECK_LAYER] = wear_neck.build_worn_icon(state = wear_neck.icon_state, default_layer = NECK_LAYER, default_icon_file = 'icons/mob/neck.dmi') + update_hud_neck(wear_neck) + + apply_overlay(NECK_LAYER) + +/mob/living/carbon/update_inv_back() + remove_overlay(BACK_LAYER) + + if(client && hud_used && hud_used.inv_slots[SLOT_BACK]) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_BACK] + inv.update_icon() + + if(back) + overlays_standing[BACK_LAYER] = back.build_worn_icon(state = back.icon_state, default_layer = BACK_LAYER, default_icon_file = 'icons/mob/back.dmi') + update_hud_back(back) + + apply_overlay(BACK_LAYER) + +/mob/living/carbon/update_inv_head() + remove_overlay(HEAD_LAYER) + + if(!get_bodypart(BODY_ZONE_HEAD)) //Decapitated + return + + if(client && hud_used && hud_used.inv_slots[SLOT_BACK]) + var/obj/screen/inventory/inv = hud_used.inv_slots[SLOT_HEAD] + inv.update_icon() + + if(head) + overlays_standing[HEAD_LAYER] = head.build_worn_icon(state = head.icon_state, default_layer = HEAD_LAYER, default_icon_file = 'icons/mob/head.dmi') + update_hud_head(head) + + apply_overlay(HEAD_LAYER) + + +/mob/living/carbon/update_inv_handcuffed() + remove_overlay(HANDCUFF_LAYER) + if(handcuffed) + var/mutable_appearance/cuffs = mutable_appearance('icons/mob/restraints.dmi', handcuffed.item_state, -HANDCUFF_LAYER) + cuffs.color = handcuffed.color + + overlays_standing[HANDCUFF_LAYER] = cuffs + apply_overlay(HANDCUFF_LAYER) + +/mob/living/carbon/update_inv_legcuffed() + remove_overlay(LEGCUFF_LAYER) + clear_alert("legcuffed") + if(legcuffed) + var/mutable_appearance/legcuffs = mutable_appearance('icons/mob/restraints.dmi', legcuffed.item_state, -LEGCUFF_LAYER) + legcuffs.color = legcuffed.color + + overlays_standing[LEGCUFF_LAYER] = legcuffs + apply_overlay(LEGCUFF_LAYER) + throw_alert("legcuffed", /obj/screen/alert/restrained/legcuffed, new_master = legcuffed) + +//mob HUD updates for items in our inventory + +//update whether handcuffs appears on our hud. +/mob/living/carbon/proc/update_hud_handcuffed() + if(hud_used) + for(var/hand in hud_used.hand_slots) + var/obj/screen/inventory/hand/H = hud_used.hand_slots[hand] + if(H) + H.update_icon() + +//update whether our head item appears on our hud. +/mob/living/carbon/proc/update_hud_head(obj/item/I) + return + +//update whether our mask item appears on our hud. +/mob/living/carbon/proc/update_hud_wear_mask(obj/item/I) + return + +//update whether our neck item appears on our hud. +/mob/living/carbon/proc/update_hud_neck(obj/item/I) + return + +//update whether our back item appears on our hud. +/mob/living/carbon/proc/update_hud_back(obj/item/I) + return + + + +//Overlays for the worn overlay so you can overlay while you overlay +//eg: ammo counters, primed grenade flashing, etc. +//"icon_file" is used automatically for inhands etc. to make sure it gets the right inhand file +/obj/item/proc/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE) + . = list() + + +/mob/living/carbon/update_body() + update_body_parts() + +/mob/living/carbon/proc/update_body_parts() + //CHECK FOR UPDATE + var/oldkey = icon_render_key + icon_render_key = generate_icon_render_key() + if(oldkey == icon_render_key) + return + + remove_overlay(BODYPARTS_LAYER) + + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + BP.update_limb() + + //LOAD ICONS + if(limb_icon_cache[icon_render_key]) + load_limb_from_cache() + return + + //GENERATE NEW LIMBS + var/list/new_limbs = list() + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + new_limbs += BP.get_limb_icon() + if(new_limbs.len) + overlays_standing[BODYPARTS_LAYER] = new_limbs + limb_icon_cache[icon_render_key] = new_limbs + + apply_overlay(BODYPARTS_LAYER) + update_damage_overlays() + + + +///////////////////// +// Limb Icon Cache // +///////////////////// +/* + Called from update_body_parts() these procs handle the limb icon cache. + the limb icon cache adds an icon_render_key to a human mob, it represents: + - skin_tone (if applicable) + - gender + - limbs (stores as the limb name and whether it is removed/fine, organic/robotic) + These procs only store limbs as to increase the number of matching icon_render_keys + This cache exists because drawing 6/7 icons for humans constantly is quite a waste + See RemieRichards on irc.rizon.net #coderbus +*/ + +//produces a key based on the mob's limbs + +/mob/living/carbon/proc/generate_icon_render_key() + for(var/X in bodyparts) + var/obj/item/bodypart/BP = X + . += "-[BP.body_zone]" + if(BP.use_digitigrade) + . += "-digitigrade[BP.use_digitigrade]" + if(BP.animal_origin) + . += "-[BP.animal_origin]" + if(BP.status == BODYPART_ORGANIC) + . += "-organic" + else + . += "-robotic" + + if(HAS_TRAIT(src, TRAIT_HUSK)) + . += "-husk" + + +//change the mob's icon to the one matching its key +/mob/living/carbon/proc/load_limb_from_cache() + if(limb_icon_cache[icon_render_key]) + remove_overlay(BODYPARTS_LAYER) + overlays_standing[BODYPARTS_LAYER] = limb_icon_cache[icon_render_key] + apply_overlay(BODYPARTS_LAYER) + update_damage_overlays() diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index 57b65a53f7..45d9c80a35 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -1,280 +1,280 @@ - -/* - apply_damage(a,b,c) - args - a:damage - How much damage to take - b:damage_type - What type of damage to take, brute, burn - c:def_zone - Where to take the damage if its brute or burn - Returns - standard 0 if fail -*/ -/mob/living/proc/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE) - var/hit_percent = (100-blocked)/100 - if(!damage || (hit_percent <= 0)) - return 0 - var/damage_amount = forced ? damage : damage * hit_percent - switch(damagetype) - if(BRUTE) - adjustBruteLoss(damage_amount, forced = forced) - if(BURN) - adjustFireLoss(damage_amount, forced = forced) - if(TOX) - adjustToxLoss(damage_amount, forced = forced) - if(OXY) - adjustOxyLoss(damage_amount, forced = forced) - if(CLONE) - adjustCloneLoss(damage_amount, forced = forced) - if(STAMINA) - adjustStaminaLoss(damage_amount, forced = forced) - return 1 - -/mob/living/proc/apply_damage_type(damage = 0, damagetype = BRUTE) //like apply damage except it always uses the damage procs - switch(damagetype) - if(BRUTE) - return adjustBruteLoss(damage) - if(BURN) - return adjustFireLoss(damage) - if(TOX) - return adjustToxLoss(damage) - if(OXY) - return adjustOxyLoss(damage) - if(CLONE) - return adjustCloneLoss(damage) - if(STAMINA) - return adjustStaminaLoss(damage) - -/mob/living/proc/get_damage_amount(damagetype = BRUTE) - switch(damagetype) - if(BRUTE) - return getBruteLoss() - if(BURN) - return getFireLoss() - if(TOX) - return getToxLoss() - if(OXY) - return getOxyLoss() - if(CLONE) - return getCloneLoss() - if(STAMINA) - return getStaminaLoss() - - -/mob/living/proc/apply_damages(brute = 0, burn = 0, tox = 0, oxy = 0, clone = 0, def_zone = null, blocked = FALSE, stamina = 0, brain = 0) - if(blocked >= 100) - return 0 - if(brute) - apply_damage(brute, BRUTE, def_zone, blocked) - if(burn) - apply_damage(burn, BURN, def_zone, blocked) - if(tox) - apply_damage(tox, TOX, def_zone, blocked) - if(oxy) - apply_damage(oxy, OXY, def_zone, blocked) - if(clone) - apply_damage(clone, CLONE, def_zone, blocked) - if(stamina) - apply_damage(stamina, STAMINA, def_zone, blocked) - if(brain) - apply_damage(brain, BRAIN, def_zone, blocked) - return 1 - - - -/mob/living/proc/apply_effect(effect = 0,effecttype = EFFECT_STUN, blocked = FALSE, knockdown_stamoverride, knockdown_stammax) - var/hit_percent = (100-blocked)/100 - if(!effect || (hit_percent <= 0)) - return 0 - switch(effecttype) - if(EFFECT_STUN) - Stun(effect * hit_percent) - if(EFFECT_KNOCKDOWN) - Knockdown(effect * hit_percent, override_stamdmg = knockdown_stammax ? CLAMP(knockdown_stamoverride, 0, knockdown_stammax-getStaminaLoss()) : knockdown_stamoverride) - if(EFFECT_UNCONSCIOUS) - Unconscious(effect * hit_percent) - if(EFFECT_IRRADIATE) - radiation += max(effect * hit_percent, 0) - if(EFFECT_SLUR) - slurring = max(slurring,(effect * hit_percent)) - if(EFFECT_STUTTER) - if((status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) // stun is usually associated with stutter - stuttering = max(stuttering,(effect * hit_percent)) - if(EFFECT_EYE_BLUR) - blur_eyes(effect * hit_percent) - if(EFFECT_DROWSY) - drowsyness = max(drowsyness,(effect * hit_percent)) - if(EFFECT_JITTER) - if((status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) - jitteriness = max(jitteriness,(effect * hit_percent)) - return 1 - - -/mob/living/proc/apply_effects(stun = 0, knockdown = 0, unconscious = 0, irradiate = 0, slur = 0, stutter = 0, eyeblur = 0, drowsy = 0, blocked = FALSE, stamina = 0, jitter = 0, kd_stamoverride, kd_stammax) - if(blocked >= 100) - return 0 - if(stun) - apply_effect(stun, EFFECT_STUN, blocked) - if(knockdown) - apply_effect(knockdown, EFFECT_KNOCKDOWN, blocked, kd_stamoverride, kd_stammax) - if(unconscious) - apply_effect(unconscious, EFFECT_UNCONSCIOUS, blocked) - if(irradiate) - apply_effect(irradiate, EFFECT_IRRADIATE, blocked) - if(slur) - apply_effect(slur, EFFECT_SLUR, blocked) - if(stutter) - apply_effect(stutter, EFFECT_STUTTER, blocked) - if(eyeblur) - apply_effect(eyeblur, EFFECT_EYE_BLUR, blocked) - if(drowsy) - apply_effect(drowsy, EFFECT_DROWSY, blocked) - if(stamina) - apply_damage(stamina, STAMINA, null, blocked) - if(jitter) - apply_effect(jitter, EFFECT_JITTER, blocked) - return 1 - - -/mob/living/proc/getBruteLoss() - return bruteloss - -/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) - if(!forced && (status_flags & GODMODE)) - return FALSE - bruteloss = CLAMP((bruteloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) - if(updating_health) - updatehealth() - return amount - -/mob/living/proc/getOxyLoss() - return oxyloss - -/mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE) - if(!forced && (status_flags & GODMODE)) - return FALSE - oxyloss = CLAMP((oxyloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) - if(updating_health) - updatehealth() - return amount - -/mob/living/proc/setOxyLoss(amount, updating_health = TRUE, forced = FALSE) - if(status_flags & GODMODE) - return 0 - oxyloss = amount - if(updating_health) - updatehealth() - return amount - -/mob/living/proc/getToxLoss() - return toxloss - -/mob/living/proc/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE) - if(!forced && (status_flags & GODMODE)) - return FALSE - toxloss = CLAMP((toxloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) - if(updating_health) - updatehealth() - return amount - -/mob/living/proc/setToxLoss(amount, updating_health = TRUE, forced = FALSE) - if(!forced && (status_flags & GODMODE)) - return FALSE - toxloss = amount - if(updating_health) - updatehealth() - return amount - -/mob/living/proc/getFireLoss() - return fireloss - -/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE) - if(!forced && (status_flags & GODMODE)) - return FALSE - fireloss = CLAMP((fireloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) - if(updating_health) - updatehealth() - return amount - -/mob/living/proc/getCloneLoss() - return cloneloss - -/mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE) - if(!forced && (status_flags & GODMODE)) - return FALSE - cloneloss = CLAMP((cloneloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) - if(updating_health) - updatehealth() - return amount - -/mob/living/proc/setCloneLoss(amount, updating_health = TRUE, forced = FALSE) - if(!forced && (status_flags & GODMODE)) - return FALSE - cloneloss = amount - if(updating_health) - updatehealth() - return amount - -/mob/living/proc/adjustOrganLoss(slot, amount, maximum) - return - -/mob/living/proc/setOrganLoss(slot, amount, maximum) - return - -/mob/living/proc/getOrganLoss(slot) - return - -/mob/living/proc/getStaminaLoss() - return staminaloss - -/mob/living/proc/adjustStaminaLoss(amount, updating_health = TRUE, forced = FALSE) - return - -/mob/living/proc/setStaminaLoss(amount, updating_health = TRUE, forced = FALSE) - return - -// heal ONE external organ, organ gets randomly selected from damaged ones. -/mob/living/proc/heal_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE) - adjustBruteLoss(-brute, FALSE) //zero as argument for no instant health update - adjustFireLoss(-burn, FALSE) - adjustStaminaLoss(-stamina, FALSE) - if(updating_health) - updatehealth() - update_stamina() - -// damage ONE external organ, organ gets randomly selected from damaged ones. -/mob/living/proc/take_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE) - adjustBruteLoss(brute, FALSE) //zero as argument for no instant health update - adjustFireLoss(burn, FALSE) - adjustStaminaLoss(stamina, FALSE) - if(updating_health) - updatehealth() - update_stamina() - -// heal MANY bodyparts, in random order -/mob/living/proc/heal_overall_damage(brute = 0, burn = 0, stamina = 0, only_robotic = FALSE, only_organic = TRUE, updating_health = TRUE) - adjustBruteLoss(-brute, FALSE) //zero as argument for no instant health update - adjustFireLoss(-burn, FALSE) - adjustStaminaLoss(-stamina, FALSE) - if(updating_health) - updatehealth() - update_stamina() - -// damage MANY bodyparts, in random order -/mob/living/proc/take_overall_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE) - adjustBruteLoss(brute, FALSE) //zero as argument for no instant health update - adjustFireLoss(burn, FALSE) - adjustStaminaLoss(stamina, FALSE) - if(updating_health) - updatehealth() - update_stamina() - -//heal up to amount damage, in a given order -/mob/living/proc/heal_ordered_damage(amount, list/damage_types) - . = amount //we'll return the amount of damage healed - for(var/i in damage_types) - var/amount_to_heal = min(amount, get_damage_amount(i)) //heal only up to the amount of damage we have - if(amount_to_heal) - apply_damage_type(-amount_to_heal, i) - amount -= amount_to_heal //remove what we healed from our current amount - if(!amount) - break - . -= amount //if there's leftover healing, remove it from what we return + +/* + apply_damage(a,b,c) + args + a:damage - How much damage to take + b:damage_type - What type of damage to take, brute, burn + c:def_zone - Where to take the damage if its brute or burn + Returns + standard 0 if fail +*/ +/mob/living/proc/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE) + var/hit_percent = (100-blocked)/100 + if(!damage || (hit_percent <= 0)) + return 0 + var/damage_amount = forced ? damage : damage * hit_percent + switch(damagetype) + if(BRUTE) + adjustBruteLoss(damage_amount, forced = forced) + if(BURN) + adjustFireLoss(damage_amount, forced = forced) + if(TOX) + adjustToxLoss(damage_amount, forced = forced) + if(OXY) + adjustOxyLoss(damage_amount, forced = forced) + if(CLONE) + adjustCloneLoss(damage_amount, forced = forced) + if(STAMINA) + adjustStaminaLoss(damage_amount, forced = forced) + return 1 + +/mob/living/proc/apply_damage_type(damage = 0, damagetype = BRUTE) //like apply damage except it always uses the damage procs + switch(damagetype) + if(BRUTE) + return adjustBruteLoss(damage) + if(BURN) + return adjustFireLoss(damage) + if(TOX) + return adjustToxLoss(damage) + if(OXY) + return adjustOxyLoss(damage) + if(CLONE) + return adjustCloneLoss(damage) + if(STAMINA) + return adjustStaminaLoss(damage) + +/mob/living/proc/get_damage_amount(damagetype = BRUTE) + switch(damagetype) + if(BRUTE) + return getBruteLoss() + if(BURN) + return getFireLoss() + if(TOX) + return getToxLoss() + if(OXY) + return getOxyLoss() + if(CLONE) + return getCloneLoss() + if(STAMINA) + return getStaminaLoss() + + +/mob/living/proc/apply_damages(brute = 0, burn = 0, tox = 0, oxy = 0, clone = 0, def_zone = null, blocked = FALSE, stamina = 0, brain = 0) + if(blocked >= 100) + return 0 + if(brute) + apply_damage(brute, BRUTE, def_zone, blocked) + if(burn) + apply_damage(burn, BURN, def_zone, blocked) + if(tox) + apply_damage(tox, TOX, def_zone, blocked) + if(oxy) + apply_damage(oxy, OXY, def_zone, blocked) + if(clone) + apply_damage(clone, CLONE, def_zone, blocked) + if(stamina) + apply_damage(stamina, STAMINA, def_zone, blocked) + if(brain) + apply_damage(brain, BRAIN, def_zone, blocked) + return 1 + + + +/mob/living/proc/apply_effect(effect = 0,effecttype = EFFECT_STUN, blocked = FALSE, knockdown_stamoverride, knockdown_stammax) + var/hit_percent = (100-blocked)/100 + if(!effect || (hit_percent <= 0)) + return 0 + switch(effecttype) + if(EFFECT_STUN) + Stun(effect * hit_percent) + if(EFFECT_KNOCKDOWN) + Knockdown(effect * hit_percent, override_stamdmg = knockdown_stammax ? CLAMP(knockdown_stamoverride, 0, knockdown_stammax-getStaminaLoss()) : knockdown_stamoverride) + if(EFFECT_UNCONSCIOUS) + Unconscious(effect * hit_percent) + if(EFFECT_IRRADIATE) + radiation += max(effect * hit_percent, 0) + if(EFFECT_SLUR) + slurring = max(slurring,(effect * hit_percent)) + if(EFFECT_STUTTER) + if((status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) // stun is usually associated with stutter + stuttering = max(stuttering,(effect * hit_percent)) + if(EFFECT_EYE_BLUR) + blur_eyes(effect * hit_percent) + if(EFFECT_DROWSY) + drowsyness = max(drowsyness,(effect * hit_percent)) + if(EFFECT_JITTER) + if((status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) + jitteriness = max(jitteriness,(effect * hit_percent)) + return 1 + + +/mob/living/proc/apply_effects(stun = 0, knockdown = 0, unconscious = 0, irradiate = 0, slur = 0, stutter = 0, eyeblur = 0, drowsy = 0, blocked = FALSE, stamina = 0, jitter = 0, kd_stamoverride, kd_stammax) + if(blocked >= 100) + return 0 + if(stun) + apply_effect(stun, EFFECT_STUN, blocked) + if(knockdown) + apply_effect(knockdown, EFFECT_KNOCKDOWN, blocked, kd_stamoverride, kd_stammax) + if(unconscious) + apply_effect(unconscious, EFFECT_UNCONSCIOUS, blocked) + if(irradiate) + apply_effect(irradiate, EFFECT_IRRADIATE, blocked) + if(slur) + apply_effect(slur, EFFECT_SLUR, blocked) + if(stutter) + apply_effect(stutter, EFFECT_STUTTER, blocked) + if(eyeblur) + apply_effect(eyeblur, EFFECT_EYE_BLUR, blocked) + if(drowsy) + apply_effect(drowsy, EFFECT_DROWSY, blocked) + if(stamina) + apply_damage(stamina, STAMINA, null, blocked) + if(jitter) + apply_effect(jitter, EFFECT_JITTER, blocked) + return 1 + + +/mob/living/proc/getBruteLoss() + return bruteloss + +/mob/living/proc/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE) + if(!forced && (status_flags & GODMODE)) + return FALSE + bruteloss = CLAMP((bruteloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + if(updating_health) + updatehealth() + return amount + +/mob/living/proc/getOxyLoss() + return oxyloss + +/mob/living/proc/adjustOxyLoss(amount, updating_health = TRUE, forced = FALSE) + if(!forced && (status_flags & GODMODE)) + return FALSE + oxyloss = CLAMP((oxyloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + if(updating_health) + updatehealth() + return amount + +/mob/living/proc/setOxyLoss(amount, updating_health = TRUE, forced = FALSE) + if(status_flags & GODMODE) + return 0 + oxyloss = amount + if(updating_health) + updatehealth() + return amount + +/mob/living/proc/getToxLoss() + return toxloss + +/mob/living/proc/adjustToxLoss(amount, updating_health = TRUE, forced = FALSE) + if(!forced && (status_flags & GODMODE)) + return FALSE + toxloss = CLAMP((toxloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + if(updating_health) + updatehealth() + return amount + +/mob/living/proc/setToxLoss(amount, updating_health = TRUE, forced = FALSE) + if(!forced && (status_flags & GODMODE)) + return FALSE + toxloss = amount + if(updating_health) + updatehealth() + return amount + +/mob/living/proc/getFireLoss() + return fireloss + +/mob/living/proc/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE) + if(!forced && (status_flags & GODMODE)) + return FALSE + fireloss = CLAMP((fireloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + if(updating_health) + updatehealth() + return amount + +/mob/living/proc/getCloneLoss() + return cloneloss + +/mob/living/proc/adjustCloneLoss(amount, updating_health = TRUE, forced = FALSE) + if(!forced && (status_flags & GODMODE)) + return FALSE + cloneloss = CLAMP((cloneloss + (amount * CONFIG_GET(number/damage_multiplier))), 0, maxHealth * 2) + if(updating_health) + updatehealth() + return amount + +/mob/living/proc/setCloneLoss(amount, updating_health = TRUE, forced = FALSE) + if(!forced && (status_flags & GODMODE)) + return FALSE + cloneloss = amount + if(updating_health) + updatehealth() + return amount + +/mob/living/proc/adjustOrganLoss(slot, amount, maximum) + return + +/mob/living/proc/setOrganLoss(slot, amount, maximum) + return + +/mob/living/proc/getOrganLoss(slot) + return + +/mob/living/proc/getStaminaLoss() + return staminaloss + +/mob/living/proc/adjustStaminaLoss(amount, updating_health = TRUE, forced = FALSE) + return + +/mob/living/proc/setStaminaLoss(amount, updating_health = TRUE, forced = FALSE) + return + +// heal ONE external organ, organ gets randomly selected from damaged ones. +/mob/living/proc/heal_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE) + adjustBruteLoss(-brute, FALSE) //zero as argument for no instant health update + adjustFireLoss(-burn, FALSE) + adjustStaminaLoss(-stamina, FALSE) + if(updating_health) + updatehealth() + update_stamina() + +// damage ONE external organ, organ gets randomly selected from damaged ones. +/mob/living/proc/take_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE) + adjustBruteLoss(brute, FALSE) //zero as argument for no instant health update + adjustFireLoss(burn, FALSE) + adjustStaminaLoss(stamina, FALSE) + if(updating_health) + updatehealth() + update_stamina() + +// heal MANY bodyparts, in random order +/mob/living/proc/heal_overall_damage(brute = 0, burn = 0, stamina = 0, only_robotic = FALSE, only_organic = TRUE, updating_health = TRUE) + adjustBruteLoss(-brute, FALSE) //zero as argument for no instant health update + adjustFireLoss(-burn, FALSE) + adjustStaminaLoss(-stamina, FALSE) + if(updating_health) + updatehealth() + update_stamina() + +// damage MANY bodyparts, in random order +/mob/living/proc/take_overall_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE) + adjustBruteLoss(brute, FALSE) //zero as argument for no instant health update + adjustFireLoss(burn, FALSE) + adjustStaminaLoss(stamina, FALSE) + if(updating_health) + updatehealth() + update_stamina() + +//heal up to amount damage, in a given order +/mob/living/proc/heal_ordered_damage(amount, list/damage_types) + . = amount //we'll return the amount of damage healed + for(var/i in damage_types) + var/amount_to_heal = min(amount, get_damage_amount(i)) //heal only up to the amount of damage we have + if(amount_to_heal) + apply_damage_type(-amount_to_heal, i) + amount -= amount_to_heal //remove what we healed from our current amount + if(!amount) + break + . -= amount //if there's leftover healing, remove it from what we return diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 1c6546fd83..5880392a1b 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -1,107 +1,107 @@ -/mob/living/gib(no_brain, no_organs, no_bodyparts) - var/prev_lying = lying - if(stat != DEAD) - death(1) - - if(!prev_lying) - gib_animation() - - spill_organs(no_brain, no_organs, no_bodyparts) - - release_vore_contents(silent = TRUE) // return of the bomb safe internals. - - if(!no_bodyparts) - spread_bodyparts(no_brain, no_organs) - - for(var/X in implants) - var/obj/item/implant/I = X - qdel(I) - - spawn_gibs(no_bodyparts) - qdel(src) - -/mob/living/proc/gib_animation() - return - -/mob/living/proc/spawn_gibs(with_bodyparts, atom/loc_override) - var/location = loc_override ? loc_override.drop_location() : drop_location() - if(MOB_ROBOTIC in mob_biotypes) - new /obj/effect/gibspawner/robot(location, src, get_static_viruses()) - else - new /obj/effect/gibspawner/generic(location, src, get_static_viruses()) - -/mob/living/proc/spill_organs() - return - -/mob/living/proc/spread_bodyparts() - return - -/mob/living/dust(just_ash, drop_items, force) - death(TRUE) - - if(drop_items) - unequip_everything() - - if(buckled) - buckled.unbuckle_mob(src, force = TRUE) - - dust_animation() - release_vore_contents(silent = TRUE) //technically grief protection, I guess? if they're SM'd it doesn't matter seconds after anyway. - spawn_dust(just_ash) - QDEL_IN(src,5) // since this is sometimes called in the middle of movement, allow half a second for movement to finish, ghosting to happen and animation to play. Looks much nicer and doesn't cause multiple runtimes. - -/mob/living/proc/dust_animation() - return - -/mob/living/proc/spawn_dust(just_ash = FALSE) - new /obj/effect/decal/cleanable/ash(loc) - - -/mob/living/death(gibbed) - stat = DEAD - unset_machine() - timeofdeath = world.time - tod = STATION_TIME_TIMESTAMP("hh:mm:ss") - for(var/obj/item/I in contents) - I.on_mob_death(src, gibbed) - if(mind) - mind.store_memory("Time of death: [tod]", 0) - GLOB.alive_mob_list -= src - if(!gibbed) - GLOB.dead_mob_list += src - set_drugginess(0) - set_disgust(0) - SetSleeping(0, 0) - blind_eyes(1) - reset_perspective(null) - reload_fullscreen() - update_action_buttons_icon() - update_damage_hud() - update_health_hud() - update_canmove() - med_hud_set_health() - med_hud_set_status() - if(!gibbed && !QDELETED(src)) - addtimer(CALLBACK(src, .proc/med_hud_set_status), (DEFIB_TIME_LIMIT * 10) + 1) - stop_pulling() - - var/signal = SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed) - - var/turf/T = get_turf(src) - if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf) && !(signal & COMPONENT_BLOCK_DEATH_BROADCAST)) - var/rendered = "[mind.name] has died at [get_area_name(T)]." - deadchat_broadcast(rendered, follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE) - if (client && client.prefs && client.prefs.auto_ooc) - if (!(client.prefs.chat_toggles & CHAT_OOC)) - client.prefs.chat_toggles ^= CHAT_OOC - if (client) - client.move_delay = initial(client.move_delay) - - for(var/s in ownedSoullinks) - var/datum/soullink/S = s - S.ownerDies(gibbed) - for(var/s in sharedSoullinks) - var/datum/soullink/S = s - S.sharerDies(gibbed) - - return TRUE +/mob/living/gib(no_brain, no_organs, no_bodyparts) + var/prev_lying = lying + if(stat != DEAD) + death(1) + + if(!prev_lying) + gib_animation() + + spill_organs(no_brain, no_organs, no_bodyparts) + + release_vore_contents(silent = TRUE) // return of the bomb safe internals. + + if(!no_bodyparts) + spread_bodyparts(no_brain, no_organs) + + for(var/X in implants) + var/obj/item/implant/I = X + qdel(I) + + spawn_gibs(no_bodyparts) + qdel(src) + +/mob/living/proc/gib_animation() + return + +/mob/living/proc/spawn_gibs(with_bodyparts, atom/loc_override) + var/location = loc_override ? loc_override.drop_location() : drop_location() + if(MOB_ROBOTIC in mob_biotypes) + new /obj/effect/gibspawner/robot(location, src, get_static_viruses()) + else + new /obj/effect/gibspawner/generic(location, src, get_static_viruses()) + +/mob/living/proc/spill_organs() + return + +/mob/living/proc/spread_bodyparts() + return + +/mob/living/dust(just_ash, drop_items, force) + death(TRUE) + + if(drop_items) + unequip_everything() + + if(buckled) + buckled.unbuckle_mob(src, force = TRUE) + + dust_animation() + release_vore_contents(silent = TRUE) //technically grief protection, I guess? if they're SM'd it doesn't matter seconds after anyway. + spawn_dust(just_ash) + QDEL_IN(src,5) // since this is sometimes called in the middle of movement, allow half a second for movement to finish, ghosting to happen and animation to play. Looks much nicer and doesn't cause multiple runtimes. + +/mob/living/proc/dust_animation() + return + +/mob/living/proc/spawn_dust(just_ash = FALSE) + new /obj/effect/decal/cleanable/ash(loc) + + +/mob/living/death(gibbed) + stat = DEAD + unset_machine() + timeofdeath = world.time + tod = STATION_TIME_TIMESTAMP("hh:mm:ss") + for(var/obj/item/I in contents) + I.on_mob_death(src, gibbed) + if(mind) + mind.store_memory("Time of death: [tod]", 0) + GLOB.alive_mob_list -= src + if(!gibbed) + GLOB.dead_mob_list += src + set_drugginess(0) + set_disgust(0) + SetSleeping(0, 0) + blind_eyes(1) + reset_perspective(null) + reload_fullscreen() + update_action_buttons_icon() + update_damage_hud() + update_health_hud() + update_canmove() + med_hud_set_health() + med_hud_set_status() + if(!gibbed && !QDELETED(src)) + addtimer(CALLBACK(src, .proc/med_hud_set_status), (DEFIB_TIME_LIMIT * 10) + 1) + stop_pulling() + + var/signal = SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed) + + var/turf/T = get_turf(src) + if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf) && !(signal & COMPONENT_BLOCK_DEATH_BROADCAST)) + var/rendered = "[mind.name] has died at [get_area_name(T)]." + deadchat_broadcast(rendered, follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE) + if (client && client.prefs && client.prefs.auto_ooc) + if (!(client.prefs.chat_toggles & CHAT_OOC)) + client.prefs.chat_toggles ^= CHAT_OOC + if (client) + client.move_delay = initial(client.move_delay) + + for(var/s in ownedSoullinks) + var/datum/soullink/S = s + S.ownerDies(gibbed) + for(var/s in sharedSoullinks) + var/datum/soullink/S = s + S.sharerDies(gibbed) + + return TRUE diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 041b367ebf..aba640c550 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -1,524 +1,524 @@ - -/* EMOTE DATUMS */ -/datum/emote/living - mob_type_allowed_typecache = /mob/living - mob_type_blacklist_typecache = list(/mob/living/simple_animal/slime, /mob/living/brain) - -/datum/emote/living/blush - key = "blush" - key_third_person = "blushes" - message = "blushes." - -/datum/emote/living/bow - key = "bow" - key_third_person = "bows" - message = "bows." - message_param = "bows to %t." - restraint_check = TRUE - -/datum/emote/living/burp - key = "burp" - key_third_person = "burps" - message = "burps." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/choke - key = "choke" - key_third_person = "chokes" - message = "chokes!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/cross - key = "cross" - key_third_person = "crosses" - message = "crosses their arms." - restraint_check = TRUE - -/datum/emote/living/chuckle - key = "chuckle" - key_third_person = "chuckles" - message = "chuckles." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/collapse - key = "collapse" - key_third_person = "collapses" - message = "collapses!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/collapse/run_emote(mob/user, params) - . = ..() - if(. && isliving(user)) - var/mob/living/L = user - L.Unconscious(40) - -/datum/emote/living/cough - key = "cough" - key_third_person = "coughs" - message = "coughs!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE , intentional) - . = ..() - if(HAS_TRAIT(user, TRAIT_SOOTHED_THROAT)) - return FALSE - -/datum/emote/living/dance - key = "dance" - key_third_person = "dances" - message = "dances around happily." - restraint_check = TRUE - -/datum/emote/living/deathgasp - key = "deathgasp" - key_third_person = "deathgasps" - message = "seizes up and falls limp, their eyes dead and lifeless..." - message_robot = "shudders violently for a moment before falling still, its eyes slowly darkening." - message_AI = "lets out a flurry of sparks, its screen flickering as its systems slowly halt." - message_alien = "lets out a waning guttural screech, green blood bubbling from its maw..." - message_larva = "lets out a sickly hiss of air and falls limply to the floor..." - message_monkey = "lets out a faint chimper as it collapses and stops moving..." - message_simple = "stops moving..." - stat_allowed = UNCONSCIOUS - -/datum/emote/living/deathgasp/run_emote(mob/user, params) - var/mob/living/simple_animal/S = user - if(istype(S) && S.deathmessage) - message_simple = S.deathmessage - . = ..() - message_simple = initial(message_simple) - if(. && user.deathsound) - if(isliving(user)) - var/mob/living/L = user - if(!L.can_speak_vocal() || L.oxyloss >= 50) - return //stop the sound if oxyloss too high/cant speak - playsound(user, user.deathsound, 200, TRUE, TRUE) - if(. && isalienadult(user)) - playsound(user.loc, 'sound/voice/hiss6.ogg', 80, 1, 1) - -/datum/emote/living/drool - key = "drool" - key_third_person = "drools" - message = "drools." - -/datum/emote/living/faint - key = "faint" - key_third_person = "faints" - message = "faints." - -/datum/emote/living/faint/run_emote(mob/user, params) - . = ..() - if(. && isliving(user)) - var/mob/living/L = user - L.SetSleeping(200) - -/datum/emote/living/flap - key = "flap" - key_third_person = "flaps" - message = "flaps their wings." - restraint_check = TRUE - var/wing_time = 20 - -/datum/emote/living/flap/run_emote(mob/user, params) - . = ..() - if(. && ishuman(user)) - var/mob/living/carbon/human/H = user - var/open = FALSE - if(H.dna.features["wings"] != "None") - if("wingsopen" in H.dna.species.mutant_bodyparts) - open = TRUE - H.CloseWings() - else - H.OpenWings() - addtimer(CALLBACK(H, open ? /mob/living/carbon/human.proc/OpenWings : /mob/living/carbon/human.proc/CloseWings), wing_time) - -/datum/emote/living/flap/aflap - key = "aflap" - key_third_person = "aflaps" - message = "flaps their wings ANGRILY!" - restraint_check = TRUE - wing_time = 10 - -/datum/emote/living/frown - key = "frown" - key_third_person = "frowns" - message = "frowns." - -/datum/emote/living/gag - key = "gag" - key_third_person = "gags" - message = "gags." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/gasp - key = "gasp" - key_third_person = "gasps" - message = "gasps!" - emote_type = EMOTE_AUDIBLE - stat_allowed = UNCONSCIOUS - -/datum/emote/living/giggle - key = "giggle" - key_third_person = "giggles" - message = "giggles." - message_mime = "giggles silently!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/glare - key = "glare" - key_third_person = "glares" - message = "glares." - message_param = "glares at %t." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/grin - key = "grin" - key_third_person = "grins" - message = "grins." - -/datum/emote/living/groan - key = "groan" - key_third_person = "groans" - message = "groans!" - message_mime = "appears to groan!" - -/datum/emote/living/grimace - key = "grimace" - key_third_person = "grimaces" - message = "grimaces." - -/datum/emote/living/jump - key = "jump" - key_third_person = "jumps" - message = "jumps!" - restraint_check = TRUE - -/datum/emote/living/kiss - key = "kiss" - key_third_person = "kisses" - message = "blows a kiss." - message_param = "blows a kiss to %t." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/laugh - key = "laugh" - key_third_person = "laughs" - message = "laughs." - message_mime = "laughs silently!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE) - . = ..() - if(. && iscarbon(user)) - var/mob/living/carbon/C = user - return !C.silent - -/datum/emote/living/laugh/run_emote(mob/user, params) - . = ..() - if(. && iscarbon(user)) //Citadel Edit because this is hilarious - var/mob/living/carbon/C = user - if(!C.mind || C.mind.miming) - return - if(iscatperson(C)) //we ask for is cat first because they're a subtype that tests true for ishumanbasic because HERESY - playsound(C, pick('sound/voice/catpeople/nyahaha1.ogg', - 'sound/voice/catpeople/nyahaha2.ogg', - 'sound/voice/catpeople/nyaha.ogg', - 'sound/voice/catpeople/nyahehe.ogg'), - 50, 1) - return - if(ishumanbasic(C)) - if(user.gender == FEMALE) - playsound(C, 'sound/voice/human/womanlaugh.ogg', 50, 1) - else - playsound(C, pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg'), 50, 1) - -/datum/emote/living/look - key = "look" - key_third_person = "looks" - message = "looks." - message_param = "looks at %t." - -/datum/emote/living/nod - key = "nod" - key_third_person = "nods" - message = "nods." - message_param = "nods at %t." - -/datum/emote/living/point - key = "point" - key_third_person = "points" - message = "points." - message_param = "points at %t." - restraint_check = TRUE - -/datum/emote/living/point/run_emote(mob/user, params) - message_param = initial(message_param) // reset - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.get_num_arms() == 0) - if(H.get_num_legs() != 0) - message_param = "tries to point at %t with a leg, falling down in the process!" - H.Knockdown(20) - else - message_param = "bumps [user.p_their()] head on the ground trying to motion towards %t." - H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5) - ..() - -/datum/emote/living/pout - key = "pout" - key_third_person = "pouts" - message = "pouts." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/scream - key = "scream" - key_third_person = "screams" - message = "screams." - message_mime = "acts out a scream!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/scowl - key = "scowl" - key_third_person = "scowls" - message = "scowls." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/shake - key = "shake" - key_third_person = "shakes" - message = "shakes their head." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/shiver - key = "shiver" - key_third_person = "shiver" - message = "shivers." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/sigh - key = "sigh" - key_third_person = "sighs" - message = "sighs." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/sit - key = "sit" - key_third_person = "sits" - message = "sits down." - -/datum/emote/living/smile - key = "smile" - key_third_person = "smiles" - message = "smiles." - -/datum/emote/living/sneeze - key = "sneeze" - key_third_person = "sneezes" - message = "sneezes." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/smug - key = "smug" - key_third_person = "smugs" - message = "grins smugly." - -/datum/emote/living/sniff - key = "sniff" - key_third_person = "sniffs" - message = "sniffs." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/snore - key = "snore" - key_third_person = "snores" - message = "snores." - message_mime = "sleeps soundly." - emote_type = EMOTE_AUDIBLE - stat_allowed = UNCONSCIOUS - -/datum/emote/living/stare - key = "stare" - key_third_person = "stares" - message = "stares." - message_param = "stares at %t." - -/datum/emote/living/strech - key = "stretch" - key_third_person = "stretches" - message = "stretches their arms." - -/datum/emote/living/sulk - key = "sulk" - key_third_person = "sulks" - message = "sulks down sadly." - -/datum/emote/living/surrender - key = "surrender" - key_third_person = "surrenders" - message = "puts their hands on their head and falls to the ground, they surrender!" - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/surrender/run_emote(mob/user, params) - . = ..() - if(. && isliving(user)) - var/mob/living/L = user - L.Knockdown(200) - -/datum/emote/living/sway - key = "sway" - key_third_person = "sways" - message = "sways around dizzily." - -/datum/emote/living/tremble - key = "tremble" - key_third_person = "trembles" - message = "trembles in fear!" - -/datum/emote/living/twitch - key = "twitch" - key_third_person = "twitches" - message = "twitches violently." - -/datum/emote/living/twitch_s - key = "twitch_s" - message = "twitches." - -/datum/emote/living/wave - key = "wave" - key_third_person = "waves" - message = "waves." - -/datum/emote/living/whimper - key = "whimper" - key_third_person = "whimpers" - message = "whimpers." - message_mime = "appears hurt." - -/datum/emote/living/wsmile - key = "wsmile" - key_third_person = "wsmiles" - message = "smiles weakly." - -/datum/emote/living/yawn - key = "yawn" - key_third_person = "yawns" - message = "yawns." - emote_type = EMOTE_AUDIBLE - -/datum/emote/living/custom - key = "me" - key_third_person = "custom" - message = null - -/datum/emote/living/custom/proc/check_invalid(mob/user, input) - . = TRUE - if(copytext(input,1,5) == "says") - to_chat(user, "Invalid emote.") - else if(copytext(input,1,9) == "exclaims") - to_chat(user, "Invalid emote.") - else if(copytext(input,1,6) == "yells") - to_chat(user, "Invalid emote.") - else if(copytext(input,1,5) == "asks") - to_chat(user, "Invalid emote.") - else - . = FALSE - -/datum/emote/living/custom/run_emote(mob/user, params, type_override = null) - if(jobban_isbanned(user, "emote")) - to_chat(user, "You cannot send custom emotes (banned).") - return FALSE - else if(QDELETED(user)) - return FALSE - else if(user.client && user.client.prefs.muted & MUTE_IC) - to_chat(user, "You cannot send IC messages (muted).") - return FALSE - else if(!params) - var/custom_emote = copytext(sanitize(input("Choose an emote to display.") as message|null), 1, MAX_MESSAGE_LEN) //CIT CHANGE - expands emote textbox - if(custom_emote && !check_invalid(user, custom_emote)) - var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable") - switch(type) - if("Visible") - emote_type = EMOTE_VISIBLE - if("Hearable") - emote_type = EMOTE_AUDIBLE - else - alert("Unable to use this emote, must be either hearable or visible.") - return - message = custom_emote - else - message = params - if(type_override) - emote_type = type_override - . = ..() - message = null - emote_type = EMOTE_VISIBLE - -/datum/emote/living/custom/replace_pronoun(mob/user, message) - return message - -/datum/emote/living/help - key = "help" - -/datum/emote/living/help/run_emote(mob/user, params) - var/list/keys = list() - var/list/message = list("Available emotes, you can use them with say \"*emote\": ") - - var/datum/emote/E - var/list/emote_list = E.emote_list - for(var/e in emote_list) - if(e in keys) - continue - E = emote_list[e] - if(E.can_run_emote(user, status_check = FALSE)) - keys += E.key - - keys = sortList(keys) - - for(var/emote in keys) - if(LAZYLEN(message) > 1) - message += ", [emote]" - else - message += "[emote]" - - message += "." - - message = jointext(message, "") - - to_chat(user, message) - -/datum/emote/sound/beep - key = "beep" - key_third_person = "beeps" - message = "beeps." - message_param = "beeps at %t." - sound = 'sound/machines/twobeep.ogg' - mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon, /mob/living/carbon/human) - -/datum/emote/living/circle - key = "circle" - key_third_person = "circles" - restraint_check = TRUE - -/datum/emote/living/circle/run_emote(mob/user, params) - . = ..() - var/obj/item/circlegame/N = new(user) - if(user.put_in_hands(N)) - to_chat(user, "You make a circle with your hand.") - else - qdel(N) - to_chat(user, "You don't have any free hands to make a circle with.") - -/datum/emote/living/slap - key = "slap" - key_third_person = "slaps" - restraint_check = TRUE - -/datum/emote/living/slap/run_emote(mob/user, params) - . = ..() - if(!.) - return - var/obj/item/slapper/N = new(user) - if(user.put_in_hands(N)) - to_chat(user, "You ready your slapping hand.") - else - to_chat(user, "You're incapable of slapping in your current state.") + +/* EMOTE DATUMS */ +/datum/emote/living + mob_type_allowed_typecache = /mob/living + mob_type_blacklist_typecache = list(/mob/living/simple_animal/slime, /mob/living/brain) + +/datum/emote/living/blush + key = "blush" + key_third_person = "blushes" + message = "blushes." + +/datum/emote/living/bow + key = "bow" + key_third_person = "bows" + message = "bows." + message_param = "bows to %t." + restraint_check = TRUE + +/datum/emote/living/burp + key = "burp" + key_third_person = "burps" + message = "burps." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/choke + key = "choke" + key_third_person = "chokes" + message = "chokes!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/cross + key = "cross" + key_third_person = "crosses" + message = "crosses their arms." + restraint_check = TRUE + +/datum/emote/living/chuckle + key = "chuckle" + key_third_person = "chuckles" + message = "chuckles." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/collapse + key = "collapse" + key_third_person = "collapses" + message = "collapses!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/collapse/run_emote(mob/user, params) + . = ..() + if(. && isliving(user)) + var/mob/living/L = user + L.Unconscious(40) + +/datum/emote/living/cough + key = "cough" + key_third_person = "coughs" + message = "coughs!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE , intentional) + . = ..() + if(HAS_TRAIT(user, TRAIT_SOOTHED_THROAT)) + return FALSE + +/datum/emote/living/dance + key = "dance" + key_third_person = "dances" + message = "dances around happily." + restraint_check = TRUE + +/datum/emote/living/deathgasp + key = "deathgasp" + key_third_person = "deathgasps" + message = "seizes up and falls limp, their eyes dead and lifeless..." + message_robot = "shudders violently for a moment before falling still, its eyes slowly darkening." + message_AI = "lets out a flurry of sparks, its screen flickering as its systems slowly halt." + message_alien = "lets out a waning guttural screech, green blood bubbling from its maw..." + message_larva = "lets out a sickly hiss of air and falls limply to the floor..." + message_monkey = "lets out a faint chimper as it collapses and stops moving..." + message_simple = "stops moving..." + stat_allowed = UNCONSCIOUS + +/datum/emote/living/deathgasp/run_emote(mob/user, params) + var/mob/living/simple_animal/S = user + if(istype(S) && S.deathmessage) + message_simple = S.deathmessage + . = ..() + message_simple = initial(message_simple) + if(. && user.deathsound) + if(isliving(user)) + var/mob/living/L = user + if(!L.can_speak_vocal() || L.oxyloss >= 50) + return //stop the sound if oxyloss too high/cant speak + playsound(user, user.deathsound, 200, TRUE, TRUE) + if(. && isalienadult(user)) + playsound(user.loc, 'sound/voice/hiss6.ogg', 80, 1, 1) + +/datum/emote/living/drool + key = "drool" + key_third_person = "drools" + message = "drools." + +/datum/emote/living/faint + key = "faint" + key_third_person = "faints" + message = "faints." + +/datum/emote/living/faint/run_emote(mob/user, params) + . = ..() + if(. && isliving(user)) + var/mob/living/L = user + L.SetSleeping(200) + +/datum/emote/living/flap + key = "flap" + key_third_person = "flaps" + message = "flaps their wings." + restraint_check = TRUE + var/wing_time = 20 + +/datum/emote/living/flap/run_emote(mob/user, params) + . = ..() + if(. && ishuman(user)) + var/mob/living/carbon/human/H = user + var/open = FALSE + if(H.dna.features["wings"] != "None") + if("wingsopen" in H.dna.species.mutant_bodyparts) + open = TRUE + H.CloseWings() + else + H.OpenWings() + addtimer(CALLBACK(H, open ? /mob/living/carbon/human.proc/OpenWings : /mob/living/carbon/human.proc/CloseWings), wing_time) + +/datum/emote/living/flap/aflap + key = "aflap" + key_third_person = "aflaps" + message = "flaps their wings ANGRILY!" + restraint_check = TRUE + wing_time = 10 + +/datum/emote/living/frown + key = "frown" + key_third_person = "frowns" + message = "frowns." + +/datum/emote/living/gag + key = "gag" + key_third_person = "gags" + message = "gags." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/gasp + key = "gasp" + key_third_person = "gasps" + message = "gasps!" + emote_type = EMOTE_AUDIBLE + stat_allowed = UNCONSCIOUS + +/datum/emote/living/giggle + key = "giggle" + key_third_person = "giggles" + message = "giggles." + message_mime = "giggles silently!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/glare + key = "glare" + key_third_person = "glares" + message = "glares." + message_param = "glares at %t." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/grin + key = "grin" + key_third_person = "grins" + message = "grins." + +/datum/emote/living/groan + key = "groan" + key_third_person = "groans" + message = "groans!" + message_mime = "appears to groan!" + +/datum/emote/living/grimace + key = "grimace" + key_third_person = "grimaces" + message = "grimaces." + +/datum/emote/living/jump + key = "jump" + key_third_person = "jumps" + message = "jumps!" + restraint_check = TRUE + +/datum/emote/living/kiss + key = "kiss" + key_third_person = "kisses" + message = "blows a kiss." + message_param = "blows a kiss to %t." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/laugh + key = "laugh" + key_third_person = "laughs" + message = "laughs." + message_mime = "laughs silently!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE) + . = ..() + if(. && iscarbon(user)) + var/mob/living/carbon/C = user + return !C.silent + +/datum/emote/living/laugh/run_emote(mob/user, params) + . = ..() + if(. && iscarbon(user)) //Citadel Edit because this is hilarious + var/mob/living/carbon/C = user + if(!C.mind || C.mind.miming) + return + if(iscatperson(C)) //we ask for is cat first because they're a subtype that tests true for ishumanbasic because HERESY + playsound(C, pick('sound/voice/catpeople/nyahaha1.ogg', + 'sound/voice/catpeople/nyahaha2.ogg', + 'sound/voice/catpeople/nyaha.ogg', + 'sound/voice/catpeople/nyahehe.ogg'), + 50, 1) + return + if(ishumanbasic(C)) + if(user.gender == FEMALE) + playsound(C, 'sound/voice/human/womanlaugh.ogg', 50, 1) + else + playsound(C, pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg'), 50, 1) + +/datum/emote/living/look + key = "look" + key_third_person = "looks" + message = "looks." + message_param = "looks at %t." + +/datum/emote/living/nod + key = "nod" + key_third_person = "nods" + message = "nods." + message_param = "nods at %t." + +/datum/emote/living/point + key = "point" + key_third_person = "points" + message = "points." + message_param = "points at %t." + restraint_check = TRUE + +/datum/emote/living/point/run_emote(mob/user, params) + message_param = initial(message_param) // reset + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.get_num_arms() == 0) + if(H.get_num_legs() != 0) + message_param = "tries to point at %t with a leg, falling down in the process!" + H.Knockdown(20) + else + message_param = "bumps [user.p_their()] head on the ground trying to motion towards %t." + H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5) + ..() + +/datum/emote/living/pout + key = "pout" + key_third_person = "pouts" + message = "pouts." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/scream + key = "scream" + key_third_person = "screams" + message = "screams." + message_mime = "acts out a scream!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/scowl + key = "scowl" + key_third_person = "scowls" + message = "scowls." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/shake + key = "shake" + key_third_person = "shakes" + message = "shakes their head." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/shiver + key = "shiver" + key_third_person = "shiver" + message = "shivers." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/sigh + key = "sigh" + key_third_person = "sighs" + message = "sighs." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/sit + key = "sit" + key_third_person = "sits" + message = "sits down." + +/datum/emote/living/smile + key = "smile" + key_third_person = "smiles" + message = "smiles." + +/datum/emote/living/sneeze + key = "sneeze" + key_third_person = "sneezes" + message = "sneezes." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/smug + key = "smug" + key_third_person = "smugs" + message = "grins smugly." + +/datum/emote/living/sniff + key = "sniff" + key_third_person = "sniffs" + message = "sniffs." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/snore + key = "snore" + key_third_person = "snores" + message = "snores." + message_mime = "sleeps soundly." + emote_type = EMOTE_AUDIBLE + stat_allowed = UNCONSCIOUS + +/datum/emote/living/stare + key = "stare" + key_third_person = "stares" + message = "stares." + message_param = "stares at %t." + +/datum/emote/living/strech + key = "stretch" + key_third_person = "stretches" + message = "stretches their arms." + +/datum/emote/living/sulk + key = "sulk" + key_third_person = "sulks" + message = "sulks down sadly." + +/datum/emote/living/surrender + key = "surrender" + key_third_person = "surrenders" + message = "puts their hands on their head and falls to the ground, they surrender!" + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/surrender/run_emote(mob/user, params) + . = ..() + if(. && isliving(user)) + var/mob/living/L = user + L.Knockdown(200) + +/datum/emote/living/sway + key = "sway" + key_third_person = "sways" + message = "sways around dizzily." + +/datum/emote/living/tremble + key = "tremble" + key_third_person = "trembles" + message = "trembles in fear!" + +/datum/emote/living/twitch + key = "twitch" + key_third_person = "twitches" + message = "twitches violently." + +/datum/emote/living/twitch_s + key = "twitch_s" + message = "twitches." + +/datum/emote/living/wave + key = "wave" + key_third_person = "waves" + message = "waves." + +/datum/emote/living/whimper + key = "whimper" + key_third_person = "whimpers" + message = "whimpers." + message_mime = "appears hurt." + +/datum/emote/living/wsmile + key = "wsmile" + key_third_person = "wsmiles" + message = "smiles weakly." + +/datum/emote/living/yawn + key = "yawn" + key_third_person = "yawns" + message = "yawns." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/custom + key = "me" + key_third_person = "custom" + message = null + +/datum/emote/living/custom/proc/check_invalid(mob/user, input) + . = TRUE + if(copytext(input,1,5) == "says") + to_chat(user, "Invalid emote.") + else if(copytext(input,1,9) == "exclaims") + to_chat(user, "Invalid emote.") + else if(copytext(input,1,6) == "yells") + to_chat(user, "Invalid emote.") + else if(copytext(input,1,5) == "asks") + to_chat(user, "Invalid emote.") + else + . = FALSE + +/datum/emote/living/custom/run_emote(mob/user, params, type_override = null) + if(jobban_isbanned(user, "emote")) + to_chat(user, "You cannot send custom emotes (banned).") + return FALSE + else if(QDELETED(user)) + return FALSE + else if(user.client && user.client.prefs.muted & MUTE_IC) + to_chat(user, "You cannot send IC messages (muted).") + return FALSE + else if(!params) + var/custom_emote = copytext(sanitize(input("Choose an emote to display.") as message|null), 1, MAX_MESSAGE_LEN) //CIT CHANGE - expands emote textbox + if(custom_emote && !check_invalid(user, custom_emote)) + var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable") + switch(type) + if("Visible") + emote_type = EMOTE_VISIBLE + if("Hearable") + emote_type = EMOTE_AUDIBLE + else + alert("Unable to use this emote, must be either hearable or visible.") + return + message = custom_emote + else + message = params + if(type_override) + emote_type = type_override + . = ..() + message = null + emote_type = EMOTE_VISIBLE + +/datum/emote/living/custom/replace_pronoun(mob/user, message) + return message + +/datum/emote/living/help + key = "help" + +/datum/emote/living/help/run_emote(mob/user, params) + var/list/keys = list() + var/list/message = list("Available emotes, you can use them with say \"*emote\": ") + + var/datum/emote/E + var/list/emote_list = E.emote_list + for(var/e in emote_list) + if(e in keys) + continue + E = emote_list[e] + if(E.can_run_emote(user, status_check = FALSE)) + keys += E.key + + keys = sortList(keys) + + for(var/emote in keys) + if(LAZYLEN(message) > 1) + message += ", [emote]" + else + message += "[emote]" + + message += "." + + message = jointext(message, "") + + to_chat(user, message) + +/datum/emote/sound/beep + key = "beep" + key_third_person = "beeps" + message = "beeps." + message_param = "beeps at %t." + sound = 'sound/machines/twobeep.ogg' + mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon, /mob/living/carbon/human) + +/datum/emote/living/circle + key = "circle" + key_third_person = "circles" + restraint_check = TRUE + +/datum/emote/living/circle/run_emote(mob/user, params) + . = ..() + var/obj/item/circlegame/N = new(user) + if(user.put_in_hands(N)) + to_chat(user, "You make a circle with your hand.") + else + qdel(N) + to_chat(user, "You don't have any free hands to make a circle with.") + +/datum/emote/living/slap + key = "slap" + key_third_person = "slaps" + restraint_check = TRUE + +/datum/emote/living/slap/run_emote(mob/user, params) + . = ..() + if(!.) + return + var/obj/item/slapper/N = new(user) + if(user.put_in_hands(N)) + to_chat(user, "You ready your slapping hand.") + else + to_chat(user, "You're incapable of slapping in your current state.") diff --git a/code/modules/mob/living/inhand_holder.dm b/code/modules/mob/living/inhand_holder.dm index e57e5e2936..695258f944 100644 --- a/code/modules/mob/living/inhand_holder.dm +++ b/code/modules/mob/living/inhand_holder.dm @@ -1,130 +1,130 @@ -//Generic system for picking up mobs. -//Currently works for head and hands. -/obj/item/clothing/head/mob_holder - name = "bugged mob" - desc = "Yell at coderbrush." - icon = null - icon_state = "" - var/mob/living/held_mob - var/can_head = FALSE - w_class = WEIGHT_CLASS_BULKY - -/obj/item/clothing/head/mob_holder/Initialize(mapload, mob/living/M, _worn_state, alt_worn, lh_icon, rh_icon, _can_head_override = FALSE) - . = ..() - - if(M) - M.setDir(SOUTH) - held_mob = M - M.forceMove(src) - appearance = M.appearance - name = M.name - desc = M.desc - - if(_can_head_override) - can_head = _can_head_override - if(alt_worn) - alternate_worn_icon = alt_worn - if(_worn_state) - item_state = _worn_state - icon_state = _worn_state - if(lh_icon) - lefthand_file = lh_icon - if(rh_icon) - righthand_file = rh_icon - if(!can_head) - slot_flags = NONE - -/obj/item/clothing/head/mob_holder/Destroy() - if(held_mob) - release() - return ..() - -/obj/item/clothing/head/mob_holder/dropped() - ..() - if(isturf(loc))//don't release on soft-drops - release() - -/obj/item/clothing/head/mob_holder/proc/release() - if(isliving(loc)) - var/mob/living/L = loc - L.dropItemToGround(src) - if(held_mob) - var/mob/living/m = held_mob - m.forceMove(get_turf(m)) - m.reset_perspective() - m.setDir(SOUTH) - held_mob = null - qdel(src) - -/obj/item/clothing/head/mob_holder/relaymove(mob/user) - return - -/obj/item/clothing/head/mob_holder/container_resist() - if(isliving(loc)) - var/mob/living/L = loc - visible_message("[src] escapes [L]!") - release() - -/mob/living/proc/mob_pickup(mob/living/L) - var/obj/item/clothing/head/mob_holder/holder = generate_mob_holder() - if(!holder) - return - drop_all_held_items() - L.put_in_hands(holder) - return - -/mob/living/proc/mob_try_pickup(mob/living/user) - if(!ishuman(user) || !src.Adjacent(user) || user.incapacitated() || !can_be_held) - return FALSE - if(user.get_active_held_item()) - to_chat(user, "Your hands are full!") - return FALSE - if(buckled) - to_chat(user, "[src] is buckled to something!") - return FALSE - if(src == user) - to_chat(user, "You can't pick yourself up.") - return FALSE - visible_message("[user] starts picking up [src].", \ - "[user] starts picking you up!") - if(!do_after(user, 20, target = src)) - return FALSE - - if(user.get_active_held_item()||buckled) - return FALSE - - visible_message("[user] picks up [src]!", \ - "[user] picks you up!") - to_chat(user, "You pick [src] up.") - mob_pickup(user) - return TRUE - -/mob/living/AltClick(mob/user) - . = ..() - if(mob_try_pickup(user)) - return TRUE - - -// I didn't define these for mobs, because you shouldn't be able to breathe out of mobs and using their loc isn't always the logical thing to do. - -/obj/item/clothing/head/mob_holder/assume_air(datum/gas_mixture/env) - var/atom/location = loc - if(!loc) - return //null - var/turf/T = get_turf(loc) - while(location != T) - location = location.loc - if(ismob(location)) - return location.loc.assume_air(env) - return loc.assume_air(env) - -/obj/item/clothing/head/mob_holder/remove_air(amount) - var/atom/location = loc - if(!loc) - return //null - var/turf/T = get_turf(loc) - while(location != T) - location = location.loc - if(ismob(location)) - return location.loc.remove_air(amount) - return loc.remove_air(amount) +//Generic system for picking up mobs. +//Currently works for head and hands. +/obj/item/clothing/head/mob_holder + name = "bugged mob" + desc = "Yell at coderbrush." + icon = null + icon_state = "" + var/mob/living/held_mob + var/can_head = FALSE + w_class = WEIGHT_CLASS_BULKY + +/obj/item/clothing/head/mob_holder/Initialize(mapload, mob/living/M, _worn_state, alt_worn, lh_icon, rh_icon, _can_head_override = FALSE) + . = ..() + + if(M) + M.setDir(SOUTH) + held_mob = M + M.forceMove(src) + appearance = M.appearance + name = M.name + desc = M.desc + + if(_can_head_override) + can_head = _can_head_override + if(alt_worn) + alternate_worn_icon = alt_worn + if(_worn_state) + item_state = _worn_state + icon_state = _worn_state + if(lh_icon) + lefthand_file = lh_icon + if(rh_icon) + righthand_file = rh_icon + if(!can_head) + slot_flags = NONE + +/obj/item/clothing/head/mob_holder/Destroy() + if(held_mob) + release() + return ..() + +/obj/item/clothing/head/mob_holder/dropped() + ..() + if(isturf(loc))//don't release on soft-drops + release() + +/obj/item/clothing/head/mob_holder/proc/release() + if(isliving(loc)) + var/mob/living/L = loc + L.dropItemToGround(src) + if(held_mob) + var/mob/living/m = held_mob + m.forceMove(get_turf(m)) + m.reset_perspective() + m.setDir(SOUTH) + held_mob = null + qdel(src) + +/obj/item/clothing/head/mob_holder/relaymove(mob/user) + return + +/obj/item/clothing/head/mob_holder/container_resist() + if(isliving(loc)) + var/mob/living/L = loc + visible_message("[src] escapes [L]!") + release() + +/mob/living/proc/mob_pickup(mob/living/L) + var/obj/item/clothing/head/mob_holder/holder = generate_mob_holder() + if(!holder) + return + drop_all_held_items() + L.put_in_hands(holder) + return + +/mob/living/proc/mob_try_pickup(mob/living/user) + if(!ishuman(user) || !src.Adjacent(user) || user.incapacitated() || !can_be_held) + return FALSE + if(user.get_active_held_item()) + to_chat(user, "Your hands are full!") + return FALSE + if(buckled) + to_chat(user, "[src] is buckled to something!") + return FALSE + if(src == user) + to_chat(user, "You can't pick yourself up.") + return FALSE + visible_message("[user] starts picking up [src].", \ + "[user] starts picking you up!") + if(!do_after(user, 20, target = src)) + return FALSE + + if(user.get_active_held_item()||buckled) + return FALSE + + visible_message("[user] picks up [src]!", \ + "[user] picks you up!") + to_chat(user, "You pick [src] up.") + mob_pickup(user) + return TRUE + +/mob/living/AltClick(mob/user) + . = ..() + if(mob_try_pickup(user)) + return TRUE + + +// I didn't define these for mobs, because you shouldn't be able to breathe out of mobs and using their loc isn't always the logical thing to do. + +/obj/item/clothing/head/mob_holder/assume_air(datum/gas_mixture/env) + var/atom/location = loc + if(!loc) + return //null + var/turf/T = get_turf(loc) + while(location != T) + location = location.loc + if(ismob(location)) + return location.loc.assume_air(env) + return loc.assume_air(env) + +/obj/item/clothing/head/mob_holder/remove_air(amount) + var/atom/location = loc + if(!loc) + return //null + var/turf/T = get_turf(loc) + while(location != T) + location = location.loc + if(ismob(location)) + return location.loc.remove_air(amount) + return loc.remove_air(amount) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index e398dad65b..4c67922c18 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -1,523 +1,523 @@ - -/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = "Your armor absorbs the blow!", soften_text = "Your armor softens the blow!", armour_penetration, penetrated_text = "Your armor was penetrated!") - var/armor = getarmor(def_zone, attack_flag) - - //the if "armor" check is because this is used for everything on /living, including humans - if(armor && armour_penetration) - armor = max(0, armor - armour_penetration) - if(penetrated_text) - to_chat(src, "[penetrated_text]") - else if(armor >= 100) - if(absorb_text) - to_chat(src, "[absorb_text]") - else if(armor > 0) - if(soften_text) - to_chat(src, "[soften_text]") - return armor - - -/mob/living/proc/getarmor(def_zone, type) - return 0 - -//this returns the mob's protection against eye damage (number between -1 and 2) from bright lights -/mob/living/proc/get_eye_protection() - return 0 - -//this returns the mob's protection against ear damage (0:no protection; 1: some ear protection; 2: has no ears) -/mob/living/proc/get_ear_protection() - return 0 - -/mob/living/proc/is_mouth_covered(head_only = 0, mask_only = 0) - return FALSE - -/mob/living/proc/is_eyes_covered(check_glasses = 1, check_head = 1, check_mask = 1) - return FALSE - -/mob/living/proc/on_hit(obj/item/projectile/P) - return - -/mob/living/proc/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) - var/block_chance_modifier = round(damage / -3) - for(var/obj/item/I in held_items) - if(!istype(I, /obj/item/clothing)) - var/final_block_chance = I.block_chance - (CLAMP((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example - if(I.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return TRUE - return FALSE - -/mob/living/proc/check_reflect(def_zone) //Reflection checks for anything in your hands, based on the reflection chance of the object(s) - for(var/obj/item/I in held_items) - if(I.IsReflect(def_zone)) - return TRUE - return FALSE - -/mob/living/proc/reflect_bullet_check(obj/item/projectile/P, def_zone) - if(P.is_reflectable && check_reflect(def_zone)) // Checks if you've passed a reflection% check - visible_message("The [P.name] gets reflected by [src]!", \ - "The [P.name] gets reflected by [src]!") - // Find a turf near or on the original location to bounce to - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/turf/curloc = get_turf(src) - // redirect the projectile - P.original = locate(new_x, new_y, P.z) - P.starting = curloc - P.firer = src - P.yo = new_y - curloc.y - P.xo = new_x - curloc.x - var/new_angle_s = P.Angle + rand(120,240) - while(new_angle_s > 180) // Translate to regular projectile degrees - new_angle_s -= 360 - P.setAngle(new_angle_s) - return TRUE - return FALSE - -/mob/living/bullet_act(obj/item/projectile/P, def_zone) - if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself - if(reflect_bullet_check(P, def_zone)) - return -1 // complete projectile permutation - if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration)) - P.on_hit(src, 100, def_zone) - return 2 - var/armor = run_armor_check(def_zone, P.flag, null, null, P.armour_penetration, null) - if(!P.nodamage) - apply_damage(P.damage, P.damage_type, def_zone, armor) - if(P.dismemberment) - check_projectile_dismemberment(P, def_zone) - return P.on_hit(src, armor) - -/mob/living/proc/check_projectile_dismemberment(obj/item/projectile/P, def_zone) - return 0 - -/obj/item/proc/get_volume_by_throwforce_and_or_w_class() - if(throwforce && w_class) - return CLAMP((throwforce + w_class) * 5, 30, 100)// Add the item's throwforce to its weight class and multiply by 5, then clamp the value between 30 and 100 - else if(w_class) - return CLAMP(w_class * 8, 20, 100) // Multiply the item's weight class by 8, then clamp the value between 20 and 100 - else - return 0 - -/mob/living/proc/catch_item(obj/item/I, skip_throw_mode_check = FALSE) - return FALSE - -/mob/living/proc/embed_item(obj/item/I) - return - -/mob/living/proc/can_embed(obj/item/I) - return FALSE - -/mob/living/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE) - var/obj/item/I - var/throwpower = 30 - if(isitem(AM)) - I = AM - throwpower = I.throwforce - if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK)) - hitpush = FALSE - skipcatch = TRUE - blocked = TRUE - else if(I && I.throw_speed >= EMBED_THROWSPEED_THRESHOLD && can_embed(I, src) && prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && (!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) || incapacitated() || get_active_held_item())) - embed_item(I) - hitpush = FALSE - skipcatch = TRUE //can't catch the now embedded item - if(I) - if(!skipcatch && isturf(I.loc) && catch_item(I)) - return TRUE - var/zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest - var/dtype = BRUTE - var/volume = I.get_volume_by_throwforce_and_or_w_class() - SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, zone) - dtype = I.damtype - - if (I.throwforce > 0) //If the weapon's throwforce is greater than zero... - if (I.throwhitsound) //...and throwhitsound is defined... - playsound(loc, I.throwhitsound, volume, 1, -1) //...play the weapon's throwhitsound. - else if(I.hitsound) //Otherwise, if the weapon's hitsound is defined... - playsound(loc, I.hitsound, volume, 1, -1) //...play the weapon's hitsound. - else if(!I.throwhitsound) //Otherwise, if throwhitsound isn't defined... - playsound(loc, 'sound/weapons/genhit.ogg',volume, 1, -1) //...play genhit.ogg. - - else if(!I.throwhitsound && I.throwforce > 0) //Otherwise, if the item doesn't have a throwhitsound and has a throwforce greater than zero... - playsound(loc, 'sound/weapons/genhit.ogg', volume, 1, -1)//...play genhit.ogg - if(!I.throwforce)// Otherwise, if the item's throwforce is 0... - playsound(loc, 'sound/weapons/throwtap.ogg', 1, volume, -1)//...play throwtap.ogg. - if(!blocked) - visible_message("[src] has been hit by [I].", \ - "[src] has been hit by [I].") - var/armor = run_armor_check(zone, "melee", "Your armor has protected your [parse_zone(zone)].", "Your armor has softened hit to your [parse_zone(zone)].",I.armour_penetration) - apply_damage(I.throwforce, dtype, zone, armor) - if(I.thrownby) - log_combat(I.thrownby, src, "threw and hit", I) - else - return 1 - else - playsound(loc, 'sound/weapons/genhit.ogg', 50, 1, -1) - ..() - - -/mob/living/mech_melee_attack(obj/mecha/M) - if(M.occupant.a_intent == INTENT_HARM) - if(HAS_TRAIT(M.occupant, TRAIT_PACIFISM)) - to_chat(M.occupant, "You don't want to harm other living beings!") - return - M.do_attack_animation(src) - if(M.damtype == "brute") - step_away(src,M,15) - switch(M.damtype) - if(BRUTE) - Unconscious(20) - take_overall_damage(rand(M.force/2, M.force)) - playsound(src, 'sound/weapons/punch4.ogg', 50, 1) - if(BURN) - take_overall_damage(0, rand(M.force/2, M.force)) - playsound(src, 'sound/items/welder.ogg', 50, 1) - if(TOX) - M.mech_toxin_damage(src) - else - return - updatehealth() - visible_message("[M.name] has hit [src]!", \ - "[M.name] has hit [src]!", null, COMBAT_MESSAGE_RANGE) - log_combat(M.occupant, src, "attacked", M, "(INTENT: [uppertext(M.occupant.a_intent)]) (DAMTYPE: [uppertext(M.damtype)])") - else - step_away(src,M) - log_combat(M.occupant, src, "pushed", M) - visible_message("[M] pushes [src] out of the way.", null, null, 5) - -/mob/living/fire_act() - adjust_fire_stacks(3) - IgniteMob() - -/mob/living/proc/grabbedby(mob/living/carbon/user, supress_message = 0) - if(user == anchored || !isturf(user.loc)) - return FALSE - - //pacifist vore check. - if(user.pulling && HAS_TRAIT(user, TRAIT_PACIFISM) && user.voremode) //they can only do heals, noisy guts, absorbing (technically not harm) - if(ismob(user.pulling)) - var/mob/P = user.pulling - if(src != user) - to_chat(user, "You can't risk digestion!") - return FALSE - else - user.vore_attack(user, P, user) - return - - //normal vore check. - if(user.pulling && user.grab_state == GRAB_AGGRESSIVE && user.voremode) - if(ismob(user.pulling)) - var/mob/P = user.pulling - user.vore_attack(user, P, src) // User, Pulled, Predator target (which can be user, pulling, or src) - return - - if(user == src) //we want to be able to self click if we're voracious - return FALSE - - if(!user.pulling || user.pulling != src) - user.start_pulling(src, supress_message) - return - - if(!(status_flags & CANPUSH) || HAS_TRAIT(src, TRAIT_PUSHIMMUNE)) - to_chat(user, "[src] can't be grabbed more aggressively!") - return FALSE - - if(user.grab_state >= GRAB_AGGRESSIVE && HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You don't want to risk hurting [src]!") - return FALSE - - grippedby(user) - -//proc to upgrade a simple pull into a more aggressive grab. -/mob/living/proc/grippedby(mob/living/carbon/user, instant = FALSE) - if(user.grab_state < GRAB_KILL) - user.changeNext_move(CLICK_CD_GRABBING) - playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) - - if(user.grab_state) //only the first upgrade is instantaneous - var/old_grab_state = user.grab_state - var/grab_upgrade_time = instant ? 0 : 30 - visible_message("[user] starts to tighten [user.p_their()] grip on [src]!", \ - "[user] starts to tighten [user.p_their()] grip on you!") - switch(user.grab_state) - if(GRAB_AGGRESSIVE) - log_combat(user, src, "attempted to neck grab", addition="neck grab") - if(GRAB_NECK) - log_combat(user, src, "attempted to strangle", addition="kill grab") - if(!do_mob(user, src, grab_upgrade_time)) - return 0 - if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state || user.a_intent != INTENT_GRAB) - return 0 - if(user.voremode && user.grab_state == GRAB_AGGRESSIVE) - return 0 - user.grab_state++ - switch(user.grab_state) - if(GRAB_AGGRESSIVE) - var/add_log = "" - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - visible_message("[user] has firmly gripped [src]!", - "[user] has firmly gripped you!") - add_log = " (pacifist)" - else - visible_message("[user] has grabbed [src] aggressively!", \ - "[user] has grabbed you aggressively!") - drop_all_held_items() - stop_pulling() - log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") - if(GRAB_NECK) - log_combat(user, src, "grabbed", addition="neck grab") - visible_message("[user] has grabbed [src] by the neck!",\ - "[user] has grabbed you by the neck!") - update_canmove() //we fall down - if(!buckled && !density) - Move(user.loc) - if(GRAB_KILL) - log_combat(user, src, "strangled", addition="kill grab") - visible_message("[user] is strangling [src]!", \ - "[user] is strangling you!") - update_canmove() //we fall down - if(!buckled && !density) - Move(user.loc) - return 1 - -/mob/living/attack_hand(mob/user) - ..() //Ignoring parent return value here. - SEND_SIGNAL(src, COMSIG_MOB_ATTACK_HAND, user) - if((user != src) && user.a_intent != INTENT_HELP && check_shields(user, 0, user.name, attack_type = UNARMED_ATTACK)) - log_combat(user, src, "attempted to touch") - visible_message("[user] attempted to touch [src]!") - return TRUE - -/mob/living/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) - if(user.a_intent == INTENT_HARM) - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, "You don't want to hurt [src]!") - return TRUE - var/hulk_verb = pick("smash","pummel") - if(user != src && check_shields(user, 15, "the [hulk_verb]ing")) - return TRUE - ..() - return FALSE - -/mob/living/attack_slime(mob/living/simple_animal/slime/M) - if(!SSticker.HasRoundStarted()) - to_chat(M, "You cannot attack people before the game has started.") - return - - if(M.buckled) - if(M in buckled_mobs) - M.Feedstop() - return // can't attack while eating! - - if(HAS_TRAIT(src, TRAIT_PACIFISM)) - to_chat(M, "You don't want to hurt anyone!") - return FALSE - - var/damage = rand(5, 35) - if(M.is_adult) - damage = rand(20, 40) - if(check_shields(M, damage, "the [M.name]")) - return FALSE - - if (stat != DEAD) - log_combat(M, src, "attacked") - M.do_attack_animation(src) - visible_message("The [M.name] glomps [src]!", \ - "The [M.name] glomps [src]!", null, COMBAT_MESSAGE_RANGE) - return TRUE - -/mob/living/attack_animal(mob/living/simple_animal/M) - M.face_atom(src) - if(M.melee_damage_upper == 0) - M.visible_message("\The [M] [M.friendly] [src]!") - return FALSE - else - if(HAS_TRAIT(M, TRAIT_PACIFISM)) - to_chat(M, "You don't want to hurt anyone!") - return FALSE - if(check_shields(M, rand(M.melee_damage_lower, M.melee_damage_upper), "the [M.name]", MELEE_ATTACK, M.armour_penetration)) - return FALSE - if(M.attack_sound) - playsound(loc, M.attack_sound, 50, 1, 1) - M.do_attack_animation(src) - visible_message("\The [M] [M.attacktext] [src]!", \ - "\The [M] [M.attacktext] [src]!", null, COMBAT_MESSAGE_RANGE) - log_combat(M, src, "attacked") - return TRUE - - -/mob/living/attack_paw(mob/living/carbon/monkey/M) - if (M.a_intent == INTENT_HARM) - if(HAS_TRAIT(M, TRAIT_PACIFISM)) - to_chat(M, "You don't want to hurt anyone!") - return FALSE - - if(M.is_muzzled() || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSMOUTH)) - to_chat(M, "You can't bite with your mouth covered!") - return FALSE - if(check_shields(M, 0, "the [M.name]")) - return FALSE - M.do_attack_animation(src, ATTACK_EFFECT_BITE) - if (prob(75)) - log_combat(M, src, "attacked") - playsound(loc, 'sound/weapons/bite.ogg', 50, 1, -1) - visible_message("[M.name] bites [src]!", \ - "[M.name] bites [src]!", null, COMBAT_MESSAGE_RANGE) - return TRUE - else - visible_message("[M.name] has attempted to bite [src]!", \ - "[M.name] has attempted to bite [src]!", null, COMBAT_MESSAGE_RANGE) - return FALSE - -/mob/living/attack_larva(mob/living/carbon/alien/larva/L) - switch(L.a_intent) - if(INTENT_HELP) - visible_message("[L.name] rubs its head against [src].") - return FALSE - - else - if(HAS_TRAIT(L, TRAIT_PACIFISM)) - to_chat(L, "You don't want to hurt anyone!") - return FALSE - if(L != src && check_shields(L, rand(1, 3), "the [L.name]")) - return FALSE - L.do_attack_animation(src) - if(prob(90)) - log_combat(L, src, "attacked") - visible_message("[L.name] bites [src]!", \ - "[L.name] bites [src]!", null, COMBAT_MESSAGE_RANGE) - playsound(loc, 'sound/weapons/bite.ogg', 50, 1, -1) - return TRUE - else - visible_message("[L.name] has attempted to bite [src]!", \ - "[L.name] has attempted to bite [src]!", null, COMBAT_MESSAGE_RANGE) - -/mob/living/attack_alien(mob/living/carbon/alien/humanoid/M) - if((M != src) && M.a_intent != INTENT_HELP && check_shields(M, 0, "the [M.name]")) - visible_message("[M] attempted to touch [src]!") - return FALSE - switch(M.a_intent) - if (INTENT_HELP) - if(!isalien(src)) //I know it's ugly, but the alien vs alien attack_alien behaviour is a bit different. - visible_message("[M] caresses [src] with its scythe like arm.") - return FALSE - if (INTENT_GRAB) - grabbedby(M) - return FALSE - if(INTENT_HARM) - if(HAS_TRAIT(M, TRAIT_PACIFISM)) - to_chat(M, "You don't want to hurt anyone!") - return FALSE - if(!isalien(src)) - M.do_attack_animation(src) - return TRUE - if(INTENT_DISARM) - if(!isalien(src)) - M.do_attack_animation(src, ATTACK_EFFECT_DISARM) - return TRUE - -/mob/living/ex_act(severity, target, origin) - if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) - return - ..() - -//Looking for irradiate()? It's been moved to radiation.dm under the rad_act() for mobs. - -/mob/living/acid_act(acidpwr, acid_volume) - take_bodypart_damage(acidpwr * min(1, acid_volume * 0.1)) - return 1 - -/mob/living/proc/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = 0, tesla_shock = 0, illusion = 0, stun = TRUE) - SEND_SIGNAL(src, COMSIG_LIVING_ELECTROCUTE_ACT, shock_damage) - if(tesla_shock && (flags_1 & TESLA_IGNORE_1)) - return FALSE - if(HAS_TRAIT(src, TRAIT_SHOCKIMMUNE)) - return FALSE - if(shock_damage > 0) - if(!illusion) - adjustFireLoss(shock_damage) - visible_message( - "[src] was shocked by \the [source]!", \ - "You feel a powerful shock coursing through your body!", \ - "You hear a heavy electrical crack." \ - ) - return shock_damage - -/mob/living/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_CONTENTS) - return - for(var/obj/O in contents) - O.emp_act(severity) - -/mob/living/singularity_act() - var/gain = 20 - investigate_log("([key_name(src)]) has been consumed by the singularity.", INVESTIGATE_SINGULO) //Oh that's where the clown ended up! - gib() - return(gain) - -/mob/living/narsie_act() - if(status_flags & GODMODE || QDELETED(src)) - return - - if(is_servant_of_ratvar(src) && !stat) - to_chat(src, "You resist Nar'Sie's influence... but not all of it. Run!") - adjustBruteLoss(35) - if(src && reagents) - reagents.add_reagent(/datum/reagent/toxin/heparin, 5) - return FALSE - if(GLOB.cult_narsie && GLOB.cult_narsie.souls_needed[src]) - GLOB.cult_narsie.souls_needed -= src - GLOB.cult_narsie.souls += 1 - if((GLOB.cult_narsie.souls == GLOB.cult_narsie.soul_goal) && (GLOB.cult_narsie.resolved == FALSE)) - GLOB.cult_narsie.resolved = TRUE - sound_to_playing_players('sound/machines/alarm.ogg') - addtimer(CALLBACK(GLOBAL_PROC, .proc/cult_ending_helper, 1), 120) - addtimer(CALLBACK(GLOBAL_PROC, .proc/ending_helper), 270) - if(client) - makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, src, cultoverride = TRUE) - else - switch(rand(1, 6)) - if(1) - new /mob/living/simple_animal/hostile/construct/armored/hostile(get_turf(src)) - if(2) - new /mob/living/simple_animal/hostile/construct/wraith/hostile(get_turf(src)) - if(3 to 6) - new /mob/living/simple_animal/hostile/construct/builder/hostile(get_turf(src)) - spawn_dust() - gib() - return TRUE - - -/mob/living/ratvar_act() - if(status_flags & GODMODE) - return - if(stat != DEAD && !is_servant_of_ratvar(src)) - to_chat(src, "A blinding light boils you alive! Run!") - adjust_fire_stacks(20) - IgniteMob() - return FALSE - - -//called when the mob receives a bright flash -/mob/living/proc/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /obj/screen/fullscreen/flash) - if(get_eye_protection() < intensity && (override_blindness_check || !(HAS_TRAIT(src, TRAIT_BLIND)))) - overlay_fullscreen("flash", type) - addtimer(CALLBACK(src, .proc/clear_fullscreen, "flash", 25), 25) - return TRUE - return FALSE - -//called when the mob receives a loud bang -/mob/living/proc/soundbang_act() - return 0 - -//to damage the clothes worn by a mob -/mob/living/proc/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone) - return - - -/mob/living/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) - if(!used_item) - used_item = get_active_held_item() - ..() - setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure we restart the bouncing after the next movement. + +/mob/living/proc/run_armor_check(def_zone = null, attack_flag = "melee", absorb_text = "Your armor absorbs the blow!", soften_text = "Your armor softens the blow!", armour_penetration, penetrated_text = "Your armor was penetrated!") + var/armor = getarmor(def_zone, attack_flag) + + //the if "armor" check is because this is used for everything on /living, including humans + if(armor && armour_penetration) + armor = max(0, armor - armour_penetration) + if(penetrated_text) + to_chat(src, "[penetrated_text]") + else if(armor >= 100) + if(absorb_text) + to_chat(src, "[absorb_text]") + else if(armor > 0) + if(soften_text) + to_chat(src, "[soften_text]") + return armor + + +/mob/living/proc/getarmor(def_zone, type) + return 0 + +//this returns the mob's protection against eye damage (number between -1 and 2) from bright lights +/mob/living/proc/get_eye_protection() + return 0 + +//this returns the mob's protection against ear damage (0:no protection; 1: some ear protection; 2: has no ears) +/mob/living/proc/get_ear_protection() + return 0 + +/mob/living/proc/is_mouth_covered(head_only = 0, mask_only = 0) + return FALSE + +/mob/living/proc/is_eyes_covered(check_glasses = 1, check_head = 1, check_mask = 1) + return FALSE + +/mob/living/proc/on_hit(obj/item/projectile/P) + return + +/mob/living/proc/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) + var/block_chance_modifier = round(damage / -3) + for(var/obj/item/I in held_items) + if(!istype(I, /obj/item/clothing)) + var/final_block_chance = I.block_chance - (CLAMP((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example + if(I.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) + return TRUE + return FALSE + +/mob/living/proc/check_reflect(def_zone) //Reflection checks for anything in your hands, based on the reflection chance of the object(s) + for(var/obj/item/I in held_items) + if(I.IsReflect(def_zone)) + return TRUE + return FALSE + +/mob/living/proc/reflect_bullet_check(obj/item/projectile/P, def_zone) + if(P.is_reflectable && check_reflect(def_zone)) // Checks if you've passed a reflection% check + visible_message("The [P.name] gets reflected by [src]!", \ + "The [P.name] gets reflected by [src]!") + // Find a turf near or on the original location to bounce to + if(P.starting) + var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/turf/curloc = get_turf(src) + // redirect the projectile + P.original = locate(new_x, new_y, P.z) + P.starting = curloc + P.firer = src + P.yo = new_y - curloc.y + P.xo = new_x - curloc.x + var/new_angle_s = P.Angle + rand(120,240) + while(new_angle_s > 180) // Translate to regular projectile degrees + new_angle_s -= 360 + P.setAngle(new_angle_s) + return TRUE + return FALSE + +/mob/living/bullet_act(obj/item/projectile/P, def_zone) + if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself + if(reflect_bullet_check(P, def_zone)) + return -1 // complete projectile permutation + if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration)) + P.on_hit(src, 100, def_zone) + return 2 + var/armor = run_armor_check(def_zone, P.flag, null, null, P.armour_penetration, null) + if(!P.nodamage) + apply_damage(P.damage, P.damage_type, def_zone, armor) + if(P.dismemberment) + check_projectile_dismemberment(P, def_zone) + return P.on_hit(src, armor) + +/mob/living/proc/check_projectile_dismemberment(obj/item/projectile/P, def_zone) + return 0 + +/obj/item/proc/get_volume_by_throwforce_and_or_w_class() + if(throwforce && w_class) + return CLAMP((throwforce + w_class) * 5, 30, 100)// Add the item's throwforce to its weight class and multiply by 5, then clamp the value between 30 and 100 + else if(w_class) + return CLAMP(w_class * 8, 20, 100) // Multiply the item's weight class by 8, then clamp the value between 20 and 100 + else + return 0 + +/mob/living/proc/catch_item(obj/item/I, skip_throw_mode_check = FALSE) + return FALSE + +/mob/living/proc/embed_item(obj/item/I) + return + +/mob/living/proc/can_embed(obj/item/I) + return FALSE + +/mob/living/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE) + var/obj/item/I + var/throwpower = 30 + if(isitem(AM)) + I = AM + throwpower = I.throwforce + if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK)) + hitpush = FALSE + skipcatch = TRUE + blocked = TRUE + else if(I && I.throw_speed >= EMBED_THROWSPEED_THRESHOLD && can_embed(I, src) && prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && (!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) || incapacitated() || get_active_held_item())) + embed_item(I) + hitpush = FALSE + skipcatch = TRUE //can't catch the now embedded item + if(I) + if(!skipcatch && isturf(I.loc) && catch_item(I)) + return TRUE + var/zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest + var/dtype = BRUTE + var/volume = I.get_volume_by_throwforce_and_or_w_class() + SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, zone) + dtype = I.damtype + + if (I.throwforce > 0) //If the weapon's throwforce is greater than zero... + if (I.throwhitsound) //...and throwhitsound is defined... + playsound(loc, I.throwhitsound, volume, 1, -1) //...play the weapon's throwhitsound. + else if(I.hitsound) //Otherwise, if the weapon's hitsound is defined... + playsound(loc, I.hitsound, volume, 1, -1) //...play the weapon's hitsound. + else if(!I.throwhitsound) //Otherwise, if throwhitsound isn't defined... + playsound(loc, 'sound/weapons/genhit.ogg',volume, 1, -1) //...play genhit.ogg. + + else if(!I.throwhitsound && I.throwforce > 0) //Otherwise, if the item doesn't have a throwhitsound and has a throwforce greater than zero... + playsound(loc, 'sound/weapons/genhit.ogg', volume, 1, -1)//...play genhit.ogg + if(!I.throwforce)// Otherwise, if the item's throwforce is 0... + playsound(loc, 'sound/weapons/throwtap.ogg', 1, volume, -1)//...play throwtap.ogg. + if(!blocked) + visible_message("[src] has been hit by [I].", \ + "[src] has been hit by [I].") + var/armor = run_armor_check(zone, "melee", "Your armor has protected your [parse_zone(zone)].", "Your armor has softened hit to your [parse_zone(zone)].",I.armour_penetration) + apply_damage(I.throwforce, dtype, zone, armor) + if(I.thrownby) + log_combat(I.thrownby, src, "threw and hit", I) + else + return 1 + else + playsound(loc, 'sound/weapons/genhit.ogg', 50, 1, -1) + ..() + + +/mob/living/mech_melee_attack(obj/mecha/M) + if(M.occupant.a_intent == INTENT_HARM) + if(HAS_TRAIT(M.occupant, TRAIT_PACIFISM)) + to_chat(M.occupant, "You don't want to harm other living beings!") + return + M.do_attack_animation(src) + if(M.damtype == "brute") + step_away(src,M,15) + switch(M.damtype) + if(BRUTE) + Unconscious(20) + take_overall_damage(rand(M.force/2, M.force)) + playsound(src, 'sound/weapons/punch4.ogg', 50, 1) + if(BURN) + take_overall_damage(0, rand(M.force/2, M.force)) + playsound(src, 'sound/items/welder.ogg', 50, 1) + if(TOX) + M.mech_toxin_damage(src) + else + return + updatehealth() + visible_message("[M.name] has hit [src]!", \ + "[M.name] has hit [src]!", null, COMBAT_MESSAGE_RANGE) + log_combat(M.occupant, src, "attacked", M, "(INTENT: [uppertext(M.occupant.a_intent)]) (DAMTYPE: [uppertext(M.damtype)])") + else + step_away(src,M) + log_combat(M.occupant, src, "pushed", M) + visible_message("[M] pushes [src] out of the way.", null, null, 5) + +/mob/living/fire_act() + adjust_fire_stacks(3) + IgniteMob() + +/mob/living/proc/grabbedby(mob/living/carbon/user, supress_message = 0) + if(user == anchored || !isturf(user.loc)) + return FALSE + + //pacifist vore check. + if(user.pulling && HAS_TRAIT(user, TRAIT_PACIFISM) && user.voremode) //they can only do heals, noisy guts, absorbing (technically not harm) + if(ismob(user.pulling)) + var/mob/P = user.pulling + if(src != user) + to_chat(user, "You can't risk digestion!") + return FALSE + else + user.vore_attack(user, P, user) + return + + //normal vore check. + if(user.pulling && user.grab_state == GRAB_AGGRESSIVE && user.voremode) + if(ismob(user.pulling)) + var/mob/P = user.pulling + user.vore_attack(user, P, src) // User, Pulled, Predator target (which can be user, pulling, or src) + return + + if(user == src) //we want to be able to self click if we're voracious + return FALSE + + if(!user.pulling || user.pulling != src) + user.start_pulling(src, supress_message) + return + + if(!(status_flags & CANPUSH) || HAS_TRAIT(src, TRAIT_PUSHIMMUNE)) + to_chat(user, "[src] can't be grabbed more aggressively!") + return FALSE + + if(user.grab_state >= GRAB_AGGRESSIVE && HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to risk hurting [src]!") + return FALSE + + grippedby(user) + +//proc to upgrade a simple pull into a more aggressive grab. +/mob/living/proc/grippedby(mob/living/carbon/user, instant = FALSE) + if(user.grab_state < GRAB_KILL) + user.changeNext_move(CLICK_CD_GRABBING) + playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + + if(user.grab_state) //only the first upgrade is instantaneous + var/old_grab_state = user.grab_state + var/grab_upgrade_time = instant ? 0 : 30 + visible_message("[user] starts to tighten [user.p_their()] grip on [src]!", \ + "[user] starts to tighten [user.p_their()] grip on you!") + switch(user.grab_state) + if(GRAB_AGGRESSIVE) + log_combat(user, src, "attempted to neck grab", addition="neck grab") + if(GRAB_NECK) + log_combat(user, src, "attempted to strangle", addition="kill grab") + if(!do_mob(user, src, grab_upgrade_time)) + return 0 + if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state || user.a_intent != INTENT_GRAB) + return 0 + if(user.voremode && user.grab_state == GRAB_AGGRESSIVE) + return 0 + user.grab_state++ + switch(user.grab_state) + if(GRAB_AGGRESSIVE) + var/add_log = "" + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + visible_message("[user] has firmly gripped [src]!", + "[user] has firmly gripped you!") + add_log = " (pacifist)" + else + visible_message("[user] has grabbed [src] aggressively!", \ + "[user] has grabbed you aggressively!") + drop_all_held_items() + stop_pulling() + log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") + if(GRAB_NECK) + log_combat(user, src, "grabbed", addition="neck grab") + visible_message("[user] has grabbed [src] by the neck!",\ + "[user] has grabbed you by the neck!") + update_canmove() //we fall down + if(!buckled && !density) + Move(user.loc) + if(GRAB_KILL) + log_combat(user, src, "strangled", addition="kill grab") + visible_message("[user] is strangling [src]!", \ + "[user] is strangling you!") + update_canmove() //we fall down + if(!buckled && !density) + Move(user.loc) + return 1 + +/mob/living/attack_hand(mob/user) + ..() //Ignoring parent return value here. + SEND_SIGNAL(src, COMSIG_MOB_ATTACK_HAND, user) + if((user != src) && user.a_intent != INTENT_HELP && check_shields(user, 0, user.name, attack_type = UNARMED_ATTACK)) + log_combat(user, src, "attempted to touch") + visible_message("[user] attempted to touch [src]!") + return TRUE + +/mob/living/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE) + if(user.a_intent == INTENT_HARM) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + to_chat(user, "You don't want to hurt [src]!") + return TRUE + var/hulk_verb = pick("smash","pummel") + if(user != src && check_shields(user, 15, "the [hulk_verb]ing")) + return TRUE + ..() + return FALSE + +/mob/living/attack_slime(mob/living/simple_animal/slime/M) + if(!SSticker.HasRoundStarted()) + to_chat(M, "You cannot attack people before the game has started.") + return + + if(M.buckled) + if(M in buckled_mobs) + M.Feedstop() + return // can't attack while eating! + + if(HAS_TRAIT(src, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt anyone!") + return FALSE + + var/damage = rand(5, 35) + if(M.is_adult) + damage = rand(20, 40) + if(check_shields(M, damage, "the [M.name]")) + return FALSE + + if (stat != DEAD) + log_combat(M, src, "attacked") + M.do_attack_animation(src) + visible_message("The [M.name] glomps [src]!", \ + "The [M.name] glomps [src]!", null, COMBAT_MESSAGE_RANGE) + return TRUE + +/mob/living/attack_animal(mob/living/simple_animal/M) + M.face_atom(src) + if(M.melee_damage_upper == 0) + M.visible_message("\The [M] [M.friendly] [src]!") + return FALSE + else + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt anyone!") + return FALSE + if(check_shields(M, rand(M.melee_damage_lower, M.melee_damage_upper), "the [M.name]", MELEE_ATTACK, M.armour_penetration)) + return FALSE + if(M.attack_sound) + playsound(loc, M.attack_sound, 50, 1, 1) + M.do_attack_animation(src) + visible_message("\The [M] [M.attacktext] [src]!", \ + "\The [M] [M.attacktext] [src]!", null, COMBAT_MESSAGE_RANGE) + log_combat(M, src, "attacked") + return TRUE + + +/mob/living/attack_paw(mob/living/carbon/monkey/M) + if (M.a_intent == INTENT_HARM) + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt anyone!") + return FALSE + + if(M.is_muzzled() || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSMOUTH)) + to_chat(M, "You can't bite with your mouth covered!") + return FALSE + if(check_shields(M, 0, "the [M.name]")) + return FALSE + M.do_attack_animation(src, ATTACK_EFFECT_BITE) + if (prob(75)) + log_combat(M, src, "attacked") + playsound(loc, 'sound/weapons/bite.ogg', 50, 1, -1) + visible_message("[M.name] bites [src]!", \ + "[M.name] bites [src]!", null, COMBAT_MESSAGE_RANGE) + return TRUE + else + visible_message("[M.name] has attempted to bite [src]!", \ + "[M.name] has attempted to bite [src]!", null, COMBAT_MESSAGE_RANGE) + return FALSE + +/mob/living/attack_larva(mob/living/carbon/alien/larva/L) + switch(L.a_intent) + if(INTENT_HELP) + visible_message("[L.name] rubs its head against [src].") + return FALSE + + else + if(HAS_TRAIT(L, TRAIT_PACIFISM)) + to_chat(L, "You don't want to hurt anyone!") + return FALSE + if(L != src && check_shields(L, rand(1, 3), "the [L.name]")) + return FALSE + L.do_attack_animation(src) + if(prob(90)) + log_combat(L, src, "attacked") + visible_message("[L.name] bites [src]!", \ + "[L.name] bites [src]!", null, COMBAT_MESSAGE_RANGE) + playsound(loc, 'sound/weapons/bite.ogg', 50, 1, -1) + return TRUE + else + visible_message("[L.name] has attempted to bite [src]!", \ + "[L.name] has attempted to bite [src]!", null, COMBAT_MESSAGE_RANGE) + +/mob/living/attack_alien(mob/living/carbon/alien/humanoid/M) + if((M != src) && M.a_intent != INTENT_HELP && check_shields(M, 0, "the [M.name]")) + visible_message("[M] attempted to touch [src]!") + return FALSE + switch(M.a_intent) + if (INTENT_HELP) + if(!isalien(src)) //I know it's ugly, but the alien vs alien attack_alien behaviour is a bit different. + visible_message("[M] caresses [src] with its scythe like arm.") + return FALSE + if (INTENT_GRAB) + grabbedby(M) + return FALSE + if(INTENT_HARM) + if(HAS_TRAIT(M, TRAIT_PACIFISM)) + to_chat(M, "You don't want to hurt anyone!") + return FALSE + if(!isalien(src)) + M.do_attack_animation(src) + return TRUE + if(INTENT_DISARM) + if(!isalien(src)) + M.do_attack_animation(src, ATTACK_EFFECT_DISARM) + return TRUE + +/mob/living/ex_act(severity, target, origin) + if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) + return + ..() + +//Looking for irradiate()? It's been moved to radiation.dm under the rad_act() for mobs. + +/mob/living/acid_act(acidpwr, acid_volume) + take_bodypart_damage(acidpwr * min(1, acid_volume * 0.1)) + return 1 + +/mob/living/proc/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = 0, tesla_shock = 0, illusion = 0, stun = TRUE) + SEND_SIGNAL(src, COMSIG_LIVING_ELECTROCUTE_ACT, shock_damage) + if(tesla_shock && (flags_1 & TESLA_IGNORE_1)) + return FALSE + if(HAS_TRAIT(src, TRAIT_SHOCKIMMUNE)) + return FALSE + if(shock_damage > 0) + if(!illusion) + adjustFireLoss(shock_damage) + visible_message( + "[src] was shocked by \the [source]!", \ + "You feel a powerful shock coursing through your body!", \ + "You hear a heavy electrical crack." \ + ) + return shock_damage + +/mob/living/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_CONTENTS) + return + for(var/obj/O in contents) + O.emp_act(severity) + +/mob/living/singularity_act() + var/gain = 20 + investigate_log("([key_name(src)]) has been consumed by the singularity.", INVESTIGATE_SINGULO) //Oh that's where the clown ended up! + gib() + return(gain) + +/mob/living/narsie_act() + if(status_flags & GODMODE || QDELETED(src)) + return + + if(is_servant_of_ratvar(src) && !stat) + to_chat(src, "You resist Nar'Sie's influence... but not all of it. Run!") + adjustBruteLoss(35) + if(src && reagents) + reagents.add_reagent(/datum/reagent/toxin/heparin, 5) + return FALSE + if(GLOB.cult_narsie && GLOB.cult_narsie.souls_needed[src]) + GLOB.cult_narsie.souls_needed -= src + GLOB.cult_narsie.souls += 1 + if((GLOB.cult_narsie.souls == GLOB.cult_narsie.soul_goal) && (GLOB.cult_narsie.resolved == FALSE)) + GLOB.cult_narsie.resolved = TRUE + sound_to_playing_players('sound/machines/alarm.ogg') + addtimer(CALLBACK(GLOBAL_PROC, .proc/cult_ending_helper, 1), 120) + addtimer(CALLBACK(GLOBAL_PROC, .proc/ending_helper), 270) + if(client) + makeNewConstruct(/mob/living/simple_animal/hostile/construct/harvester, src, cultoverride = TRUE) + else + switch(rand(1, 6)) + if(1) + new /mob/living/simple_animal/hostile/construct/armored/hostile(get_turf(src)) + if(2) + new /mob/living/simple_animal/hostile/construct/wraith/hostile(get_turf(src)) + if(3 to 6) + new /mob/living/simple_animal/hostile/construct/builder/hostile(get_turf(src)) + spawn_dust() + gib() + return TRUE + + +/mob/living/ratvar_act() + if(status_flags & GODMODE) + return + if(stat != DEAD && !is_servant_of_ratvar(src)) + to_chat(src, "A blinding light boils you alive! Run!") + adjust_fire_stacks(20) + IgniteMob() + return FALSE + + +//called when the mob receives a bright flash +/mob/living/proc/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /obj/screen/fullscreen/flash) + if(get_eye_protection() < intensity && (override_blindness_check || !(HAS_TRAIT(src, TRAIT_BLIND)))) + overlay_fullscreen("flash", type) + addtimer(CALLBACK(src, .proc/clear_fullscreen, "flash", 25), 25) + return TRUE + return FALSE + +//called when the mob receives a loud bang +/mob/living/proc/soundbang_act() + return 0 + +//to damage the clothes worn by a mob +/mob/living/proc/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone) + return + + +/mob/living/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) + if(!used_item) + used_item = get_active_held_item() + ..() + setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure we restart the bouncing after the next movement. diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 783e044492..e668828392 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -1,116 +1,116 @@ -/mob/living - see_invisible = SEE_INVISIBLE_LIVING - sight = 0 - see_in_dark = 2 - hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD,RAD_HUD) - pressure_resistance = 10 - - var/resize = 1 //Badminnery resize - var/lastattacker = null - var/lastattackerckey = null - - //Health and life related vars - var/maxHealth = 100 //Maximum health that should be possible. - var/health = 100 //A mob's health - - //Damage related vars, NOTE: THESE SHOULD ONLY BE MODIFIED BY PROCS - var/bruteloss = 0 //Brutal damage caused by brute force (punching, being clubbed by a toolbox ect... this also accounts for pressure damage) - var/oxyloss = 0 //Oxygen depravation damage (no air in lungs) - var/toxloss = 0 //Toxic damage caused by being poisoned or radiated - var/fireloss = 0 //Burn damage caused by being way too hot, too cold or burnt. - var/cloneloss = 0 //Damage caused by being cloned or ejected from the cloner early. slimes also deal cloneloss damage to victims - var/staminaloss = 0 //Stamina damage, or exhaustion. You recover it slowly naturally, and are knocked down if it gets too high. Holodeck and hallucinations deal this. - var/crit_threshold = HEALTH_THRESHOLD_CRIT // when the mob goes from "normal" to crit - - var/confused = 0 //Makes the mob move in random directions. - - var/hallucination = 0 //Directly affects how long a mob will hallucinate for - - var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out. - var/timeofdeath = 0 - - //Allows mobs to move through dense areas without restriction. For instance, in space or out of holder objects. - var/incorporeal_move = FALSE //FALSE is off, INCORPOREAL_MOVE_BASIC is normal, INCORPOREAL_MOVE_SHADOW is for ninjas - //and INCORPOREAL_MOVE_JAUNT is blocked by holy water/salt - - var/list/roundstart_quirks = list() - - var/list/surgeries = list() //a list of surgery datums. generally empty, they're added when the player wants them. - - var/now_pushing = null //used by living/Bump() and living/PushAM() to prevent potential infinite loop. - - var/cameraFollow = null - - var/tod = null // Time of death - - var/on_fire = 0 //The "Are we on fire?" var - var/fire_stacks = 0 //Tracks how many stacks of fire we have on, max is usually 20 - - var/bloodcrawl = 0 //0 No blood crawling, BLOODCRAWL for bloodcrawling, BLOODCRAWL_EAT for crawling+mob devour - var/holder = null //The holder for blood crawling - var/ventcrawler = 0 //0 No vent crawling, 1 vent crawling in the nude, 2 vent crawling always - var/limb_destroyer = 0 //1 Sets AI behavior that allows mobs to target and dismember limbs with their basic attack. - - var/mob_size = MOB_SIZE_HUMAN - var/list/mob_biotypes = list(MOB_ORGANIC) - var/metabolism_efficiency = 1 //more or less efficiency to metabolize helpful/harmful reagents and regulate body temperature.. - var/has_limbs = 0 //does the mob have distinct limbs?(arms,legs, chest,head) - - var/list/pipes_shown = list() - var/last_played_vent - - var/smoke_delay = 0 //used to prevent spam with smoke reagent reaction on mob. - - var/bubble_icon = "default" //what icon the mob uses for speechbubbles - - var/last_bumped = 0 - var/unique_name = 0 //if a mob's name should be appended with an id when created e.g. Mob (666) - - var/list/butcher_results = null //these will be yielded from butchering with a probability chance equal to the butcher item's effectiveness - var/list/guaranteed_butcher_results = null //these will always be yielded from butchering - var/butcher_difficulty = 0 //effectiveness prob. is modified negatively by this amount; positive numbers make it more difficult, negative ones make it easier - - var/hellbound = 0 //People who've signed infernal contracts are unrevivable. - - var/list/weather_immunities = list() - - var/stun_absorption = null //converted to a list of stun absorption sources this mob has when one is added - - var/blood_volume = 0 //how much blood the mob has - var/blood_ratio = 1 //How much blood the mob needs, in terms of ratio (i.e 1.2 will require BLOOD_VOLUME_NORMAL of 672) DO NOT GO ABOVE 3.55 Well, actually you can but, then they can't get enough blood. - var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override - - var/see_override = 0 //0 for no override, sets see_invisible = see_override in silicon & carbon life process via update_sight() - - var/list/status_effects //a list of all status effects the mob has - var/druggy = 0 - - //Speech - var/stuttering = 0 - var/slurring = 0 - var/cultslurring = 0 - var/derpspeech = 0 - - var/list/implants = null - - var/datum/riding/riding_datum - - var/datum/language/selected_default_language - - var/last_words //used for database logging - - var/list/obj/effect/proc_holder/abilities = list() - - var/can_be_held = FALSE //whether this can be picked up and held. - - var/radiation = 0 //If the mob is irradiated. - var/ventcrawl_layer = PIPING_LAYER_DEFAULT - var/losebreath = 0 - - //List of active diseases - var/list/diseases = list() // list of all diseases in a mob - var/list/disease_resistances = list() - - var/drag_slowdown = TRUE //Whether the mob is slowed down when dragging another prone mob - +/mob/living + see_invisible = SEE_INVISIBLE_LIVING + sight = 0 + see_in_dark = 2 + hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD,RAD_HUD) + pressure_resistance = 10 + + var/resize = 1 //Badminnery resize + var/lastattacker = null + var/lastattackerckey = null + + //Health and life related vars + var/maxHealth = 100 //Maximum health that should be possible. + var/health = 100 //A mob's health + + //Damage related vars, NOTE: THESE SHOULD ONLY BE MODIFIED BY PROCS + var/bruteloss = 0 //Brutal damage caused by brute force (punching, being clubbed by a toolbox ect... this also accounts for pressure damage) + var/oxyloss = 0 //Oxygen depravation damage (no air in lungs) + var/toxloss = 0 //Toxic damage caused by being poisoned or radiated + var/fireloss = 0 //Burn damage caused by being way too hot, too cold or burnt. + var/cloneloss = 0 //Damage caused by being cloned or ejected from the cloner early. slimes also deal cloneloss damage to victims + var/staminaloss = 0 //Stamina damage, or exhaustion. You recover it slowly naturally, and are knocked down if it gets too high. Holodeck and hallucinations deal this. + var/crit_threshold = HEALTH_THRESHOLD_CRIT // when the mob goes from "normal" to crit + + var/confused = 0 //Makes the mob move in random directions. + + var/hallucination = 0 //Directly affects how long a mob will hallucinate for + + var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out. + var/timeofdeath = 0 + + //Allows mobs to move through dense areas without restriction. For instance, in space or out of holder objects. + var/incorporeal_move = FALSE //FALSE is off, INCORPOREAL_MOVE_BASIC is normal, INCORPOREAL_MOVE_SHADOW is for ninjas + //and INCORPOREAL_MOVE_JAUNT is blocked by holy water/salt + + var/list/roundstart_quirks = list() + + var/list/surgeries = list() //a list of surgery datums. generally empty, they're added when the player wants them. + + var/now_pushing = null //used by living/Bump() and living/PushAM() to prevent potential infinite loop. + + var/cameraFollow = null + + var/tod = null // Time of death + + var/on_fire = 0 //The "Are we on fire?" var + var/fire_stacks = 0 //Tracks how many stacks of fire we have on, max is usually 20 + + var/bloodcrawl = 0 //0 No blood crawling, BLOODCRAWL for bloodcrawling, BLOODCRAWL_EAT for crawling+mob devour + var/holder = null //The holder for blood crawling + var/ventcrawler = 0 //0 No vent crawling, 1 vent crawling in the nude, 2 vent crawling always + var/limb_destroyer = 0 //1 Sets AI behavior that allows mobs to target and dismember limbs with their basic attack. + + var/mob_size = MOB_SIZE_HUMAN + var/list/mob_biotypes = list(MOB_ORGANIC) + var/metabolism_efficiency = 1 //more or less efficiency to metabolize helpful/harmful reagents and regulate body temperature.. + var/has_limbs = 0 //does the mob have distinct limbs?(arms,legs, chest,head) + + var/list/pipes_shown = list() + var/last_played_vent + + var/smoke_delay = 0 //used to prevent spam with smoke reagent reaction on mob. + + var/bubble_icon = "default" //what icon the mob uses for speechbubbles + + var/last_bumped = 0 + var/unique_name = 0 //if a mob's name should be appended with an id when created e.g. Mob (666) + + var/list/butcher_results = null //these will be yielded from butchering with a probability chance equal to the butcher item's effectiveness + var/list/guaranteed_butcher_results = null //these will always be yielded from butchering + var/butcher_difficulty = 0 //effectiveness prob. is modified negatively by this amount; positive numbers make it more difficult, negative ones make it easier + + var/hellbound = 0 //People who've signed infernal contracts are unrevivable. + + var/list/weather_immunities = list() + + var/stun_absorption = null //converted to a list of stun absorption sources this mob has when one is added + + var/blood_volume = 0 //how much blood the mob has + var/blood_ratio = 1 //How much blood the mob needs, in terms of ratio (i.e 1.2 will require BLOOD_VOLUME_NORMAL of 672) DO NOT GO ABOVE 3.55 Well, actually you can but, then they can't get enough blood. + var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override + + var/see_override = 0 //0 for no override, sets see_invisible = see_override in silicon & carbon life process via update_sight() + + var/list/status_effects //a list of all status effects the mob has + var/druggy = 0 + + //Speech + var/stuttering = 0 + var/slurring = 0 + var/cultslurring = 0 + var/derpspeech = 0 + + var/list/implants = null + + var/datum/riding/riding_datum + + var/datum/language/selected_default_language + + var/last_words //used for database logging + + var/list/obj/effect/proc_holder/abilities = list() + + var/can_be_held = FALSE //whether this can be picked up and held. + + var/radiation = 0 //If the mob is irradiated. + var/ventcrawl_layer = PIPING_LAYER_DEFAULT + var/losebreath = 0 + + //List of active diseases + var/list/diseases = list() // list of all diseases in a mob + var/list/disease_resistances = list() + + var/drag_slowdown = TRUE //Whether the mob is slowed down when dragging another prone mob + var/rotate_on_lying = FALSE \ No newline at end of file diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm index 046d25a4b2..f2e6fdce03 100644 --- a/code/modules/mob/living/login.dm +++ b/code/modules/mob/living/login.dm @@ -1,27 +1,27 @@ -/mob/living/Login() - ..() - //Mind updates - sync_mind() - mind.show_memory(src, 0) - - //Round specific stuff - if(SSticker.mode) - switch(SSticker.mode.name) - if("sandbox") - CanBuild() - - update_damage_hud() - update_health_hud() - - var/turf/T = get_turf(src) - if (isturf(T)) - update_z(T.z) - - //Vents - if(ventcrawler) - to_chat(src, "You can ventcrawl! Use alt+click on vents to quickly travel about the station.") - - if(ranged_ability) - ranged_ability.add_ranged_ability(src, "You currently have [ranged_ability] active!") - if(vore_init && !vorepref_init) //Vore's been initialized, voreprefs haven't. If this triggers then that means that voreprefs failed to load due to the client being missing. - apply_vore_prefs() +/mob/living/Login() + ..() + //Mind updates + sync_mind() + mind.show_memory(src, 0) + + //Round specific stuff + if(SSticker.mode) + switch(SSticker.mode.name) + if("sandbox") + CanBuild() + + update_damage_hud() + update_health_hud() + + var/turf/T = get_turf(src) + if (isturf(T)) + update_z(T.z) + + //Vents + if(ventcrawler) + to_chat(src, "You can ventcrawl! Use alt+click on vents to quickly travel about the station.") + + if(ranged_ability) + ranged_ability.add_ranged_ability(src, "You currently have [ranged_ability] active!") + if(vore_init && !vorepref_init) //Vore's been initialized, voreprefs haven't. If this triggers then that means that voreprefs failed to load due to the client being missing. + apply_vore_prefs() diff --git a/code/modules/mob/living/logout.dm b/code/modules/mob/living/logout.dm index d2c50559c6..97b6ec8648 100644 --- a/code/modules/mob/living/logout.dm +++ b/code/modules/mob/living/logout.dm @@ -1,5 +1,5 @@ -/mob/living/Logout() - update_z(null) - ..() - if(!key && mind) //key and mind have become separated. +/mob/living/Logout() + update_z(null) + ..() + if(!key && mind) //key and mind have become separated. mind.active = 0 //This is to stop say, a mind.transfer_to call on a corpse causing a ghost to re-enter its body. \ No newline at end of file diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 2cab7d4c8a..472bcdfc3d 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -1,419 +1,419 @@ -GLOBAL_LIST_INIT(department_radio_prefixes, list(":", ".")) - -GLOBAL_LIST_INIT(department_radio_keys, list( - // Location - MODE_KEY_R_HAND = MODE_R_HAND, - MODE_KEY_L_HAND = MODE_L_HAND, - MODE_KEY_INTERCOM = MODE_INTERCOM, - - // Department - MODE_KEY_DEPARTMENT = MODE_DEPARTMENT, - RADIO_KEY_COMMAND = RADIO_CHANNEL_COMMAND, - RADIO_KEY_SCIENCE = RADIO_CHANNEL_SCIENCE, - RADIO_KEY_MEDICAL = RADIO_CHANNEL_MEDICAL, - RADIO_KEY_ENGINEERING = RADIO_CHANNEL_ENGINEERING, - RADIO_KEY_SECURITY = RADIO_CHANNEL_SECURITY, - RADIO_KEY_SUPPLY = RADIO_CHANNEL_SUPPLY, - RADIO_KEY_SERVICE = RADIO_CHANNEL_SERVICE, - - // Faction - RADIO_KEY_SYNDICATE = RADIO_CHANNEL_SYNDICATE, - RADIO_KEY_CENTCOM = RADIO_CHANNEL_CENTCOM, - - // Admin - MODE_KEY_ADMIN = MODE_ADMIN, - MODE_KEY_DEADMIN = MODE_DEADMIN, - - // Misc - RADIO_KEY_AI_PRIVATE = RADIO_CHANNEL_AI_PRIVATE, // AI Upload channel - MODE_KEY_VOCALCORDS = MODE_VOCALCORDS, // vocal cords, used by Voice of God - - - //kinda localization -- rastaf0 - //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding. - // Location - "ê" = MODE_R_HAND, - "ä" = MODE_L_HAND, - "ø" = MODE_INTERCOM, - - // Department - "ð" = MODE_DEPARTMENT, - "ñ" = RADIO_CHANNEL_COMMAND, - "ò" = RADIO_CHANNEL_SCIENCE, - "ü" = RADIO_CHANNEL_MEDICAL, - "ó" = RADIO_CHANNEL_ENGINEERING, - "û" = RADIO_CHANNEL_SECURITY, - "ã" = RADIO_CHANNEL_SUPPLY, - "ì" = RADIO_CHANNEL_SERVICE, - - // Faction - "å" = RADIO_CHANNEL_SYNDICATE, - "í" = RADIO_CHANNEL_CENTCOM, - - // Admin - "ç" = MODE_ADMIN, - "â" = MODE_ADMIN, - - // Misc - "ù" = RADIO_CHANNEL_AI_PRIVATE, - "÷" = MODE_VOCALCORDS -)) - -/mob/living/proc/Ellipsis(original_msg, chance = 50, keep_words) - if(chance <= 0) - return "..." - if(chance >= 100) - return original_msg - - var/list/words = splittext(original_msg," ") - var/list/new_words = list() - - var/new_msg = "" - - for(var/w in words) - if(prob(chance)) - new_words += "..." - if(!keep_words) - continue - new_words += w - - new_msg = jointext(new_words," ") - - return new_msg - -/mob/living/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - var/static/list/crit_allowed_modes = list(MODE_WHISPER = TRUE, MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE) - var/static/list/unconscious_allowed_modes = list(MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE) - var/talk_key = get_key(message) - - var/static/list/one_character_prefix = list(MODE_HEADSET = TRUE, MODE_ROBOT = TRUE, MODE_WHISPER = TRUE) - - if(sanitize) - message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) - if(!message || message == "") - return - - var/datum/saymode/saymode = SSradio.saymodes[talk_key] - var/message_mode = get_message_mode(message) - var/original_message = message - var/in_critical = InCritical() - - if(one_character_prefix[message_mode]) - message = copytext(message, 2) - else if(message_mode || saymode) - message = copytext(message, 3) - if(findtext(message, " ", 1, 2)) - message = copytext(message, 2) - - if(message_mode == MODE_ADMIN) - if(client) - client.cmd_admin_say(message) - return - - if(message_mode == MODE_DEADMIN) - if(client) - client.dsay(message) - return - - if(stat == DEAD) - say_dead(original_message) - return - - if(check_emote(original_message) || !can_speak_basic(original_message, ignore_spam)) - return - - if(in_critical) - if(!(crit_allowed_modes[message_mode])) - return - else if(stat == UNCONSCIOUS) - if(!(unconscious_allowed_modes[message_mode])) - return - - // language comma detection. - var/datum/language/message_language = get_message_language(message) - if(message_language) - // No, you cannot speak in xenocommon just because you know the key - if(can_speak_in_language(message_language)) - language = message_language - message = copytext(message, 3) - - // Trim the space if they said ",0 I LOVE LANGUAGES" - if(findtext(message, " ", 1, 2)) - message = copytext(message, 2) - - if(!language) - language = get_default_language() - - // Detection of language needs to be before inherent channels, because - // AIs use inherent channels for the holopad. Most inherent channels - // ignore the language argument however. - - if(saymode && !saymode.handle_message(src, message, language)) - return - - if(!can_speak_vocal(message)) - to_chat(src, "You find yourself unable to speak!") - return - - var/message_range = 7 - - var/succumbed = FALSE - - var/fullcrit = InFullCritical() - if((InCritical() && !fullcrit) || message_mode == MODE_WHISPER) - message_range = 1 - message_mode = MODE_WHISPER - src.log_talk(message, LOG_WHISPER) - if(fullcrit) - var/health_diff = round(-HEALTH_THRESHOLD_DEAD + health) - // If we cut our message short, abruptly end it with a-.. - var/message_len = length(message) - message = copytext(message, 1, health_diff) + "[message_len > health_diff ? "-.." : "..."]" - message = Ellipsis(message, 10, 1) - message_mode = MODE_WHISPER_CRIT - succumbed = TRUE - else - src.log_talk(message, LOG_SAY, forced_by=forced) - - message = treat_message(message) // unfortunately we still need this - var/sigreturn = SEND_SIGNAL(src, COMSIG_MOB_SAY, args) - if (sigreturn & COMPONENT_UPPERCASE_SPEECH) - message = uppertext(message) - if(!message) - return - - last_words = message - - spans |= speech_span - - if(language) - var/datum/language/L = GLOB.language_datum_instances[language] - spans |= L.spans - - var/radio_return = radio(message, message_mode, spans, language) - if(radio_return & ITALICS) - spans |= SPAN_ITALICS - if(radio_return & REDUCE_RANGE) - message_range = 1 - if(radio_return & NOPASS) - return 1 - - //No screams in space, unless you're next to someone. - var/turf/T = get_turf(src) - var/datum/gas_mixture/environment = T.return_air() - var/pressure = (environment)? environment.return_pressure() : 0 - if(pressure < SOUND_MINIMUM_PRESSURE) - message_range = 1 - - if(pressure < ONE_ATMOSPHERE*0.4) //Thin air, let's italicise the message - spans |= SPAN_ITALICS - - send_speech(message, message_range, src, bubble_type, spans, language, message_mode) - - if(succumbed) - succumb() - to_chat(src, compose_message(src, language, message, null, spans, message_mode)) - - return 1 - -/mob/living/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE, atom/movable/source) - . = ..() - if(isliving(speaker)) - var/turf/sourceturf = get_turf(source) - var/turf/T = get_turf(src) - if(sourceturf && T && !(sourceturf in get_hear(5, T))) - . = "[.]" - -/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) - . = ..() - if(!client) - return - var/deaf_message - var/deaf_type - if(speaker != src) - if(!radio_freq) //These checks have to be seperate, else people talking on the radio will make "You can't hear yourself!" appear when hearing people over the radio while deaf. - deaf_message = "[speaker] [speaker.verb_say] something but you cannot hear [speaker.p_them()]." - deaf_type = 1 - else - deaf_message = "You can't hear yourself!" - deaf_type = 2 // Since you should be able to hear yourself without looking - - // Recompose message for AI hrefs, language incomprehension. - message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) - - show_message(message, MSG_AUDIBLE, deaf_message, deaf_type) - return message - -/mob/living/send_speech(message, message_range = 6, obj/source = src, bubble_type = bubble_icon, list/spans, datum/language/message_language=null, message_mode) - var/static/list/eavesdropping_modes = list(MODE_WHISPER = TRUE, MODE_WHISPER_CRIT = TRUE) - var/eavesdrop_range = 0 - if(eavesdropping_modes[message_mode]) - eavesdrop_range = EAVESDROP_EXTRA_RANGE - var/list/listening = get_hearers_in_view(message_range+eavesdrop_range, source) - var/list/the_dead = list() - var/list/yellareas //CIT CHANGE - adds the ability for yelling to penetrate walls and echo throughout areas - if(!eavesdrop_range && say_test(message) == "2") //CIT CHANGE - ditto - yellareas = get_areas_in_range(message_range*0.5, source) //CIT CHANGE - ditto - for(var/_M in GLOB.player_list) - var/mob/M = _M - if(M.stat != DEAD) //not dead, not important - if(yellareas) //CIT CHANGE - see above. makes yelling penetrate walls - var/area/A = get_area(M) //CIT CHANGE - ditto - if(istype(A) && A.ambientsounds != SPACE && A in yellareas) //CIT CHANGE - ditto - listening |= M //CIT CHANGE - ditto - continue - if(!M.client || !client) //client is so that ghosts don't have to listen to mice - continue - if(get_dist(M, source) > 7 || M.z != z) //they're out of range of normal hearing - if(eavesdropping_modes[message_mode] && !(M.client.prefs.chat_toggles & CHAT_GHOSTWHISPER)) //they're whispering and we have hearing whispers at any range off - continue - if(!(M.client.prefs.chat_toggles & CHAT_GHOSTEARS)) //they're talking normally and we have hearing at any range off - continue - listening |= M - the_dead[M] = TRUE - - var/eavesdropping - var/eavesrendered - if(eavesdrop_range) - eavesdropping = stars(message) - eavesrendered = compose_message(src, message_language, eavesdropping, null, spans, message_mode, FALSE, source) - - var/rendered = compose_message(src, message_language, message, null, spans, message_mode, FALSE, source) - for(var/_AM in listening) - var/atom/movable/AM = _AM - if(eavesdrop_range && get_dist(source, AM) > message_range && !(the_dead[AM])) - AM.Hear(eavesrendered, src, message_language, eavesdropping, null, spans, message_mode, source) - else - AM.Hear(rendered, src, message_language, message, null, spans, message_mode, source) - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message) - - //speech bubble - var/list/speech_bubble_recipients = list() - for(var/mob/M in listening) - if(M.client) - speech_bubble_recipients.Add(M.client) - var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER) - I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_recipients, 30) - -/mob/proc/binarycheck() - return FALSE - -/mob/living/can_speak(message) //For use outside of Say() - if(can_speak_basic(message) && can_speak_vocal(message)) - return 1 - -/mob/living/proc/can_speak_basic(message, ignore_spam = FALSE) //Check BEFORE handling of xeno and ling channels - if(client) - if(client.prefs.muted & MUTE_IC) - to_chat(src, "You cannot speak in IC (muted).") - return 0 - if(!ignore_spam && client.handle_spam_prevention(message,MUTE_IC)) - return 0 - - return 1 - -/mob/living/proc/can_speak_vocal(message) //Check AFTER handling of xeno and ling channels - if(HAS_TRAIT(src, TRAIT_MUTE)) - return 0 - - if(is_muzzled()) - return 0 - - if(!IsVocal()) - return 0 - - return 1 - -/mob/living/proc/get_key(message) - var/key = copytext(message, 1, 2) - if(key in GLOB.department_radio_prefixes) - return lowertext(copytext(message, 2, 3)) - -/mob/living/proc/get_message_language(message) - if(copytext(message, 1, 2) == ",") - var/key = copytext(message, 2, 3) - for(var/ld in GLOB.all_languages) - var/datum/language/LD = ld - if(initial(LD.key) == key) - return LD - return null - -/mob/living/proc/treat_message(message) - - if(HAS_TRAIT(src, TRAIT_UNINTELLIGIBLE_SPEECH)) - message = unintelligize(message) - - if(derpspeech) - message = derpspeech(message, stuttering) - - if(stuttering) - message = stutter(message) - - if(slurring) - message = slur(message,slurring) - - if(cultslurring) - message = cultslur(message) - - message = capitalize(message) - - return message - -/mob/living/proc/radio(message, message_mode, list/spans, language) - var/obj/item/implant/radio/imp = locate() in implants - if(imp?.radio.on) - if(message_mode == MODE_HEADSET) - imp.radio.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - if(message_mode == MODE_DEPARTMENT || message_mode in GLOB.radiochannels) - imp.radio.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - switch(message_mode) - if(MODE_WHISPER) - return ITALICS - if(MODE_R_HAND) - for(var/obj/item/r_hand in get_held_items_for_side("r", all = TRUE)) - if (r_hand) - return r_hand.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - if(MODE_L_HAND) - for(var/obj/item/l_hand in get_held_items_for_side("l", all = TRUE)) - if (l_hand) - return l_hand.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - - if(MODE_INTERCOM) - for (var/obj/item/radio/intercom/I in view(1, null)) - I.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - - if(MODE_BINARY) - return ITALICS | REDUCE_RANGE //Does not return 0 since this is only reached by humans, not borgs or AIs. - - return 0 - -/mob/living/say_mod(input, message_mode) - . = ..() - if(message_mode == MODE_WHISPER_CRIT) - . = "[verb_whisper] in [p_their()] last breath" - else if(message_mode != MODE_CUSTOM_SAY) - if(message_mode == MODE_WHISPER) - . = verb_whisper - else if(stuttering) - . = "stammers" - else if(derpspeech) - . = "gibbers" - -/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - say("#[message]", bubble_type, spans, sanitize, language, ignore_spam, forced) - -/mob/living/get_language_holder(shadow=TRUE) - if(mind && shadow) - // Mind language holders shadow mob holders. - . = mind.get_language_holder() - if(.) - return . - - . = ..() +GLOBAL_LIST_INIT(department_radio_prefixes, list(":", ".")) + +GLOBAL_LIST_INIT(department_radio_keys, list( + // Location + MODE_KEY_R_HAND = MODE_R_HAND, + MODE_KEY_L_HAND = MODE_L_HAND, + MODE_KEY_INTERCOM = MODE_INTERCOM, + + // Department + MODE_KEY_DEPARTMENT = MODE_DEPARTMENT, + RADIO_KEY_COMMAND = RADIO_CHANNEL_COMMAND, + RADIO_KEY_SCIENCE = RADIO_CHANNEL_SCIENCE, + RADIO_KEY_MEDICAL = RADIO_CHANNEL_MEDICAL, + RADIO_KEY_ENGINEERING = RADIO_CHANNEL_ENGINEERING, + RADIO_KEY_SECURITY = RADIO_CHANNEL_SECURITY, + RADIO_KEY_SUPPLY = RADIO_CHANNEL_SUPPLY, + RADIO_KEY_SERVICE = RADIO_CHANNEL_SERVICE, + + // Faction + RADIO_KEY_SYNDICATE = RADIO_CHANNEL_SYNDICATE, + RADIO_KEY_CENTCOM = RADIO_CHANNEL_CENTCOM, + + // Admin + MODE_KEY_ADMIN = MODE_ADMIN, + MODE_KEY_DEADMIN = MODE_DEADMIN, + + // Misc + RADIO_KEY_AI_PRIVATE = RADIO_CHANNEL_AI_PRIVATE, // AI Upload channel + MODE_KEY_VOCALCORDS = MODE_VOCALCORDS, // vocal cords, used by Voice of God + + + //kinda localization -- rastaf0 + //same keys as above, but on russian keyboard layout. This file uses cp1251 as encoding. + // Location + "ê" = MODE_R_HAND, + "ä" = MODE_L_HAND, + "ø" = MODE_INTERCOM, + + // Department + "ð" = MODE_DEPARTMENT, + "ñ" = RADIO_CHANNEL_COMMAND, + "ò" = RADIO_CHANNEL_SCIENCE, + "ü" = RADIO_CHANNEL_MEDICAL, + "ó" = RADIO_CHANNEL_ENGINEERING, + "û" = RADIO_CHANNEL_SECURITY, + "ã" = RADIO_CHANNEL_SUPPLY, + "ì" = RADIO_CHANNEL_SERVICE, + + // Faction + "å" = RADIO_CHANNEL_SYNDICATE, + "í" = RADIO_CHANNEL_CENTCOM, + + // Admin + "ç" = MODE_ADMIN, + "â" = MODE_ADMIN, + + // Misc + "ù" = RADIO_CHANNEL_AI_PRIVATE, + "÷" = MODE_VOCALCORDS +)) + +/mob/living/proc/Ellipsis(original_msg, chance = 50, keep_words) + if(chance <= 0) + return "..." + if(chance >= 100) + return original_msg + + var/list/words = splittext(original_msg," ") + var/list/new_words = list() + + var/new_msg = "" + + for(var/w in words) + if(prob(chance)) + new_words += "..." + if(!keep_words) + continue + new_words += w + + new_msg = jointext(new_words," ") + + return new_msg + +/mob/living/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) + var/static/list/crit_allowed_modes = list(MODE_WHISPER = TRUE, MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE) + var/static/list/unconscious_allowed_modes = list(MODE_CHANGELING = TRUE, MODE_ALIEN = TRUE) + var/talk_key = get_key(message) + + var/static/list/one_character_prefix = list(MODE_HEADSET = TRUE, MODE_ROBOT = TRUE, MODE_WHISPER = TRUE) + + if(sanitize) + message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) + if(!message || message == "") + return + + var/datum/saymode/saymode = SSradio.saymodes[talk_key] + var/message_mode = get_message_mode(message) + var/original_message = message + var/in_critical = InCritical() + + if(one_character_prefix[message_mode]) + message = copytext(message, 2) + else if(message_mode || saymode) + message = copytext(message, 3) + if(findtext(message, " ", 1, 2)) + message = copytext(message, 2) + + if(message_mode == MODE_ADMIN) + if(client) + client.cmd_admin_say(message) + return + + if(message_mode == MODE_DEADMIN) + if(client) + client.dsay(message) + return + + if(stat == DEAD) + say_dead(original_message) + return + + if(check_emote(original_message) || !can_speak_basic(original_message, ignore_spam)) + return + + if(in_critical) + if(!(crit_allowed_modes[message_mode])) + return + else if(stat == UNCONSCIOUS) + if(!(unconscious_allowed_modes[message_mode])) + return + + // language comma detection. + var/datum/language/message_language = get_message_language(message) + if(message_language) + // No, you cannot speak in xenocommon just because you know the key + if(can_speak_in_language(message_language)) + language = message_language + message = copytext(message, 3) + + // Trim the space if they said ",0 I LOVE LANGUAGES" + if(findtext(message, " ", 1, 2)) + message = copytext(message, 2) + + if(!language) + language = get_default_language() + + // Detection of language needs to be before inherent channels, because + // AIs use inherent channels for the holopad. Most inherent channels + // ignore the language argument however. + + if(saymode && !saymode.handle_message(src, message, language)) + return + + if(!can_speak_vocal(message)) + to_chat(src, "You find yourself unable to speak!") + return + + var/message_range = 7 + + var/succumbed = FALSE + + var/fullcrit = InFullCritical() + if((InCritical() && !fullcrit) || message_mode == MODE_WHISPER) + message_range = 1 + message_mode = MODE_WHISPER + src.log_talk(message, LOG_WHISPER) + if(fullcrit) + var/health_diff = round(-HEALTH_THRESHOLD_DEAD + health) + // If we cut our message short, abruptly end it with a-.. + var/message_len = length(message) + message = copytext(message, 1, health_diff) + "[message_len > health_diff ? "-.." : "..."]" + message = Ellipsis(message, 10, 1) + message_mode = MODE_WHISPER_CRIT + succumbed = TRUE + else + src.log_talk(message, LOG_SAY, forced_by=forced) + + message = treat_message(message) // unfortunately we still need this + var/sigreturn = SEND_SIGNAL(src, COMSIG_MOB_SAY, args) + if (sigreturn & COMPONENT_UPPERCASE_SPEECH) + message = uppertext(message) + if(!message) + return + + last_words = message + + spans |= speech_span + + if(language) + var/datum/language/L = GLOB.language_datum_instances[language] + spans |= L.spans + + var/radio_return = radio(message, message_mode, spans, language) + if(radio_return & ITALICS) + spans |= SPAN_ITALICS + if(radio_return & REDUCE_RANGE) + message_range = 1 + if(radio_return & NOPASS) + return 1 + + //No screams in space, unless you're next to someone. + var/turf/T = get_turf(src) + var/datum/gas_mixture/environment = T.return_air() + var/pressure = (environment)? environment.return_pressure() : 0 + if(pressure < SOUND_MINIMUM_PRESSURE) + message_range = 1 + + if(pressure < ONE_ATMOSPHERE*0.4) //Thin air, let's italicise the message + spans |= SPAN_ITALICS + + send_speech(message, message_range, src, bubble_type, spans, language, message_mode) + + if(succumbed) + succumb() + to_chat(src, compose_message(src, language, message, null, spans, message_mode)) + + return 1 + +/mob/living/compose_message(atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, face_name = FALSE, atom/movable/source) + . = ..() + if(isliving(speaker)) + var/turf/sourceturf = get_turf(source) + var/turf/T = get_turf(src) + if(sourceturf && T && !(sourceturf in get_hear(5, T))) + . = "[.]" + +/mob/living/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if(!client) + return + var/deaf_message + var/deaf_type + if(speaker != src) + if(!radio_freq) //These checks have to be seperate, else people talking on the radio will make "You can't hear yourself!" appear when hearing people over the radio while deaf. + deaf_message = "[speaker] [speaker.verb_say] something but you cannot hear [speaker.p_them()]." + deaf_type = 1 + else + deaf_message = "You can't hear yourself!" + deaf_type = 2 // Since you should be able to hear yourself without looking + + // Recompose message for AI hrefs, language incomprehension. + message = compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source) + + show_message(message, MSG_AUDIBLE, deaf_message, deaf_type) + return message + +/mob/living/send_speech(message, message_range = 6, obj/source = src, bubble_type = bubble_icon, list/spans, datum/language/message_language=null, message_mode) + var/static/list/eavesdropping_modes = list(MODE_WHISPER = TRUE, MODE_WHISPER_CRIT = TRUE) + var/eavesdrop_range = 0 + if(eavesdropping_modes[message_mode]) + eavesdrop_range = EAVESDROP_EXTRA_RANGE + var/list/listening = get_hearers_in_view(message_range+eavesdrop_range, source) + var/list/the_dead = list() + var/list/yellareas //CIT CHANGE - adds the ability for yelling to penetrate walls and echo throughout areas + if(!eavesdrop_range && say_test(message) == "2") //CIT CHANGE - ditto + yellareas = get_areas_in_range(message_range*0.5, source) //CIT CHANGE - ditto + for(var/_M in GLOB.player_list) + var/mob/M = _M + if(M.stat != DEAD) //not dead, not important + if(yellareas) //CIT CHANGE - see above. makes yelling penetrate walls + var/area/A = get_area(M) //CIT CHANGE - ditto + if(istype(A) && A.ambientsounds != SPACE && A in yellareas) //CIT CHANGE - ditto + listening |= M //CIT CHANGE - ditto + continue + if(!M.client || !client) //client is so that ghosts don't have to listen to mice + continue + if(get_dist(M, source) > 7 || M.z != z) //they're out of range of normal hearing + if(eavesdropping_modes[message_mode] && !(M.client.prefs.chat_toggles & CHAT_GHOSTWHISPER)) //they're whispering and we have hearing whispers at any range off + continue + if(!(M.client.prefs.chat_toggles & CHAT_GHOSTEARS)) //they're talking normally and we have hearing at any range off + continue + listening |= M + the_dead[M] = TRUE + + var/eavesdropping + var/eavesrendered + if(eavesdrop_range) + eavesdropping = stars(message) + eavesrendered = compose_message(src, message_language, eavesdropping, null, spans, message_mode, FALSE, source) + + var/rendered = compose_message(src, message_language, message, null, spans, message_mode, FALSE, source) + for(var/_AM in listening) + var/atom/movable/AM = _AM + if(eavesdrop_range && get_dist(source, AM) > message_range && !(the_dead[AM])) + AM.Hear(eavesrendered, src, message_language, eavesdropping, null, spans, message_mode, source) + else + AM.Hear(rendered, src, message_language, message, null, spans, message_mode, source) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message) + + //speech bubble + var/list/speech_bubble_recipients = list() + for(var/mob/M in listening) + if(M.client) + speech_bubble_recipients.Add(M.client) + var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER) + I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_recipients, 30) + +/mob/proc/binarycheck() + return FALSE + +/mob/living/can_speak(message) //For use outside of Say() + if(can_speak_basic(message) && can_speak_vocal(message)) + return 1 + +/mob/living/proc/can_speak_basic(message, ignore_spam = FALSE) //Check BEFORE handling of xeno and ling channels + if(client) + if(client.prefs.muted & MUTE_IC) + to_chat(src, "You cannot speak in IC (muted).") + return 0 + if(!ignore_spam && client.handle_spam_prevention(message,MUTE_IC)) + return 0 + + return 1 + +/mob/living/proc/can_speak_vocal(message) //Check AFTER handling of xeno and ling channels + if(HAS_TRAIT(src, TRAIT_MUTE)) + return 0 + + if(is_muzzled()) + return 0 + + if(!IsVocal()) + return 0 + + return 1 + +/mob/living/proc/get_key(message) + var/key = copytext(message, 1, 2) + if(key in GLOB.department_radio_prefixes) + return lowertext(copytext(message, 2, 3)) + +/mob/living/proc/get_message_language(message) + if(copytext(message, 1, 2) == ",") + var/key = copytext(message, 2, 3) + for(var/ld in GLOB.all_languages) + var/datum/language/LD = ld + if(initial(LD.key) == key) + return LD + return null + +/mob/living/proc/treat_message(message) + + if(HAS_TRAIT(src, TRAIT_UNINTELLIGIBLE_SPEECH)) + message = unintelligize(message) + + if(derpspeech) + message = derpspeech(message, stuttering) + + if(stuttering) + message = stutter(message) + + if(slurring) + message = slur(message,slurring) + + if(cultslurring) + message = cultslur(message) + + message = capitalize(message) + + return message + +/mob/living/proc/radio(message, message_mode, list/spans, language) + var/obj/item/implant/radio/imp = locate() in implants + if(imp?.radio.on) + if(message_mode == MODE_HEADSET) + imp.radio.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + if(message_mode == MODE_DEPARTMENT || message_mode in GLOB.radiochannels) + imp.radio.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + switch(message_mode) + if(MODE_WHISPER) + return ITALICS + if(MODE_R_HAND) + for(var/obj/item/r_hand in get_held_items_for_side("r", all = TRUE)) + if (r_hand) + return r_hand.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + if(MODE_L_HAND) + for(var/obj/item/l_hand in get_held_items_for_side("l", all = TRUE)) + if (l_hand) + return l_hand.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + + if(MODE_INTERCOM) + for (var/obj/item/radio/intercom/I in view(1, null)) + I.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + + if(MODE_BINARY) + return ITALICS | REDUCE_RANGE //Does not return 0 since this is only reached by humans, not borgs or AIs. + + return 0 + +/mob/living/say_mod(input, message_mode) + . = ..() + if(message_mode == MODE_WHISPER_CRIT) + . = "[verb_whisper] in [p_their()] last breath" + else if(message_mode != MODE_CUSTOM_SAY) + if(message_mode == MODE_WHISPER) + . = verb_whisper + else if(stuttering) + . = "stammers" + else if(derpspeech) + . = "gibbers" + +/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) + say("#[message]", bubble_type, spans, sanitize, language, ignore_spam, forced) + +/mob/living/get_language_holder(shadow=TRUE) + if(mind && shadow) + // Mind language holders shadow mob holders. + . = mind.get_language_holder() + if(.) + return . + + . = ..() diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 1694d04b82..5ae649c87e 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1,996 +1,996 @@ -#define CALL_BOT_COOLDOWN 900 - -//Not sure why this is necessary... -/proc/AutoUpdateAI(obj/subject) - var/is_in_use = 0 - if (subject!=null) - for(var/A in GLOB.ai_list) - var/mob/living/silicon/ai/M = A - if ((M.client && M.machine == subject)) - is_in_use = 1 - subject.attack_ai(M) - return is_in_use - - -/mob/living/silicon/ai - name = "AI" - icon = 'icons/mob/ai.dmi' - icon_state = "ai" - anchored = TRUE - density = TRUE - canmove = FALSE - status_flags = CANSTUN|CANPUSH - a_intent = INTENT_HARM //so we always get pushed instead of trying to swap - sight = SEE_TURFS | SEE_MOBS | SEE_OBJS - see_in_dark = 8 - hud_type = /datum/hud/ai - med_hud = DATA_HUD_MEDICAL_BASIC - sec_hud = DATA_HUD_SECURITY_BASIC - d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED - mob_size = MOB_SIZE_LARGE - var/list/network = list("ss13") - var/obj/machinery/camera/current - var/list/connected_robots = list() - var/aiRestorePowerRoutine = 0 - var/requires_power = POWER_REQ_ALL - var/can_be_carded = TRUE - var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=list()) - var/viewalerts = 0 - var/icon/holo_icon//Female is assigned when AI is created. - var/obj/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye. - var/radio_enabled = TRUE //Determins if a carded AI can speak with its built in radio or not. - radiomod = ";" //AIs will, by default, state their laws on the internal radio. - var/obj/item/pda/ai/aiPDA - var/obj/item/multitool/aiMulti - var/mob/living/simple_animal/bot/Bot - var/tracking = FALSE //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed. - var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N - - //MALFUNCTION - var/datum/module_picker/malf_picker - var/list/datum/AI_Module/current_modules = list() - var/can_dominate_mechs = FALSE - var/shunted = FALSE //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead - - var/control_disabled = FALSE // Set to 1 to stop AI from interacting via Click() - var/malfhacking = FALSE // More or less a copy of the above var, so that malf AIs can hack and still get new cyborgs -- NeoFite - var/malf_cooldown = 0 //Cooldown var for malf modules, stores a worldtime + cooldown - - var/obj/machinery/power/apc/malfhack - var/explosive = FALSE //does the AI explode when it dies? - - var/mob/living/silicon/ai/parent - var/camera_light_on = FALSE - var/list/obj/machinery/camera/lit_cameras = list() - - var/datum/trackable/track = new - - var/last_paper_seen = null - var/can_shunt = TRUE - var/last_announcement = "" // For AI VOX, if enabled - var/turf/waypoint //Holds the turf of the currently selected waypoint. - var/waypoint_mode = FALSE //Waypoint mode is for selecting a turf via clicking. - var/call_bot_cooldown = 0 //time of next call bot command - var/apc_override = FALSE //hack for letting the AI use its APC even when visionless - var/nuking = FALSE - var/obj/machinery/doomsday_device/doomsday_device - - var/mob/camera/aiEye/eyeobj - var/sprint = 10 - var/cooldown = 0 - var/acceleration = 1 - - var/obj/structure/AIcore/deactivated/linked_core //For exosuit control - var/mob/living/silicon/robot/deployed_shell = null //For shell control - var/datum/action/innate/deploy_shell/deploy_action = new - var/datum/action/innate/deploy_last_shell/redeploy_action = new - var/chnotify = 0 - - - var/multicam_on = FALSE - var/obj/screen/movable/pic_in_pic/ai/master_multicam - var/list/multicam_screens = list() - var/list/all_eyes = list() - var/max_multicams = 6 - var/display_icon_override - var/emote_display = "Neutral" //text string of the current emote we set for the status displays, to prevent logins resetting it. - -/mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, mob/target_ai) - . = ..() - if(!target_ai) //If there is no player/brain inside. - new/obj/structure/AIcore/deactivated(loc) //New empty terminal. - return INITIALIZE_HINT_QDEL //Delete AI. - - if(L && istype(L, /datum/ai_laws)) - laws = L - laws.associate(src) - else - make_laws() - - if(target_ai.mind) - target_ai.mind.transfer_to(src) - if(mind.special_role) - mind.store_memory("As an AI, you must obey your silicon laws above all else. Your objectives will consider you to be dead.") - to_chat(src, "You have been installed as an AI! ") - to_chat(src, "You must obey your silicon laws above all else. Your objectives will consider you to be dead.") - - to_chat(src, "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).") - to_chat(src, "To look at other parts of the station, click on yourself to get a camera menu.") - to_chat(src, "While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.") - to_chat(src, "To use something, simply click on it.") - to_chat(src, "Use say :b to speak to your cyborgs through binary.") - to_chat(src, "For department channels, use the following say commands:") - to_chat(src, ":o - AI Private, :c - Command, :s - Security, :e - Engineering, :u - Supply, :v - Service, :m - Medical, :n - Science.") - show_laws() - to_chat(src, "These laws may be changed by other players, or by you being the traitor.") - - job = "AI" - - create_eye() - apply_pref_name("ai") - - set_core_display_icon() - - holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"female")) - - spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, src) - spark_system.attach(src) - - verbs += /mob/living/silicon/ai/proc/show_laws_verb - - aiPDA = new/obj/item/pda/ai(src) - aiPDA.owner = name - aiPDA.ownjob = "AI" - aiPDA.name = name + " (" + aiPDA.ownjob + ")" - - aiMulti = new(src) - radio = new /obj/item/radio/headset/ai(src) - aicamera = new/obj/item/camera/siliconcam/ai_camera(src) - - deploy_action.Grant(src) - - if(isturf(loc)) - verbs.Add(/mob/living/silicon/ai/proc/ai_network_change, \ - /mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \ - /mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, \ - /mob/living/silicon/ai/proc/set_automatic_say_channel) - - GLOB.ai_list += src - GLOB.shuttle_caller_list += src - - builtInCamera = new (src) - builtInCamera.network = list("ss13") - - -/mob/living/silicon/ai/Destroy() - GLOB.ai_list -= src - GLOB.shuttle_caller_list -= src - SSshuttle.autoEvac() - qdel(eyeobj) // No AI, no Eye - malfhack = null - - . = ..() - -/mob/living/silicon/ai/IgniteMob() - fire_stacks = 0 - . = ..() - -/mob/living/silicon/ai/proc/set_core_display_icon(input, client/C) - if(client && !C) - C = client - if(!input && !C?.prefs?.preferred_ai_core_display) - icon_state = initial(icon_state) - else - var/preferred_icon = input ? input : C.prefs.preferred_ai_core_display - icon_state = resolve_ai_icon(preferred_icon) - -/mob/living/silicon/ai/verb/pick_icon() - set category = "AI Commands" - set name = "Set AI Core Display" - if(incapacitated()) - return - var/list/iconstates = GLOB.ai_core_display_screens - for(var/option in iconstates) - if(option == "Random") - iconstates[option] = image(icon = src.icon, icon_state = "ai-random") - continue - iconstates[option] = image(icon = src.icon, icon_state = resolve_ai_icon(option)) - - view_core() - var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42) - - if(!ai_core_icon || incapacitated()) - return - - display_icon_override = ai_core_icon - set_core_display_icon(ai_core_icon) - -/mob/living/silicon/ai/Stat() - ..() - if(statpanel("Status")) - if(!stat) - stat(null, text("System integrity: [(health+100)/2]%")) - stat(null, text("Connected cyborgs: [connected_robots.len]")) - for(var/mob/living/silicon/robot/R in connected_robots) - var/robot_status = "Nominal" - if(R.shell) - robot_status = "AI SHELL" - else if(R.stat || !R.client) - robot_status = "OFFLINE" - else if(!R.cell || R.cell.charge <= 0) - robot_status = "DEPOWERED" - //Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies! - stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \ - Module: [R.designation] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]")) - stat(null, text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]")) //Count of total AI shells - else - stat(null, text("Systems nonfunctional")) - -/mob/living/silicon/ai/proc/ai_alerts() - var/dat = "Current Station Alerts\n" - dat += "Close

                " - for (var/cat in alarms) - dat += text("[]
                \n", cat) - var/list/L = alarms[cat] - if (L.len) - for (var/alarm in L) - var/list/alm = L[alarm] - var/area/A = alm[1] - var/C = alm[2] - var/list/sources = alm[3] - dat += "" - if (C && istype(C, /list)) - var/dat2 = "" - for (var/obj/machinery/camera/I in C) - dat2 += text("[][]", (dat2=="") ? "" : " | ", I.c_tag) - dat += text("-- [] ([])", A.name, (dat2!="") ? dat2 : "No Camera") - else if (C && istype(C, /obj/machinery/camera)) - var/obj/machinery/camera/Ctmp = C - dat += text("-- [] ([])", A.name, Ctmp.c_tag) - else - dat += text("-- [] (No Camera)", A.name) - if (sources.len > 1) - dat += text("- [] sources", sources.len) - dat += "
                \n" - else - dat += "-- All Systems Nominal
                \n" - dat += "
                \n" - - viewalerts = 1 - src << browse(dat, "window=aialerts&can_close=0") - -/mob/living/silicon/ai/proc/ai_call_shuttle() - if(control_disabled) - to_chat(usr, "Wireless control is disabled!") - return - - var/reason = input(src, "What is the nature of your emergency? ([CALL_SHUTTLE_REASON_LENGTH] characters required.)", "Confirm Shuttle Call") as null|text - - if(incapacitated()) - return - - if(trim(reason)) - SSshuttle.requestEvac(src, reason) - - // hack to display shuttle timer - if(!EMERGENCY_IDLE_OR_RECALLED) - var/obj/machinery/computer/communications/C = locate() in GLOB.machines - if(C) - C.post_status("shuttle") - -/mob/living/silicon/ai/can_interact_with(atom/A) - . = ..() - var/turf/ai = get_turf(src) - var/turf/target = get_turf(A) - if (.) - return - - if(!target) - return - - if ((ai.z != target.z) && !is_station_level(ai.z)) - return FALSE - - if (istype(loc, /obj/item/aicard)) - var/turf/T0 = get_turf(src) - var/turf/T1 = get_turf(A) - if (!T0 || ! T1) - return FALSE - return ISINRANGE(T1.x, T0.x - interaction_range, T0.x + interaction_range) && ISINRANGE(T1.y, T0.y - interaction_range, T0.y + interaction_range) - else - return GLOB.cameranet.checkTurfVis(get_turf(A)) - -/mob/living/silicon/ai/cancel_camera() - view_core() - -/mob/living/silicon/ai/verb/toggle_anchor() - set category = "AI Commands" - set name = "Toggle Floor Bolts" - if(!isturf(loc)) // if their location isn't a turf - return // stop - if(incapacitated()) - return - anchored = !anchored // Toggles the anchor - - to_chat(src, "You are now [anchored ? "" : "un"]anchored.") - // the message in the [] will change depending whether or not the AI is anchored - -/mob/living/silicon/ai/update_canmove() //If the AI dies, mobs won't go through it anymore - return 0 - -/mob/living/silicon/ai/proc/ai_cancel_call() - set category = "Malfunction" - if(control_disabled) - to_chat(src, "Wireless control is disabled!") - return - SSshuttle.cancelEvac(src) - -/mob/living/silicon/ai/restrained(ignore_grab) - . = 0 - -/mob/living/silicon/ai/Topic(href, href_list) - if(usr != src || incapacitated()) - return - ..() - if (href_list["mach_close"]) - if (href_list["mach_close"] == "aialerts") - viewalerts = 0 - var/t1 = text("window=[]", href_list["mach_close"]) - unset_machine() - src << browse(null, t1) - if (href_list["switchcamera"]) - switchCamera(locate(href_list["switchcamera"])) in GLOB.cameranet.cameras - if (href_list["showalerts"]) - ai_alerts() -#ifdef AI_VOX - if(href_list["say_word"]) - play_vox_word(href_list["say_word"], null, src) - return -#endif - if(href_list["show_paper"]) - if(last_paper_seen) - src << browse(last_paper_seen, "window=show_paper") - //Carn: holopad requests - if(href_list["jumptoholopad"]) - var/obj/machinery/holopad/H = locate(href_list["jumptoholopad"]) - if(H) - H.attack_ai(src) //may as well recycle - else - to_chat(src, "Unable to locate the holopad.") - if(href_list["track"]) - var/string = href_list["track"] - trackable_mobs() - var/list/trackeable = list() - trackeable += track.humans + track.others - var/list/target = list() - for(var/I in trackeable) - var/mob/M = trackeable[I] - if(M.name == string) - target += M - if(name == string) - target += src - if(target.len) - ai_actual_track(pick(target)) - else - to_chat(src, "Target is not on or near any active cameras on the station.") - return - if(href_list["callbot"]) //Command a bot to move to a selected location. - if(call_bot_cooldown > world.time) - to_chat(src, "Error: Your last call bot command is still processing, please wait for the bot to finish calculating a route.") - return - Bot = locate(href_list["callbot"]) in GLOB.alive_mob_list - if(!Bot || Bot.remote_disabled || src.control_disabled) - return //True if there is no bot found, the bot is manually emagged, or the AI is carded with wireless off. - waypoint_mode = 1 - to_chat(src, "Set your waypoint by clicking on a valid location free of obstructions.") - return - if(href_list["interface"]) //Remotely connect to a bot! - Bot = locate(href_list["interface"]) in GLOB.alive_mob_list - if(!Bot || Bot.remote_disabled || src.control_disabled) - return - Bot.attack_ai(src) - if(href_list["botrefresh"]) //Refreshes the bot control panel. - botcall() - return - - if (href_list["ai_take_control"]) //Mech domination - var/obj/mecha/M = locate(href_list["ai_take_control"]) - if(controlled_mech) - to_chat(src, "You are already loaded into an onboard computer!") - return - if(!GLOB.cameranet.checkCameraVis(M)) - to_chat(src, "Exosuit is no longer near active cameras.") - return - if(!isturf(loc)) - to_chat(src, "You aren't in your core!") - return - if(M) - M.transfer_ai(AI_MECH_HACK,src, usr) //Called om the mech itself. - - -/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C) - if(QDELETED(C)) - return FALSE - - if(!tracking) - cameraFollow = null - - if(QDELETED(eyeobj)) - view_core() - return - // ok, we're alive, camera is good and in our network... - eyeobj.setLoc(get_turf(C)) - return TRUE - -/mob/living/silicon/ai/proc/botcall() - set category = "AI Commands" - set name = "Access Robot Control" - set desc = "Wirelessly control various automatic robots." - if(incapacitated()) - return - - if(control_disabled) - to_chat(src, "Wireless control is disabled.") - return - var/turf/ai_current_turf = get_turf(src) - var/ai_Zlevel = ai_current_turf.z - var/d - d += "Query network status
                " - d += "" - - for (Bot in GLOB.alive_mob_list) - if(Bot.z == ai_Zlevel && !Bot.remote_disabled) //Only non-emagged bots on the same Z-level are detected! - var/bot_mode = Bot.get_mode() - d += "" - //If the bot is on, it will display the bot's current mode status. If the bot is not mode, it will just report "Idle". "Inactive if it is not on at all. - d += "" - d += "" - d += "" - d += "" - d += "" - d = format_text(d) - - var/datum/browser/popup = new(src, "botcall", "Remote Robot Control", 700, 400) - popup.set_content(d) - popup.open() - -/mob/living/silicon/ai/proc/set_waypoint(atom/A) - var/turf/turf_check = get_turf(A) - //The target must be in view of a camera or near the core. - if(turf_check in range(get_turf(src))) - call_bot(turf_check) - else if(GLOB.cameranet && GLOB.cameranet.checkTurfVis(turf_check)) - call_bot(turf_check) - else - to_chat(src, "Selected location is not visible.") - -/mob/living/silicon/ai/proc/call_bot(turf/waypoint) - - if(!Bot) - return - - if(Bot.calling_ai && Bot.calling_ai != src) //Prevents an override if another AI is controlling this bot. - to_chat(src, "Interface error. Unit is already in use.") - return - to_chat(src, "Sending command to bot...") - call_bot_cooldown = world.time + CALL_BOT_COOLDOWN - Bot.call_bot(src, waypoint) - call_bot_cooldown = 0 - - -/mob/living/silicon/ai/triggerAlarm(class, area/A, O, obj/alarmsource) - if(alarmsource.z != z) - 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 (!(alarmsource in sources)) - sources += alarmsource - return 1 - var/obj/machinery/camera/C = null - var/list/CL = null - if (O && istype(O, /list)) - 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(alarmsource)) - if (O) - if (C && C.can_use()) - queueAlarm("--- [class] alarm detected in [A.name]! ([C.c_tag])", class) - else if (CL && CL.len) - var/foo = 0 - var/dat2 = "" - for (var/obj/machinery/camera/I in CL) - dat2 += text("[][]", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit... - foo = 1 - queueAlarm(text ("--- [] alarm detected in []! ([])", class, A.name, dat2), class) - else - queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class) - else - queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class) - if (viewalerts) ai_alerts() - return 1 - -/mob/living/silicon/ai/cancelAlarm(class, area/A, obj/origin) - 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 - if (cleared) - queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0) - if (viewalerts) ai_alerts() - return !cleared - -//Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm -//Adds in /mob/living/silicon/ai/proc/ai_network_change() instead -//Addition by Mord_Sith to define AI's network change ability -/mob/living/silicon/ai/proc/ai_network_change() - set category = "AI Commands" - set name = "Jump To Network" - unset_machine() - cameraFollow = null - var/cameralist[0] - - if(incapacitated()) - return - - var/mob/living/silicon/ai/U = usr - - for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) - var/list/tempnetwork = C.network - if(!(is_station_level(C.z) || is_mining_level(C.z) || ("ss13" in tempnetwork))) - continue - if(!C.can_use()) - continue - - tempnetwork.Remove("rd", "toxins", "prison") - if(tempnetwork.len) - for(var/i in C.network) - cameralist[i] = i - var/old_network = network - network = input(U, "Which network would you like to view?") as null|anything in cameralist - - if(!U.eyeobj) - U.view_core() - return - - if(isnull(network)) - network = old_network // If nothing is selected - else - for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) - if(!C.can_use()) - continue - if(network in C.network) - U.eyeobj.setLoc(get_turf(C)) - break - to_chat(src, "Switched to the \"[uppertext(network)]\" camera network.") -//End of code by Mord_Sith - - -/mob/living/silicon/ai/proc/choose_modules() - set category = "Malfunction" - set name = "Choose Module" - - malf_picker.use(src) - -/mob/living/silicon/ai/proc/ai_statuschange() - set category = "AI Commands" - set name = "AI Status" - - if(incapacitated()) - return - var/list/ai_emotions = list("Very Happy", "Happy", "Neutral", "Unsure", "Confused", "Sad", "BSOD", "Blank", "Problems?", "Awesome", "Facepalm", "Thinking", "Friend Computer", "Dorfy", "Blue Glow", "Red Glow") - var/n_emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions - if(!n_emote) - return - emote_display = n_emote - for (var/each in GLOB.ai_status_displays) //change status of displays - var/obj/machinery/status_display/ai/M = each - M.emotion = emote_display - M.update() - if (emote_display == "Friend Computer") - var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) - - if(!frequency) - return - - var/datum/signal/status_signal = new(list("command" = "friendcomputer")) - frequency.post_signal(src, status_signal) - return - -//I am the icon meister. Bow fefore me. //>fefore -/mob/living/silicon/ai/proc/ai_hologram_change() - set name = "Change Hologram" - set desc = "Change the default hologram available to AI to something else." - set category = "AI Commands" - - if(incapacitated()) - return - var/input - switch(alert("Would you like to select a hologram based on a crew member, an animal, or switch to a unique avatar?",,"Crew Member","Unique","Animal")) - if("Crew Member") - var/list/personnel_list = list() - - for(var/datum/data/record/t in GLOB.data_core.locked)//Look in data core locked. - personnel_list["[t.fields["name"]]: [t.fields["rank"]]"] = t.fields["image"]//Pull names, rank, and image. - - if(personnel_list.len) - input = input("Select a crew member:") as null|anything in personnel_list - var/icon/character_icon = personnel_list[input] - if(character_icon) - qdel(holo_icon)//Clear old icon so we're not storing it in memory. - holo_icon = getHologramIcon(icon(character_icon)) - else - alert("No suitable records found. Aborting.") - - if("Animal") - var/list/icon_list = list( - "bear" = 'icons/mob/animal.dmi', - "carp" = 'icons/mob/animal.dmi', - "chicken" = 'icons/mob/animal.dmi', - "corgi" = 'icons/mob/pets.dmi', - "cow" = 'icons/mob/animal.dmi', - "crab" = 'icons/mob/animal.dmi', - "fox" = 'icons/mob/pets.dmi', - "goat" = 'icons/mob/animal.dmi', - "cat" = 'icons/mob/pets.dmi', - "cat2" = 'icons/mob/pets.dmi', - "poly" = 'icons/mob/animal.dmi', - "pug" = 'icons/mob/pets.dmi', - "spider" = 'icons/mob/animal.dmi' - ) - - input = input("Please select a hologram:") as null|anything in icon_list - if(input) - qdel(holo_icon) - switch(input) - if("poly") - holo_icon = getHologramIcon(icon(icon_list[input],"parrot_fly")) - if("chicken") - holo_icon = getHologramIcon(icon(icon_list[input],"chicken_brown")) - if("spider") - holo_icon = getHologramIcon(icon(icon_list[input],"guard")) - else - holo_icon = getHologramIcon(icon(icon_list[input], input)) - else - var/list/icon_list = list( - "female" = 'icons/mob/ai.dmi', - "male" = 'icons/mob/ai.dmi', - "floating face" = 'icons/mob/ai.dmi', - "green face" = 'icons/mob/ai.dmi', - "xeno queen" = 'icons/mob/alien.dmi', - "horror" = 'icons/mob/ai.dmi', - "creature" = 'icons/mob/ai.dmi', - "custom" - ) - - input = input("Please select a hologram:") as null|anything in icon_list - if(input) - qdel(holo_icon) - switch(input) - if("custom") - if(client?.prefs?.custom_holoform_icon) - holo_icon = client.prefs.get_filtered_holoform(HOLOFORM_FILTER_AI) - else - holo_icon = getHologramIcon(icon('icons/mob/ai.dmi', "female")) - else if("xeno queen") - holo_icon = getHologramIcon(icon(icon_list[input],"alienq")) - else - holo_icon = getHologramIcon(icon(icon_list[input], input)) - -/mob/living/silicon/ai/proc/corereturn() - set category = "Malfunction" - set name = "Return to Main Core" - - var/obj/machinery/power/apc/apc = src.loc - if(!istype(apc)) - to_chat(src, "You are already in your Main Core.") - return - apc.malfvacate() - -/mob/living/silicon/ai/proc/toggle_camera_light() - camera_light_on = !camera_light_on - - if (!camera_light_on) - to_chat(src, "Camera lights deactivated.") - - for (var/obj/machinery/camera/C in lit_cameras) - C.set_light(0) - lit_cameras = list() - - return - - light_cameras() - - to_chat(src, "Camera lights activated.") - -//AI_CAMERA_LUMINOSITY - -/mob/living/silicon/ai/proc/light_cameras() - var/list/obj/machinery/camera/add = list() - var/list/obj/machinery/camera/remove = list() - var/list/obj/machinery/camera/visible = list() - for (var/datum/camerachunk/CC in eyeobj.visibleCameraChunks) - for (var/obj/machinery/camera/C in CC.cameras) - if (!C.can_use() || get_dist(C, eyeobj) > 7 || !C.internal_light) - continue - visible |= C - - add = visible - lit_cameras - remove = lit_cameras - visible - - for (var/obj/machinery/camera/C in remove) - lit_cameras -= C //Removed from list before turning off the light so that it doesn't check the AI looking away. - C.Togglelight(0) - for (var/obj/machinery/camera/C in add) - C.Togglelight(1) - lit_cameras |= C - -/mob/living/silicon/ai/proc/control_integrated_radio() - set name = "Transceiver Settings" - set desc = "Allows you to change settings of your radio." - set category = "AI Commands" - - if(incapacitated()) - return - - to_chat(src, "Accessing Subspace Transceiver control...") - if (radio) - radio.interact(src) - -/mob/living/silicon/ai/proc/set_syndie_radio() - if(radio) - radio.make_syndie() - -/mob/living/silicon/ai/proc/set_automatic_say_channel() - set name = "Set Auto Announce Mode" - set desc = "Modify the default radio setting for your automatic announcements." - set category = "AI Commands" - - if(incapacitated()) - return - set_autosay() - -/mob/living/silicon/ai/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) - if(!..()) - return - if(interaction == AI_TRANS_TO_CARD)//The only possible interaction. Upload AI mob to a card. - if(!can_be_carded) - to_chat(user, "Transfer failed.") - return - disconnect_shell() //If the AI is controlling a borg, force the player back to core! - if(!mind) - to_chat(user, "No intelligence patterns detected." ) - return - ShutOffDoomsdayDevice() - new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location. - ai_restore_power()//So the AI initially has power. - control_disabled = 1//Can't control things remotely if you're stuck in a card! - radio_enabled = 0 //No talking on the built-in radio for you either! - forceMove(card) - card.AI = src - to_chat(src, "You have been downloaded to a mobile storage device. Remote device connection severed.") - to_chat(user, "Transfer successful: [name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.") - -/mob/living/silicon/ai/can_buckle() - return 0 - -/mob/living/silicon/ai/incapacitated(ignore_restraints, ignore_grab) - if(aiRestorePowerRoutine) - return TRUE - return ..() - -/mob/living/silicon/ai/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) - if(control_disabled || incapacitated()) - to_chat(src, "You can't do that right now!") - return FALSE - if(be_close && !in_range(M, src)) - to_chat(src, "You are too far away!") - return FALSE - return can_see(M) //stop AIs from leaving windows open and using then after they lose vision - -/mob/living/silicon/ai/proc/can_see(atom/A) - if(isturf(loc)) //AI in core, check if on cameras - //get_turf_pixel() is because APCs in maint aren't actually in view of the inner camera - //apc_override is needed here because AIs use their own APC when depowered - return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(get_turf_pixel(A))) || apc_override - //AI is carded/shunted - //view(src) returns nothing for carded/shunted AIs and they have X-ray vision so just use get_dist - var/list/viewscale = getviewsize(client.view) - return get_dist(src, A) <= max(viewscale[1]*0.5,viewscale[2]*0.5) - -/mob/living/silicon/ai/proc/relay_speech(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) - raw_message = lang_treat(speaker, message_language, raw_message, spans, message_mode) - var/start = "Relayed Speech: " - var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]" - var/hrefpart = "" - var/jobpart - - if (iscarbon(speaker)) - var/mob/living/carbon/S = speaker - if(S.job) - jobpart = "[S.job]" - else - jobpart = "Unknown" - - var/rendered = "[start][hrefpart][namepart] ([jobpart])[raw_message]" - - show_message(rendered, MSG_AUDIBLE) - -/mob/living/silicon/ai/fully_replace_character_name(oldname,newname) - ..() - if(oldname != real_name) - if(eyeobj) - eyeobj.name = "[newname] (AI Eye)" - - // Notify Cyborgs - for(var/mob/living/silicon/robot/Slave in connected_robots) - Slave.show_laws() - -/mob/living/silicon/ai/replace_identification_name(oldname,newname) - if(aiPDA) - aiPDA.owner = newname - aiPDA.name = newname + " (" + aiPDA.ownjob + ")" - - -/mob/living/silicon/ai/proc/add_malf_picker() - to_chat(src, "In the top right corner of the screen you will find the Malfunctions tab, where you can purchase various abilities, from upgraded surveillance to station ending doomsday devices.") - to_chat(src, "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds.") - view_core() //A BYOND bug requires you to be viewing your core before your verbs update - verbs += /mob/living/silicon/ai/proc/choose_modules - malf_picker = new /datum/module_picker - - -/mob/living/silicon/ai/reset_perspective(atom/A) - if(camera_light_on) - light_cameras() - if(istype(A, /obj/machinery/camera)) - current = A - if(client) - if(ismovableatom(A)) - if(A != GLOB.ai_camera_room_landmark) - end_multicam() - client.perspective = EYE_PERSPECTIVE - client.eye = A - else - end_multicam() - if(isturf(loc)) - if(eyeobj) - client.eye = eyeobj - client.perspective = EYE_PERSPECTIVE - else - client.eye = client.mob - client.perspective = MOB_PERSPECTIVE - else - client.perspective = EYE_PERSPECTIVE - client.eye = loc - update_sight() - if(client.eye != src) - var/atom/AT = client.eye - AT.get_remote_view_fullscreens(src) - else - clear_fullscreen("remote_view", 0) - -/mob/living/silicon/ai/revive(full_heal = 0, admin_revive = 0) - . = ..() - if(.) //successfully ressuscitated from death - set_eyeobj_visible(TRUE) - set_core_display_icon(display_icon_override) - -/mob/living/silicon/ai/proc/malfhacked(obj/machinery/power/apc/apc) - malfhack = null - malfhacking = 0 - clear_alert("hackingapc") - - if(!istype(apc) || QDELETED(apc) || apc.stat & BROKEN) - to_chat(src, "Hack aborted. The designated APC no longer exists on the power network.") - playsound(get_turf(src), 'sound/machines/buzz-two.ogg', 50, 1) - else if(apc.aidisabled) - to_chat(src, "Hack aborted. \The [apc] is no longer responding to our systems.") - playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 1) - else - malf_picker.processing_time += 10 - - apc.malfai = parent || src - apc.malfhack = TRUE - apc.locked = TRUE - apc.coverlocked = TRUE - - playsound(get_turf(src), 'sound/machines/ding.ogg', 50, 1) - to_chat(src, "Hack complete. \The [apc] is now under your exclusive control.") - apc.update_icon() - -/mob/living/silicon/ai/verb/deploy_to_shell(var/mob/living/silicon/robot/target) - set category = "AI Commands" - set name = "Deploy to Shell" - - if(incapacitated()) - return - if(control_disabled) - to_chat(src, "Wireless networking module is offline.") - return - - var/list/possible = list() - - for(var/borgie in GLOB.available_ai_shells) - var/mob/living/silicon/robot/R = borgie - if(R.shell && !R.deployed && (R.stat != DEAD) && (!R.connected_ai ||(R.connected_ai == src))) - possible += R - - if(!LAZYLEN(possible)) - to_chat(src, "No usable AI shell beacons detected.") - - if(!target || !(target in possible)) //If the AI is looking for a new shell, or its pre-selected shell is no longer valid - target = input(src, "Which body to control?") as null|anything in possible - - if (!target || target.stat == DEAD || target.deployed || !(!target.connected_ai ||(target.connected_ai == src))) - return - - else if(mind) - soullink(/datum/soullink/sharedbody, src, target) - deployed_shell = target - target.deploy_init(src) - mind.transfer_to(target) - diag_hud_set_deployed() - -/datum/action/innate/deploy_shell - name = "Deploy to AI Shell" - desc = "Wirelessly control a specialized cyborg shell." - icon_icon = 'icons/mob/actions/actions_AI.dmi' - button_icon_state = "ai_shell" - -/datum/action/innate/deploy_shell/Trigger() - var/mob/living/silicon/ai/AI = owner - if(!AI) - return - AI.deploy_to_shell() - -/datum/action/innate/deploy_last_shell - name = "Reconnect to shell" - desc = "Reconnect to the most recently used AI shell." - icon_icon = 'icons/mob/actions/actions_AI.dmi' - button_icon_state = "ai_last_shell" - var/mob/living/silicon/robot/last_used_shell - -/datum/action/innate/deploy_last_shell/Trigger() - if(!owner) - return - if(last_used_shell) - var/mob/living/silicon/ai/AI = owner - AI.deploy_to_shell(last_used_shell) - else - Remove(owner) //If the last shell is blown, destroy it. - -/mob/living/silicon/ai/proc/disconnect_shell() - if(deployed_shell) //Forcibly call back AI in event of things such as damage, EMP or power loss. - to_chat(src, "Your remote connection has been reset!") - deployed_shell.undeploy() - diag_hud_set_deployed() - -/mob/living/silicon/ai/resist() - return - -/mob/living/silicon/ai/spawned/Initialize(mapload, datum/ai_laws/L, mob/target_ai) - . = ..() - if(!target_ai) - target_ai = src //cheat! just give... ourselves as the spawned AI, because that's technically correct - -/mob/living/silicon/ai/proc/camera_visibility(mob/camera/aiEye/moved_eye) - GLOB.cameranet.visibility(moved_eye, client, all_eyes, USE_STATIC_OPAQUE) - -/mob/living/silicon/ai/forceMove(atom/destination) - . = ..() - if(.) - end_multicam() +#define CALL_BOT_COOLDOWN 900 + +//Not sure why this is necessary... +/proc/AutoUpdateAI(obj/subject) + var/is_in_use = 0 + if (subject!=null) + for(var/A in GLOB.ai_list) + var/mob/living/silicon/ai/M = A + if ((M.client && M.machine == subject)) + is_in_use = 1 + subject.attack_ai(M) + return is_in_use + + +/mob/living/silicon/ai + name = "AI" + icon = 'icons/mob/ai.dmi' + icon_state = "ai" + anchored = TRUE + density = TRUE + canmove = FALSE + status_flags = CANSTUN|CANPUSH + a_intent = INTENT_HARM //so we always get pushed instead of trying to swap + sight = SEE_TURFS | SEE_MOBS | SEE_OBJS + see_in_dark = 8 + hud_type = /datum/hud/ai + med_hud = DATA_HUD_MEDICAL_BASIC + sec_hud = DATA_HUD_SECURITY_BASIC + d_hud = DATA_HUD_DIAGNOSTIC_ADVANCED + mob_size = MOB_SIZE_LARGE + var/list/network = list("ss13") + var/obj/machinery/camera/current + var/list/connected_robots = list() + var/aiRestorePowerRoutine = 0 + var/requires_power = POWER_REQ_ALL + var/can_be_carded = TRUE + var/alarms = list("Motion"=list(), "Fire"=list(), "Atmosphere"=list(), "Power"=list(), "Camera"=list(), "Burglar"=list()) + var/viewalerts = 0 + var/icon/holo_icon//Female is assigned when AI is created. + var/obj/mecha/controlled_mech //For controlled_mech a mech, to determine whether to relaymove or use the AI eye. + var/radio_enabled = TRUE //Determins if a carded AI can speak with its built in radio or not. + radiomod = ";" //AIs will, by default, state their laws on the internal radio. + var/obj/item/pda/ai/aiPDA + var/obj/item/multitool/aiMulti + var/mob/living/simple_animal/bot/Bot + var/tracking = FALSE //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed. + var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N + + //MALFUNCTION + var/datum/module_picker/malf_picker + var/list/datum/AI_Module/current_modules = list() + var/can_dominate_mechs = FALSE + var/shunted = FALSE //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead + + var/control_disabled = FALSE // Set to 1 to stop AI from interacting via Click() + var/malfhacking = FALSE // More or less a copy of the above var, so that malf AIs can hack and still get new cyborgs -- NeoFite + var/malf_cooldown = 0 //Cooldown var for malf modules, stores a worldtime + cooldown + + var/obj/machinery/power/apc/malfhack + var/explosive = FALSE //does the AI explode when it dies? + + var/mob/living/silicon/ai/parent + var/camera_light_on = FALSE + var/list/obj/machinery/camera/lit_cameras = list() + + var/datum/trackable/track = new + + var/last_paper_seen = null + var/can_shunt = TRUE + var/last_announcement = "" // For AI VOX, if enabled + var/turf/waypoint //Holds the turf of the currently selected waypoint. + var/waypoint_mode = FALSE //Waypoint mode is for selecting a turf via clicking. + var/call_bot_cooldown = 0 //time of next call bot command + var/apc_override = FALSE //hack for letting the AI use its APC even when visionless + var/nuking = FALSE + var/obj/machinery/doomsday_device/doomsday_device + + var/mob/camera/aiEye/eyeobj + var/sprint = 10 + var/cooldown = 0 + var/acceleration = 1 + + var/obj/structure/AIcore/deactivated/linked_core //For exosuit control + var/mob/living/silicon/robot/deployed_shell = null //For shell control + var/datum/action/innate/deploy_shell/deploy_action = new + var/datum/action/innate/deploy_last_shell/redeploy_action = new + var/chnotify = 0 + + + var/multicam_on = FALSE + var/obj/screen/movable/pic_in_pic/ai/master_multicam + var/list/multicam_screens = list() + var/list/all_eyes = list() + var/max_multicams = 6 + var/display_icon_override + var/emote_display = "Neutral" //text string of the current emote we set for the status displays, to prevent logins resetting it. + +/mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, mob/target_ai) + . = ..() + if(!target_ai) //If there is no player/brain inside. + new/obj/structure/AIcore/deactivated(loc) //New empty terminal. + return INITIALIZE_HINT_QDEL //Delete AI. + + if(L && istype(L, /datum/ai_laws)) + laws = L + laws.associate(src) + else + make_laws() + + if(target_ai.mind) + target_ai.mind.transfer_to(src) + if(mind.special_role) + mind.store_memory("As an AI, you must obey your silicon laws above all else. Your objectives will consider you to be dead.") + to_chat(src, "You have been installed as an AI! ") + to_chat(src, "You must obey your silicon laws above all else. Your objectives will consider you to be dead.") + + to_chat(src, "You are playing the station's AI. The AI cannot move, but can interact with many objects while viewing them (through cameras).") + to_chat(src, "To look at other parts of the station, click on yourself to get a camera menu.") + to_chat(src, "While observing through a camera, you can use most (networked) devices which you can see, such as computers, APCs, intercoms, doors, etc.") + to_chat(src, "To use something, simply click on it.") + to_chat(src, "Use say :b to speak to your cyborgs through binary.") + to_chat(src, "For department channels, use the following say commands:") + to_chat(src, ":o - AI Private, :c - Command, :s - Security, :e - Engineering, :u - Supply, :v - Service, :m - Medical, :n - Science.") + show_laws() + to_chat(src, "These laws may be changed by other players, or by you being the traitor.") + + job = "AI" + + create_eye() + apply_pref_name("ai") + + set_core_display_icon() + + holo_icon = getHologramIcon(icon('icons/mob/ai.dmi',"female")) + + spark_system = new /datum/effect_system/spark_spread() + spark_system.set_up(5, 0, src) + spark_system.attach(src) + + verbs += /mob/living/silicon/ai/proc/show_laws_verb + + aiPDA = new/obj/item/pda/ai(src) + aiPDA.owner = name + aiPDA.ownjob = "AI" + aiPDA.name = name + " (" + aiPDA.ownjob + ")" + + aiMulti = new(src) + radio = new /obj/item/radio/headset/ai(src) + aicamera = new/obj/item/camera/siliconcam/ai_camera(src) + + deploy_action.Grant(src) + + if(isturf(loc)) + verbs.Add(/mob/living/silicon/ai/proc/ai_network_change, \ + /mob/living/silicon/ai/proc/ai_statuschange, /mob/living/silicon/ai/proc/ai_hologram_change, \ + /mob/living/silicon/ai/proc/botcall, /mob/living/silicon/ai/proc/control_integrated_radio, \ + /mob/living/silicon/ai/proc/set_automatic_say_channel) + + GLOB.ai_list += src + GLOB.shuttle_caller_list += src + + builtInCamera = new (src) + builtInCamera.network = list("ss13") + + +/mob/living/silicon/ai/Destroy() + GLOB.ai_list -= src + GLOB.shuttle_caller_list -= src + SSshuttle.autoEvac() + qdel(eyeobj) // No AI, no Eye + malfhack = null + + . = ..() + +/mob/living/silicon/ai/IgniteMob() + fire_stacks = 0 + . = ..() + +/mob/living/silicon/ai/proc/set_core_display_icon(input, client/C) + if(client && !C) + C = client + if(!input && !C?.prefs?.preferred_ai_core_display) + icon_state = initial(icon_state) + else + var/preferred_icon = input ? input : C.prefs.preferred_ai_core_display + icon_state = resolve_ai_icon(preferred_icon) + +/mob/living/silicon/ai/verb/pick_icon() + set category = "AI Commands" + set name = "Set AI Core Display" + if(incapacitated()) + return + var/list/iconstates = GLOB.ai_core_display_screens + for(var/option in iconstates) + if(option == "Random") + iconstates[option] = image(icon = src.icon, icon_state = "ai-random") + continue + iconstates[option] = image(icon = src.icon, icon_state = resolve_ai_icon(option)) + + view_core() + var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42) + + if(!ai_core_icon || incapacitated()) + return + + display_icon_override = ai_core_icon + set_core_display_icon(ai_core_icon) + +/mob/living/silicon/ai/Stat() + ..() + if(statpanel("Status")) + if(!stat) + stat(null, text("System integrity: [(health+100)/2]%")) + stat(null, text("Connected cyborgs: [connected_robots.len]")) + for(var/mob/living/silicon/robot/R in connected_robots) + var/robot_status = "Nominal" + if(R.shell) + robot_status = "AI SHELL" + else if(R.stat || !R.client) + robot_status = "OFFLINE" + else if(!R.cell || R.cell.charge <= 0) + robot_status = "DEPOWERED" + //Name, Health, Battery, Module, Area, and Status! Everything an AI wants to know about its borgies! + stat(null, text("[R.name] | S.Integrity: [R.health]% | Cell: [R.cell ? "[R.cell.charge]/[R.cell.maxcharge]" : "Empty"] | \ + Module: [R.designation] | Loc: [get_area_name(R, TRUE)] | Status: [robot_status]")) + stat(null, text("AI shell beacons detected: [LAZYLEN(GLOB.available_ai_shells)]")) //Count of total AI shells + else + stat(null, text("Systems nonfunctional")) + +/mob/living/silicon/ai/proc/ai_alerts() + var/dat = "Current Station Alerts\n" + dat += "Close

                " + for (var/cat in alarms) + dat += text("[]
                \n", cat) + var/list/L = alarms[cat] + if (L.len) + for (var/alarm in L) + var/list/alm = L[alarm] + var/area/A = alm[1] + var/C = alm[2] + var/list/sources = alm[3] + dat += "" + if (C && istype(C, /list)) + var/dat2 = "" + for (var/obj/machinery/camera/I in C) + dat2 += text("[][]", (dat2=="") ? "" : " | ", I.c_tag) + dat += text("-- [] ([])", A.name, (dat2!="") ? dat2 : "No Camera") + else if (C && istype(C, /obj/machinery/camera)) + var/obj/machinery/camera/Ctmp = C + dat += text("-- [] ([])", A.name, Ctmp.c_tag) + else + dat += text("-- [] (No Camera)", A.name) + if (sources.len > 1) + dat += text("- [] sources", sources.len) + dat += "
                \n" + else + dat += "-- All Systems Nominal
                \n" + dat += "
                \n" + + viewalerts = 1 + src << browse(dat, "window=aialerts&can_close=0") + +/mob/living/silicon/ai/proc/ai_call_shuttle() + if(control_disabled) + to_chat(usr, "Wireless control is disabled!") + return + + var/reason = input(src, "What is the nature of your emergency? ([CALL_SHUTTLE_REASON_LENGTH] characters required.)", "Confirm Shuttle Call") as null|text + + if(incapacitated()) + return + + if(trim(reason)) + SSshuttle.requestEvac(src, reason) + + // hack to display shuttle timer + if(!EMERGENCY_IDLE_OR_RECALLED) + var/obj/machinery/computer/communications/C = locate() in GLOB.machines + if(C) + C.post_status("shuttle") + +/mob/living/silicon/ai/can_interact_with(atom/A) + . = ..() + var/turf/ai = get_turf(src) + var/turf/target = get_turf(A) + if (.) + return + + if(!target) + return + + if ((ai.z != target.z) && !is_station_level(ai.z)) + return FALSE + + if (istype(loc, /obj/item/aicard)) + var/turf/T0 = get_turf(src) + var/turf/T1 = get_turf(A) + if (!T0 || ! T1) + return FALSE + return ISINRANGE(T1.x, T0.x - interaction_range, T0.x + interaction_range) && ISINRANGE(T1.y, T0.y - interaction_range, T0.y + interaction_range) + else + return GLOB.cameranet.checkTurfVis(get_turf(A)) + +/mob/living/silicon/ai/cancel_camera() + view_core() + +/mob/living/silicon/ai/verb/toggle_anchor() + set category = "AI Commands" + set name = "Toggle Floor Bolts" + if(!isturf(loc)) // if their location isn't a turf + return // stop + if(incapacitated()) + return + anchored = !anchored // Toggles the anchor + + to_chat(src, "You are now [anchored ? "" : "un"]anchored.") + // the message in the [] will change depending whether or not the AI is anchored + +/mob/living/silicon/ai/update_canmove() //If the AI dies, mobs won't go through it anymore + return 0 + +/mob/living/silicon/ai/proc/ai_cancel_call() + set category = "Malfunction" + if(control_disabled) + to_chat(src, "Wireless control is disabled!") + return + SSshuttle.cancelEvac(src) + +/mob/living/silicon/ai/restrained(ignore_grab) + . = 0 + +/mob/living/silicon/ai/Topic(href, href_list) + if(usr != src || incapacitated()) + return + ..() + if (href_list["mach_close"]) + if (href_list["mach_close"] == "aialerts") + viewalerts = 0 + var/t1 = text("window=[]", href_list["mach_close"]) + unset_machine() + src << browse(null, t1) + if (href_list["switchcamera"]) + switchCamera(locate(href_list["switchcamera"])) in GLOB.cameranet.cameras + if (href_list["showalerts"]) + ai_alerts() +#ifdef AI_VOX + if(href_list["say_word"]) + play_vox_word(href_list["say_word"], null, src) + return +#endif + if(href_list["show_paper"]) + if(last_paper_seen) + src << browse(last_paper_seen, "window=show_paper") + //Carn: holopad requests + if(href_list["jumptoholopad"]) + var/obj/machinery/holopad/H = locate(href_list["jumptoholopad"]) + if(H) + H.attack_ai(src) //may as well recycle + else + to_chat(src, "Unable to locate the holopad.") + if(href_list["track"]) + var/string = href_list["track"] + trackable_mobs() + var/list/trackeable = list() + trackeable += track.humans + track.others + var/list/target = list() + for(var/I in trackeable) + var/mob/M = trackeable[I] + if(M.name == string) + target += M + if(name == string) + target += src + if(target.len) + ai_actual_track(pick(target)) + else + to_chat(src, "Target is not on or near any active cameras on the station.") + return + if(href_list["callbot"]) //Command a bot to move to a selected location. + if(call_bot_cooldown > world.time) + to_chat(src, "Error: Your last call bot command is still processing, please wait for the bot to finish calculating a route.") + return + Bot = locate(href_list["callbot"]) in GLOB.alive_mob_list + if(!Bot || Bot.remote_disabled || src.control_disabled) + return //True if there is no bot found, the bot is manually emagged, or the AI is carded with wireless off. + waypoint_mode = 1 + to_chat(src, "Set your waypoint by clicking on a valid location free of obstructions.") + return + if(href_list["interface"]) //Remotely connect to a bot! + Bot = locate(href_list["interface"]) in GLOB.alive_mob_list + if(!Bot || Bot.remote_disabled || src.control_disabled) + return + Bot.attack_ai(src) + if(href_list["botrefresh"]) //Refreshes the bot control panel. + botcall() + return + + if (href_list["ai_take_control"]) //Mech domination + var/obj/mecha/M = locate(href_list["ai_take_control"]) + if(controlled_mech) + to_chat(src, "You are already loaded into an onboard computer!") + return + if(!GLOB.cameranet.checkCameraVis(M)) + to_chat(src, "Exosuit is no longer near active cameras.") + return + if(!isturf(loc)) + to_chat(src, "You aren't in your core!") + return + if(M) + M.transfer_ai(AI_MECH_HACK,src, usr) //Called om the mech itself. + + +/mob/living/silicon/ai/proc/switchCamera(obj/machinery/camera/C) + if(QDELETED(C)) + return FALSE + + if(!tracking) + cameraFollow = null + + if(QDELETED(eyeobj)) + view_core() + return + // ok, we're alive, camera is good and in our network... + eyeobj.setLoc(get_turf(C)) + return TRUE + +/mob/living/silicon/ai/proc/botcall() + set category = "AI Commands" + set name = "Access Robot Control" + set desc = "Wirelessly control various automatic robots." + if(incapacitated()) + return + + if(control_disabled) + to_chat(src, "Wireless control is disabled.") + return + var/turf/ai_current_turf = get_turf(src) + var/ai_Zlevel = ai_current_turf.z + var/d + d += "Query network status
                " + d += "

                Name

                Status

                Location

                Control

                [Bot.hacked ? "(!)" : ""] [Bot.name] ([Bot.model])[bot_mode][get_area_name(Bot, TRUE)]InterfaceCall
                " + + for (Bot in GLOB.alive_mob_list) + if(Bot.z == ai_Zlevel && !Bot.remote_disabled) //Only non-emagged bots on the same Z-level are detected! + var/bot_mode = Bot.get_mode() + d += "" + //If the bot is on, it will display the bot's current mode status. If the bot is not mode, it will just report "Idle". "Inactive if it is not on at all. + d += "" + d += "" + d += "" + d += "" + d += "" + d = format_text(d) + + var/datum/browser/popup = new(src, "botcall", "Remote Robot Control", 700, 400) + popup.set_content(d) + popup.open() + +/mob/living/silicon/ai/proc/set_waypoint(atom/A) + var/turf/turf_check = get_turf(A) + //The target must be in view of a camera or near the core. + if(turf_check in range(get_turf(src))) + call_bot(turf_check) + else if(GLOB.cameranet && GLOB.cameranet.checkTurfVis(turf_check)) + call_bot(turf_check) + else + to_chat(src, "Selected location is not visible.") + +/mob/living/silicon/ai/proc/call_bot(turf/waypoint) + + if(!Bot) + return + + if(Bot.calling_ai && Bot.calling_ai != src) //Prevents an override if another AI is controlling this bot. + to_chat(src, "Interface error. Unit is already in use.") + return + to_chat(src, "Sending command to bot...") + call_bot_cooldown = world.time + CALL_BOT_COOLDOWN + Bot.call_bot(src, waypoint) + call_bot_cooldown = 0 + + +/mob/living/silicon/ai/triggerAlarm(class, area/A, O, obj/alarmsource) + if(alarmsource.z != z) + 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 (!(alarmsource in sources)) + sources += alarmsource + return 1 + var/obj/machinery/camera/C = null + var/list/CL = null + if (O && istype(O, /list)) + 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(alarmsource)) + if (O) + if (C && C.can_use()) + queueAlarm("--- [class] alarm detected in [A.name]! ([C.c_tag])", class) + else if (CL && CL.len) + var/foo = 0 + var/dat2 = "" + for (var/obj/machinery/camera/I in CL) + dat2 += text("[][]", (!foo) ? "" : " | ", I.c_tag) //I'm not fixing this shit... + foo = 1 + queueAlarm(text ("--- [] alarm detected in []! ([])", class, A.name, dat2), class) + else + queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class) + else + queueAlarm(text("--- [] alarm detected in []! (No Camera)", class, A.name), class) + if (viewalerts) ai_alerts() + return 1 + +/mob/living/silicon/ai/cancelAlarm(class, area/A, obj/origin) + 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 + if (cleared) + queueAlarm("--- [class] alarm in [A.name] has been cleared.", class, 0) + if (viewalerts) ai_alerts() + return !cleared + +//Replaces /mob/living/silicon/ai/verb/change_network() in ai.dm & camera.dm +//Adds in /mob/living/silicon/ai/proc/ai_network_change() instead +//Addition by Mord_Sith to define AI's network change ability +/mob/living/silicon/ai/proc/ai_network_change() + set category = "AI Commands" + set name = "Jump To Network" + unset_machine() + cameraFollow = null + var/cameralist[0] + + if(incapacitated()) + return + + var/mob/living/silicon/ai/U = usr + + for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) + var/list/tempnetwork = C.network + if(!(is_station_level(C.z) || is_mining_level(C.z) || ("ss13" in tempnetwork))) + continue + if(!C.can_use()) + continue + + tempnetwork.Remove("rd", "toxins", "prison") + if(tempnetwork.len) + for(var/i in C.network) + cameralist[i] = i + var/old_network = network + network = input(U, "Which network would you like to view?") as null|anything in cameralist + + if(!U.eyeobj) + U.view_core() + return + + if(isnull(network)) + network = old_network // If nothing is selected + else + for(var/obj/machinery/camera/C in GLOB.cameranet.cameras) + if(!C.can_use()) + continue + if(network in C.network) + U.eyeobj.setLoc(get_turf(C)) + break + to_chat(src, "Switched to the \"[uppertext(network)]\" camera network.") +//End of code by Mord_Sith + + +/mob/living/silicon/ai/proc/choose_modules() + set category = "Malfunction" + set name = "Choose Module" + + malf_picker.use(src) + +/mob/living/silicon/ai/proc/ai_statuschange() + set category = "AI Commands" + set name = "AI Status" + + if(incapacitated()) + return + var/list/ai_emotions = list("Very Happy", "Happy", "Neutral", "Unsure", "Confused", "Sad", "BSOD", "Blank", "Problems?", "Awesome", "Facepalm", "Thinking", "Friend Computer", "Dorfy", "Blue Glow", "Red Glow") + var/n_emote = input("Please, select a status!", "AI Status", null, null) in ai_emotions + if(!n_emote) + return + emote_display = n_emote + for (var/each in GLOB.ai_status_displays) //change status of displays + var/obj/machinery/status_display/ai/M = each + M.emotion = emote_display + M.update() + if (emote_display == "Friend Computer") + var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) + + if(!frequency) + return + + var/datum/signal/status_signal = new(list("command" = "friendcomputer")) + frequency.post_signal(src, status_signal) + return + +//I am the icon meister. Bow fefore me. //>fefore +/mob/living/silicon/ai/proc/ai_hologram_change() + set name = "Change Hologram" + set desc = "Change the default hologram available to AI to something else." + set category = "AI Commands" + + if(incapacitated()) + return + var/input + switch(alert("Would you like to select a hologram based on a crew member, an animal, or switch to a unique avatar?",,"Crew Member","Unique","Animal")) + if("Crew Member") + var/list/personnel_list = list() + + for(var/datum/data/record/t in GLOB.data_core.locked)//Look in data core locked. + personnel_list["[t.fields["name"]]: [t.fields["rank"]]"] = t.fields["image"]//Pull names, rank, and image. + + if(personnel_list.len) + input = input("Select a crew member:") as null|anything in personnel_list + var/icon/character_icon = personnel_list[input] + if(character_icon) + qdel(holo_icon)//Clear old icon so we're not storing it in memory. + holo_icon = getHologramIcon(icon(character_icon)) + else + alert("No suitable records found. Aborting.") + + if("Animal") + var/list/icon_list = list( + "bear" = 'icons/mob/animal.dmi', + "carp" = 'icons/mob/animal.dmi', + "chicken" = 'icons/mob/animal.dmi', + "corgi" = 'icons/mob/pets.dmi', + "cow" = 'icons/mob/animal.dmi', + "crab" = 'icons/mob/animal.dmi', + "fox" = 'icons/mob/pets.dmi', + "goat" = 'icons/mob/animal.dmi', + "cat" = 'icons/mob/pets.dmi', + "cat2" = 'icons/mob/pets.dmi', + "poly" = 'icons/mob/animal.dmi', + "pug" = 'icons/mob/pets.dmi', + "spider" = 'icons/mob/animal.dmi' + ) + + input = input("Please select a hologram:") as null|anything in icon_list + if(input) + qdel(holo_icon) + switch(input) + if("poly") + holo_icon = getHologramIcon(icon(icon_list[input],"parrot_fly")) + if("chicken") + holo_icon = getHologramIcon(icon(icon_list[input],"chicken_brown")) + if("spider") + holo_icon = getHologramIcon(icon(icon_list[input],"guard")) + else + holo_icon = getHologramIcon(icon(icon_list[input], input)) + else + var/list/icon_list = list( + "female" = 'icons/mob/ai.dmi', + "male" = 'icons/mob/ai.dmi', + "floating face" = 'icons/mob/ai.dmi', + "green face" = 'icons/mob/ai.dmi', + "xeno queen" = 'icons/mob/alien.dmi', + "horror" = 'icons/mob/ai.dmi', + "creature" = 'icons/mob/ai.dmi', + "custom" + ) + + input = input("Please select a hologram:") as null|anything in icon_list + if(input) + qdel(holo_icon) + switch(input) + if("custom") + if(client?.prefs?.custom_holoform_icon) + holo_icon = client.prefs.get_filtered_holoform(HOLOFORM_FILTER_AI) + else + holo_icon = getHologramIcon(icon('icons/mob/ai.dmi', "female")) + else if("xeno queen") + holo_icon = getHologramIcon(icon(icon_list[input],"alienq")) + else + holo_icon = getHologramIcon(icon(icon_list[input], input)) + +/mob/living/silicon/ai/proc/corereturn() + set category = "Malfunction" + set name = "Return to Main Core" + + var/obj/machinery/power/apc/apc = src.loc + if(!istype(apc)) + to_chat(src, "You are already in your Main Core.") + return + apc.malfvacate() + +/mob/living/silicon/ai/proc/toggle_camera_light() + camera_light_on = !camera_light_on + + if (!camera_light_on) + to_chat(src, "Camera lights deactivated.") + + for (var/obj/machinery/camera/C in lit_cameras) + C.set_light(0) + lit_cameras = list() + + return + + light_cameras() + + to_chat(src, "Camera lights activated.") + +//AI_CAMERA_LUMINOSITY + +/mob/living/silicon/ai/proc/light_cameras() + var/list/obj/machinery/camera/add = list() + var/list/obj/machinery/camera/remove = list() + var/list/obj/machinery/camera/visible = list() + for (var/datum/camerachunk/CC in eyeobj.visibleCameraChunks) + for (var/obj/machinery/camera/C in CC.cameras) + if (!C.can_use() || get_dist(C, eyeobj) > 7 || !C.internal_light) + continue + visible |= C + + add = visible - lit_cameras + remove = lit_cameras - visible + + for (var/obj/machinery/camera/C in remove) + lit_cameras -= C //Removed from list before turning off the light so that it doesn't check the AI looking away. + C.Togglelight(0) + for (var/obj/machinery/camera/C in add) + C.Togglelight(1) + lit_cameras |= C + +/mob/living/silicon/ai/proc/control_integrated_radio() + set name = "Transceiver Settings" + set desc = "Allows you to change settings of your radio." + set category = "AI Commands" + + if(incapacitated()) + return + + to_chat(src, "Accessing Subspace Transceiver control...") + if (radio) + radio.interact(src) + +/mob/living/silicon/ai/proc/set_syndie_radio() + if(radio) + radio.make_syndie() + +/mob/living/silicon/ai/proc/set_automatic_say_channel() + set name = "Set Auto Announce Mode" + set desc = "Modify the default radio setting for your automatic announcements." + set category = "AI Commands" + + if(incapacitated()) + return + set_autosay() + +/mob/living/silicon/ai/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card) + if(!..()) + return + if(interaction == AI_TRANS_TO_CARD)//The only possible interaction. Upload AI mob to a card. + if(!can_be_carded) + to_chat(user, "Transfer failed.") + return + disconnect_shell() //If the AI is controlling a borg, force the player back to core! + if(!mind) + to_chat(user, "No intelligence patterns detected." ) + return + ShutOffDoomsdayDevice() + new /obj/structure/AIcore/deactivated(loc)//Spawns a deactivated terminal at AI location. + ai_restore_power()//So the AI initially has power. + control_disabled = 1//Can't control things remotely if you're stuck in a card! + radio_enabled = 0 //No talking on the built-in radio for you either! + forceMove(card) + card.AI = src + to_chat(src, "You have been downloaded to a mobile storage device. Remote device connection severed.") + to_chat(user, "Transfer successful: [name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.") + +/mob/living/silicon/ai/can_buckle() + return 0 + +/mob/living/silicon/ai/incapacitated(ignore_restraints, ignore_grab) + if(aiRestorePowerRoutine) + return TRUE + return ..() + +/mob/living/silicon/ai/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) + if(control_disabled || incapacitated()) + to_chat(src, "You can't do that right now!") + return FALSE + if(be_close && !in_range(M, src)) + to_chat(src, "You are too far away!") + return FALSE + return can_see(M) //stop AIs from leaving windows open and using then after they lose vision + +/mob/living/silicon/ai/proc/can_see(atom/A) + if(isturf(loc)) //AI in core, check if on cameras + //get_turf_pixel() is because APCs in maint aren't actually in view of the inner camera + //apc_override is needed here because AIs use their own APC when depowered + return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(get_turf_pixel(A))) || apc_override + //AI is carded/shunted + //view(src) returns nothing for carded/shunted AIs and they have X-ray vision so just use get_dist + var/list/viewscale = getviewsize(client.view) + return get_dist(src, A) <= max(viewscale[1]*0.5,viewscale[2]*0.5) + +/mob/living/silicon/ai/proc/relay_speech(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode) + raw_message = lang_treat(speaker, message_language, raw_message, spans, message_mode) + var/start = "Relayed Speech: " + var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]" + var/hrefpart = "" + var/jobpart + + if (iscarbon(speaker)) + var/mob/living/carbon/S = speaker + if(S.job) + jobpart = "[S.job]" + else + jobpart = "Unknown" + + var/rendered = "[start][hrefpart][namepart] ([jobpart])[raw_message]" + + show_message(rendered, MSG_AUDIBLE) + +/mob/living/silicon/ai/fully_replace_character_name(oldname,newname) + ..() + if(oldname != real_name) + if(eyeobj) + eyeobj.name = "[newname] (AI Eye)" + + // Notify Cyborgs + for(var/mob/living/silicon/robot/Slave in connected_robots) + Slave.show_laws() + +/mob/living/silicon/ai/replace_identification_name(oldname,newname) + if(aiPDA) + aiPDA.owner = newname + aiPDA.name = newname + " (" + aiPDA.ownjob + ")" + + +/mob/living/silicon/ai/proc/add_malf_picker() + to_chat(src, "In the top right corner of the screen you will find the Malfunctions tab, where you can purchase various abilities, from upgraded surveillance to station ending doomsday devices.") + to_chat(src, "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds.") + view_core() //A BYOND bug requires you to be viewing your core before your verbs update + verbs += /mob/living/silicon/ai/proc/choose_modules + malf_picker = new /datum/module_picker + + +/mob/living/silicon/ai/reset_perspective(atom/A) + if(camera_light_on) + light_cameras() + if(istype(A, /obj/machinery/camera)) + current = A + if(client) + if(ismovableatom(A)) + if(A != GLOB.ai_camera_room_landmark) + end_multicam() + client.perspective = EYE_PERSPECTIVE + client.eye = A + else + end_multicam() + if(isturf(loc)) + if(eyeobj) + client.eye = eyeobj + client.perspective = EYE_PERSPECTIVE + else + client.eye = client.mob + client.perspective = MOB_PERSPECTIVE + else + client.perspective = EYE_PERSPECTIVE + client.eye = loc + update_sight() + if(client.eye != src) + var/atom/AT = client.eye + AT.get_remote_view_fullscreens(src) + else + clear_fullscreen("remote_view", 0) + +/mob/living/silicon/ai/revive(full_heal = 0, admin_revive = 0) + . = ..() + if(.) //successfully ressuscitated from death + set_eyeobj_visible(TRUE) + set_core_display_icon(display_icon_override) + +/mob/living/silicon/ai/proc/malfhacked(obj/machinery/power/apc/apc) + malfhack = null + malfhacking = 0 + clear_alert("hackingapc") + + if(!istype(apc) || QDELETED(apc) || apc.stat & BROKEN) + to_chat(src, "Hack aborted. The designated APC no longer exists on the power network.") + playsound(get_turf(src), 'sound/machines/buzz-two.ogg', 50, 1) + else if(apc.aidisabled) + to_chat(src, "Hack aborted. \The [apc] is no longer responding to our systems.") + playsound(get_turf(src), 'sound/machines/buzz-sigh.ogg', 50, 1) + else + malf_picker.processing_time += 10 + + apc.malfai = parent || src + apc.malfhack = TRUE + apc.locked = TRUE + apc.coverlocked = TRUE + + playsound(get_turf(src), 'sound/machines/ding.ogg', 50, 1) + to_chat(src, "Hack complete. \The [apc] is now under your exclusive control.") + apc.update_icon() + +/mob/living/silicon/ai/verb/deploy_to_shell(var/mob/living/silicon/robot/target) + set category = "AI Commands" + set name = "Deploy to Shell" + + if(incapacitated()) + return + if(control_disabled) + to_chat(src, "Wireless networking module is offline.") + return + + var/list/possible = list() + + for(var/borgie in GLOB.available_ai_shells) + var/mob/living/silicon/robot/R = borgie + if(R.shell && !R.deployed && (R.stat != DEAD) && (!R.connected_ai ||(R.connected_ai == src))) + possible += R + + if(!LAZYLEN(possible)) + to_chat(src, "No usable AI shell beacons detected.") + + if(!target || !(target in possible)) //If the AI is looking for a new shell, or its pre-selected shell is no longer valid + target = input(src, "Which body to control?") as null|anything in possible + + if (!target || target.stat == DEAD || target.deployed || !(!target.connected_ai ||(target.connected_ai == src))) + return + + else if(mind) + soullink(/datum/soullink/sharedbody, src, target) + deployed_shell = target + target.deploy_init(src) + mind.transfer_to(target) + diag_hud_set_deployed() + +/datum/action/innate/deploy_shell + name = "Deploy to AI Shell" + desc = "Wirelessly control a specialized cyborg shell." + icon_icon = 'icons/mob/actions/actions_AI.dmi' + button_icon_state = "ai_shell" + +/datum/action/innate/deploy_shell/Trigger() + var/mob/living/silicon/ai/AI = owner + if(!AI) + return + AI.deploy_to_shell() + +/datum/action/innate/deploy_last_shell + name = "Reconnect to shell" + desc = "Reconnect to the most recently used AI shell." + icon_icon = 'icons/mob/actions/actions_AI.dmi' + button_icon_state = "ai_last_shell" + var/mob/living/silicon/robot/last_used_shell + +/datum/action/innate/deploy_last_shell/Trigger() + if(!owner) + return + if(last_used_shell) + var/mob/living/silicon/ai/AI = owner + AI.deploy_to_shell(last_used_shell) + else + Remove(owner) //If the last shell is blown, destroy it. + +/mob/living/silicon/ai/proc/disconnect_shell() + if(deployed_shell) //Forcibly call back AI in event of things such as damage, EMP or power loss. + to_chat(src, "Your remote connection has been reset!") + deployed_shell.undeploy() + diag_hud_set_deployed() + +/mob/living/silicon/ai/resist() + return + +/mob/living/silicon/ai/spawned/Initialize(mapload, datum/ai_laws/L, mob/target_ai) + . = ..() + if(!target_ai) + target_ai = src //cheat! just give... ourselves as the spawned AI, because that's technically correct + +/mob/living/silicon/ai/proc/camera_visibility(mob/camera/aiEye/moved_eye) + GLOB.cameranet.visibility(moved_eye, client, all_eyes, USE_STATIC_OPAQUE) + +/mob/living/silicon/ai/forceMove(atom/destination) + . = ..() + if(.) + end_multicam() diff --git a/code/modules/mob/living/silicon/ai/death.dm b/code/modules/mob/living/silicon/ai/death.dm index 2900c89967..6ff744e0da 100644 --- a/code/modules/mob/living/silicon/ai/death.dm +++ b/code/modules/mob/living/silicon/ai/death.dm @@ -1,56 +1,56 @@ -/mob/living/silicon/ai/death(gibbed) - if(stat == DEAD) - return - - . = ..() - - var/old_icon = icon_state - if("[icon_state]_dead" in icon_states(icon)) - icon_state = "[icon_state]_dead" - else - icon_state = "ai_dead" - if("[old_icon]_death_transition" in icon_states(icon)) - flick("[old_icon]_death_transition", src) - - cameraFollow = null - - anchored = FALSE //unbolt floorbolts - update_canmove() - if(eyeobj) - eyeobj.setLoc(get_turf(src)) - set_eyeobj_visible(FALSE) - - GLOB.shuttle_caller_list -= src - SSshuttle.autoEvac() - - ShutOffDoomsdayDevice() - - if(explosive) - spawn(10) - explosion(src.loc, 3, 6, 12, 15) - - if(src.key) - for(var/each in GLOB.ai_status_displays) //change status - var/obj/machinery/status_display/ai/O = each - O.mode = 2 - O.update() - - if(istype(loc, /obj/item/aicard/aitater)) - loc.icon_state = "aitater-404" - else if(istype(loc, /obj/item/aicard/aispook)) - loc.icon_state = "aispook-404" - else if(istype(loc, /obj/item/aicard)) - loc.icon_state = "aicard-404" - -/mob/living/silicon/ai/proc/ShutOffDoomsdayDevice() - if(nuking) - set_security_level("red") - nuking = FALSE - for(var/obj/item/pinpointer/nuke/P in GLOB.pinpointer_list) - P.switch_mode_to(TRACK_NUKE_DISK) //Party's over, back to work, everyone - P.alert = FALSE - - if(doomsday_device) - doomsday_device.timing = FALSE - SSshuttle.clearHostileEnvironment(doomsday_device) - qdel(doomsday_device) +/mob/living/silicon/ai/death(gibbed) + if(stat == DEAD) + return + + . = ..() + + var/old_icon = icon_state + if("[icon_state]_dead" in icon_states(icon)) + icon_state = "[icon_state]_dead" + else + icon_state = "ai_dead" + if("[old_icon]_death_transition" in icon_states(icon)) + flick("[old_icon]_death_transition", src) + + cameraFollow = null + + anchored = FALSE //unbolt floorbolts + update_canmove() + if(eyeobj) + eyeobj.setLoc(get_turf(src)) + set_eyeobj_visible(FALSE) + + GLOB.shuttle_caller_list -= src + SSshuttle.autoEvac() + + ShutOffDoomsdayDevice() + + if(explosive) + spawn(10) + explosion(src.loc, 3, 6, 12, 15) + + if(src.key) + for(var/each in GLOB.ai_status_displays) //change status + var/obj/machinery/status_display/ai/O = each + O.mode = 2 + O.update() + + if(istype(loc, /obj/item/aicard/aitater)) + loc.icon_state = "aitater-404" + else if(istype(loc, /obj/item/aicard/aispook)) + loc.icon_state = "aispook-404" + else if(istype(loc, /obj/item/aicard)) + loc.icon_state = "aicard-404" + +/mob/living/silicon/ai/proc/ShutOffDoomsdayDevice() + if(nuking) + set_security_level("red") + nuking = FALSE + for(var/obj/item/pinpointer/nuke/P in GLOB.pinpointer_list) + P.switch_mode_to(TRACK_NUKE_DISK) //Party's over, back to work, everyone + P.alert = FALSE + + if(doomsday_device) + doomsday_device.timing = FALSE + SSshuttle.clearHostileEnvironment(doomsday_device) + qdel(doomsday_device) diff --git a/code/modules/mob/living/silicon/ai/examine.dm b/code/modules/mob/living/silicon/ai/examine.dm index 10b88ae14a..1d0637335c 100644 --- a/code/modules/mob/living/silicon/ai/examine.dm +++ b/code/modules/mob/living/silicon/ai/examine.dm @@ -1,22 +1,22 @@ -/mob/living/silicon/ai/examine(mob/user) - . = list("*---------*\nThis is [icon2html(src, user)] [src]!") - if (stat == DEAD) - . += "It appears to be powered-down." - else - if (getBruteLoss()) - if (getBruteLoss() < 30) - . += "It looks slightly dented." - else - . += "It looks severely dented!" - if (getFireLoss()) - if (getFireLoss() < 30) - . += "It looks slightly charred." - else - . += "Its casing is melted and heat-warped!" - if(deployed_shell) - . += "The wireless networking light is blinking." - else if (!shunted && !client) - . += "[src]Core.exe has stopped responding! NTOS is searching for a solution to the problem..." - . += "*---------*" - +/mob/living/silicon/ai/examine(mob/user) + . = list("*---------*\nThis is [icon2html(src, user)] [src]!") + if (stat == DEAD) + . += "It appears to be powered-down." + else + if (getBruteLoss()) + if (getBruteLoss() < 30) + . += "It looks slightly dented." + else + . += "It looks severely dented!" + if (getFireLoss()) + if (getFireLoss() < 30) + . += "It looks slightly charred." + else + . += "Its casing is melted and heat-warped!" + if(deployed_shell) + . += "The wireless networking light is blinking." + else if (!shunted && !client) + . += "[src]Core.exe has stopped responding! NTOS is searching for a solution to the problem..." + . += "*---------*" + . += ..() \ No newline at end of file diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index dfe76fe948..a3ffd460dd 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -1,204 +1,204 @@ -// CAMERA NET -// -// The datum containing all the chunks. - -#define CHUNK_SIZE 16 // Only chunk sizes that are to the power of 2. E.g: 2, 4, 8, 16, etc.. - -GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) - -/datum/cameranet - var/name = "Camera Net" // Name to show for VV and stat() - - // The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Del(). - var/list/cameras = list() - // The chunks of the map, mapping the areas that the cameras can see. - var/list/chunks = list() - var/ready = 0 - - // The object used for the clickable stat() button. - var/obj/effect/statclick/statclick - - // The objects used in vis_contents of obscured turfs - var/list/vis_contents_objects - var/obj/effect/overlay/camera_static/vis_contents_opaque - var/obj/effect/overlay/camera_static/vis_contents_transparent - // The image given to the effect in vis_contents on AI clients - var/image/obscured - var/image/obscured_transparent - -/datum/cameranet/New() - vis_contents_opaque = new /obj/effect/overlay/camera_static() - vis_contents_transparent = new /obj/effect/overlay/camera_static/transparent() - vis_contents_objects = list(vis_contents_opaque, vis_contents_transparent) - - obscured = new('icons/effects/cameravis.dmi', vis_contents_opaque, null, CAMERA_STATIC_LAYER) - obscured.plane = CAMERA_STATIC_PLANE - - obscured_transparent = new('icons/effects/cameravis.dmi', vis_contents_transparent, null, CAMERA_STATIC_LAYER) - obscured_transparent.plane = CAMERA_STATIC_PLANE - -// Checks if a chunk has been Generated in x, y, z. -/datum/cameranet/proc/chunkGenerated(x, y, z) - x &= ~(CHUNK_SIZE - 1) - y &= ~(CHUNK_SIZE - 1) - return chunks["[x],[y],[z]"] - -// Returns the chunk in the x, y, z. -// If there is no chunk, it creates a new chunk and returns that. -/datum/cameranet/proc/getCameraChunk(x, y, z) - x &= ~(CHUNK_SIZE - 1) - y &= ~(CHUNK_SIZE - 1) - var/key = "[x],[y],[z]" - . = chunks[key] - if(!.) - chunks[key] = . = new /datum/camerachunk(x, y, z) - -// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set. - -/datum/cameranet/proc/visibility(list/moved_eyes, client/C, list/other_eyes, use_static = USE_STATIC_OPAQUE) - if(!islist(moved_eyes)) - moved_eyes = moved_eyes ? list(moved_eyes) : list() - if(islist(other_eyes)) - other_eyes = (other_eyes - moved_eyes) - else - other_eyes = list() - - if(C) - switch(use_static) - if(USE_STATIC_TRANSPARENT) - C.images += obscured_transparent - if(USE_STATIC_OPAQUE) - C.images += obscured - - for(var/V in moved_eyes) - var/mob/camera/aiEye/eye = V - var/list/visibleChunks = list() - if(eye.loc) - // 0xf = 15 - var/static_range = eye.static_visibility_range - var/x1 = max(0, eye.x - static_range) & ~(CHUNK_SIZE - 1) - var/y1 = max(0, eye.y - static_range) & ~(CHUNK_SIZE - 1) - var/x2 = min(world.maxx, eye.x + static_range) & ~(CHUNK_SIZE - 1) - var/y2 = min(world.maxy, eye.y + static_range) & ~(CHUNK_SIZE - 1) - - - for(var/x = x1; x <= x2; x += CHUNK_SIZE) - for(var/y = y1; y <= y2; y += CHUNK_SIZE) - visibleChunks |= getCameraChunk(x, y, eye.z) - - var/list/remove = eye.visibleCameraChunks - visibleChunks - var/list/add = visibleChunks - eye.visibleCameraChunks - - for(var/chunk in remove) - var/datum/camerachunk/c = chunk - c.remove(eye, FALSE) - - for(var/chunk in add) - var/datum/camerachunk/c = chunk - c.add(eye) - - if(!eye.visibleCameraChunks.len) - var/client/client = eye.GetViewerClient() - if(client) - switch(eye.use_static) - if(USE_STATIC_TRANSPARENT) - client.images -= GLOB.cameranet.obscured_transparent - if(USE_STATIC_OPAQUE) - client.images -= GLOB.cameranet.obscured - -// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open. - -/datum/cameranet/proc/updateVisibility(atom/A, opacity_check = 1) - if(!SSticker || (opacity_check && !A.opacity)) - return - majorChunkChange(A, 2) - -/datum/cameranet/proc/updateChunk(x, y, z) - var/datum/camerachunk/chunk = chunkGenerated(x, y, z) - if (!chunk) - return - chunk.hasChanged() - -// Removes a camera from a chunk. - -/datum/cameranet/proc/removeCamera(obj/machinery/camera/c) - majorChunkChange(c, 0) - -// Add a camera to a chunk. - -/datum/cameranet/proc/addCamera(obj/machinery/camera/c) - if(c.can_use()) - majorChunkChange(c, 1) - -// Used for Cyborg cameras. Since portable cameras can be in ANY chunk. - -/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c) - if(c.can_use()) - majorChunkChange(c, 1) - -// Never access this proc directly!!!! -// This will update the chunk and all the surrounding chunks. -// It will also add the atom to the cameras list if you set the choice to 1. -// Setting the choice to 0 will remove the camera from the chunks. -// If you want to update the chunks around an object, without adding/removing a camera, use choice 2. - -/datum/cameranet/proc/majorChunkChange(atom/c, choice) - if(!c) - return - - var/turf/T = get_turf(c) - if(T) - var/x1 = max(0, T.x - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) - var/y1 = max(0, T.y - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) - var/x2 = min(world.maxx, T.x + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) - var/y2 = min(world.maxy, T.y + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) - for(var/x = x1; x <= x2; x += CHUNK_SIZE) - for(var/y = y1; y <= y2; y += CHUNK_SIZE) - var/datum/camerachunk/chunk = chunkGenerated(x, y, T.z) - if(chunk) - if(choice == 0) - // Remove the camera. - chunk.cameras -= c - else if(choice == 1) - // You can't have the same camera in the list twice. - chunk.cameras |= c - chunk.hasChanged() - -// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0. - -/datum/cameranet/proc/checkCameraVis(mob/living/target) - var/turf/position = get_turf(target) - return checkTurfVis(position) - - -/datum/cameranet/proc/checkTurfVis(turf/position) - var/datum/camerachunk/chunk = chunkGenerated(position.x, position.y, position.z) - if(chunk) - if(chunk.changed) - chunk.hasChanged(1) // Update now, no matter if it's visible or not. - if(chunk.visibleTurfs[position]) - return 1 - return 0 - -/datum/cameranet/proc/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - stat(name, statclick.update("Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]")) - -/obj/effect/overlay/camera_static - name = "static" - icon = null - icon_state = null - anchored = TRUE // should only appear in vis_contents, but to be safe - appearance_flags = RESET_TRANSFORM | TILE_BOUND - // this combination makes the static block clicks to everything below it, - // without appearing in the right-click menu for non-AI clients - mouse_opacity = MOUSE_OPACITY_ICON - invisibility = INVISIBILITY_ABSTRACT - - layer = CAMERA_STATIC_LAYER - plane = CAMERA_STATIC_PLANE - -/obj/effect/overlay/camera_static/transparent - mouse_opacity = MOUSE_OPACITY_TRANSPARENT +// CAMERA NET +// +// The datum containing all the chunks. + +#define CHUNK_SIZE 16 // Only chunk sizes that are to the power of 2. E.g: 2, 4, 8, 16, etc.. + +GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) + +/datum/cameranet + var/name = "Camera Net" // Name to show for VV and stat() + + // The cameras on the map, no matter if they work or not. Updated in obj/machinery/camera.dm by New() and Del(). + var/list/cameras = list() + // The chunks of the map, mapping the areas that the cameras can see. + var/list/chunks = list() + var/ready = 0 + + // The object used for the clickable stat() button. + var/obj/effect/statclick/statclick + + // The objects used in vis_contents of obscured turfs + var/list/vis_contents_objects + var/obj/effect/overlay/camera_static/vis_contents_opaque + var/obj/effect/overlay/camera_static/vis_contents_transparent + // The image given to the effect in vis_contents on AI clients + var/image/obscured + var/image/obscured_transparent + +/datum/cameranet/New() + vis_contents_opaque = new /obj/effect/overlay/camera_static() + vis_contents_transparent = new /obj/effect/overlay/camera_static/transparent() + vis_contents_objects = list(vis_contents_opaque, vis_contents_transparent) + + obscured = new('icons/effects/cameravis.dmi', vis_contents_opaque, null, CAMERA_STATIC_LAYER) + obscured.plane = CAMERA_STATIC_PLANE + + obscured_transparent = new('icons/effects/cameravis.dmi', vis_contents_transparent, null, CAMERA_STATIC_LAYER) + obscured_transparent.plane = CAMERA_STATIC_PLANE + +// Checks if a chunk has been Generated in x, y, z. +/datum/cameranet/proc/chunkGenerated(x, y, z) + x &= ~(CHUNK_SIZE - 1) + y &= ~(CHUNK_SIZE - 1) + return chunks["[x],[y],[z]"] + +// Returns the chunk in the x, y, z. +// If there is no chunk, it creates a new chunk and returns that. +/datum/cameranet/proc/getCameraChunk(x, y, z) + x &= ~(CHUNK_SIZE - 1) + y &= ~(CHUNK_SIZE - 1) + var/key = "[x],[y],[z]" + . = chunks[key] + if(!.) + chunks[key] = . = new /datum/camerachunk(x, y, z) + +// Updates what the aiEye can see. It is recommended you use this when the aiEye moves or it's location is set. + +/datum/cameranet/proc/visibility(list/moved_eyes, client/C, list/other_eyes, use_static = USE_STATIC_OPAQUE) + if(!islist(moved_eyes)) + moved_eyes = moved_eyes ? list(moved_eyes) : list() + if(islist(other_eyes)) + other_eyes = (other_eyes - moved_eyes) + else + other_eyes = list() + + if(C) + switch(use_static) + if(USE_STATIC_TRANSPARENT) + C.images += obscured_transparent + if(USE_STATIC_OPAQUE) + C.images += obscured + + for(var/V in moved_eyes) + var/mob/camera/aiEye/eye = V + var/list/visibleChunks = list() + if(eye.loc) + // 0xf = 15 + var/static_range = eye.static_visibility_range + var/x1 = max(0, eye.x - static_range) & ~(CHUNK_SIZE - 1) + var/y1 = max(0, eye.y - static_range) & ~(CHUNK_SIZE - 1) + var/x2 = min(world.maxx, eye.x + static_range) & ~(CHUNK_SIZE - 1) + var/y2 = min(world.maxy, eye.y + static_range) & ~(CHUNK_SIZE - 1) + + + for(var/x = x1; x <= x2; x += CHUNK_SIZE) + for(var/y = y1; y <= y2; y += CHUNK_SIZE) + visibleChunks |= getCameraChunk(x, y, eye.z) + + var/list/remove = eye.visibleCameraChunks - visibleChunks + var/list/add = visibleChunks - eye.visibleCameraChunks + + for(var/chunk in remove) + var/datum/camerachunk/c = chunk + c.remove(eye, FALSE) + + for(var/chunk in add) + var/datum/camerachunk/c = chunk + c.add(eye) + + if(!eye.visibleCameraChunks.len) + var/client/client = eye.GetViewerClient() + if(client) + switch(eye.use_static) + if(USE_STATIC_TRANSPARENT) + client.images -= GLOB.cameranet.obscured_transparent + if(USE_STATIC_OPAQUE) + client.images -= GLOB.cameranet.obscured + +// Updates the chunks that the turf is located in. Use this when obstacles are destroyed or when doors open. + +/datum/cameranet/proc/updateVisibility(atom/A, opacity_check = 1) + if(!SSticker || (opacity_check && !A.opacity)) + return + majorChunkChange(A, 2) + +/datum/cameranet/proc/updateChunk(x, y, z) + var/datum/camerachunk/chunk = chunkGenerated(x, y, z) + if (!chunk) + return + chunk.hasChanged() + +// Removes a camera from a chunk. + +/datum/cameranet/proc/removeCamera(obj/machinery/camera/c) + majorChunkChange(c, 0) + +// Add a camera to a chunk. + +/datum/cameranet/proc/addCamera(obj/machinery/camera/c) + if(c.can_use()) + majorChunkChange(c, 1) + +// Used for Cyborg cameras. Since portable cameras can be in ANY chunk. + +/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c) + if(c.can_use()) + majorChunkChange(c, 1) + +// Never access this proc directly!!!! +// This will update the chunk and all the surrounding chunks. +// It will also add the atom to the cameras list if you set the choice to 1. +// Setting the choice to 0 will remove the camera from the chunks. +// If you want to update the chunks around an object, without adding/removing a camera, use choice 2. + +/datum/cameranet/proc/majorChunkChange(atom/c, choice) + if(!c) + return + + var/turf/T = get_turf(c) + if(T) + var/x1 = max(0, T.x - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) + var/y1 = max(0, T.y - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) + var/x2 = min(world.maxx, T.x + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) + var/y2 = min(world.maxy, T.y + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) + for(var/x = x1; x <= x2; x += CHUNK_SIZE) + for(var/y = y1; y <= y2; y += CHUNK_SIZE) + var/datum/camerachunk/chunk = chunkGenerated(x, y, T.z) + if(chunk) + if(choice == 0) + // Remove the camera. + chunk.cameras -= c + else if(choice == 1) + // You can't have the same camera in the list twice. + chunk.cameras |= c + chunk.hasChanged() + +// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0. + +/datum/cameranet/proc/checkCameraVis(mob/living/target) + var/turf/position = get_turf(target) + return checkTurfVis(position) + + +/datum/cameranet/proc/checkTurfVis(turf/position) + var/datum/camerachunk/chunk = chunkGenerated(position.x, position.y, position.z) + if(chunk) + if(chunk.changed) + chunk.hasChanged(1) // Update now, no matter if it's visible or not. + if(chunk.visibleTurfs[position]) + return 1 + return 0 + +/datum/cameranet/proc/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) + + stat(name, statclick.update("Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]")) + +/obj/effect/overlay/camera_static + name = "static" + icon = null + icon_state = null + anchored = TRUE // should only appear in vis_contents, but to be safe + appearance_flags = RESET_TRANSFORM | TILE_BOUND + // this combination makes the static block clicks to everything below it, + // without appearing in the right-click menu for non-AI clients + mouse_opacity = MOUSE_OPACITY_ICON + invisibility = INVISIBILITY_ABSTRACT + + layer = CAMERA_STATIC_LAYER + plane = CAMERA_STATIC_PLANE + +/obj/effect/overlay/camera_static/transparent + mouse_opacity = MOUSE_OPACITY_TRANSPARENT diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm index c2794394ed..4591720b79 100644 --- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm +++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm @@ -1,142 +1,142 @@ -#define UPDATE_BUFFER 25 // 2.5 seconds - -// CAMERA CHUNK -// -// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed. -// Allows the AI Eye to stream these chunks and know what it can and cannot see. - -/datum/camerachunk - var/list/obscuredTurfs = list() - var/list/visibleTurfs = list() - var/list/cameras = list() - var/list/turfs = list() - var/list/seenby = list() - var/changed = 0 - var/x = 0 - var/y = 0 - var/z = 0 - -// Add an AI eye to the chunk, then update if changed. - -/datum/camerachunk/proc/add(mob/camera/aiEye/eye) - eye.visibleCameraChunks += src - seenby += eye - if(changed) - update() - -// Remove an AI eye from the chunk, then update if changed. - -/datum/camerachunk/proc/remove(mob/camera/aiEye/eye, remove_static_with_last_chunk = TRUE) - eye.visibleCameraChunks -= src - seenby -= eye - if(remove_static_with_last_chunk && !eye.visibleCameraChunks.len) - var/client/client = eye.GetViewerClient() - if(client) - switch(eye.use_static) - if(USE_STATIC_TRANSPARENT) - client.images -= GLOB.cameranet.obscured_transparent - if(USE_STATIC_OPAQUE) - client.images -= GLOB.cameranet.obscured - -// Called when a chunk has changed. I.E: A wall was deleted. - -/datum/camerachunk/proc/visibilityChanged(turf/loc) - if(!visibleTurfs[loc]) - return - hasChanged() - -// Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will -// instead be flagged to update the next time an AI Eye moves near it. - -/datum/camerachunk/proc/hasChanged(update_now = 0) - if(seenby.len || update_now) - addtimer(CALLBACK(src, .proc/update), UPDATE_BUFFER, TIMER_UNIQUE) - else - changed = 1 - -// The actual updating. It gathers the visible turfs from cameras and puts them into the appropiate lists. - -/datum/camerachunk/proc/update() - var/list/newVisibleTurfs = list() - - for(var/camera in cameras) - var/obj/machinery/camera/c = camera - - if(!c) - continue - - if(!c.can_use()) - continue - - var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), src.z) - if(get_dist(point, c) > CHUNK_SIZE + (CHUNK_SIZE / 2)) - continue - - for(var/turf/t in c.can_see()) - // Possible optimization: if(turfs[t]) here, rather than &= turfs afterwards. - // List associations use a tree or hashmap of some sort (alongside the list itself) - // so are surprisingly fast. (significantly faster than var/thingy/x in list, in testing) - newVisibleTurfs[t] = t - - // Removes turf that isn't in turfs. - newVisibleTurfs &= turfs - - var/list/visAdded = newVisibleTurfs - visibleTurfs - var/list/visRemoved = visibleTurfs - newVisibleTurfs - - visibleTurfs = newVisibleTurfs - obscuredTurfs = turfs - newVisibleTurfs - - for(var/turf in visAdded) - var/turf/t = turf - t.vis_contents -= GLOB.cameranet.vis_contents_objects - - for(var/turf in visRemoved) - var/turf/t = turf - if(obscuredTurfs[t] && !istype(t, /turf/open/ai_visible)) - t.vis_contents += GLOB.cameranet.vis_contents_objects - - changed = 0 - -// Create a new camera chunk, since the chunks are made as they are needed. - -/datum/camerachunk/New(x, y, z) - x &= ~(CHUNK_SIZE - 1) - y &= ~(CHUNK_SIZE - 1) - - src.x = x - src.y = y - src.z = z - - for(var/obj/machinery/camera/c in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z))) - if(c.can_use()) - cameras += c - - for(var/turf/t in block(locate(max(x, 1), max(y, 1), z), locate(min(x + CHUNK_SIZE - 1, world.maxx), min(y + CHUNK_SIZE - 1, world.maxy), z))) - turfs[t] = t - - for(var/camera in cameras) - var/obj/machinery/camera/c = camera - if(!c) - continue - - if(!c.can_use()) - continue - - for(var/turf/t in c.can_see()) - // Possible optimization: if(turfs[t]) here, rather than &= turfs afterwards. - // List associations use a tree or hashmap of some sort (alongside the list itself) - // so are surprisingly fast. (significantly faster than var/thingy/x in list, in testing) - visibleTurfs[t] = t - - // Removes turf that isn't in turfs. - visibleTurfs &= turfs - - obscuredTurfs = turfs - visibleTurfs - - for(var/turf in obscuredTurfs) - var/turf/t = turf - t.vis_contents += GLOB.cameranet.vis_contents_objects - -#undef UPDATE_BUFFER -#undef CHUNK_SIZE +#define UPDATE_BUFFER 25 // 2.5 seconds + +// CAMERA CHUNK +// +// A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed. +// Allows the AI Eye to stream these chunks and know what it can and cannot see. + +/datum/camerachunk + var/list/obscuredTurfs = list() + var/list/visibleTurfs = list() + var/list/cameras = list() + var/list/turfs = list() + var/list/seenby = list() + var/changed = 0 + var/x = 0 + var/y = 0 + var/z = 0 + +// Add an AI eye to the chunk, then update if changed. + +/datum/camerachunk/proc/add(mob/camera/aiEye/eye) + eye.visibleCameraChunks += src + seenby += eye + if(changed) + update() + +// Remove an AI eye from the chunk, then update if changed. + +/datum/camerachunk/proc/remove(mob/camera/aiEye/eye, remove_static_with_last_chunk = TRUE) + eye.visibleCameraChunks -= src + seenby -= eye + if(remove_static_with_last_chunk && !eye.visibleCameraChunks.len) + var/client/client = eye.GetViewerClient() + if(client) + switch(eye.use_static) + if(USE_STATIC_TRANSPARENT) + client.images -= GLOB.cameranet.obscured_transparent + if(USE_STATIC_OPAQUE) + client.images -= GLOB.cameranet.obscured + +// Called when a chunk has changed. I.E: A wall was deleted. + +/datum/camerachunk/proc/visibilityChanged(turf/loc) + if(!visibleTurfs[loc]) + return + hasChanged() + +// Updates the chunk, makes sure that it doesn't update too much. If the chunk isn't being watched it will +// instead be flagged to update the next time an AI Eye moves near it. + +/datum/camerachunk/proc/hasChanged(update_now = 0) + if(seenby.len || update_now) + addtimer(CALLBACK(src, .proc/update), UPDATE_BUFFER, TIMER_UNIQUE) + else + changed = 1 + +// The actual updating. It gathers the visible turfs from cameras and puts them into the appropiate lists. + +/datum/camerachunk/proc/update() + var/list/newVisibleTurfs = list() + + for(var/camera in cameras) + var/obj/machinery/camera/c = camera + + if(!c) + continue + + if(!c.can_use()) + continue + + var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), src.z) + if(get_dist(point, c) > CHUNK_SIZE + (CHUNK_SIZE / 2)) + continue + + for(var/turf/t in c.can_see()) + // Possible optimization: if(turfs[t]) here, rather than &= turfs afterwards. + // List associations use a tree or hashmap of some sort (alongside the list itself) + // so are surprisingly fast. (significantly faster than var/thingy/x in list, in testing) + newVisibleTurfs[t] = t + + // Removes turf that isn't in turfs. + newVisibleTurfs &= turfs + + var/list/visAdded = newVisibleTurfs - visibleTurfs + var/list/visRemoved = visibleTurfs - newVisibleTurfs + + visibleTurfs = newVisibleTurfs + obscuredTurfs = turfs - newVisibleTurfs + + for(var/turf in visAdded) + var/turf/t = turf + t.vis_contents -= GLOB.cameranet.vis_contents_objects + + for(var/turf in visRemoved) + var/turf/t = turf + if(obscuredTurfs[t] && !istype(t, /turf/open/ai_visible)) + t.vis_contents += GLOB.cameranet.vis_contents_objects + + changed = 0 + +// Create a new camera chunk, since the chunks are made as they are needed. + +/datum/camerachunk/New(x, y, z) + x &= ~(CHUNK_SIZE - 1) + y &= ~(CHUNK_SIZE - 1) + + src.x = x + src.y = y + src.z = z + + for(var/obj/machinery/camera/c in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z))) + if(c.can_use()) + cameras += c + + for(var/turf/t in block(locate(max(x, 1), max(y, 1), z), locate(min(x + CHUNK_SIZE - 1, world.maxx), min(y + CHUNK_SIZE - 1, world.maxy), z))) + turfs[t] = t + + for(var/camera in cameras) + var/obj/machinery/camera/c = camera + if(!c) + continue + + if(!c.can_use()) + continue + + for(var/turf/t in c.can_see()) + // Possible optimization: if(turfs[t]) here, rather than &= turfs afterwards. + // List associations use a tree or hashmap of some sort (alongside the list itself) + // so are surprisingly fast. (significantly faster than var/thingy/x in list, in testing) + visibleTurfs[t] = t + + // Removes turf that isn't in turfs. + visibleTurfs &= turfs + + obscuredTurfs = turfs - visibleTurfs + + for(var/turf in obscuredTurfs) + var/turf/t = turf + t.vis_contents += GLOB.cameranet.vis_contents_objects + +#undef UPDATE_BUFFER +#undef CHUNK_SIZE diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index 38afaf8313..8ec39095fb 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -1,206 +1,206 @@ -// AI EYE -// -// An invisible (no icon) mob that the AI controls to look around the station with. -// It streams chunks as it moves around, which will show it what the AI can and cannot see. - -/mob/camera/aiEye - name = "Inactive AI Eye" - - icon_state = "ai_camera" - icon = 'icons/mob/cameramob.dmi' - invisibility = INVISIBILITY_MAXIMUM - hud_possible = list(ANTAG_HUD, AI_DETECT_HUD = HUD_LIST_LIST) - var/list/visibleCameraChunks = list() - var/mob/living/silicon/ai/ai = null - var/relay_speech = FALSE - var/use_static = USE_STATIC_OPAQUE - var/static_visibility_range = 16 - var/ai_detector_visible = TRUE - var/ai_detector_color = COLOR_RED - -/mob/camera/aiEye/Initialize() - . = ..() - GLOB.aiEyes += src - update_ai_detect_hud() - setLoc(loc, TRUE) - -/mob/camera/aiEye/proc/update_ai_detect_hud() - var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] - var/list/old_images = hud_list[AI_DETECT_HUD] - if(!ai_detector_visible) - hud.remove_from_hud(src) - QDEL_LIST(old_images) - return - - if(!hud.hudusers.len) - //no one is watching, do not bother updating anything - return - hud.remove_from_hud(src) - - var/static/list/vis_contents_objects = list() - var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_objects[ai_detector_color] - if(!hud_obj) - hud_obj = new /obj/effect/overlay/ai_detect_hud() - hud_obj.color = ai_detector_color - vis_contents_objects[ai_detector_color] = hud_obj - - var/list/new_images = list() - var/list/turfs = get_visible_turfs() - for(var/T in turfs) - var/image/I = (old_images.len > new_images.len) ? old_images[new_images.len + 1] : image(null, T) - I.loc = T - I.vis_contents += hud_obj - new_images += I - for(var/i in (new_images.len + 1) to old_images.len) - qdel(old_images[i]) - hud_list[AI_DETECT_HUD] = new_images - hud.add_to_hud(src) - -/mob/camera/aiEye/proc/get_visible_turfs() - if(!isturf(loc)) - return list() - var/client/C = GetViewerClient() - var/view = C ? getviewsize(C.view) : getviewsize(world.view) - var/turf/lowerleft = locate(max(1, x - (view[1] - 1)/2), max(1, y - (view[2] - 1)/2), z) - var/turf/upperright = locate(min(world.maxx, lowerleft.x + (view[1] - 1)), min(world.maxy, lowerleft.y + (view[2] - 1)), lowerleft.z) - return block(lowerleft, upperright) - -// Use this when setting the aiEye's location. -// It will also stream the chunk that the new loc is in. - -/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE, dir) - if(ai) - if(!isturf(ai.loc)) - return - T = get_turf(T) - if(!force_update && (T == get_turf(src)) ) - return //we are already here! - if (T) - forceMove(T) - else - moveToNullspace() - if(use_static != USE_STATIC_NONE) - ai.camera_visibility(src) - if(ai.client && !ai.multicam_on) - ai.client.eye = src - update_ai_detect_hud() - update_parallax_contents() - //Holopad - if(istype(ai.current, /obj/machinery/holopad)) - var/obj/machinery/holopad/H = ai.current - H.move_hologram(ai, T, dir) - if(ai.camera_light_on) - ai.light_cameras() - if(ai.master_multicam) - ai.master_multicam.refresh_view() - -/mob/camera/aiEye/Move() - return 0 - -/mob/camera/aiEye/proc/GetViewerClient() - if(ai) - return ai.client - return null - -/mob/camera/aiEye/Destroy() - if(ai) - ai.all_eyes -= src - ai = null - for(var/V in visibleCameraChunks) - var/datum/camerachunk/c = V - c.remove(src) - GLOB.aiEyes -= src - if(ai_detector_visible) - var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] - hud.remove_from_hud(src) - var/list/L = hud_list[AI_DETECT_HUD] - QDEL_LIST(L) - return ..() - -/atom/proc/move_camera_by_click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - if(AI.eyeobj && (AI.multicam_on || (AI.client.eye == AI.eyeobj)) && (AI.eyeobj.z == z)) - AI.cameraFollow = null - if (isturf(loc) || isturf(src)) - AI.eyeobj.setLoc(src) - -// This will move the AIEye. It will also cause lights near the eye to light up, if toggled. -// This is handled in the proc below this one. - -/client/proc/AIMove(n, direct, mob/living/silicon/ai/user) - - var/initial = initial(user.sprint) - var/max_sprint = 50 - - if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds - user.sprint = initial - - for(var/i = 0; i < max(user.sprint, initial); i += 20) - var/turf/step = get_turf(get_step(user.eyeobj, direct)) - if(step) - user.eyeobj.setLoc(step, null, direct) - - user.cooldown = world.timeofday + 5 - if(user.acceleration) - user.sprint = min(user.sprint + 0.5, max_sprint) - else - user.sprint = initial - - if(!user.tracking) - user.cameraFollow = null - -// Return to the Core. -/mob/living/silicon/ai/proc/view_core() - if(istype(current,/obj/machinery/holopad)) - var/obj/machinery/holopad/H = current - H.clear_holo(src) - else - current = null - cameraFollow = null - unset_machine() - - if(isturf(loc) && (QDELETED(eyeobj) || !eyeobj.loc)) - to_chat(src, "ERROR: Eyeobj not found. Creating new eye...") - create_eye() - - eyeobj?.setLoc(loc) - -/mob/living/silicon/ai/proc/create_eye() - if(eyeobj) - return - eyeobj = new /mob/camera/aiEye() - all_eyes += eyeobj - eyeobj.ai = src - eyeobj.setLoc(loc) - eyeobj.name = "[name] (AI Eye)" - set_eyeobj_visible(TRUE) - -/mob/living/silicon/ai/proc/set_eyeobj_visible(state = TRUE) - if(!eyeobj) - return - eyeobj.mouse_opacity = state ? MOUSE_OPACITY_ICON : initial(eyeobj.mouse_opacity) - eyeobj.invisibility = state ? INVISIBILITY_OBSERVER : initial(eyeobj.invisibility) - -/mob/living/silicon/ai/verb/toggle_acceleration() - set category = "AI Commands" - set name = "Toggle Camera Acceleration" - - if(incapacitated()) - return - acceleration = !acceleration - to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].") - -/mob/camera/aiEye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) - . = ..() - if(relay_speech && speaker && ai && !radio_freq && speaker != ai && near_camera(speaker)) - ai.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) - -/obj/effect/overlay/ai_detect_hud - name = "" - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - icon = 'icons/effects/alphacolors.dmi' - icon_state = "" - alpha = 100 - layer = ABOVE_ALL_MOB_LAYER - plane = GAME_PLANE +// AI EYE +// +// An invisible (no icon) mob that the AI controls to look around the station with. +// It streams chunks as it moves around, which will show it what the AI can and cannot see. + +/mob/camera/aiEye + name = "Inactive AI Eye" + + icon_state = "ai_camera" + icon = 'icons/mob/cameramob.dmi' + invisibility = INVISIBILITY_MAXIMUM + hud_possible = list(ANTAG_HUD, AI_DETECT_HUD = HUD_LIST_LIST) + var/list/visibleCameraChunks = list() + var/mob/living/silicon/ai/ai = null + var/relay_speech = FALSE + var/use_static = USE_STATIC_OPAQUE + var/static_visibility_range = 16 + var/ai_detector_visible = TRUE + var/ai_detector_color = COLOR_RED + +/mob/camera/aiEye/Initialize() + . = ..() + GLOB.aiEyes += src + update_ai_detect_hud() + setLoc(loc, TRUE) + +/mob/camera/aiEye/proc/update_ai_detect_hud() + var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] + var/list/old_images = hud_list[AI_DETECT_HUD] + if(!ai_detector_visible) + hud.remove_from_hud(src) + QDEL_LIST(old_images) + return + + if(!hud.hudusers.len) + //no one is watching, do not bother updating anything + return + hud.remove_from_hud(src) + + var/static/list/vis_contents_objects = list() + var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_objects[ai_detector_color] + if(!hud_obj) + hud_obj = new /obj/effect/overlay/ai_detect_hud() + hud_obj.color = ai_detector_color + vis_contents_objects[ai_detector_color] = hud_obj + + var/list/new_images = list() + var/list/turfs = get_visible_turfs() + for(var/T in turfs) + var/image/I = (old_images.len > new_images.len) ? old_images[new_images.len + 1] : image(null, T) + I.loc = T + I.vis_contents += hud_obj + new_images += I + for(var/i in (new_images.len + 1) to old_images.len) + qdel(old_images[i]) + hud_list[AI_DETECT_HUD] = new_images + hud.add_to_hud(src) + +/mob/camera/aiEye/proc/get_visible_turfs() + if(!isturf(loc)) + return list() + var/client/C = GetViewerClient() + var/view = C ? getviewsize(C.view) : getviewsize(world.view) + var/turf/lowerleft = locate(max(1, x - (view[1] - 1)/2), max(1, y - (view[2] - 1)/2), z) + var/turf/upperright = locate(min(world.maxx, lowerleft.x + (view[1] - 1)), min(world.maxy, lowerleft.y + (view[2] - 1)), lowerleft.z) + return block(lowerleft, upperright) + +// Use this when setting the aiEye's location. +// It will also stream the chunk that the new loc is in. + +/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE, dir) + if(ai) + if(!isturf(ai.loc)) + return + T = get_turf(T) + if(!force_update && (T == get_turf(src)) ) + return //we are already here! + if (T) + forceMove(T) + else + moveToNullspace() + if(use_static != USE_STATIC_NONE) + ai.camera_visibility(src) + if(ai.client && !ai.multicam_on) + ai.client.eye = src + update_ai_detect_hud() + update_parallax_contents() + //Holopad + if(istype(ai.current, /obj/machinery/holopad)) + var/obj/machinery/holopad/H = ai.current + H.move_hologram(ai, T, dir) + if(ai.camera_light_on) + ai.light_cameras() + if(ai.master_multicam) + ai.master_multicam.refresh_view() + +/mob/camera/aiEye/Move() + return 0 + +/mob/camera/aiEye/proc/GetViewerClient() + if(ai) + return ai.client + return null + +/mob/camera/aiEye/Destroy() + if(ai) + ai.all_eyes -= src + ai = null + for(var/V in visibleCameraChunks) + var/datum/camerachunk/c = V + c.remove(src) + GLOB.aiEyes -= src + if(ai_detector_visible) + var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] + hud.remove_from_hud(src) + var/list/L = hud_list[AI_DETECT_HUD] + QDEL_LIST(L) + return ..() + +/atom/proc/move_camera_by_click() + if(isAI(usr)) + var/mob/living/silicon/ai/AI = usr + if(AI.eyeobj && (AI.multicam_on || (AI.client.eye == AI.eyeobj)) && (AI.eyeobj.z == z)) + AI.cameraFollow = null + if (isturf(loc) || isturf(src)) + AI.eyeobj.setLoc(src) + +// This will move the AIEye. It will also cause lights near the eye to light up, if toggled. +// This is handled in the proc below this one. + +/client/proc/AIMove(n, direct, mob/living/silicon/ai/user) + + var/initial = initial(user.sprint) + var/max_sprint = 50 + + if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds + user.sprint = initial + + for(var/i = 0; i < max(user.sprint, initial); i += 20) + var/turf/step = get_turf(get_step(user.eyeobj, direct)) + if(step) + user.eyeobj.setLoc(step, null, direct) + + user.cooldown = world.timeofday + 5 + if(user.acceleration) + user.sprint = min(user.sprint + 0.5, max_sprint) + else + user.sprint = initial + + if(!user.tracking) + user.cameraFollow = null + +// Return to the Core. +/mob/living/silicon/ai/proc/view_core() + if(istype(current,/obj/machinery/holopad)) + var/obj/machinery/holopad/H = current + H.clear_holo(src) + else + current = null + cameraFollow = null + unset_machine() + + if(isturf(loc) && (QDELETED(eyeobj) || !eyeobj.loc)) + to_chat(src, "ERROR: Eyeobj not found. Creating new eye...") + create_eye() + + eyeobj?.setLoc(loc) + +/mob/living/silicon/ai/proc/create_eye() + if(eyeobj) + return + eyeobj = new /mob/camera/aiEye() + all_eyes += eyeobj + eyeobj.ai = src + eyeobj.setLoc(loc) + eyeobj.name = "[name] (AI Eye)" + set_eyeobj_visible(TRUE) + +/mob/living/silicon/ai/proc/set_eyeobj_visible(state = TRUE) + if(!eyeobj) + return + eyeobj.mouse_opacity = state ? MOUSE_OPACITY_ICON : initial(eyeobj.mouse_opacity) + eyeobj.invisibility = state ? INVISIBILITY_OBSERVER : initial(eyeobj.invisibility) + +/mob/living/silicon/ai/verb/toggle_acceleration() + set category = "AI Commands" + set name = "Toggle Camera Acceleration" + + if(incapacitated()) + return + acceleration = !acceleration + to_chat(usr, "Camera acceleration has been toggled [acceleration ? "on" : "off"].") + +/mob/camera/aiEye/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if(relay_speech && speaker && ai && !radio_freq && speaker != ai && near_camera(speaker)) + ai.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode) + +/obj/effect/overlay/ai_detect_hud + name = "" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + icon = 'icons/effects/alphacolors.dmi' + icon_state = "" + alpha = 100 + layer = ABOVE_ALL_MOB_LAYER + plane = GAME_PLANE diff --git a/code/modules/mob/living/silicon/ai/login.dm b/code/modules/mob/living/silicon/ai/login.dm index 930c55b627..36e87a8e16 100644 --- a/code/modules/mob/living/silicon/ai/login.dm +++ b/code/modules/mob/living/silicon/ai/login.dm @@ -1,12 +1,12 @@ -/mob/living/silicon/ai/Login() - ..() - if(stat != DEAD) - for(var/each in GLOB.ai_status_displays) //change status - var/obj/machinery/status_display/ai/O = each - O.mode = 1 - O.emotion = emote_display - O.update() - set_eyeobj_visible(TRUE) - if(multicam_on) - end_multicam() - view_core() +/mob/living/silicon/ai/Login() + ..() + if(stat != DEAD) + for(var/each in GLOB.ai_status_displays) //change status + var/obj/machinery/status_display/ai/O = each + O.mode = 1 + O.emotion = emote_display + O.update() + set_eyeobj_visible(TRUE) + if(multicam_on) + end_multicam() + view_core() diff --git a/code/modules/mob/living/silicon/ai/logout.dm b/code/modules/mob/living/silicon/ai/logout.dm index 22d67ef53f..73161f12d5 100644 --- a/code/modules/mob/living/silicon/ai/logout.dm +++ b/code/modules/mob/living/silicon/ai/logout.dm @@ -1,8 +1,8 @@ -/mob/living/silicon/ai/Logout() - ..() - for(var/each in GLOB.ai_status_displays) //change status - var/obj/machinery/status_display/ai/O = each - O.mode = 0 - O.update() - set_eyeobj_visible(FALSE) - view_core() +/mob/living/silicon/ai/Logout() + ..() + for(var/each in GLOB.ai_status_displays) //change status + var/obj/machinery/status_display/ai/O = each + O.mode = 0 + O.update() + set_eyeobj_visible(FALSE) + view_core() diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm index f757203237..d9cc3a0c7b 100644 --- a/code/modules/mob/living/silicon/ai/say.dm +++ b/code/modules/mob/living/silicon/ai/say.dm @@ -1,179 +1,179 @@ -/mob/living/silicon/ai/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - if(parent && istype(parent) && parent.stat != DEAD) //If there is a defined "parent" AI, it is actually an AI, and it is alive, anything the AI tries to say is said by the parent instead. - parent.say(message, language) - return - ..(message) - -/mob/living/silicon/ai/compose_track_href(atom/movable/speaker, namepart) - var/mob/M = speaker.GetSource() - if(M) - return "" - return "" - -/mob/living/silicon/ai/compose_job(atom/movable/speaker, message_langs, raw_message, radio_freq) - //Also includes the for AI hrefs, for convenience. - return "[radio_freq ? " (" + speaker.GetJob() + ")" : ""]" + "[speaker.GetSource() ? "" : ""]" - -/mob/living/silicon/ai/IsVocal() - return !CONFIG_GET(flag/silent_ai) - -/mob/living/silicon/ai/radio(message, message_mode, list/spans, language) - if(incapacitated()) - return FALSE - if(!radio_enabled) //AI cannot speak if radio is disabled (via intellicard) or depowered. - to_chat(src, "Your radio transmitter is offline!") - return FALSE - ..() - -/mob/living/silicon/ai/get_message_mode(message) - if(copytext(message, 1, 3) in list(":h", ":H", ".h", ".H", "#h", "#H")) - return MODE_HOLOPAD - else - return ..() - -//For holopads only. Usable by AI. -/mob/living/silicon/ai/proc/holopad_talk(message, language) - - - message = trim(message) - - if (!message) - return - - var/obj/machinery/holopad/T = current - if(istype(T) && T.masters[src])//If there is a hologram and its master is the user. - var/turf/padturf = get_turf(T) - var/padloc - if(padturf) - padloc = AREACOORD(padturf) - else - padloc = "(UNKNOWN)" - src.log_talk(message, LOG_SAY, tag="HOLOPAD in [padloc]") - send_speech(message, 7, T, "robot", message_language = language) - to_chat(src, "Holopad transmitted, [real_name] \"[message]\"") - else - to_chat(src, "No holopad connected.") - - -// Make sure that the code compiles with AI_VOX undefined -#ifdef AI_VOX -#define VOX_DELAY 600 -/mob/living/silicon/ai/verb/announcement_help() - - set name = "Announcement Help" - set desc = "Display a list of vocal words to announce to the crew." - set category = "AI Commands" - - if(incapacitated()) - return - - var/dat = {" - WARNING: Misuse of the announcement system will get you job banned.

                - Here is a list of words you can type into the 'Announcement' button to create sentences to vocally announce to everyone on the same level at you.
                -
                • You can also click on the word to PREVIEW it.
                • -
                • You can only say 30 words for every announcement.
                • -
                • Do not use punctuation as you would normally, if you want a pause you can use the full stop and comma characters by separating them with spaces, like so: 'Alpha . Test , Bravo'.
                • -
                • Numbers are in word format, e.g. eight, sixty, etc
                • -
                • Sound effects begin with an 's' before the actual word, e.g. scensor
                • -
                • Use Ctrl+F to see if a word exists in the list.

                - "} - - var/index = 0 - for(var/word in GLOB.vox_sounds) - index++ - dat += "[capitalize(word)]" - if(index != GLOB.vox_sounds.len) - dat += " / " - - var/datum/browser/popup = new(src, "announce_help", "Announcement Help", 500, 400) - popup.set_content(dat) - popup.open() - - -/mob/living/silicon/ai/proc/announcement() - var/static/announcing_vox = 0 // Stores the time of the last announcement - if(announcing_vox > world.time) - to_chat(src, "Please wait [DisplayTimeText(announcing_vox - world.time)].") - return - - var/message = input(src, "WARNING: Misuse of this verb can result in you being job banned. More help is available in 'Announcement Help'", "Announcement", src.last_announcement) as text - - last_announcement = message - - var/voxType = input(src, "Male or female VOX?", "VOX-gender") in list("male", "female") - - if(!message || announcing_vox > world.time) - return - - if(incapacitated()) - return - - if(control_disabled) - to_chat(src, "Wireless interface disabled, unable to interact with announcement PA.") - return - - var/list/words = splittext(trim(message), " ") - var/list/incorrect_words = list() - - if(words.len > 30) - words.len = 30 - - for(var/word in words) - word = lowertext(trim(word)) - if(!word) - words -= word - continue - if(!GLOB.vox_sounds[word] && voxType == "female") - incorrect_words += word - if(!GLOB.vox_sounds_male[word] && voxType == "male") - incorrect_words += word - - if(incorrect_words.len) - to_chat(src, "These words are not available on the announcement system: [english_list(incorrect_words)].") - return - - announcing_vox = world.time + VOX_DELAY - - log_game("[key_name(src)] made a vocal announcement with the following message: [message].") - - for(var/word in words) - play_vox_word(word, src.z, null, voxType) - - -/proc/play_vox_word(word, z_level, mob/only_listener, voxType = "female") - - word = lowertext(word) - - if( (GLOB.vox_sounds[word] && voxType == "female") || (GLOB.vox_sounds_male[word] && voxType == "male") ) - - var/sound_file - - if(voxType == "female") - sound_file = GLOB.vox_sounds[word] - else - sound_file = GLOB.vox_sounds_male[word] - var/sound/voice = sound(sound_file, wait = 1, channel = CHANNEL_VOX) - voice.status = SOUND_STREAM - - // If there is no single listener, broadcast to everyone in the same z level - if(!only_listener) - // Play voice for all mobs in the z level - for(var/mob/M in GLOB.player_list) - if(M.client && M.can_hear() && (M.client.prefs.toggles & SOUND_ANNOUNCEMENTS)) - var/turf/T = get_turf(M) - if(T.z == z_level) - SEND_SOUND(M, voice) - else - SEND_SOUND(only_listener, voice) - return 1 - return 0 - -#undef VOX_DELAY -#endif - -/mob/living/silicon/ai/could_speak_in_language(datum/language/dt) - if(is_servant_of_ratvar(src)) - // Ratvarian AIs can only speak Ratvarian - . = ispath(dt, /datum/language/ratvar) - else - . = ..() +/mob/living/silicon/ai/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) + if(parent && istype(parent) && parent.stat != DEAD) //If there is a defined "parent" AI, it is actually an AI, and it is alive, anything the AI tries to say is said by the parent instead. + parent.say(message, language) + return + ..(message) + +/mob/living/silicon/ai/compose_track_href(atom/movable/speaker, namepart) + var/mob/M = speaker.GetSource() + if(M) + return "" + return "" + +/mob/living/silicon/ai/compose_job(atom/movable/speaker, message_langs, raw_message, radio_freq) + //Also includes the for AI hrefs, for convenience. + return "[radio_freq ? " (" + speaker.GetJob() + ")" : ""]" + "[speaker.GetSource() ? "" : ""]" + +/mob/living/silicon/ai/IsVocal() + return !CONFIG_GET(flag/silent_ai) + +/mob/living/silicon/ai/radio(message, message_mode, list/spans, language) + if(incapacitated()) + return FALSE + if(!radio_enabled) //AI cannot speak if radio is disabled (via intellicard) or depowered. + to_chat(src, "Your radio transmitter is offline!") + return FALSE + ..() + +/mob/living/silicon/ai/get_message_mode(message) + if(copytext(message, 1, 3) in list(":h", ":H", ".h", ".H", "#h", "#H")) + return MODE_HOLOPAD + else + return ..() + +//For holopads only. Usable by AI. +/mob/living/silicon/ai/proc/holopad_talk(message, language) + + + message = trim(message) + + if (!message) + return + + var/obj/machinery/holopad/T = current + if(istype(T) && T.masters[src])//If there is a hologram and its master is the user. + var/turf/padturf = get_turf(T) + var/padloc + if(padturf) + padloc = AREACOORD(padturf) + else + padloc = "(UNKNOWN)" + src.log_talk(message, LOG_SAY, tag="HOLOPAD in [padloc]") + send_speech(message, 7, T, "robot", message_language = language) + to_chat(src, "Holopad transmitted, [real_name] \"[message]\"") + else + to_chat(src, "No holopad connected.") + + +// Make sure that the code compiles with AI_VOX undefined +#ifdef AI_VOX +#define VOX_DELAY 600 +/mob/living/silicon/ai/verb/announcement_help() + + set name = "Announcement Help" + set desc = "Display a list of vocal words to announce to the crew." + set category = "AI Commands" + + if(incapacitated()) + return + + var/dat = {" + WARNING: Misuse of the announcement system will get you job banned.

                + Here is a list of words you can type into the 'Announcement' button to create sentences to vocally announce to everyone on the same level at you.
                +
                • You can also click on the word to PREVIEW it.
                • +
                • You can only say 30 words for every announcement.
                • +
                • Do not use punctuation as you would normally, if you want a pause you can use the full stop and comma characters by separating them with spaces, like so: 'Alpha . Test , Bravo'.
                • +
                • Numbers are in word format, e.g. eight, sixty, etc
                • +
                • Sound effects begin with an 's' before the actual word, e.g. scensor
                • +
                • Use Ctrl+F to see if a word exists in the list.

                + "} + + var/index = 0 + for(var/word in GLOB.vox_sounds) + index++ + dat += "[capitalize(word)]" + if(index != GLOB.vox_sounds.len) + dat += " / " + + var/datum/browser/popup = new(src, "announce_help", "Announcement Help", 500, 400) + popup.set_content(dat) + popup.open() + + +/mob/living/silicon/ai/proc/announcement() + var/static/announcing_vox = 0 // Stores the time of the last announcement + if(announcing_vox > world.time) + to_chat(src, "Please wait [DisplayTimeText(announcing_vox - world.time)].") + return + + var/message = input(src, "WARNING: Misuse of this verb can result in you being job banned. More help is available in 'Announcement Help'", "Announcement", src.last_announcement) as text + + last_announcement = message + + var/voxType = input(src, "Male or female VOX?", "VOX-gender") in list("male", "female") + + if(!message || announcing_vox > world.time) + return + + if(incapacitated()) + return + + if(control_disabled) + to_chat(src, "Wireless interface disabled, unable to interact with announcement PA.") + return + + var/list/words = splittext(trim(message), " ") + var/list/incorrect_words = list() + + if(words.len > 30) + words.len = 30 + + for(var/word in words) + word = lowertext(trim(word)) + if(!word) + words -= word + continue + if(!GLOB.vox_sounds[word] && voxType == "female") + incorrect_words += word + if(!GLOB.vox_sounds_male[word] && voxType == "male") + incorrect_words += word + + if(incorrect_words.len) + to_chat(src, "These words are not available on the announcement system: [english_list(incorrect_words)].") + return + + announcing_vox = world.time + VOX_DELAY + + log_game("[key_name(src)] made a vocal announcement with the following message: [message].") + + for(var/word in words) + play_vox_word(word, src.z, null, voxType) + + +/proc/play_vox_word(word, z_level, mob/only_listener, voxType = "female") + + word = lowertext(word) + + if( (GLOB.vox_sounds[word] && voxType == "female") || (GLOB.vox_sounds_male[word] && voxType == "male") ) + + var/sound_file + + if(voxType == "female") + sound_file = GLOB.vox_sounds[word] + else + sound_file = GLOB.vox_sounds_male[word] + var/sound/voice = sound(sound_file, wait = 1, channel = CHANNEL_VOX) + voice.status = SOUND_STREAM + + // If there is no single listener, broadcast to everyone in the same z level + if(!only_listener) + // Play voice for all mobs in the z level + for(var/mob/M in GLOB.player_list) + if(M.client && M.can_hear() && (M.client.prefs.toggles & SOUND_ANNOUNCEMENTS)) + var/turf/T = get_turf(M) + if(T.z == z_level) + SEND_SOUND(M, voice) + else + SEND_SOUND(only_listener, voice) + return 1 + return 0 + +#undef VOX_DELAY +#endif + +/mob/living/silicon/ai/could_speak_in_language(datum/language/dt) + if(is_servant_of_ratvar(src)) + // Ratvarian AIs can only speak Ratvarian + . = ispath(dt, /datum/language/ratvar) + else + . = ..() diff --git a/code/modules/mob/living/silicon/ai/vox_sounds.dm b/code/modules/mob/living/silicon/ai/vox_sounds.dm index 50a808032d..22449795b3 100644 --- a/code/modules/mob/living/silicon/ai/vox_sounds.dm +++ b/code/modules/mob/living/silicon/ai/vox_sounds.dm @@ -1,1609 +1,1609 @@ -// List is required to compile the resources into the game when it loads. -// Dynamically loading it has bad results with sounds overtaking each other, even with the wait variable. -#ifdef AI_VOX - -// Regex for collecting a list of ogg files -// (([a-zA-Z,.]+)\.ogg) - -// For vim -// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox_fem\/\1',/g -GLOBAL_LIST_INIT(vox_sounds, list("abduction" = 'sound/vox_fem/abduction.ogg', -"abortions" = 'sound/vox_fem/abortions.ogg', -"above" = 'sound/vox_fem/above.ogg', -"abstain" = 'sound/vox_fem/abstain.ogg', -"accelerating" = 'sound/vox_fem/accelerating.ogg', -"accelerator" = 'sound/vox_fem/accelerator.ogg', -"accepted" = 'sound/vox_fem/accepted.ogg', -"access" = 'sound/vox_fem/access.ogg', -"acknowledged" = 'sound/vox_fem/acknowledged.ogg', -"acknowledge" = 'sound/vox_fem/acknowledge.ogg', -"acquired" = 'sound/vox_fem/acquired.ogg', -"acquisition" = 'sound/vox_fem/acquisition.ogg', -"across" = 'sound/vox_fem/across.ogg', -"activated" = 'sound/vox_fem/activated.ogg', -"activate" = 'sound/vox_fem/activate.ogg', -"activity" = 'sound/vox_fem/activity.ogg', -"adios" = 'sound/vox_fem/adios.ogg', -"administration" = 'sound/vox_fem/administration.ogg', -"advanced" = 'sound/vox_fem/advanced.ogg', -"advised" = 'sound/vox_fem/advised.ogg', -"after" = 'sound/vox_fem/after.ogg', -"aft" = 'sound/vox_fem/aft.ogg', -"agent" = 'sound/vox_fem/agent.ogg', -"ai" = 'sound/vox_fem/ai.ogg', -"airlock" = 'sound/vox_fem/airlock.ogg', -"air" = 'sound/vox_fem/air.ogg', -"alarm" = 'sound/vox_fem/alarm.ogg', -"alert" = 'sound/vox_fem/alert.ogg', -"alien" = 'sound/vox_fem/alien.ogg', -"aligned" = 'sound/vox_fem/aligned.ogg', -"all" = 'sound/vox_fem/all.ogg', -"alpha" = 'sound/vox_fem/alpha.ogg', -"also" = 'sound/vox_fem/also.ogg', -"amigo" = 'sound/vox_fem/amigo.ogg', -"ammunition" = 'sound/vox_fem/ammunition.ogg', -"am" = 'sound/vox_fem/am.ogg', -"and" = 'sound/vox_fem/and.ogg', -"animal" = 'sound/vox_fem/animal.ogg', -"announcement" = 'sound/vox_fem/announcement.ogg', -"an" = 'sound/vox_fem/an.ogg', -"anomalous" = 'sound/vox_fem/anomalous.ogg', -"answer" = 'sound/vox_fem/answer.ogg', -"antenna" = 'sound/vox_fem/antenna.ogg', -"any" = 'sound/vox_fem/any.ogg', -"a" = 'sound/vox_fem/a.ogg', -"apc" = 'sound/vox_fem/apc.ogg', -"apprehend" = 'sound/vox_fem/apprehend.ogg', -"approach" = 'sound/vox_fem/approach.ogg', -"area" = 'sound/vox_fem/area.ogg', -"are" = 'sound/vox_fem/are.ogg', -"armed" = 'sound/vox_fem/armed.ogg', -"arm" = 'sound/vox_fem/arm.ogg', -"armor" = 'sound/vox_fem/armor.ogg', -"armory" = 'sound/vox_fem/armory.ogg', -"array" = 'sound/vox_fem/array.ogg', -"arrest" = 'sound/vox_fem/arrest.ogg', -"asimov" = 'sound/vox_fem/asimov.ogg', -"asshole" = 'sound/vox_fem/asshole.ogg', -"assholes" = 'sound/vox_fem/assholes.ogg', -"assistance" = 'sound/vox_fem/assistance.ogg', -"assistant" = 'sound/vox_fem/assistant.ogg', -"ass" = 'sound/vox_fem/ass.ogg', -"atmosphere" = 'sound/vox_fem/atmosphere.ogg', -"atmospheric" = 'sound/vox_fem/atmospheric.ogg', -"atmospherics" = 'sound/vox_fem/atmospherics.ogg', -"at" = 'sound/vox_fem/at.ogg', -"atomic" = 'sound/vox_fem/atomic.ogg', -"attention" = 'sound/vox_fem/attention.ogg', -"authentication" = 'sound/vox_fem/authentication.ogg', -"authorized" = 'sound/vox_fem/authorized.ogg', -"authorize" = 'sound/vox_fem/authorize.ogg', -"automatic" = 'sound/vox_fem/automatic.ogg', -"away" = 'sound/vox_fem/away.ogg', -"awful" = 'sound/vox_fem/awful.ogg', -"backman" = 'sound/vox_fem/backman.ogg', -"back" = 'sound/vox_fem/back.ogg', -"bad" = 'sound/vox_fem/bad.ogg', -"bag" = 'sound/vox_fem/bag.ogg', -"bailey" = 'sound/vox_fem/bailey.ogg', -"bar" = 'sound/vox_fem/bar.ogg', -"barracks" = 'sound/vox_fem/barracks.ogg', -"bartender" = 'sound/vox_fem/bartender.ogg', -"base" = 'sound/vox_fem/base.ogg', -"bay" = 'sound/vox_fem/bay.ogg', -"beam" = 'sound/vox_fem/beam.ogg', -"been" = 'sound/vox_fem/been.ogg', -"beep" = 'sound/vox_fem/beep.ogg', -"before" = 'sound/vox_fem/before.ogg', -"below" = 'sound/vox_fem/below.ogg', -"be" = 'sound/vox_fem/be.ogg', -"beside" = 'sound/vox_fem/beside.ogg', -"beware" = 'sound/vox_fem/beware.ogg', -"beyond" = 'sound/vox_fem/beyond.ogg', -"biohazard" = 'sound/vox_fem/biohazard.ogg', -"biological" = 'sound/vox_fem/biological.ogg', -"birdwell" = 'sound/vox_fem/birdwell.ogg', -"bitches" = 'sound/vox_fem/bitches.ogg', -"bitch" = 'sound/vox_fem/bitch.ogg', -"bitcoin" = 'sound/vox_fem/bitcoin.ogg', -"black" = 'sound/vox_fem/black.ogg', -"blast" = 'sound/vox_fem/blast.ogg', -"bleed" = 'sound/vox_fem/bleed.ogg', -"blob" = 'sound/vox_fem/blob.ogg', -"blocked" = 'sound/vox_fem/blocked.ogg', -"blood" = 'sound/vox_fem/blood.ogg', -"bloop" = 'sound/vox_fem/bloop.ogg', -"blue" = 'sound/vox_fem/blue.ogg', -"b" = 'sound/vox_fem/b.ogg', -"bomb" = 'sound/vox_fem/bomb.ogg', -"bone" = 'sound/vox_fem/bone.ogg', -"botanist" = 'sound/vox_fem/botanist.ogg', -"botany" = 'sound/vox_fem/botany.ogg', -"bottom" = 'sound/vox_fem/bottom.ogg', -"bravo" = 'sound/vox_fem/bravo.ogg', -"breached" = 'sound/vox_fem/breached.ogg', -"breach" = 'sound/vox_fem/breach.ogg', -"break" = 'sound/vox_fem/break.ogg', -"bridge" = 'sound/vox_fem/bridge.ogg', -"brig" = 'sound/vox_fem/brig.ogg', -"bust" = 'sound/vox_fem/bust.ogg', -"but" = 'sound/vox_fem/but.ogg', -"button" = 'sound/vox_fem/button.ogg', -"bypass" = 'sound/vox_fem/bypass.ogg', -"cable" = 'sound/vox_fem/cable.ogg', -"called" = 'sound/vox_fem/called.ogg', -"call" = 'sound/vox_fem/call.ogg', -"canal" = 'sound/vox_fem/canal.ogg', -"canister" = 'sound/vox_fem/canister.ogg', -"cap" = 'sound/vox_fem/cap.ogg', -"captain" = 'sound/vox_fem/captain.ogg', -"capture" = 'sound/vox_fem/capture.ogg', -"carbon" = 'sound/vox_fem/carbon.ogg', -"cargo" = 'sound/vox_fem/cargo.ogg', -"cat" = 'sound/vox_fem/cat.ogg', -"cease" = 'sound/vox_fem/cease.ogg', -"ceiling" = 'sound/vox_fem/ceiling.ogg', -"celsius" = 'sound/vox_fem/celsius.ogg', -"centcom" = 'sound/vox_fem/centcom.ogg', -"center" = 'sound/vox_fem/center.ogg', -"centi" = 'sound/vox_fem/centi.ogg', -"central" = 'sound/vox_fem/central.ogg', -"ce" = 'sound/vox_fem/ce.ogg', -"challenge" = 'sound/vox_fem/challenge.ogg', -"chamber" = 'sound/vox_fem/chamber.ogg', -"changed" = 'sound/vox_fem/changed.ogg', -"changeling" = 'sound/vox_fem/changeling.ogg', -"change" = 'sound/vox_fem/change.ogg', -"chapel" = 'sound/vox_fem/chapel.ogg', -"chaplain" = 'sound/vox_fem/chaplain.ogg', -"charlie" = 'sound/vox_fem/charlie.ogg', -"check" = 'sound/vox_fem/check.ogg', -"checkpoint" = 'sound/vox_fem/checkpoint.ogg', -"chemical" = 'sound/vox_fem/chemical.ogg', -"chemist" = 'sound/vox_fem/chemist.ogg', -"chief" = 'sound/vox_fem/chief.ogg', -"christ" = 'sound/vox_fem/christ.ogg', -"chuckle" = 'sound/vox_fem/chuckle.ogg', -"circuit" = 'sound/vox_fem/circuit.ogg', -"cleanup" = 'sound/vox_fem/cleanup.ogg', -"clearance" = 'sound/vox_fem/clearance.ogg', -"clear" = 'sound/vox_fem/clear.ogg', -"clockwork" = 'sound/vox_fem/clockwork.ogg', -"close" = 'sound/vox_fem/close.ogg', -"clowning" = 'sound/vox_fem/clowning.ogg', -"clown" = 'sound/vox_fem/clown.ogg', -"cmo" = 'sound/vox_fem/cmo.ogg', -"coded" = 'sound/vox_fem/coded.ogg', -"code" = 'sound/vox_fem/code.ogg', -"c" = 'sound/vox_fem/c.ogg', -"cold" = 'sound/vox_fem/cold.ogg', -"collider" = 'sound/vox_fem/collider.ogg', -"come" = 'sound/vox_fem/come.ogg', -"command" = 'sound/vox_fem/command.ogg', -"communication" = 'sound/vox_fem/communication.ogg', -"complex" = 'sound/vox_fem/complex.ogg', -"comply" = 'sound/vox_fem/comply.ogg', -"computer" = 'sound/vox_fem/computer.ogg', -"condition" = 'sound/vox_fem/condition.ogg', -"condom" = 'sound/vox_fem/condom.ogg', -"confirmed" = 'sound/vox_fem/confirmed.ogg', -"connor" = 'sound/vox_fem/connor.ogg', -"console2" = 'sound/vox_fem/console2.ogg', -"console" = 'sound/vox_fem/console.ogg', -"construct" = 'sound/vox_fem/construct.ogg', -"containment" = 'sound/vox_fem/containment.ogg', -"contamination" = 'sound/vox_fem/contamination.ogg', -"contraband" = 'sound/vox_fem/contraband.ogg', -"control" = 'sound/vox_fem/control.ogg', -"cook" = 'sound/vox_fem/cook.ogg', -"coolant" = 'sound/vox_fem/coolant.ogg', -"coomer" = 'sound/vox_fem/coomer.ogg', -"core" = 'sound/vox_fem/core.ogg', -"corgi" = 'sound/vox_fem/corgi.ogg', -"corporation" = 'sound/vox_fem/corporation.ogg', -"correct" = 'sound/vox_fem/correct.ogg', -"corridor" = 'sound/vox_fem/corridor.ogg', -"corridors" = 'sound/vox_fem/corridors.ogg', -"coward" = 'sound/vox_fem/coward.ogg', -"cowards" = 'sound/vox_fem/cowards.ogg', -"crate" = 'sound/vox_fem/crate.ogg', -"created" = 'sound/vox_fem/created.ogg', -"creature" = 'sound/vox_fem/creature.ogg', -"crew" = 'sound/vox_fem/crew.ogg', -"critical" = 'sound/vox_fem/critical.ogg', -"cross" = 'sound/vox_fem/cross.ogg', -"cryogenic" = 'sound/vox_fem/cryogenic.ogg', -"crystal" = 'sound/vox_fem/crystal.ogg', -"cultist" = 'sound/vox_fem/cultist.ogg', -"cult" = 'sound/vox_fem/cult.ogg', -"cunt" = 'sound/vox_fem/cunt.ogg', -"curator" = 'sound/vox_fem/curator.ogg', -"cyborg" = 'sound/vox_fem/cyborg.ogg', -"cyborgs" = 'sound/vox_fem/cyborgs.ogg', -"damaged" = 'sound/vox_fem/damaged.ogg', -"damage" = 'sound/vox_fem/damage.ogg', -"danger" = 'sound/vox_fem/danger.ogg', -"dangerous" = 'sound/vox_fem/dangerous.ogg', -"day" = 'sound/vox_fem/day.ogg', -"deactivated" = 'sound/vox_fem/deactivated.ogg', -"dead" = 'sound/vox_fem/dead.ogg', -"death" = 'sound/vox_fem/death.ogg', -"decompression" = 'sound/vox_fem/decompression.ogg', -"decontamination" = 'sound/vox_fem/decontamination.ogg', -"deeoo" = 'sound/vox_fem/deeoo.ogg', -"defense" = 'sound/vox_fem/defense.ogg', -"degrees" = 'sound/vox_fem/degrees.ogg', -"delta" = 'sound/vox_fem/delta.ogg', -"demon" = 'sound/vox_fem/demon.ogg', -"denied" = 'sound/vox_fem/denied.ogg', -"departures" = 'sound/vox_fem/departures.ogg', -"deployed" = 'sound/vox_fem/deployed.ogg', -"deploy" = 'sound/vox_fem/deploy.ogg', -"desire" = 'sound/vox_fem/desire.ogg', -"desist" = 'sound/vox_fem/desist.ogg', -"destroyed" = 'sound/vox_fem/destroyed.ogg', -"destroy" = 'sound/vox_fem/destroy.ogg', -"destruction" = 'sound/vox_fem/destruction.ogg', -"detain" = 'sound/vox_fem/detain.ogg', -"detected" = 'sound/vox_fem/detected.ogg', -"detective" = 'sound/vox_fem/detective.ogg', -"detonation" = 'sound/vox_fem/detonation.ogg', -"device" = 'sound/vox_fem/device.ogg', -"devil" = 'sound/vox_fem/devil.ogg', -"did" = 'sound/vox_fem/did.ogg', -"die" = 'sound/vox_fem/die.ogg', -"dimensional" = 'sound/vox_fem/dimensional.ogg', -"dioxide" = 'sound/vox_fem/dioxide.ogg', -"director" = 'sound/vox_fem/director.ogg', -"dirt" = 'sound/vox_fem/dirt.ogg', -"disabled" = 'sound/vox_fem/disabled.ogg', -"disease" = 'sound/vox_fem/disease.ogg', -"disengaged" = 'sound/vox_fem/disengaged.ogg', -"dish" = 'sound/vox_fem/dish.ogg', -"disk" = 'sound/vox_fem/disk.ogg', -"disposal" = 'sound/vox_fem/disposal.ogg', -"distance" = 'sound/vox_fem/distance.ogg', -"distortion" = 'sound/vox_fem/distortion.ogg', -"doctor" = 'sound/vox_fem/doctor.ogg', -"d" = 'sound/vox_fem/d.ogg', -"dog" = 'sound/vox_fem/dog.ogg', -"do" = 'sound/vox_fem/do.ogg', -"doomsday" = 'sound/vox_fem/doomsday.ogg', -"doop" = 'sound/vox_fem/doop.ogg', -"door" = 'sound/vox_fem/door.ogg', -"dormitory" = 'sound/vox_fem/dormitory.ogg', -"dot" = 'sound/vox_fem/dot.ogg', -"down" = 'sound/vox_fem/down.ogg', -"drone" = 'sound/vox_fem/drone.ogg', -"dual" = 'sound/vox_fem/dual.ogg', -"duct" = 'sound/vox_fem/duct.ogg', -"east" = 'sound/vox_fem/east.ogg', -"echo" = 'sound/vox_fem/echo.ogg', -"ed" = 'sound/vox_fem/ed.ogg', -"effect" = 'sound/vox_fem/effect.ogg', -"egress" = 'sound/vox_fem/egress.ogg', -"eighteen" = 'sound/vox_fem/eighteen.ogg', -"eight" = 'sound/vox_fem/eight.ogg', -"eighty" = 'sound/vox_fem/eighty.ogg', -"electric" = 'sound/vox_fem/electric.ogg', -"electromagnetic" = 'sound/vox_fem/electromagnetic.ogg', -"elevator" = 'sound/vox_fem/elevator.ogg', -"eleven" = 'sound/vox_fem/eleven.ogg', -"eliminate" = 'sound/vox_fem/eliminate.ogg', -"emergency" = 'sound/vox_fem/emergency.ogg', -"enabled" = 'sound/vox_fem/enabled.ogg', -"energy" = 'sound/vox_fem/energy.ogg', -"engaged" = 'sound/vox_fem/engaged.ogg', -"engage" = 'sound/vox_fem/engage.ogg', -"engineering" = 'sound/vox_fem/engineering.ogg', -"engineer" = 'sound/vox_fem/engineer.ogg', -"engine" = 'sound/vox_fem/engine.ogg', -"enter" = 'sound/vox_fem/enter.ogg', -"entity" = 'sound/vox_fem/entity.ogg', -"entry" = 'sound/vox_fem/entry.ogg', -"environment" = 'sound/vox_fem/environment.ogg', -"e" = 'sound/vox_fem/e.ogg', -"epic" = 'sound/vox_fem/epic.ogg', -"equipment" = 'sound/vox_fem/equipment.ogg', -"error" = 'sound/vox_fem/error.ogg', -"escape" = 'sound/vox_fem/escape.ogg', -"evacuate" = 'sound/vox_fem/evacuate.ogg', -"eva" = 'sound/vox_fem/eva.ogg', -"exchange" = 'sound/vox_fem/exchange.ogg', -"exit" = 'sound/vox_fem/exit.ogg', -"expect" = 'sound/vox_fem/expect.ogg', -"experimental" = 'sound/vox_fem/experimental.ogg', -"experiment" = 'sound/vox_fem/experiment.ogg', -"explode" = 'sound/vox_fem/explode.ogg', -"explosion" = 'sound/vox_fem/explosion.ogg', -"explosive" = 'sound/vox_fem/explosive.ogg', -"exposure" = 'sound/vox_fem/exposure.ogg', -"exterminate" = 'sound/vox_fem/exterminate.ogg', -"extinguisher" = 'sound/vox_fem/extinguisher.ogg', -"extinguish" = 'sound/vox_fem/extinguish.ogg', -"extreme" = 'sound/vox_fem/extreme.ogg', -"facility" = 'sound/vox_fem/facility.ogg', -"factory" = 'sound/vox_fem/factory.ogg', -"fahrenheit" = 'sound/vox_fem/fahrenheit.ogg', -"failed" = 'sound/vox_fem/failed.ogg', -"failure" = 'sound/vox_fem/failure.ogg', -"false" = 'sound/vox_fem/false.ogg', -"farthest" = 'sound/vox_fem/farthest.ogg', -"fast" = 'sound/vox_fem/fast.ogg', -"fauna" = 'sound/vox_fem/fauna.ogg', -"feet" = 'sound/vox_fem/feet.ogg', -"field" = 'sound/vox_fem/field.ogg', -"fifteen" = 'sound/vox_fem/fifteen.ogg', -"fifth" = 'sound/vox_fem/fifth.ogg', -"fifty" = 'sound/vox_fem/fifty.ogg', -"final" = 'sound/vox_fem/final.ogg', -"fine" = 'sound/vox_fem/fine.ogg', -"fire" = 'sound/vox_fem/fire.ogg', -"first" = 'sound/vox_fem/first.ogg', -"five" = 'sound/vox_fem/five.ogg', -"fix" = 'sound/vox_fem/fix.ogg', -"flooding" = 'sound/vox_fem/flooding.ogg', -"floor" = 'sound/vox_fem/floor.ogg', -"flyman" = 'sound/vox_fem/flyman.ogg', -"f" = 'sound/vox_fem/f.ogg', -"fool" = 'sound/vox_fem/fool.ogg', -"forbidden" = 'sound/vox_fem/forbidden.ogg', -"force" = 'sound/vox_fem/force.ogg', -"fore" = 'sound/vox_fem/fore.ogg', -"formed" = 'sound/vox_fem/formed.ogg', -"form" = 'sound/vox_fem/form.ogg', -"forms" = 'sound/vox_fem/forms.ogg', -"for" = 'sound/vox_fem/for.ogg', -"found" = 'sound/vox_fem/found.ogg', -"four" = 'sound/vox_fem/four.ogg', -"fourteen" = 'sound/vox_fem/fourteen.ogg', -"fourth" = 'sound/vox_fem/fourth.ogg', -"fourty" = 'sound/vox_fem/fourty.ogg', -"foxtrot" = 'sound/vox_fem/foxtrot.ogg', -"freeman" = 'sound/vox_fem/freeman.ogg', -"free" = 'sound/vox_fem/free.ogg', -"freezer" = 'sound/vox_fem/freezer.ogg', -"freezing" = 'sound/vox_fem/freezing.ogg', -"from" = 'sound/vox_fem/from.ogg', -"front" = 'sound/vox_fem/front.ogg', -"fucking" = 'sound/vox_fem/fucking.ogg', -"fuck" = 'sound/vox_fem/fuck.ogg', -"fucks" = 'sound/vox_fem/fucks.ogg', -"fuel" = 'sound/vox_fem/fuel.ogg', -"gas" = 'sound/vox_fem/gas.ogg', -"generator" = 'sound/vox_fem/generator.ogg', -"geneticist" = 'sound/vox_fem/geneticist.ogg', -"get" = 'sound/vox_fem/get.ogg', -"glory" = 'sound/vox_fem/glory.ogg', -"god" = 'sound/vox_fem/god.ogg', -"g" = 'sound/vox_fem/g.ogg', -"going" = 'sound/vox_fem/going.ogg', -"golem" = 'sound/vox_fem/golem.ogg', -"goodbye" = 'sound/vox_fem/goodbye.ogg', -"good" = 'sound/vox_fem/good.ogg', -"go" = 'sound/vox_fem/go.ogg', -"gordon" = 'sound/vox_fem/gordon.ogg', -"got" = 'sound/vox_fem/got.ogg', -"government" = 'sound/vox_fem/government.ogg', -"granted" = 'sound/vox_fem/granted.ogg', -"gravity" = 'sound/vox_fem/gravity.ogg', -"gray" = 'sound/vox_fem/gray.ogg', -"great" = 'sound/vox_fem/great.ogg', -"green" = 'sound/vox_fem/green.ogg', -"grenade" = 'sound/vox_fem/grenade.ogg', -"guard" = 'sound/vox_fem/guard.ogg', -"gulf" = 'sound/vox_fem/gulf.ogg', -"gun" = 'sound/vox_fem/gun.ogg', -"guthrie" = 'sound/vox_fem/guthrie.ogg', -"hacker" = 'sound/vox_fem/hacker.ogg', -"hackers" = 'sound/vox_fem/hackers.ogg', -"hall" = 'sound/vox_fem/hall.ogg', -"hallway" = 'sound/vox_fem/hallway.ogg', -"handling" = 'sound/vox_fem/handling.ogg', -"hangar" = 'sound/vox_fem/hangar.ogg', -"harmful" = 'sound/vox_fem/harmful.ogg', -"harm" = 'sound/vox_fem/harm.ogg', -"has" = 'sound/vox_fem/has.ogg', -"have" = 'sound/vox_fem/have.ogg', -"hazard" = 'sound/vox_fem/hazard.ogg', -"head" = 'sound/vox_fem/head.ogg', -"health" = 'sound/vox_fem/health.ogg', -"heat" = 'sound/vox_fem/heat.ogg', -"helicopter" = 'sound/vox_fem/helicopter.ogg', -"helium" = 'sound/vox_fem/helium.ogg', -"hello" = 'sound/vox_fem/hello.ogg', -"help" = 'sound/vox_fem/help.ogg', -"he" = 'sound/vox_fem/he.ogg', -"here" = 'sound/vox_fem/here.ogg', -"hide" = 'sound/vox_fem/hide.ogg', -"highest" = 'sound/vox_fem/highest.ogg', -"high" = 'sound/vox_fem/high.ogg', -"hit" = 'sound/vox_fem/hit.ogg', -"h" = 'sound/vox_fem/h.ogg', -"hole" = 'sound/vox_fem/hole.ogg', -"honk" = 'sound/vox_fem/honk.ogg', -"hop" = 'sound/vox_fem/hop.ogg', -"hos" = 'sound/vox_fem/hos.ogg', -"hostile" = 'sound/vox_fem/hostile.ogg', -"hotel" = 'sound/vox_fem/hotel.ogg', -"hot" = 'sound/vox_fem/hot.ogg', -"hour" = 'sound/vox_fem/hour.ogg', -"hours" = 'sound/vox_fem/hours.ogg', -"how" = 'sound/vox_fem/how.ogg', -"human" = 'sound/vox_fem/human.ogg', -"humanoid" = 'sound/vox_fem/humanoid.ogg', -"humans" = 'sound/vox_fem/humans.ogg', -"hundred" = 'sound/vox_fem/hundred.ogg', -"hunger" = 'sound/vox_fem/hunger.ogg', -"hurt" = 'sound/vox_fem/hurt.ogg', -"hydro" = 'sound/vox_fem/hydro.ogg', -"hydroponics" = 'sound/vox_fem/hydroponics.ogg', -"ian" = 'sound/vox_fem/ian.ogg', -"idiot" = 'sound/vox_fem/idiot.ogg', -"if2" = 'sound/vox_fem/if2.ogg', -"if" = 'sound/vox_fem/if.ogg', -"illegal" = 'sound/vox_fem/illegal.ogg', -"immediately" = 'sound/vox_fem/immediately.ogg', -"immediate" = 'sound/vox_fem/immediate.ogg', -"immortal" = 'sound/vox_fem/immortal.ogg', -"impossible" = 'sound/vox_fem/impossible.ogg', -"inches" = 'sound/vox_fem/inches.ogg', -"india" = 'sound/vox_fem/india.ogg', -"ing" = 'sound/vox_fem/ing.ogg', -"in" = 'sound/vox_fem/in.ogg', -"inoperative" = 'sound/vox_fem/inoperative.ogg', -"inside" = 'sound/vox_fem/inside.ogg', -"inspection" = 'sound/vox_fem/inspection.ogg', -"inspector" = 'sound/vox_fem/inspector.ogg', -"interchange" = 'sound/vox_fem/interchange.ogg', -"internals" = 'sound/vox_fem/internals.ogg', -"intruder" = 'sound/vox_fem/intruder.ogg', -"invalid" = 'sound/vox_fem/invalid.ogg', -"invasion" = 'sound/vox_fem/invasion.ogg', -"i" = 'sound/vox_fem/i.ogg', -"is" = 'sound/vox_fem/is.ogg', -"it" = 'sound/vox_fem/it.ogg', -"janitor" = 'sound/vox_fem/janitor.ogg', -"jesus" = 'sound/vox_fem/jesus.ogg', -"j" = 'sound/vox_fem/j.ogg', -"johnson" = 'sound/vox_fem/johnson.ogg', -"juliet" = 'sound/vox_fem/juliet.ogg', -"key" = 'sound/vox_fem/key.ogg', -"kidnapped" = 'sound/vox_fem/kidnapped.ogg', -"kidnapping" = 'sound/vox_fem/kidnapping.ogg', -"killed" = 'sound/vox_fem/killed.ogg', -"kill" = 'sound/vox_fem/kill.ogg', -"kilo" = 'sound/vox_fem/kilo.ogg', -"kitchen" = 'sound/vox_fem/kitchen.ogg', -"kit" = 'sound/vox_fem/kit.ogg', -"k" = 'sound/vox_fem/k.ogg', -"lab" = 'sound/vox_fem/lab.ogg', -"lambda" = 'sound/vox_fem/lambda.ogg', -"laser" = 'sound/vox_fem/laser.ogg', -"last" = 'sound/vox_fem/last.ogg', -"launch" = 'sound/vox_fem/launch.ogg', -"lavaland" = 'sound/vox_fem/lavaland.ogg', -"law" = 'sound/vox_fem/law.ogg', -"laws" = 'sound/vox_fem/laws.ogg', -"lawyer" = 'sound/vox_fem/lawyer.ogg', -"leak" = 'sound/vox_fem/leak.ogg', -"leave" = 'sound/vox_fem/leave.ogg', -"left" = 'sound/vox_fem/left.ogg', -"legal" = 'sound/vox_fem/legal.ogg', -"level" = 'sound/vox_fem/level.ogg', -"lever" = 'sound/vox_fem/lever.ogg', -"library" = 'sound/vox_fem/library.ogg', -"lie" = 'sound/vox_fem/lie.ogg', -"lieutenant" = 'sound/vox_fem/lieutenant.ogg', -"lifeform" = 'sound/vox_fem/lifeform.ogg', -"life" = 'sound/vox_fem/life.ogg', -"light" = 'sound/vox_fem/light.ogg', -"lima" = 'sound/vox_fem/lima.ogg', -"liquid" = 'sound/vox_fem/liquid.ogg', -"live2" = 'sound/vox_fem/live2.ogg', -"live" = 'sound/vox_fem/live.ogg', -"lizard" = 'sound/vox_fem/lizard.ogg', -"loading" = 'sound/vox_fem/loading.ogg', -"located" = 'sound/vox_fem/located.ogg', -"locate" = 'sound/vox_fem/locate.ogg', -"location" = 'sound/vox_fem/location.ogg', -"locked" = 'sound/vox_fem/locked.ogg', -"locker" = 'sound/vox_fem/locker.ogg', -"lock" = 'sound/vox_fem/lock.ogg', -"lockout" = 'sound/vox_fem/lockout.ogg', -"l" = 'sound/vox_fem/l.ogg', -"long" = 'sound/vox_fem/long.ogg', -"look" = 'sound/vox_fem/look.ogg', -"loop" = 'sound/vox_fem/loop.ogg', -"loose" = 'sound/vox_fem/loose.ogg', -"lot" = 'sound/vox_fem/lot.ogg', -"lower" = 'sound/vox_fem/lower.ogg', -"lowest" = 'sound/vox_fem/lowest.ogg', -"lusty" = 'sound/vox_fem/lusty.ogg', -"machine" = 'sound/vox_fem/machine.ogg', -"magic" = 'sound/vox_fem/magic.ogg', -"magnetic" = 'sound/vox_fem/magnetic.ogg', -"main" = 'sound/vox_fem/main.ogg', -"maintenance" = 'sound/vox_fem/maintenance.ogg', -"malfunction" = 'sound/vox_fem/malfunction.ogg', -"man" = 'sound/vox_fem/man.ogg', -"many" = 'sound/vox_fem/many.ogg', -"mass" = 'sound/vox_fem/mass.ogg', -"materials" = 'sound/vox_fem/materials.ogg', -"maximum" = 'sound/vox_fem/maximum.ogg', -"may" = 'sound/vox_fem/may.ogg', -"meat" = 'sound/vox_fem/meat.ogg', -"medbay" = 'sound/vox_fem/medbay.ogg', -"medical" = 'sound/vox_fem/medical.ogg', -"megafauna" = 'sound/vox_fem/megafauna.ogg', -"men" = 'sound/vox_fem/men.ogg', -"me" = 'sound/vox_fem/me.ogg', -"mercy" = 'sound/vox_fem/mercy.ogg', -"mesa" = 'sound/vox_fem/mesa.ogg', -"message" = 'sound/vox_fem/message.ogg', -"meter" = 'sound/vox_fem/meter.ogg', -"micro" = 'sound/vox_fem/micro.ogg', -"middle" = 'sound/vox_fem/middle.ogg', -"mike" = 'sound/vox_fem/mike.ogg', -"miles" = 'sound/vox_fem/miles.ogg', -"military" = 'sound/vox_fem/military.ogg', -"milli" = 'sound/vox_fem/milli.ogg', -"million" = 'sound/vox_fem/million.ogg', -"mime" = 'sound/vox_fem/mime.ogg', -"minefield" = 'sound/vox_fem/minefield.ogg', -"miner" = 'sound/vox_fem/miner.ogg', -"minimum" = 'sound/vox_fem/minimum.ogg', -"minutes" = 'sound/vox_fem/minutes.ogg', -"mister" = 'sound/vox_fem/mister.ogg', -"mode" = 'sound/vox_fem/mode.ogg', -"modification" = 'sound/vox_fem/modification.ogg', -"m" = 'sound/vox_fem/m.ogg', -"money" = 'sound/vox_fem/money.ogg', -"monkey" = 'sound/vox_fem/monkey.ogg', -"moth" = 'sound/vox_fem/moth.ogg', -"motor" = 'sound/vox_fem/motor.ogg', -"motorpool" = 'sound/vox_fem/motorpool.ogg', -"move" = 'sound/vox_fem/move.ogg', -"multitude" = 'sound/vox_fem/multitude.ogg', -"murder" = 'sound/vox_fem/murder.ogg', -"must" = 'sound/vox_fem/must.ogg', -"my" = 'sound/vox_fem/my.ogg', -"mythic" = 'sound/vox_fem/mythic.ogg', -"nanotrasen" = 'sound/vox_fem/nanotrasen.ogg', -"nearest" = 'sound/vox_fem/nearest.ogg', -"need" = 'sound/vox_fem/need.ogg', -"nice" = 'sound/vox_fem/nice.ogg', -"nine" = 'sound/vox_fem/nine.ogg', -"nineteen" = 'sound/vox_fem/nineteen.ogg', -"ninety" = 'sound/vox_fem/ninety.ogg', -"nitrogen" = 'sound/vox_fem/nitrogen.ogg', -"n" = 'sound/vox_fem/n.ogg', -"nominal" = 'sound/vox_fem/nominal.ogg', -"no" = 'sound/vox_fem/no.ogg', -"north" = 'sound/vox_fem/north.ogg', -"not" = 'sound/vox_fem/not.ogg', -"november" = 'sound/vox_fem/november.ogg', -"now" = 'sound/vox_fem/now.ogg', -"nuclear" = 'sound/vox_fem/nuclear.ogg', -"nuke" = 'sound/vox_fem/nuke.ogg', -"number" = 'sound/vox_fem/number.ogg', -"objective" = 'sound/vox_fem/objective.ogg', -"observation" = 'sound/vox_fem/observation.ogg', -"obtain" = 'sound/vox_fem/obtain.ogg', -"office" = 'sound/vox_fem/office.ogg', -"officer" = 'sound/vox_fem/officer.ogg', -"off" = 'sound/vox_fem/off.ogg', -"of" = 'sound/vox_fem/of.ogg', -"," = 'sound/vox_fem/,.ogg', -"." = 'sound/vox_fem/..ogg', -"oh" = 'sound/vox_fem/oh.ogg', -"ok" = 'sound/vox_fem/ok.ogg', -"one" = 'sound/vox_fem/one.ogg', -"on" = 'sound/vox_fem/on.ogg', -"oof" = 'sound/vox_fem/oof.ogg', -"o" = 'sound/vox_fem/o.ogg', -"open" = 'sound/vox_fem/open.ogg', -"operating" = 'sound/vox_fem/operating.ogg', -"operations" = 'sound/vox_fem/operations.ogg', -"operative" = 'sound/vox_fem/operative.ogg', -"option" = 'sound/vox_fem/option.ogg', -"order" = 'sound/vox_fem/order.ogg', -"organic" = 'sound/vox_fem/organic.ogg', -"or" = 'sound/vox_fem/or.ogg', -"oscar" = 'sound/vox_fem/oscar.ogg', -"out" = 'sound/vox_fem/out.ogg', -"outside" = 'sound/vox_fem/outside.ogg', -"overload" = 'sound/vox_fem/overload.ogg', -"over" = 'sound/vox_fem/over.ogg', -"override" = 'sound/vox_fem/override.ogg', -"oxygen" = 'sound/vox_fem/oxygen.ogg', -"pacification" = 'sound/vox_fem/pacification.ogg', -"pacify" = 'sound/vox_fem/pacify.ogg', -"pain" = 'sound/vox_fem/pain.ogg', -"pal" = 'sound/vox_fem/pal.ogg', -"panel" = 'sound/vox_fem/panel.ogg', -"panting" = 'sound/vox_fem/panting.ogg', -"pathetic" = 'sound/vox_fem/pathetic.ogg', -"percent" = 'sound/vox_fem/percent.ogg', -"perfect" = 'sound/vox_fem/perfect.ogg', -"perimeter" = 'sound/vox_fem/perimeter.ogg', -"permitted" = 'sound/vox_fem/permitted.ogg', -"personal" = 'sound/vox_fem/personal.ogg', -"personnel" = 'sound/vox_fem/personnel.ogg', -"pipe" = 'sound/vox_fem/pipe.ogg', -"piping" = 'sound/vox_fem/piping.ogg', -"piss" = 'sound/vox_fem/piss.ogg', -"plant" = 'sound/vox_fem/plant.ogg', -"plasmaman" = 'sound/vox_fem/plasmaman.ogg', -"plasma" = 'sound/vox_fem/plasma.ogg', -"platform" = 'sound/vox_fem/platform.ogg', -"plausible" = 'sound/vox_fem/plausible.ogg', -"please" = 'sound/vox_fem/please.ogg', -"p" = 'sound/vox_fem/p.ogg', -"point" = 'sound/vox_fem/point.ogg', -"portal" = 'sound/vox_fem/portal.ogg', -"port" = 'sound/vox_fem/port.ogg', -"possible" = 'sound/vox_fem/possible.ogg', -"power" = 'sound/vox_fem/power.ogg', -"presence" = 'sound/vox_fem/presence.ogg', -"press" = 'sound/vox_fem/press.ogg', -"pressure" = 'sound/vox_fem/pressure.ogg', -"primary" = 'sound/vox_fem/primary.ogg', -"prisoner" = 'sound/vox_fem/prisoner.ogg', -"prison" = 'sound/vox_fem/prison.ogg', -"proceed" = 'sound/vox_fem/proceed.ogg', -"processing" = 'sound/vox_fem/processing.ogg', -"progress" = 'sound/vox_fem/progress.ogg', -"proper" = 'sound/vox_fem/proper.ogg', -"propulsion" = 'sound/vox_fem/propulsion.ogg', -"prosecute" = 'sound/vox_fem/prosecute.ogg', -"protective" = 'sound/vox_fem/protective.ogg', -"push" = 'sound/vox_fem/push.ogg', -"put" = 'sound/vox_fem/put.ogg', -"q" = 'sound/vox_fem/q.ogg', -"quantum" = 'sound/vox_fem/quantum.ogg', -"quarantine" = 'sound/vox_fem/quarantine.ogg', -"quartermaster" = 'sound/vox_fem/quartermaster.ogg', -"quebec" = 'sound/vox_fem/quebec.ogg', -"queen" = 'sound/vox_fem/queen.ogg', -"questionable" = 'sound/vox_fem/questionable.ogg', -"questioning" = 'sound/vox_fem/questioning.ogg', -"question" = 'sound/vox_fem/question.ogg', -"quick" = 'sound/vox_fem/quick.ogg', -"quit" = 'sound/vox_fem/quit.ogg', -"radiation" = 'sound/vox_fem/radiation.ogg', -"radioactive" = 'sound/vox_fem/radioactive.ogg', -"rads" = 'sound/vox_fem/rads.ogg', -"raider" = 'sound/vox_fem/raider.ogg', -"raiders" = 'sound/vox_fem/raiders.ogg', -"rapid" = 'sound/vox_fem/rapid.ogg', -"reached" = 'sound/vox_fem/reached.ogg', -"reach" = 'sound/vox_fem/reach.ogg', -"reactor" = 'sound/vox_fem/reactor.ogg', -"red" = 'sound/vox_fem/red.ogg', -"relay" = 'sound/vox_fem/relay.ogg', -"released" = 'sound/vox_fem/released.ogg', -"remaining" = 'sound/vox_fem/remaining.ogg', -"removal" = 'sound/vox_fem/removal.ogg', -"renegade" = 'sound/vox_fem/renegade.ogg', -"repair" = 'sound/vox_fem/repair.ogg', -"report" = 'sound/vox_fem/report.ogg', -"reports" = 'sound/vox_fem/reports.ogg', -"required" = 'sound/vox_fem/required.ogg', -"require" = 'sound/vox_fem/require.ogg', -"research" = 'sound/vox_fem/research.ogg', -"resevoir" = 'sound/vox_fem/resevoir.ogg', -"resistance" = 'sound/vox_fem/resistance.ogg', -"rest" = 'sound/vox_fem/rest.ogg', -"restoration" = 'sound/vox_fem/restoration.ogg', -"revolutionary" = 'sound/vox_fem/revolutionary.ogg', -"revolution" = 'sound/vox_fem/revolution.ogg', -"right" = 'sound/vox_fem/right.ogg', -"riot" = 'sound/vox_fem/riot.ogg', -"roboticist" = 'sound/vox_fem/roboticist.ogg', -"rocket" = 'sound/vox_fem/rocket.ogg', -"roger" = 'sound/vox_fem/roger.ogg', -"r" = 'sound/vox_fem/r.ogg', -"rogue" = 'sound/vox_fem/rogue.ogg', -"romeo" = 'sound/vox_fem/romeo.ogg', -"room" = 'sound/vox_fem/room.ogg', -"round" = 'sound/vox_fem/round.ogg', -"rune" = 'sound/vox_fem/rune.ogg', -"run" = 'sound/vox_fem/run.ogg', -"runtime" = 'sound/vox_fem/runtime.ogg', -"sabotage" = 'sound/vox_fem/sabotage.ogg', -"safe" = 'sound/vox_fem/safe.ogg', -"safety" = 'sound/vox_fem/safety.ogg', -"sairhorn" = 'sound/vox_fem/sairhorn.ogg', -"sarah" = 'sound/vox_fem/sarah.ogg', -"sargeant" = 'sound/vox_fem/sargeant.ogg', -"satellite" = 'sound/vox_fem/satellite.ogg', -"save" = 'sound/vox_fem/save.ogg', -"scensor" = 'sound/vox_fem/scensor.ogg', -"science" = 'sound/vox_fem/science.ogg', -"scientist" = 'sound/vox_fem/scientist.ogg', -"scream" = 'sound/vox_fem/scream.ogg', -"screen" = 'sound/vox_fem/screen.ogg', -"search" = 'sound/vox_fem/search.ogg', -"secondary" = 'sound/vox_fem/secondary.ogg', -"second" = 'sound/vox_fem/second.ogg', -"seconds" = 'sound/vox_fem/seconds.ogg', -"section" = 'sound/vox_fem/section.ogg', -"sector" = 'sound/vox_fem/sector.ogg', -"secured" = 'sound/vox_fem/secured.ogg', -"secure" = 'sound/vox_fem/secure.ogg', -"security" = 'sound/vox_fem/security.ogg', -"selected" = 'sound/vox_fem/selected.ogg', -"select" = 'sound/vox_fem/select.ogg', -"self" = 'sound/vox_fem/self.ogg', -"sensors" = 'sound/vox_fem/sensors.ogg', -"server" = 'sound/vox_fem/server.ogg', -"service" = 'sound/vox_fem/service.ogg', -"seven" = 'sound/vox_fem/seven.ogg', -"seventeen" = 'sound/vox_fem/seventeen.ogg', -"seventy" = 'sound/vox_fem/seventy.ogg', -"severe" = 'sound/vox_fem/severe.ogg', -"sewage" = 'sound/vox_fem/sewage.ogg', -"sewer" = 'sound/vox_fem/sewer.ogg', -"shaft" = 'sound/vox_fem/shaft.ogg', -"she" = 'sound/vox_fem/she.ogg', -"shield" = 'sound/vox_fem/shield.ogg', -"shipment" = 'sound/vox_fem/shipment.ogg', -"shirt" = 'sound/vox_fem/shirt.ogg', -"shitlord" = 'sound/vox_fem/shitlord.ogg', -"shit" = 'sound/vox_fem/shit.ogg', -"shits" = 'sound/vox_fem/shits.ogg', -"shitting" = 'sound/vox_fem/shitting.ogg', -"shock" = 'sound/vox_fem/shock.ogg', -"shonk" = 'sound/vox_fem/shonk.ogg', -"shoot" = 'sound/vox_fem/shoot.ogg', -"shower" = 'sound/vox_fem/shower.ogg', -"shut" = 'sound/vox_fem/shut.ogg', -"shuttle" = 'sound/vox_fem/shuttle.ogg', -"sick" = 'sound/vox_fem/sick.ogg', -"side" = 'sound/vox_fem/side.ogg', -"sierra" = 'sound/vox_fem/sierra.ogg', -"sight" = 'sound/vox_fem/sight.ogg', -"silicon" = 'sound/vox_fem/silicon.ogg', -"silo" = 'sound/vox_fem/silo.ogg', -"singularity" = 'sound/vox_fem/singularity.ogg', -"six" = 'sound/vox_fem/six.ogg', -"sixteen" = 'sound/vox_fem/sixteen.ogg', -"sixty" = 'sound/vox_fem/sixty.ogg', -"skeleton" = 'sound/vox_fem/skeleton.ogg', -"slaughter" = 'sound/vox_fem/slaughter.ogg', -"slime" = 'sound/vox_fem/slime.ogg', -"slip" = 'sound/vox_fem/slip.ogg', -"slippery" = 'sound/vox_fem/slippery.ogg', -"slow" = 'sound/vox_fem/slow.ogg', -"sm" = 'sound/vox_fem/sm.ogg', -"s" = 'sound/vox_fem/s.ogg', -"solar" = 'sound/vox_fem/solar.ogg', -"solars" = 'sound/vox_fem/solars.ogg', -"soldier" = 'sound/vox_fem/soldier.ogg', -"some" = 'sound/vox_fem/some.ogg', -"someone" = 'sound/vox_fem/someone.ogg', -"something" = 'sound/vox_fem/something.ogg', -"son" = 'sound/vox_fem/son.ogg', -"sorry" = 'sound/vox_fem/sorry.ogg', -"south" = 'sound/vox_fem/south.ogg', -"space" = 'sound/vox_fem/space.ogg', -"squad" = 'sound/vox_fem/squad.ogg', -"square" = 'sound/vox_fem/square.ogg', -"ss13" = 'sound/vox_fem/ss13.ogg', -"stairway" = 'sound/vox_fem/stairway.ogg', -"starboard" = 'sound/vox_fem/starboard.ogg', -"station" = 'sound/vox_fem/station.ogg', -"status" = 'sound/vox_fem/status.ogg', -"stay" = 'sound/vox_fem/stay.ogg', -"sterile" = 'sound/vox_fem/sterile.ogg', -"sterilization" = 'sound/vox_fem/sterilization.ogg', -"stop" = 'sound/vox_fem/stop.ogg', -"storage" = 'sound/vox_fem/storage.ogg', -"strong" = 'sound/vox_fem/strong.ogg', -"stuck" = 'sound/vox_fem/stuck.ogg', -"sub" = 'sound/vox_fem/sub.ogg', -"subsurface" = 'sound/vox_fem/subsurface.ogg', -"sudden" = 'sound/vox_fem/sudden.ogg', -"suffer" = 'sound/vox_fem/suffer.ogg', -"suit" = 'sound/vox_fem/suit.ogg', -"superconducting" = 'sound/vox_fem/superconducting.ogg', -"supercooled" = 'sound/vox_fem/supercooled.ogg', -"supermatter" = 'sound/vox_fem/supermatter.ogg', -"supply" = 'sound/vox_fem/supply.ogg', -"surface" = 'sound/vox_fem/surface.ogg', -"surrender" = 'sound/vox_fem/surrender.ogg', -"surrounded" = 'sound/vox_fem/surrounded.ogg', -"surround" = 'sound/vox_fem/surround.ogg', -"sweating" = 'sound/vox_fem/sweating.ogg', -"swhitenoise" = 'sound/vox_fem/swhitenoise.ogg', -"switch" = 'sound/vox_fem/switch.ogg', -"syndicate" = 'sound/vox_fem/syndicate.ogg', -"system" = 'sound/vox_fem/system.ogg', -"systems" = 'sound/vox_fem/systems.ogg', -"table" = 'sound/vox_fem/table.ogg', -"tactical" = 'sound/vox_fem/tactical.ogg', -"take" = 'sound/vox_fem/take.ogg', -"talk" = 'sound/vox_fem/talk.ogg', -"tampered" = 'sound/vox_fem/tampered.ogg', -"tango" = 'sound/vox_fem/tango.ogg', -"tank" = 'sound/vox_fem/tank.ogg', -"target" = 'sound/vox_fem/target.ogg', -"team" = 'sound/vox_fem/team.ogg', -"technician" = 'sound/vox_fem/technician.ogg', -"technology" = 'sound/vox_fem/technology.ogg', -"tech" = 'sound/vox_fem/tech.ogg', -"temperature" = 'sound/vox_fem/temperature.ogg', -"temporal" = 'sound/vox_fem/temporal.ogg', -"ten" = 'sound/vox_fem/ten.ogg', -"terminal" = 'sound/vox_fem/terminal.ogg', -"terminated" = 'sound/vox_fem/terminated.ogg', -"termination" = 'sound/vox_fem/termination.ogg', -"test" = 'sound/vox_fem/test.ogg', -"text" = 'sound/vox_fem/text.ogg', -"that" = 'sound/vox_fem/that.ogg', -"theater" = 'sound/vox_fem/theater.ogg', -"them" = 'sound/vox_fem/them.ogg', -"then" = 'sound/vox_fem/then.ogg', -"the" = 'sound/vox_fem/the.ogg', -"there" = 'sound/vox_fem/there.ogg', -"they" = 'sound/vox_fem/they.ogg', -"third" = 'sound/vox_fem/third.ogg', -"thirteen" = 'sound/vox_fem/thirteen.ogg', -"thirty" = 'sound/vox_fem/thirty.ogg', -"this" = 'sound/vox_fem/this.ogg', -"those" = 'sound/vox_fem/those.ogg', -"thousand" = 'sound/vox_fem/thousand.ogg', -"threat" = 'sound/vox_fem/threat.ogg', -"three" = 'sound/vox_fem/three.ogg', -"through" = 'sound/vox_fem/through.ogg', -"tide" = 'sound/vox_fem/tide.ogg', -"time" = 'sound/vox_fem/time.ogg', -"t" = 'sound/vox_fem/t.ogg', -"to" = 'sound/vox_fem/to.ogg', -"top" = 'sound/vox_fem/top.ogg', -"topside" = 'sound/vox_fem/topside.ogg', -"touch" = 'sound/vox_fem/touch.ogg', -"towards" = 'sound/vox_fem/towards.ogg', -"toxins" = 'sound/vox_fem/toxins.ogg', -"track" = 'sound/vox_fem/track.ogg', -"train" = 'sound/vox_fem/train.ogg', -"traitor" = 'sound/vox_fem/traitor.ogg', -"transportation" = 'sound/vox_fem/transportation.ogg', -"truck" = 'sound/vox_fem/truck.ogg', -"true" = 'sound/vox_fem/true.ogg', -"tunnel" = 'sound/vox_fem/tunnel.ogg', -"turn" = 'sound/vox_fem/turn.ogg', -"turret" = 'sound/vox_fem/turret.ogg', -"twelve" = 'sound/vox_fem/twelve.ogg', -"twenty" = 'sound/vox_fem/twenty.ogg', -"two" = 'sound/vox_fem/two.ogg', -"ughh" = 'sound/vox_fem/ughh.ogg', -"ugh" = 'sound/vox_fem/ugh.ogg', -"unable" = 'sound/vox_fem/unable.ogg', -"unauthorized" = 'sound/vox_fem/unauthorized.ogg', -"under" = 'sound/vox_fem/under.ogg', -"uniform" = 'sound/vox_fem/uniform.ogg', -"unknown" = 'sound/vox_fem/unknown.ogg', -"unlocked" = 'sound/vox_fem/unlocked.ogg', -"unsafe" = 'sound/vox_fem/unsafe.ogg', -"until" = 'sound/vox_fem/until.ogg', -"u" = 'sound/vox_fem/u.ogg', -"updated" = 'sound/vox_fem/updated.ogg', -"update" = 'sound/vox_fem/update.ogg', -"updating" = 'sound/vox_fem/updating.ogg', -"upload" = 'sound/vox_fem/upload.ogg', -"up" = 'sound/vox_fem/up.ogg', -"upper" = 'sound/vox_fem/upper.ogg', -"uranium" = 'sound/vox_fem/uranium.ogg', -"usa" = 'sound/vox_fem/usa.ogg', -"used" = 'sound/vox_fem/used.ogg', -"use" = 'sound/vox_fem/use.ogg', -"user" = 'sound/vox_fem/user.ogg', -"us" = 'sound/vox_fem/us.ogg', -"vacate" = 'sound/vox_fem/vacate.ogg', -"vacuum" = 'sound/vox_fem/vacuum.ogg', -"valid" = 'sound/vox_fem/valid.ogg', -"vapor" = 'sound/vox_fem/vapor.ogg', -"vendor" = 'sound/vox_fem/vendor.ogg', -"ventilation" = 'sound/vox_fem/ventilation.ogg', -"vent" = 'sound/vox_fem/vent.ogg', -"very" = 'sound/vox_fem/very.ogg', -"victor" = 'sound/vox_fem/victor.ogg', -"violated" = 'sound/vox_fem/violated.ogg', -"violation" = 'sound/vox_fem/violation.ogg', -"virologist" = 'sound/vox_fem/virologist.ogg', -"virology" = 'sound/vox_fem/virology.ogg', -"virus" = 'sound/vox_fem/virus.ogg', -"vitals" = 'sound/vox_fem/vitals.ogg', -"v" = 'sound/vox_fem/v.ogg', -"voltage" = 'sound/vox_fem/voltage.ogg', -"vox_login" = 'sound/vox_fem/vox_login.ogg', -"vox" = 'sound/vox_fem/vox.ogg', -"voxtest" = 'sound/vox_fem/voxtest.ogg', -"walk" = 'sound/vox_fem/walk.ogg', -"wall" = 'sound/vox_fem/wall.ogg', -"wanker" = 'sound/vox_fem/wanker.ogg', -"wanted" = 'sound/vox_fem/wanted.ogg', -"want" = 'sound/vox_fem/want.ogg', -"warden" = 'sound/vox_fem/warden.ogg', -"warm" = 'sound/vox_fem/warm.ogg', -"warning" = 'sound/vox_fem/warning.ogg', -"warn" = 'sound/vox_fem/warn.ogg', -"waste" = 'sound/vox_fem/waste.ogg', -"water" = 'sound/vox_fem/water.ogg', -"weak" = 'sound/vox_fem/weak.ogg', -"weapon" = 'sound/vox_fem/weapon.ogg', -"welcome" = 'sound/vox_fem/welcome.ogg', -"we" = 'sound/vox_fem/we.ogg', -"west" = 'sound/vox_fem/west.ogg', -"wew" = 'sound/vox_fem/wew.ogg', -"what" = 'sound/vox_fem/what.ogg', -"when" = 'sound/vox_fem/when.ogg', -"where" = 'sound/vox_fem/where.ogg', -"whiskey" = 'sound/vox_fem/whiskey.ogg', -"white" = 'sound/vox_fem/white.ogg', -"why" = 'sound/vox_fem/why.ogg', -"wilco" = 'sound/vox_fem/wilco.ogg', -"will" = 'sound/vox_fem/will.ogg', -"wing" = 'sound/vox_fem/wing.ogg', -"wire" = 'sound/vox_fem/wire.ogg', -"with" = 'sound/vox_fem/with.ogg', -"without" = 'sound/vox_fem/without.ogg', -"wizard" = 'sound/vox_fem/wizard.ogg', -"w" = 'sound/vox_fem/w.ogg', -"wood" = 'sound/vox_fem/wood.ogg', -"woody" = 'sound/vox_fem/woody.ogg', -"woop" = 'sound/vox_fem/woop.ogg', -"wow" = 'sound/vox_fem/wow.ogg', -"xenobiology" = 'sound/vox_fem/xenobiology.ogg', -"xenomorph" = 'sound/vox_fem/xenomorph.ogg', -"xenomorphs" = 'sound/vox_fem/xenomorphs.ogg', -"xeno" = 'sound/vox_fem/xeno.ogg', -"x" = 'sound/vox_fem/x.ogg', -"yankee" = 'sound/vox_fem/yankee.ogg', -"yards" = 'sound/vox_fem/yards.ogg', -"year" = 'sound/vox_fem/year.ogg', -"yellow" = 'sound/vox_fem/yellow.ogg', -"yes" = 'sound/vox_fem/yes.ogg', -"y" = 'sound/vox_fem/y.ogg', -"you" = 'sound/vox_fem/you.ogg', -"your" = 'sound/vox_fem/your.ogg', -"yourself" = 'sound/vox_fem/yourself.ogg', -"zero" = 'sound/vox_fem/zero.ogg', -"z" = 'sound/vox_fem/z.ogg', -"zombie" = 'sound/vox_fem/zombie.ogg', -"zone" = 'sound/vox_fem/zone.ogg', -"zulu" = 'sound/vox_fem/zulu.ogg')) - -//for vim -// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox\/\1',/g -GLOBAL_LIST_INIT(vox_sounds_male, list("," = 'sound/vox/_comma.ogg', -"." = 'sound/vox/_period.ogg', -"a" = 'sound/vox/a.ogg', -"accelerating" = 'sound/vox/accelerating.ogg', -"accelerator" = 'sound/vox/accelerator.ogg', -"accepted" = 'sound/vox/accepted.ogg', -"access" = 'sound/vox/access.ogg', -"acknowledge" = 'sound/vox/acknowledge.ogg', -"acknowledged" = 'sound/vox/acknowledged.ogg', -"acquired" = 'sound/vox/acquired.ogg', -"acquisition" = 'sound/vox/acquisition.ogg', -"across" = 'sound/vox/across.ogg', -"activate" = 'sound/vox/activate.ogg', -"activated" = 'sound/vox/activated.ogg', -"activity" = 'sound/vox/activity.ogg', -"adios" = 'sound/vox/adios.ogg', -"administration" = 'sound/vox/administration.ogg', -"advanced" = 'sound/vox/advanced.ogg', -"after" = 'sound/vox/after.ogg', -"agent" = 'sound/vox/agent.ogg', -"alarm" = 'sound/vox/alarm.ogg', -"alert" = 'sound/vox/alert.ogg', -"alien" = 'sound/vox/alien.ogg', -"aligned" = 'sound/vox/aligned.ogg', -"all" = 'sound/vox/all.ogg', -"alpha" = 'sound/vox/alpha.ogg', -"am" = 'sound/vox/am.ogg', -"amigo" = 'sound/vox/amigo.ogg', -"ammunition" = 'sound/vox/ammunition.ogg', -"an" = 'sound/vox/an.ogg', -"and" = 'sound/vox/and.ogg', -"announcement" = 'sound/vox/announcement.ogg', -"anomalous" = 'sound/vox/anomalous.ogg', -"antenna" = 'sound/vox/antenna.ogg', -"any" = 'sound/vox/any.ogg', -"apprehend" = 'sound/vox/apprehend.ogg', -"approach" = 'sound/vox/approach.ogg', -"are" = 'sound/vox/are.ogg', -"area" = 'sound/vox/area.ogg', -"arm" = 'sound/vox/arm.ogg', -"armed" = 'sound/vox/armed.ogg', -"armor" = 'sound/vox/armor.ogg', -"armory" = 'sound/vox/armory.ogg', -"arrest" = 'sound/vox/arrest.ogg', -"ass" = 'sound/vox/ass.ogg', -"at" = 'sound/vox/at.ogg', -"atomic" = 'sound/vox/atomic.ogg', -"attention" = 'sound/vox/attention.ogg', -"authorize" = 'sound/vox/authorize.ogg', -"authorized" = 'sound/vox/authorized.ogg', -"automatic" = 'sound/vox/automatic.ogg', -"away" = 'sound/vox/away.ogg', -"b" = 'sound/vox/b.ogg', -"back" = 'sound/vox/back.ogg', -"backman" = 'sound/vox/backman.ogg', -"bad" = 'sound/vox/bad.ogg', -"bag" = 'sound/vox/bag.ogg', -"bailey" = 'sound/vox/bailey.ogg', -"barracks" = 'sound/vox/barracks.ogg', -"base" = 'sound/vox/base.ogg', -"bay" = 'sound/vox/bay.ogg', -"be" = 'sound/vox/be.ogg', -"been" = 'sound/vox/been.ogg', -"before" = 'sound/vox/before.ogg', -"beyond" = 'sound/vox/beyond.ogg', -"biohazard" = 'sound/vox/biohazard.ogg', -"biological" = 'sound/vox/biological.ogg', -"birdwell" = 'sound/vox/birdwell.ogg', -"bizwarn" = 'sound/vox/bizwarn.ogg', -"black" = 'sound/vox/black.ogg', -"blast" = 'sound/vox/blast.ogg', -"blocked" = 'sound/vox/blocked.ogg', -"bloop" = 'sound/vox/bloop.ogg', -"blue" = 'sound/vox/blue.ogg', -"bottom" = 'sound/vox/bottom.ogg', -"bravo" = 'sound/vox/bravo.ogg', -"breach" = 'sound/vox/breach.ogg', -"breached" = 'sound/vox/breached.ogg', -"break" = 'sound/vox/break.ogg', -"bridge" = 'sound/vox/bridge.ogg', -"bust" = 'sound/vox/bust.ogg', -"but" = 'sound/vox/but.ogg', -"button" = 'sound/vox/button.ogg', -"buzwarn" = 'sound/vox/buzwarn.ogg', -"bypass" = 'sound/vox/bypass.ogg', -"c" = 'sound/vox/c.ogg', -"cable" = 'sound/vox/cable.ogg', -"call" = 'sound/vox/call.ogg', -"called" = 'sound/vox/called.ogg', -"canal" = 'sound/vox/canal.ogg', -"cap" = 'sound/vox/cap.ogg', -"captain" = 'sound/vox/captain.ogg', -"capture" = 'sound/vox/capture.ogg', -"captured" = 'sound/vox/captured.ogg', -"ceiling" = 'sound/vox/ceiling.ogg', -"celsius" = 'sound/vox/celsius.ogg', -"center" = 'sound/vox/center.ogg', -"centi" = 'sound/vox/centi.ogg', -"central" = 'sound/vox/central.ogg', -"chamber" = 'sound/vox/chamber.ogg', -"charlie" = 'sound/vox/charlie.ogg', -"check" = 'sound/vox/check.ogg', -"checkpoint" = 'sound/vox/checkpoint.ogg', -"chemical" = 'sound/vox/chemical.ogg', -"cleanup" = 'sound/vox/cleanup.ogg', -"clear" = 'sound/vox/clear.ogg', -"clearance" = 'sound/vox/clearance.ogg', -"close" = 'sound/vox/close.ogg', -"clown" = 'sound/vox/clown.ogg', -"code" = 'sound/vox/code.ogg', -"coded" = 'sound/vox/coded.ogg', -"collider" = 'sound/vox/collider.ogg', -"command" = 'sound/vox/command.ogg', -"communication" = 'sound/vox/communication.ogg', -"complex" = 'sound/vox/complex.ogg', -"computer" = 'sound/vox/computer.ogg', -"condition" = 'sound/vox/condition.ogg', -"containment" = 'sound/vox/containment.ogg', -"contamination" = 'sound/vox/contamination.ogg', -"control" = 'sound/vox/control.ogg', -"coolant" = 'sound/vox/coolant.ogg', -"coomer" = 'sound/vox/coomer.ogg', -"core" = 'sound/vox/core.ogg', -"correct" = 'sound/vox/correct.ogg', -"corridor" = 'sound/vox/corridor.ogg', -"crew" = 'sound/vox/crew.ogg', -"cross" = 'sound/vox/cross.ogg', -"cryogenic" = 'sound/vox/cryogenic.ogg', -"d" = 'sound/vox/d.ogg', -"dadeda" = 'sound/vox/dadeda.ogg', -"damage" = 'sound/vox/damage.ogg', -"damaged" = 'sound/vox/damaged.ogg', -"danger" = 'sound/vox/danger.ogg', -"day" = 'sound/vox/day.ogg', -"deactivated" = 'sound/vox/deactivated.ogg', -"decompression" = 'sound/vox/decompression.ogg', -"decontamination" = 'sound/vox/decontamination.ogg', -"deeoo" = 'sound/vox/deeoo.ogg', -"defense" = 'sound/vox/defense.ogg', -"degrees" = 'sound/vox/degrees.ogg', -"delta" = 'sound/vox/delta.ogg', -"denied" = 'sound/vox/denied.ogg', -"deploy" = 'sound/vox/deploy.ogg', -"deployed" = 'sound/vox/deployed.ogg', -"destroy" = 'sound/vox/destroy.ogg', -"destroyed" = 'sound/vox/destroyed.ogg', -"detain" = 'sound/vox/detain.ogg', -"detected" = 'sound/vox/detected.ogg', -"detonation" = 'sound/vox/detonation.ogg', -"device" = 'sound/vox/device.ogg', -"did" = 'sound/vox/did.ogg', -"die" = 'sound/vox/die.ogg', -"dimensional" = 'sound/vox/dimensional.ogg', -"dirt" = 'sound/vox/dirt.ogg', -"disengaged" = 'sound/vox/disengaged.ogg', -"dish" = 'sound/vox/dish.ogg', -"disposal" = 'sound/vox/disposal.ogg', -"distance" = 'sound/vox/distance.ogg', -"distortion" = 'sound/vox/distortion.ogg', -"do" = 'sound/vox/do.ogg', -"doctor" = 'sound/vox/doctor.ogg', -"doop" = 'sound/vox/doop.ogg', -"door" = 'sound/vox/door.ogg', -"down" = 'sound/vox/down.ogg', -"dual" = 'sound/vox/dual.ogg', -"duct" = 'sound/vox/duct.ogg', -"e" = 'sound/vox/e.ogg', -"east" = 'sound/vox/east.ogg', -"echo" = 'sound/vox/echo.ogg', -"ed" = 'sound/vox/ed.ogg', -"effect" = 'sound/vox/effect.ogg', -"egress" = 'sound/vox/egress.ogg', -"eight" = 'sound/vox/eight.ogg', -"eighteen" = 'sound/vox/eighteen.ogg', -"eighty" = 'sound/vox/eighty.ogg', -"electric" = 'sound/vox/electric.ogg', -"electromagnetic" = 'sound/vox/electromagnetic.ogg', -"elevator" = 'sound/vox/elevator.ogg', -"eleven" = 'sound/vox/eleven.ogg', -"eliminate" = 'sound/vox/eliminate.ogg', -"emergency" = 'sound/vox/emergency.ogg', -"enemy" = 'sound/vox/enemy.ogg', -"energy" = 'sound/vox/energy.ogg', -"engage" = 'sound/vox/engage.ogg', -"engaged" = 'sound/vox/engaged.ogg', -"engine" = 'sound/vox/engine.ogg', -"enter" = 'sound/vox/enter.ogg', -"entry" = 'sound/vox/entry.ogg', -"environment" = 'sound/vox/environment.ogg', -"error" = 'sound/vox/error.ogg', -"escape" = 'sound/vox/escape.ogg', -"evacuate" = 'sound/vox/evacuate.ogg', -"exchange" = 'sound/vox/exchange.ogg', -"exit" = 'sound/vox/exit.ogg', -"expect" = 'sound/vox/expect.ogg', -"experiment" = 'sound/vox/experiment.ogg', -"experimental" = 'sound/vox/experimental.ogg', -"explode" = 'sound/vox/explode.ogg', -"explosion" = 'sound/vox/explosion.ogg', -"exposure" = 'sound/vox/exposure.ogg', -"exterminate" = 'sound/vox/exterminate.ogg', -"extinguish" = 'sound/vox/extinguish.ogg', -"extinguisher" = 'sound/vox/extinguisher.ogg', -"extreme" = 'sound/vox/extreme.ogg', -"f" = 'sound/vox/f.ogg', -"face" = 'sound/vox/face.ogg', -"facility" = 'sound/vox/facility.ogg', -"fahrenheit" = 'sound/vox/fahrenheit.ogg', -"failed" = 'sound/vox/failed.ogg', -"failure" = 'sound/vox/failure.ogg', -"farthest" = 'sound/vox/farthest.ogg', -"fast" = 'sound/vox/fast.ogg', -"feet" = 'sound/vox/feet.ogg', -"field" = 'sound/vox/field.ogg', -"fifteen" = 'sound/vox/fifteen.ogg', -"fifth" = 'sound/vox/fifth.ogg', -"fifty" = 'sound/vox/fifty.ogg', -"final" = 'sound/vox/final.ogg', -"fine" = 'sound/vox/fine.ogg', -"fire" = 'sound/vox/fire.ogg', -"first" = 'sound/vox/first.ogg', -"five" = 'sound/vox/five.ogg', -"flag" = 'sound/vox/flag.ogg', -"flooding" = 'sound/vox/flooding.ogg', -"floor" = 'sound/vox/floor.ogg', -"fool" = 'sound/vox/fool.ogg', -"for" = 'sound/vox/for.ogg', -"forbidden" = 'sound/vox/forbidden.ogg', -"force" = 'sound/vox/force.ogg', -"forms" = 'sound/vox/forms.ogg', -"found" = 'sound/vox/found.ogg', -"four" = 'sound/vox/four.ogg', -"fourteen" = 'sound/vox/fourteen.ogg', -"fourth" = 'sound/vox/fourth.ogg', -"fourty" = 'sound/vox/fourty.ogg', -"foxtrot" = 'sound/vox/foxtrot.ogg', -"freeman" = 'sound/vox/freeman.ogg', -"freezer" = 'sound/vox/freezer.ogg', -"from" = 'sound/vox/from.ogg', -"front" = 'sound/vox/front.ogg', -"fuel" = 'sound/vox/fuel.ogg', -"g" = 'sound/vox/g.ogg', -"gay" = 'sound/vox/gay.ogg', -"get" = 'sound/vox/get.ogg', -"go" = 'sound/vox/go.ogg', -"going" = 'sound/vox/going.ogg', -"good" = 'sound/vox/good.ogg', -"goodbye" = 'sound/vox/goodbye.ogg', -"gordon" = 'sound/vox/gordon.ogg', -"got" = 'sound/vox/got.ogg', -"government" = 'sound/vox/government.ogg', -"granted" = 'sound/vox/granted.ogg', -"great" = 'sound/vox/great.ogg', -"green" = 'sound/vox/green.ogg', -"grenade" = 'sound/vox/grenade.ogg', -"guard" = 'sound/vox/guard.ogg', -"gulf" = 'sound/vox/gulf.ogg', -"gun" = 'sound/vox/gun.ogg', -"guthrie" = 'sound/vox/guthrie.ogg', -"handling" = 'sound/vox/handling.ogg', -"hangar" = 'sound/vox/hangar.ogg', -"has" = 'sound/vox/has.ogg', -"have" = 'sound/vox/have.ogg', -"hazard" = 'sound/vox/hazard.ogg', -"head" = 'sound/vox/head.ogg', -"health" = 'sound/vox/health.ogg', -"heat" = 'sound/vox/heat.ogg', -"helicopter" = 'sound/vox/helicopter.ogg', -"helium" = 'sound/vox/helium.ogg', -"hello" = 'sound/vox/hello.ogg', -"help" = 'sound/vox/help.ogg', -"here" = 'sound/vox/here.ogg', -"hide" = 'sound/vox/hide.ogg', -"high" = 'sound/vox/high.ogg', -"highest" = 'sound/vox/highest.ogg', -"hit" = 'sound/vox/hit.ogg', -"holds" = 'sound/vox/holds.ogg', -"hole" = 'sound/vox/hole.ogg', -"hostile" = 'sound/vox/hostile.ogg', -"hot" = 'sound/vox/hot.ogg', -"hotel" = 'sound/vox/hotel.ogg', -"hour" = 'sound/vox/hour.ogg', -"hours" = 'sound/vox/hours.ogg', -"hundred" = 'sound/vox/hundred.ogg', -"hydro" = 'sound/vox/hydro.ogg', -"i" = 'sound/vox/i.ogg', -"idiot" = 'sound/vox/idiot.ogg', -"illegal" = 'sound/vox/illegal.ogg', -"immediate" = 'sound/vox/immediate.ogg', -"immediately" = 'sound/vox/immediately.ogg', -"in" = 'sound/vox/in.ogg', -"inches" = 'sound/vox/inches.ogg', -"india" = 'sound/vox/india.ogg', -"ing" = 'sound/vox/ing.ogg', -"inoperative" = 'sound/vox/inoperative.ogg', -"inside" = 'sound/vox/inside.ogg', -"inspection" = 'sound/vox/inspection.ogg', -"inspector" = 'sound/vox/inspector.ogg', -"interchange" = 'sound/vox/interchange.ogg', -"intruder" = 'sound/vox/intruder.ogg', -"invallid" = 'sound/vox/invallid.ogg', -"invasion" = 'sound/vox/invasion.ogg', -"is" = 'sound/vox/is.ogg', -"it" = 'sound/vox/it.ogg', -"johnson" = 'sound/vox/johnson.ogg', -"juliet" = 'sound/vox/juliet.ogg', -"key" = 'sound/vox/key.ogg', -"kill" = 'sound/vox/kill.ogg', -"kilo" = 'sound/vox/kilo.ogg', -"kit" = 'sound/vox/kit.ogg', -"lab" = 'sound/vox/lab.ogg', -"lambda" = 'sound/vox/lambda.ogg', -"laser" = 'sound/vox/laser.ogg', -"last" = 'sound/vox/last.ogg', -"launch" = 'sound/vox/launch.ogg', -"leak" = 'sound/vox/leak.ogg', -"leave" = 'sound/vox/leave.ogg', -"left" = 'sound/vox/left.ogg', -"legal" = 'sound/vox/legal.ogg', -"level" = 'sound/vox/level.ogg', -"lever" = 'sound/vox/lever.ogg', -"lie" = 'sound/vox/lie.ogg', -"lieutenant" = 'sound/vox/lieutenant.ogg', -"life" = 'sound/vox/life.ogg', -"light" = 'sound/vox/light.ogg', -"lima" = 'sound/vox/lima.ogg', -"liquid" = 'sound/vox/liquid.ogg', -"loading" = 'sound/vox/loading.ogg', -"locate" = 'sound/vox/locate.ogg', -"located" = 'sound/vox/located.ogg', -"location" = 'sound/vox/location.ogg', -"lock" = 'sound/vox/lock.ogg', -"locked" = 'sound/vox/locked.ogg', -"locker" = 'sound/vox/locker.ogg', -"lockout" = 'sound/vox/lockout.ogg', -"lower" = 'sound/vox/lower.ogg', -"lowest" = 'sound/vox/lowest.ogg', -"magnetic" = 'sound/vox/magnetic.ogg', -"main" = 'sound/vox/main.ogg', -"maintenance" = 'sound/vox/maintenance.ogg', -"malfunction" = 'sound/vox/malfunction.ogg', -"man" = 'sound/vox/man.ogg', -"mass" = 'sound/vox/mass.ogg', -"materials" = 'sound/vox/materials.ogg', -"maximum" = 'sound/vox/maximum.ogg', -"may" = 'sound/vox/may.ogg', -"med" = 'sound/vox/med.ogg', -"medical" = 'sound/vox/medical.ogg', -"men" = 'sound/vox/men.ogg', -"mercy" = 'sound/vox/mercy.ogg', -"mesa" = 'sound/vox/mesa.ogg', -"message" = 'sound/vox/message.ogg', -"meter" = 'sound/vox/meter.ogg', -"micro" = 'sound/vox/micro.ogg', -"middle" = 'sound/vox/middle.ogg', -"mike" = 'sound/vox/mike.ogg', -"miles" = 'sound/vox/miles.ogg', -"military" = 'sound/vox/military.ogg', -"milli" = 'sound/vox/milli.ogg', -"million" = 'sound/vox/million.ogg', -"minefield" = 'sound/vox/minefield.ogg', -"minimum" = 'sound/vox/minimum.ogg', -"minutes" = 'sound/vox/minutes.ogg', -"mister" = 'sound/vox/mister.ogg', -"mode" = 'sound/vox/mode.ogg', -"motor" = 'sound/vox/motor.ogg', -"motorpool" = 'sound/vox/motorpool.ogg', -"move" = 'sound/vox/move.ogg', -"must" = 'sound/vox/must.ogg', -"nearest" = 'sound/vox/nearest.ogg', -"nice" = 'sound/vox/nice.ogg', -"nine" = 'sound/vox/nine.ogg', -"nineteen" = 'sound/vox/nineteen.ogg', -"ninety" = 'sound/vox/ninety.ogg', -"no" = 'sound/vox/no.ogg', -"nominal" = 'sound/vox/nominal.ogg', -"north" = 'sound/vox/north.ogg', -"not" = 'sound/vox/not.ogg', -"november" = 'sound/vox/november.ogg', -"now" = 'sound/vox/now.ogg', -"number" = 'sound/vox/number.ogg', -"objective" = 'sound/vox/objective.ogg', -"observation" = 'sound/vox/observation.ogg', -"of" = 'sound/vox/of.ogg', -"officer" = 'sound/vox/officer.ogg', -"ok" = 'sound/vox/ok.ogg', -"on" = 'sound/vox/on.ogg', -"one" = 'sound/vox/one.ogg', -"open" = 'sound/vox/open.ogg', -"operating" = 'sound/vox/operating.ogg', -"operations" = 'sound/vox/operations.ogg', -"operative" = 'sound/vox/operative.ogg', -"option" = 'sound/vox/option.ogg', -"order" = 'sound/vox/order.ogg', -"organic" = 'sound/vox/organic.ogg', -"oscar" = 'sound/vox/oscar.ogg', -"out" = 'sound/vox/out.ogg', -"outside" = 'sound/vox/outside.ogg', -"over" = 'sound/vox/over.ogg', -"overload" = 'sound/vox/overload.ogg', -"override" = 'sound/vox/override.ogg', -"pacify" = 'sound/vox/pacify.ogg', -"pain" = 'sound/vox/pain.ogg', -"pal" = 'sound/vox/pal.ogg', -"panel" = 'sound/vox/panel.ogg', -"percent" = 'sound/vox/percent.ogg', -"perimeter" = 'sound/vox/perimeter.ogg', -"permitted" = 'sound/vox/permitted.ogg', -"personnel" = 'sound/vox/personnel.ogg', -"pipe" = 'sound/vox/pipe.ogg', -"plant" = 'sound/vox/plant.ogg', -"platform" = 'sound/vox/platform.ogg', -"please" = 'sound/vox/please.ogg', -"point" = 'sound/vox/point.ogg', -"portal" = 'sound/vox/portal.ogg', -"power" = 'sound/vox/power.ogg', -"presence" = 'sound/vox/presence.ogg', -"press" = 'sound/vox/press.ogg', -"primary" = 'sound/vox/primary.ogg', -"proceed" = 'sound/vox/proceed.ogg', -"processing" = 'sound/vox/processing.ogg', -"progress" = 'sound/vox/progress.ogg', -"proper" = 'sound/vox/proper.ogg', -"propulsion" = 'sound/vox/propulsion.ogg', -"prosecute" = 'sound/vox/prosecute.ogg', -"protective" = 'sound/vox/protective.ogg', -"push" = 'sound/vox/push.ogg', -"quantum" = 'sound/vox/quantum.ogg', -"quebec" = 'sound/vox/quebec.ogg', -"question" = 'sound/vox/question.ogg', -"questioning" = 'sound/vox/questioning.ogg', -"quick" = 'sound/vox/quick.ogg', -"quit" = 'sound/vox/quit.ogg', -"radiation" = 'sound/vox/radiation.ogg', -"radioactive" = 'sound/vox/radioactive.ogg', -"rads" = 'sound/vox/rads.ogg', -"rapid" = 'sound/vox/rapid.ogg', -"reach" = 'sound/vox/reach.ogg', -"reached" = 'sound/vox/reached.ogg', -"reactor" = 'sound/vox/reactor.ogg', -"red" = 'sound/vox/red.ogg', -"relay" = 'sound/vox/relay.ogg', -"released" = 'sound/vox/released.ogg', -"remaining" = 'sound/vox/remaining.ogg', -"renegade" = 'sound/vox/renegade.ogg', -"repair" = 'sound/vox/repair.ogg', -"report" = 'sound/vox/report.ogg', -"reports" = 'sound/vox/reports.ogg', -"required" = 'sound/vox/required.ogg', -"research" = 'sound/vox/research.ogg', -"reset" = 'sound/vox/reset.ogg', -"resevoir" = 'sound/vox/resevoir.ogg', -"resistance" = 'sound/vox/resistance.ogg', -"returned" = 'sound/vox/returned.ogg', -"right" = 'sound/vox/right.ogg', -"rocket" = 'sound/vox/rocket.ogg', -"roger" = 'sound/vox/roger.ogg', -"romeo" = 'sound/vox/romeo.ogg', -"room" = 'sound/vox/room.ogg', -"round" = 'sound/vox/round.ogg', -"run" = 'sound/vox/run.ogg', -"safe" = 'sound/vox/safe.ogg', -"safety" = 'sound/vox/safety.ogg', -"sargeant" = 'sound/vox/sargeant.ogg', -"satellite" = 'sound/vox/satellite.ogg', -"save" = 'sound/vox/save.ogg', -"science" = 'sound/vox/science.ogg', -"scores" = 'sound/vox/scores.ogg', -"scream" = 'sound/vox/scream.ogg', -"screen" = 'sound/vox/screen.ogg', -"search" = 'sound/vox/search.ogg', -"second" = 'sound/vox/second.ogg', -"secondary" = 'sound/vox/secondary.ogg', -"seconds" = 'sound/vox/seconds.ogg', -"sector" = 'sound/vox/sector.ogg', -"secure" = 'sound/vox/secure.ogg', -"secured" = 'sound/vox/secured.ogg', -"security" = 'sound/vox/security.ogg', -"select" = 'sound/vox/select.ogg', -"selected" = 'sound/vox/selected.ogg', -"service" = 'sound/vox/service.ogg', -"seven" = 'sound/vox/seven.ogg', -"seventeen" = 'sound/vox/seventeen.ogg', -"seventy" = 'sound/vox/seventy.ogg', -"severe" = 'sound/vox/severe.ogg', -"sewage" = 'sound/vox/sewage.ogg', -"sewer" = 'sound/vox/sewer.ogg', -"shield" = 'sound/vox/shield.ogg', -"shipment" = 'sound/vox/shipment.ogg', -"shock" = 'sound/vox/shock.ogg', -"shoot" = 'sound/vox/shoot.ogg', -"shower" = 'sound/vox/shower.ogg', -"shut" = 'sound/vox/shut.ogg', -"side" = 'sound/vox/side.ogg', -"sierra" = 'sound/vox/sierra.ogg', -"sight" = 'sound/vox/sight.ogg', -"silo" = 'sound/vox/silo.ogg', -"six" = 'sound/vox/six.ogg', -"sixteen" = 'sound/vox/sixteen.ogg', -"sixty" = 'sound/vox/sixty.ogg', -"slime" = 'sound/vox/slime.ogg', -"slow" = 'sound/vox/slow.ogg', -"soldier" = 'sound/vox/soldier.ogg', -"some" = 'sound/vox/some.ogg', -"someone" = 'sound/vox/someone.ogg', -"something" = 'sound/vox/something.ogg', -"son" = 'sound/vox/son.ogg', -"sorry" = 'sound/vox/sorry.ogg', -"south" = 'sound/vox/south.ogg', -"squad" = 'sound/vox/squad.ogg', -"square" = 'sound/vox/square.ogg', -"stairway" = 'sound/vox/stairway.ogg', -"status" = 'sound/vox/status.ogg', -"sterile" = 'sound/vox/sterile.ogg', -"sterilization" = 'sound/vox/sterilization.ogg', -"stolen" = 'sound/vox/stolen.ogg', -"storage" = 'sound/vox/storage.ogg', -"sub" = 'sound/vox/sub.ogg', -"subsurface" = 'sound/vox/subsurface.ogg', -"sudden" = 'sound/vox/sudden.ogg', -"suit" = 'sound/vox/suit.ogg', -"superconducting" = 'sound/vox/superconducting.ogg', -"supercooled" = 'sound/vox/supercooled.ogg', -"supply" = 'sound/vox/supply.ogg', -"surface" = 'sound/vox/surface.ogg', -"surrender" = 'sound/vox/surrender.ogg', -"surround" = 'sound/vox/surround.ogg', -"surrounded" = 'sound/vox/surrounded.ogg', -"switch" = 'sound/vox/switch.ogg', -"system" = 'sound/vox/system.ogg', -"systems" = 'sound/vox/systems.ogg', -"tactical" = 'sound/vox/tactical.ogg', -"take" = 'sound/vox/take.ogg', -"talk" = 'sound/vox/talk.ogg', -"tango" = 'sound/vox/tango.ogg', -"tank" = 'sound/vox/tank.ogg', -"target" = 'sound/vox/target.ogg', -"team" = 'sound/vox/team.ogg', -"temperature" = 'sound/vox/temperature.ogg', -"temporal" = 'sound/vox/temporal.ogg', -"ten" = 'sound/vox/ten.ogg', -"terminal" = 'sound/vox/terminal.ogg', -"terminated" = 'sound/vox/terminated.ogg', -"termination" = 'sound/vox/termination.ogg', -"test" = 'sound/vox/test.ogg', -"that" = 'sound/vox/that.ogg', -"the" = 'sound/vox/the.ogg', -"then" = 'sound/vox/then.ogg', -"there" = 'sound/vox/there.ogg', -"third" = 'sound/vox/third.ogg', -"thirteen" = 'sound/vox/thirteen.ogg', -"thirty" = 'sound/vox/thirty.ogg', -"this" = 'sound/vox/this.ogg', -"those" = 'sound/vox/those.ogg', -"thousand" = 'sound/vox/thousand.ogg', -"threat" = 'sound/vox/threat.ogg', -"three" = 'sound/vox/three.ogg', -"through" = 'sound/vox/through.ogg', -"time" = 'sound/vox/time.ogg', -"to" = 'sound/vox/to.ogg', -"top" = 'sound/vox/top.ogg', -"topside" = 'sound/vox/topside.ogg', -"touch" = 'sound/vox/touch.ogg', -"towards" = 'sound/vox/towards.ogg', -"track" = 'sound/vox/track.ogg', -"train" = 'sound/vox/train.ogg', -"transportation" = 'sound/vox/transportation.ogg', -"truck" = 'sound/vox/truck.ogg', -"tunnel" = 'sound/vox/tunnel.ogg', -"turn" = 'sound/vox/turn.ogg', -"turret" = 'sound/vox/turret.ogg', -"twelve" = 'sound/vox/twelve.ogg', -"twenty" = 'sound/vox/twenty.ogg', -"two" = 'sound/vox/two.ogg', -"unauthorized" = 'sound/vox/unauthorized.ogg', -"under" = 'sound/vox/under.ogg', -"uniform" = 'sound/vox/uniform.ogg', -"unlocked" = 'sound/vox/unlocked.ogg', -"until" = 'sound/vox/until.ogg', -"up" = 'sound/vox/up.ogg', -"upper" = 'sound/vox/upper.ogg', -"uranium" = 'sound/vox/uranium.ogg', -"us" = 'sound/vox/us.ogg', -"usa" = 'sound/vox/usa.ogg', -"use" = 'sound/vox/use.ogg', -"used" = 'sound/vox/used.ogg', -"user" = 'sound/vox/user.ogg', -"vacate" = 'sound/vox/vacate.ogg', -"valid" = 'sound/vox/valid.ogg', -"vapor" = 'sound/vox/vapor.ogg', -"vent" = 'sound/vox/vent.ogg', -"ventillation" = 'sound/vox/ventillation.ogg', -"victor" = 'sound/vox/victor.ogg', -"violated" = 'sound/vox/violated.ogg', -"violation" = 'sound/vox/violation.ogg', -"voltage" = 'sound/vox/voltage.ogg', -"vox_login" = 'sound/vox/vox_login.ogg', -"walk" = 'sound/vox/walk.ogg', -"wall" = 'sound/vox/wall.ogg', -"want" = 'sound/vox/want.ogg', -"wanted" = 'sound/vox/wanted.ogg', -"warm" = 'sound/vox/warm.ogg', -"warn" = 'sound/vox/warn.ogg', -"warning" = 'sound/vox/warning.ogg', -"waste" = 'sound/vox/waste.ogg', -"water" = 'sound/vox/water.ogg', -"we" = 'sound/vox/we.ogg', -"weapon" = 'sound/vox/weapon.ogg', -"west" = 'sound/vox/west.ogg', -"whiskey" = 'sound/vox/whiskey.ogg', -"white" = 'sound/vox/white.ogg', -"wilco" = 'sound/vox/wilco.ogg', -"will" = 'sound/vox/will.ogg', -"with" = 'sound/vox/with.ogg', -"without" = 'sound/vox/without.ogg', -"woop" = 'sound/vox/woop.ogg', -"xeno" = 'sound/vox/xeno.ogg', -"yankee" = 'sound/vox/yankee.ogg', -"yards" = 'sound/vox/yards.ogg', -"year" = 'sound/vox/year.ogg', -"yellow" = 'sound/vox/yellow.ogg', -"yes" = 'sound/vox/yes.ogg', -"you" = 'sound/vox/you.ogg', -"your" = 'sound/vox/your.ogg', -"yourself" = 'sound/vox/yourself.ogg', -"zero" = 'sound/vox/zero.ogg', -"zone" = 'sound/vox/zone.ogg', -"zulu" = 'sound/vox/zulu.ogg',)) +// List is required to compile the resources into the game when it loads. +// Dynamically loading it has bad results with sounds overtaking each other, even with the wait variable. +#ifdef AI_VOX + +// Regex for collecting a list of ogg files +// (([a-zA-Z,.]+)\.ogg) + +// For vim +// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox_fem\/\1',/g +GLOBAL_LIST_INIT(vox_sounds, list("abduction" = 'sound/vox_fem/abduction.ogg', +"abortions" = 'sound/vox_fem/abortions.ogg', +"above" = 'sound/vox_fem/above.ogg', +"abstain" = 'sound/vox_fem/abstain.ogg', +"accelerating" = 'sound/vox_fem/accelerating.ogg', +"accelerator" = 'sound/vox_fem/accelerator.ogg', +"accepted" = 'sound/vox_fem/accepted.ogg', +"access" = 'sound/vox_fem/access.ogg', +"acknowledged" = 'sound/vox_fem/acknowledged.ogg', +"acknowledge" = 'sound/vox_fem/acknowledge.ogg', +"acquired" = 'sound/vox_fem/acquired.ogg', +"acquisition" = 'sound/vox_fem/acquisition.ogg', +"across" = 'sound/vox_fem/across.ogg', +"activated" = 'sound/vox_fem/activated.ogg', +"activate" = 'sound/vox_fem/activate.ogg', +"activity" = 'sound/vox_fem/activity.ogg', +"adios" = 'sound/vox_fem/adios.ogg', +"administration" = 'sound/vox_fem/administration.ogg', +"advanced" = 'sound/vox_fem/advanced.ogg', +"advised" = 'sound/vox_fem/advised.ogg', +"after" = 'sound/vox_fem/after.ogg', +"aft" = 'sound/vox_fem/aft.ogg', +"agent" = 'sound/vox_fem/agent.ogg', +"ai" = 'sound/vox_fem/ai.ogg', +"airlock" = 'sound/vox_fem/airlock.ogg', +"air" = 'sound/vox_fem/air.ogg', +"alarm" = 'sound/vox_fem/alarm.ogg', +"alert" = 'sound/vox_fem/alert.ogg', +"alien" = 'sound/vox_fem/alien.ogg', +"aligned" = 'sound/vox_fem/aligned.ogg', +"all" = 'sound/vox_fem/all.ogg', +"alpha" = 'sound/vox_fem/alpha.ogg', +"also" = 'sound/vox_fem/also.ogg', +"amigo" = 'sound/vox_fem/amigo.ogg', +"ammunition" = 'sound/vox_fem/ammunition.ogg', +"am" = 'sound/vox_fem/am.ogg', +"and" = 'sound/vox_fem/and.ogg', +"animal" = 'sound/vox_fem/animal.ogg', +"announcement" = 'sound/vox_fem/announcement.ogg', +"an" = 'sound/vox_fem/an.ogg', +"anomalous" = 'sound/vox_fem/anomalous.ogg', +"answer" = 'sound/vox_fem/answer.ogg', +"antenna" = 'sound/vox_fem/antenna.ogg', +"any" = 'sound/vox_fem/any.ogg', +"a" = 'sound/vox_fem/a.ogg', +"apc" = 'sound/vox_fem/apc.ogg', +"apprehend" = 'sound/vox_fem/apprehend.ogg', +"approach" = 'sound/vox_fem/approach.ogg', +"area" = 'sound/vox_fem/area.ogg', +"are" = 'sound/vox_fem/are.ogg', +"armed" = 'sound/vox_fem/armed.ogg', +"arm" = 'sound/vox_fem/arm.ogg', +"armor" = 'sound/vox_fem/armor.ogg', +"armory" = 'sound/vox_fem/armory.ogg', +"array" = 'sound/vox_fem/array.ogg', +"arrest" = 'sound/vox_fem/arrest.ogg', +"asimov" = 'sound/vox_fem/asimov.ogg', +"asshole" = 'sound/vox_fem/asshole.ogg', +"assholes" = 'sound/vox_fem/assholes.ogg', +"assistance" = 'sound/vox_fem/assistance.ogg', +"assistant" = 'sound/vox_fem/assistant.ogg', +"ass" = 'sound/vox_fem/ass.ogg', +"atmosphere" = 'sound/vox_fem/atmosphere.ogg', +"atmospheric" = 'sound/vox_fem/atmospheric.ogg', +"atmospherics" = 'sound/vox_fem/atmospherics.ogg', +"at" = 'sound/vox_fem/at.ogg', +"atomic" = 'sound/vox_fem/atomic.ogg', +"attention" = 'sound/vox_fem/attention.ogg', +"authentication" = 'sound/vox_fem/authentication.ogg', +"authorized" = 'sound/vox_fem/authorized.ogg', +"authorize" = 'sound/vox_fem/authorize.ogg', +"automatic" = 'sound/vox_fem/automatic.ogg', +"away" = 'sound/vox_fem/away.ogg', +"awful" = 'sound/vox_fem/awful.ogg', +"backman" = 'sound/vox_fem/backman.ogg', +"back" = 'sound/vox_fem/back.ogg', +"bad" = 'sound/vox_fem/bad.ogg', +"bag" = 'sound/vox_fem/bag.ogg', +"bailey" = 'sound/vox_fem/bailey.ogg', +"bar" = 'sound/vox_fem/bar.ogg', +"barracks" = 'sound/vox_fem/barracks.ogg', +"bartender" = 'sound/vox_fem/bartender.ogg', +"base" = 'sound/vox_fem/base.ogg', +"bay" = 'sound/vox_fem/bay.ogg', +"beam" = 'sound/vox_fem/beam.ogg', +"been" = 'sound/vox_fem/been.ogg', +"beep" = 'sound/vox_fem/beep.ogg', +"before" = 'sound/vox_fem/before.ogg', +"below" = 'sound/vox_fem/below.ogg', +"be" = 'sound/vox_fem/be.ogg', +"beside" = 'sound/vox_fem/beside.ogg', +"beware" = 'sound/vox_fem/beware.ogg', +"beyond" = 'sound/vox_fem/beyond.ogg', +"biohazard" = 'sound/vox_fem/biohazard.ogg', +"biological" = 'sound/vox_fem/biological.ogg', +"birdwell" = 'sound/vox_fem/birdwell.ogg', +"bitches" = 'sound/vox_fem/bitches.ogg', +"bitch" = 'sound/vox_fem/bitch.ogg', +"bitcoin" = 'sound/vox_fem/bitcoin.ogg', +"black" = 'sound/vox_fem/black.ogg', +"blast" = 'sound/vox_fem/blast.ogg', +"bleed" = 'sound/vox_fem/bleed.ogg', +"blob" = 'sound/vox_fem/blob.ogg', +"blocked" = 'sound/vox_fem/blocked.ogg', +"blood" = 'sound/vox_fem/blood.ogg', +"bloop" = 'sound/vox_fem/bloop.ogg', +"blue" = 'sound/vox_fem/blue.ogg', +"b" = 'sound/vox_fem/b.ogg', +"bomb" = 'sound/vox_fem/bomb.ogg', +"bone" = 'sound/vox_fem/bone.ogg', +"botanist" = 'sound/vox_fem/botanist.ogg', +"botany" = 'sound/vox_fem/botany.ogg', +"bottom" = 'sound/vox_fem/bottom.ogg', +"bravo" = 'sound/vox_fem/bravo.ogg', +"breached" = 'sound/vox_fem/breached.ogg', +"breach" = 'sound/vox_fem/breach.ogg', +"break" = 'sound/vox_fem/break.ogg', +"bridge" = 'sound/vox_fem/bridge.ogg', +"brig" = 'sound/vox_fem/brig.ogg', +"bust" = 'sound/vox_fem/bust.ogg', +"but" = 'sound/vox_fem/but.ogg', +"button" = 'sound/vox_fem/button.ogg', +"bypass" = 'sound/vox_fem/bypass.ogg', +"cable" = 'sound/vox_fem/cable.ogg', +"called" = 'sound/vox_fem/called.ogg', +"call" = 'sound/vox_fem/call.ogg', +"canal" = 'sound/vox_fem/canal.ogg', +"canister" = 'sound/vox_fem/canister.ogg', +"cap" = 'sound/vox_fem/cap.ogg', +"captain" = 'sound/vox_fem/captain.ogg', +"capture" = 'sound/vox_fem/capture.ogg', +"carbon" = 'sound/vox_fem/carbon.ogg', +"cargo" = 'sound/vox_fem/cargo.ogg', +"cat" = 'sound/vox_fem/cat.ogg', +"cease" = 'sound/vox_fem/cease.ogg', +"ceiling" = 'sound/vox_fem/ceiling.ogg', +"celsius" = 'sound/vox_fem/celsius.ogg', +"centcom" = 'sound/vox_fem/centcom.ogg', +"center" = 'sound/vox_fem/center.ogg', +"centi" = 'sound/vox_fem/centi.ogg', +"central" = 'sound/vox_fem/central.ogg', +"ce" = 'sound/vox_fem/ce.ogg', +"challenge" = 'sound/vox_fem/challenge.ogg', +"chamber" = 'sound/vox_fem/chamber.ogg', +"changed" = 'sound/vox_fem/changed.ogg', +"changeling" = 'sound/vox_fem/changeling.ogg', +"change" = 'sound/vox_fem/change.ogg', +"chapel" = 'sound/vox_fem/chapel.ogg', +"chaplain" = 'sound/vox_fem/chaplain.ogg', +"charlie" = 'sound/vox_fem/charlie.ogg', +"check" = 'sound/vox_fem/check.ogg', +"checkpoint" = 'sound/vox_fem/checkpoint.ogg', +"chemical" = 'sound/vox_fem/chemical.ogg', +"chemist" = 'sound/vox_fem/chemist.ogg', +"chief" = 'sound/vox_fem/chief.ogg', +"christ" = 'sound/vox_fem/christ.ogg', +"chuckle" = 'sound/vox_fem/chuckle.ogg', +"circuit" = 'sound/vox_fem/circuit.ogg', +"cleanup" = 'sound/vox_fem/cleanup.ogg', +"clearance" = 'sound/vox_fem/clearance.ogg', +"clear" = 'sound/vox_fem/clear.ogg', +"clockwork" = 'sound/vox_fem/clockwork.ogg', +"close" = 'sound/vox_fem/close.ogg', +"clowning" = 'sound/vox_fem/clowning.ogg', +"clown" = 'sound/vox_fem/clown.ogg', +"cmo" = 'sound/vox_fem/cmo.ogg', +"coded" = 'sound/vox_fem/coded.ogg', +"code" = 'sound/vox_fem/code.ogg', +"c" = 'sound/vox_fem/c.ogg', +"cold" = 'sound/vox_fem/cold.ogg', +"collider" = 'sound/vox_fem/collider.ogg', +"come" = 'sound/vox_fem/come.ogg', +"command" = 'sound/vox_fem/command.ogg', +"communication" = 'sound/vox_fem/communication.ogg', +"complex" = 'sound/vox_fem/complex.ogg', +"comply" = 'sound/vox_fem/comply.ogg', +"computer" = 'sound/vox_fem/computer.ogg', +"condition" = 'sound/vox_fem/condition.ogg', +"condom" = 'sound/vox_fem/condom.ogg', +"confirmed" = 'sound/vox_fem/confirmed.ogg', +"connor" = 'sound/vox_fem/connor.ogg', +"console2" = 'sound/vox_fem/console2.ogg', +"console" = 'sound/vox_fem/console.ogg', +"construct" = 'sound/vox_fem/construct.ogg', +"containment" = 'sound/vox_fem/containment.ogg', +"contamination" = 'sound/vox_fem/contamination.ogg', +"contraband" = 'sound/vox_fem/contraband.ogg', +"control" = 'sound/vox_fem/control.ogg', +"cook" = 'sound/vox_fem/cook.ogg', +"coolant" = 'sound/vox_fem/coolant.ogg', +"coomer" = 'sound/vox_fem/coomer.ogg', +"core" = 'sound/vox_fem/core.ogg', +"corgi" = 'sound/vox_fem/corgi.ogg', +"corporation" = 'sound/vox_fem/corporation.ogg', +"correct" = 'sound/vox_fem/correct.ogg', +"corridor" = 'sound/vox_fem/corridor.ogg', +"corridors" = 'sound/vox_fem/corridors.ogg', +"coward" = 'sound/vox_fem/coward.ogg', +"cowards" = 'sound/vox_fem/cowards.ogg', +"crate" = 'sound/vox_fem/crate.ogg', +"created" = 'sound/vox_fem/created.ogg', +"creature" = 'sound/vox_fem/creature.ogg', +"crew" = 'sound/vox_fem/crew.ogg', +"critical" = 'sound/vox_fem/critical.ogg', +"cross" = 'sound/vox_fem/cross.ogg', +"cryogenic" = 'sound/vox_fem/cryogenic.ogg', +"crystal" = 'sound/vox_fem/crystal.ogg', +"cultist" = 'sound/vox_fem/cultist.ogg', +"cult" = 'sound/vox_fem/cult.ogg', +"cunt" = 'sound/vox_fem/cunt.ogg', +"curator" = 'sound/vox_fem/curator.ogg', +"cyborg" = 'sound/vox_fem/cyborg.ogg', +"cyborgs" = 'sound/vox_fem/cyborgs.ogg', +"damaged" = 'sound/vox_fem/damaged.ogg', +"damage" = 'sound/vox_fem/damage.ogg', +"danger" = 'sound/vox_fem/danger.ogg', +"dangerous" = 'sound/vox_fem/dangerous.ogg', +"day" = 'sound/vox_fem/day.ogg', +"deactivated" = 'sound/vox_fem/deactivated.ogg', +"dead" = 'sound/vox_fem/dead.ogg', +"death" = 'sound/vox_fem/death.ogg', +"decompression" = 'sound/vox_fem/decompression.ogg', +"decontamination" = 'sound/vox_fem/decontamination.ogg', +"deeoo" = 'sound/vox_fem/deeoo.ogg', +"defense" = 'sound/vox_fem/defense.ogg', +"degrees" = 'sound/vox_fem/degrees.ogg', +"delta" = 'sound/vox_fem/delta.ogg', +"demon" = 'sound/vox_fem/demon.ogg', +"denied" = 'sound/vox_fem/denied.ogg', +"departures" = 'sound/vox_fem/departures.ogg', +"deployed" = 'sound/vox_fem/deployed.ogg', +"deploy" = 'sound/vox_fem/deploy.ogg', +"desire" = 'sound/vox_fem/desire.ogg', +"desist" = 'sound/vox_fem/desist.ogg', +"destroyed" = 'sound/vox_fem/destroyed.ogg', +"destroy" = 'sound/vox_fem/destroy.ogg', +"destruction" = 'sound/vox_fem/destruction.ogg', +"detain" = 'sound/vox_fem/detain.ogg', +"detected" = 'sound/vox_fem/detected.ogg', +"detective" = 'sound/vox_fem/detective.ogg', +"detonation" = 'sound/vox_fem/detonation.ogg', +"device" = 'sound/vox_fem/device.ogg', +"devil" = 'sound/vox_fem/devil.ogg', +"did" = 'sound/vox_fem/did.ogg', +"die" = 'sound/vox_fem/die.ogg', +"dimensional" = 'sound/vox_fem/dimensional.ogg', +"dioxide" = 'sound/vox_fem/dioxide.ogg', +"director" = 'sound/vox_fem/director.ogg', +"dirt" = 'sound/vox_fem/dirt.ogg', +"disabled" = 'sound/vox_fem/disabled.ogg', +"disease" = 'sound/vox_fem/disease.ogg', +"disengaged" = 'sound/vox_fem/disengaged.ogg', +"dish" = 'sound/vox_fem/dish.ogg', +"disk" = 'sound/vox_fem/disk.ogg', +"disposal" = 'sound/vox_fem/disposal.ogg', +"distance" = 'sound/vox_fem/distance.ogg', +"distortion" = 'sound/vox_fem/distortion.ogg', +"doctor" = 'sound/vox_fem/doctor.ogg', +"d" = 'sound/vox_fem/d.ogg', +"dog" = 'sound/vox_fem/dog.ogg', +"do" = 'sound/vox_fem/do.ogg', +"doomsday" = 'sound/vox_fem/doomsday.ogg', +"doop" = 'sound/vox_fem/doop.ogg', +"door" = 'sound/vox_fem/door.ogg', +"dormitory" = 'sound/vox_fem/dormitory.ogg', +"dot" = 'sound/vox_fem/dot.ogg', +"down" = 'sound/vox_fem/down.ogg', +"drone" = 'sound/vox_fem/drone.ogg', +"dual" = 'sound/vox_fem/dual.ogg', +"duct" = 'sound/vox_fem/duct.ogg', +"east" = 'sound/vox_fem/east.ogg', +"echo" = 'sound/vox_fem/echo.ogg', +"ed" = 'sound/vox_fem/ed.ogg', +"effect" = 'sound/vox_fem/effect.ogg', +"egress" = 'sound/vox_fem/egress.ogg', +"eighteen" = 'sound/vox_fem/eighteen.ogg', +"eight" = 'sound/vox_fem/eight.ogg', +"eighty" = 'sound/vox_fem/eighty.ogg', +"electric" = 'sound/vox_fem/electric.ogg', +"electromagnetic" = 'sound/vox_fem/electromagnetic.ogg', +"elevator" = 'sound/vox_fem/elevator.ogg', +"eleven" = 'sound/vox_fem/eleven.ogg', +"eliminate" = 'sound/vox_fem/eliminate.ogg', +"emergency" = 'sound/vox_fem/emergency.ogg', +"enabled" = 'sound/vox_fem/enabled.ogg', +"energy" = 'sound/vox_fem/energy.ogg', +"engaged" = 'sound/vox_fem/engaged.ogg', +"engage" = 'sound/vox_fem/engage.ogg', +"engineering" = 'sound/vox_fem/engineering.ogg', +"engineer" = 'sound/vox_fem/engineer.ogg', +"engine" = 'sound/vox_fem/engine.ogg', +"enter" = 'sound/vox_fem/enter.ogg', +"entity" = 'sound/vox_fem/entity.ogg', +"entry" = 'sound/vox_fem/entry.ogg', +"environment" = 'sound/vox_fem/environment.ogg', +"e" = 'sound/vox_fem/e.ogg', +"epic" = 'sound/vox_fem/epic.ogg', +"equipment" = 'sound/vox_fem/equipment.ogg', +"error" = 'sound/vox_fem/error.ogg', +"escape" = 'sound/vox_fem/escape.ogg', +"evacuate" = 'sound/vox_fem/evacuate.ogg', +"eva" = 'sound/vox_fem/eva.ogg', +"exchange" = 'sound/vox_fem/exchange.ogg', +"exit" = 'sound/vox_fem/exit.ogg', +"expect" = 'sound/vox_fem/expect.ogg', +"experimental" = 'sound/vox_fem/experimental.ogg', +"experiment" = 'sound/vox_fem/experiment.ogg', +"explode" = 'sound/vox_fem/explode.ogg', +"explosion" = 'sound/vox_fem/explosion.ogg', +"explosive" = 'sound/vox_fem/explosive.ogg', +"exposure" = 'sound/vox_fem/exposure.ogg', +"exterminate" = 'sound/vox_fem/exterminate.ogg', +"extinguisher" = 'sound/vox_fem/extinguisher.ogg', +"extinguish" = 'sound/vox_fem/extinguish.ogg', +"extreme" = 'sound/vox_fem/extreme.ogg', +"facility" = 'sound/vox_fem/facility.ogg', +"factory" = 'sound/vox_fem/factory.ogg', +"fahrenheit" = 'sound/vox_fem/fahrenheit.ogg', +"failed" = 'sound/vox_fem/failed.ogg', +"failure" = 'sound/vox_fem/failure.ogg', +"false" = 'sound/vox_fem/false.ogg', +"farthest" = 'sound/vox_fem/farthest.ogg', +"fast" = 'sound/vox_fem/fast.ogg', +"fauna" = 'sound/vox_fem/fauna.ogg', +"feet" = 'sound/vox_fem/feet.ogg', +"field" = 'sound/vox_fem/field.ogg', +"fifteen" = 'sound/vox_fem/fifteen.ogg', +"fifth" = 'sound/vox_fem/fifth.ogg', +"fifty" = 'sound/vox_fem/fifty.ogg', +"final" = 'sound/vox_fem/final.ogg', +"fine" = 'sound/vox_fem/fine.ogg', +"fire" = 'sound/vox_fem/fire.ogg', +"first" = 'sound/vox_fem/first.ogg', +"five" = 'sound/vox_fem/five.ogg', +"fix" = 'sound/vox_fem/fix.ogg', +"flooding" = 'sound/vox_fem/flooding.ogg', +"floor" = 'sound/vox_fem/floor.ogg', +"flyman" = 'sound/vox_fem/flyman.ogg', +"f" = 'sound/vox_fem/f.ogg', +"fool" = 'sound/vox_fem/fool.ogg', +"forbidden" = 'sound/vox_fem/forbidden.ogg', +"force" = 'sound/vox_fem/force.ogg', +"fore" = 'sound/vox_fem/fore.ogg', +"formed" = 'sound/vox_fem/formed.ogg', +"form" = 'sound/vox_fem/form.ogg', +"forms" = 'sound/vox_fem/forms.ogg', +"for" = 'sound/vox_fem/for.ogg', +"found" = 'sound/vox_fem/found.ogg', +"four" = 'sound/vox_fem/four.ogg', +"fourteen" = 'sound/vox_fem/fourteen.ogg', +"fourth" = 'sound/vox_fem/fourth.ogg', +"fourty" = 'sound/vox_fem/fourty.ogg', +"foxtrot" = 'sound/vox_fem/foxtrot.ogg', +"freeman" = 'sound/vox_fem/freeman.ogg', +"free" = 'sound/vox_fem/free.ogg', +"freezer" = 'sound/vox_fem/freezer.ogg', +"freezing" = 'sound/vox_fem/freezing.ogg', +"from" = 'sound/vox_fem/from.ogg', +"front" = 'sound/vox_fem/front.ogg', +"fucking" = 'sound/vox_fem/fucking.ogg', +"fuck" = 'sound/vox_fem/fuck.ogg', +"fucks" = 'sound/vox_fem/fucks.ogg', +"fuel" = 'sound/vox_fem/fuel.ogg', +"gas" = 'sound/vox_fem/gas.ogg', +"generator" = 'sound/vox_fem/generator.ogg', +"geneticist" = 'sound/vox_fem/geneticist.ogg', +"get" = 'sound/vox_fem/get.ogg', +"glory" = 'sound/vox_fem/glory.ogg', +"god" = 'sound/vox_fem/god.ogg', +"g" = 'sound/vox_fem/g.ogg', +"going" = 'sound/vox_fem/going.ogg', +"golem" = 'sound/vox_fem/golem.ogg', +"goodbye" = 'sound/vox_fem/goodbye.ogg', +"good" = 'sound/vox_fem/good.ogg', +"go" = 'sound/vox_fem/go.ogg', +"gordon" = 'sound/vox_fem/gordon.ogg', +"got" = 'sound/vox_fem/got.ogg', +"government" = 'sound/vox_fem/government.ogg', +"granted" = 'sound/vox_fem/granted.ogg', +"gravity" = 'sound/vox_fem/gravity.ogg', +"gray" = 'sound/vox_fem/gray.ogg', +"great" = 'sound/vox_fem/great.ogg', +"green" = 'sound/vox_fem/green.ogg', +"grenade" = 'sound/vox_fem/grenade.ogg', +"guard" = 'sound/vox_fem/guard.ogg', +"gulf" = 'sound/vox_fem/gulf.ogg', +"gun" = 'sound/vox_fem/gun.ogg', +"guthrie" = 'sound/vox_fem/guthrie.ogg', +"hacker" = 'sound/vox_fem/hacker.ogg', +"hackers" = 'sound/vox_fem/hackers.ogg', +"hall" = 'sound/vox_fem/hall.ogg', +"hallway" = 'sound/vox_fem/hallway.ogg', +"handling" = 'sound/vox_fem/handling.ogg', +"hangar" = 'sound/vox_fem/hangar.ogg', +"harmful" = 'sound/vox_fem/harmful.ogg', +"harm" = 'sound/vox_fem/harm.ogg', +"has" = 'sound/vox_fem/has.ogg', +"have" = 'sound/vox_fem/have.ogg', +"hazard" = 'sound/vox_fem/hazard.ogg', +"head" = 'sound/vox_fem/head.ogg', +"health" = 'sound/vox_fem/health.ogg', +"heat" = 'sound/vox_fem/heat.ogg', +"helicopter" = 'sound/vox_fem/helicopter.ogg', +"helium" = 'sound/vox_fem/helium.ogg', +"hello" = 'sound/vox_fem/hello.ogg', +"help" = 'sound/vox_fem/help.ogg', +"he" = 'sound/vox_fem/he.ogg', +"here" = 'sound/vox_fem/here.ogg', +"hide" = 'sound/vox_fem/hide.ogg', +"highest" = 'sound/vox_fem/highest.ogg', +"high" = 'sound/vox_fem/high.ogg', +"hit" = 'sound/vox_fem/hit.ogg', +"h" = 'sound/vox_fem/h.ogg', +"hole" = 'sound/vox_fem/hole.ogg', +"honk" = 'sound/vox_fem/honk.ogg', +"hop" = 'sound/vox_fem/hop.ogg', +"hos" = 'sound/vox_fem/hos.ogg', +"hostile" = 'sound/vox_fem/hostile.ogg', +"hotel" = 'sound/vox_fem/hotel.ogg', +"hot" = 'sound/vox_fem/hot.ogg', +"hour" = 'sound/vox_fem/hour.ogg', +"hours" = 'sound/vox_fem/hours.ogg', +"how" = 'sound/vox_fem/how.ogg', +"human" = 'sound/vox_fem/human.ogg', +"humanoid" = 'sound/vox_fem/humanoid.ogg', +"humans" = 'sound/vox_fem/humans.ogg', +"hundred" = 'sound/vox_fem/hundred.ogg', +"hunger" = 'sound/vox_fem/hunger.ogg', +"hurt" = 'sound/vox_fem/hurt.ogg', +"hydro" = 'sound/vox_fem/hydro.ogg', +"hydroponics" = 'sound/vox_fem/hydroponics.ogg', +"ian" = 'sound/vox_fem/ian.ogg', +"idiot" = 'sound/vox_fem/idiot.ogg', +"if2" = 'sound/vox_fem/if2.ogg', +"if" = 'sound/vox_fem/if.ogg', +"illegal" = 'sound/vox_fem/illegal.ogg', +"immediately" = 'sound/vox_fem/immediately.ogg', +"immediate" = 'sound/vox_fem/immediate.ogg', +"immortal" = 'sound/vox_fem/immortal.ogg', +"impossible" = 'sound/vox_fem/impossible.ogg', +"inches" = 'sound/vox_fem/inches.ogg', +"india" = 'sound/vox_fem/india.ogg', +"ing" = 'sound/vox_fem/ing.ogg', +"in" = 'sound/vox_fem/in.ogg', +"inoperative" = 'sound/vox_fem/inoperative.ogg', +"inside" = 'sound/vox_fem/inside.ogg', +"inspection" = 'sound/vox_fem/inspection.ogg', +"inspector" = 'sound/vox_fem/inspector.ogg', +"interchange" = 'sound/vox_fem/interchange.ogg', +"internals" = 'sound/vox_fem/internals.ogg', +"intruder" = 'sound/vox_fem/intruder.ogg', +"invalid" = 'sound/vox_fem/invalid.ogg', +"invasion" = 'sound/vox_fem/invasion.ogg', +"i" = 'sound/vox_fem/i.ogg', +"is" = 'sound/vox_fem/is.ogg', +"it" = 'sound/vox_fem/it.ogg', +"janitor" = 'sound/vox_fem/janitor.ogg', +"jesus" = 'sound/vox_fem/jesus.ogg', +"j" = 'sound/vox_fem/j.ogg', +"johnson" = 'sound/vox_fem/johnson.ogg', +"juliet" = 'sound/vox_fem/juliet.ogg', +"key" = 'sound/vox_fem/key.ogg', +"kidnapped" = 'sound/vox_fem/kidnapped.ogg', +"kidnapping" = 'sound/vox_fem/kidnapping.ogg', +"killed" = 'sound/vox_fem/killed.ogg', +"kill" = 'sound/vox_fem/kill.ogg', +"kilo" = 'sound/vox_fem/kilo.ogg', +"kitchen" = 'sound/vox_fem/kitchen.ogg', +"kit" = 'sound/vox_fem/kit.ogg', +"k" = 'sound/vox_fem/k.ogg', +"lab" = 'sound/vox_fem/lab.ogg', +"lambda" = 'sound/vox_fem/lambda.ogg', +"laser" = 'sound/vox_fem/laser.ogg', +"last" = 'sound/vox_fem/last.ogg', +"launch" = 'sound/vox_fem/launch.ogg', +"lavaland" = 'sound/vox_fem/lavaland.ogg', +"law" = 'sound/vox_fem/law.ogg', +"laws" = 'sound/vox_fem/laws.ogg', +"lawyer" = 'sound/vox_fem/lawyer.ogg', +"leak" = 'sound/vox_fem/leak.ogg', +"leave" = 'sound/vox_fem/leave.ogg', +"left" = 'sound/vox_fem/left.ogg', +"legal" = 'sound/vox_fem/legal.ogg', +"level" = 'sound/vox_fem/level.ogg', +"lever" = 'sound/vox_fem/lever.ogg', +"library" = 'sound/vox_fem/library.ogg', +"lie" = 'sound/vox_fem/lie.ogg', +"lieutenant" = 'sound/vox_fem/lieutenant.ogg', +"lifeform" = 'sound/vox_fem/lifeform.ogg', +"life" = 'sound/vox_fem/life.ogg', +"light" = 'sound/vox_fem/light.ogg', +"lima" = 'sound/vox_fem/lima.ogg', +"liquid" = 'sound/vox_fem/liquid.ogg', +"live2" = 'sound/vox_fem/live2.ogg', +"live" = 'sound/vox_fem/live.ogg', +"lizard" = 'sound/vox_fem/lizard.ogg', +"loading" = 'sound/vox_fem/loading.ogg', +"located" = 'sound/vox_fem/located.ogg', +"locate" = 'sound/vox_fem/locate.ogg', +"location" = 'sound/vox_fem/location.ogg', +"locked" = 'sound/vox_fem/locked.ogg', +"locker" = 'sound/vox_fem/locker.ogg', +"lock" = 'sound/vox_fem/lock.ogg', +"lockout" = 'sound/vox_fem/lockout.ogg', +"l" = 'sound/vox_fem/l.ogg', +"long" = 'sound/vox_fem/long.ogg', +"look" = 'sound/vox_fem/look.ogg', +"loop" = 'sound/vox_fem/loop.ogg', +"loose" = 'sound/vox_fem/loose.ogg', +"lot" = 'sound/vox_fem/lot.ogg', +"lower" = 'sound/vox_fem/lower.ogg', +"lowest" = 'sound/vox_fem/lowest.ogg', +"lusty" = 'sound/vox_fem/lusty.ogg', +"machine" = 'sound/vox_fem/machine.ogg', +"magic" = 'sound/vox_fem/magic.ogg', +"magnetic" = 'sound/vox_fem/magnetic.ogg', +"main" = 'sound/vox_fem/main.ogg', +"maintenance" = 'sound/vox_fem/maintenance.ogg', +"malfunction" = 'sound/vox_fem/malfunction.ogg', +"man" = 'sound/vox_fem/man.ogg', +"many" = 'sound/vox_fem/many.ogg', +"mass" = 'sound/vox_fem/mass.ogg', +"materials" = 'sound/vox_fem/materials.ogg', +"maximum" = 'sound/vox_fem/maximum.ogg', +"may" = 'sound/vox_fem/may.ogg', +"meat" = 'sound/vox_fem/meat.ogg', +"medbay" = 'sound/vox_fem/medbay.ogg', +"medical" = 'sound/vox_fem/medical.ogg', +"megafauna" = 'sound/vox_fem/megafauna.ogg', +"men" = 'sound/vox_fem/men.ogg', +"me" = 'sound/vox_fem/me.ogg', +"mercy" = 'sound/vox_fem/mercy.ogg', +"mesa" = 'sound/vox_fem/mesa.ogg', +"message" = 'sound/vox_fem/message.ogg', +"meter" = 'sound/vox_fem/meter.ogg', +"micro" = 'sound/vox_fem/micro.ogg', +"middle" = 'sound/vox_fem/middle.ogg', +"mike" = 'sound/vox_fem/mike.ogg', +"miles" = 'sound/vox_fem/miles.ogg', +"military" = 'sound/vox_fem/military.ogg', +"milli" = 'sound/vox_fem/milli.ogg', +"million" = 'sound/vox_fem/million.ogg', +"mime" = 'sound/vox_fem/mime.ogg', +"minefield" = 'sound/vox_fem/minefield.ogg', +"miner" = 'sound/vox_fem/miner.ogg', +"minimum" = 'sound/vox_fem/minimum.ogg', +"minutes" = 'sound/vox_fem/minutes.ogg', +"mister" = 'sound/vox_fem/mister.ogg', +"mode" = 'sound/vox_fem/mode.ogg', +"modification" = 'sound/vox_fem/modification.ogg', +"m" = 'sound/vox_fem/m.ogg', +"money" = 'sound/vox_fem/money.ogg', +"monkey" = 'sound/vox_fem/monkey.ogg', +"moth" = 'sound/vox_fem/moth.ogg', +"motor" = 'sound/vox_fem/motor.ogg', +"motorpool" = 'sound/vox_fem/motorpool.ogg', +"move" = 'sound/vox_fem/move.ogg', +"multitude" = 'sound/vox_fem/multitude.ogg', +"murder" = 'sound/vox_fem/murder.ogg', +"must" = 'sound/vox_fem/must.ogg', +"my" = 'sound/vox_fem/my.ogg', +"mythic" = 'sound/vox_fem/mythic.ogg', +"nanotrasen" = 'sound/vox_fem/nanotrasen.ogg', +"nearest" = 'sound/vox_fem/nearest.ogg', +"need" = 'sound/vox_fem/need.ogg', +"nice" = 'sound/vox_fem/nice.ogg', +"nine" = 'sound/vox_fem/nine.ogg', +"nineteen" = 'sound/vox_fem/nineteen.ogg', +"ninety" = 'sound/vox_fem/ninety.ogg', +"nitrogen" = 'sound/vox_fem/nitrogen.ogg', +"n" = 'sound/vox_fem/n.ogg', +"nominal" = 'sound/vox_fem/nominal.ogg', +"no" = 'sound/vox_fem/no.ogg', +"north" = 'sound/vox_fem/north.ogg', +"not" = 'sound/vox_fem/not.ogg', +"november" = 'sound/vox_fem/november.ogg', +"now" = 'sound/vox_fem/now.ogg', +"nuclear" = 'sound/vox_fem/nuclear.ogg', +"nuke" = 'sound/vox_fem/nuke.ogg', +"number" = 'sound/vox_fem/number.ogg', +"objective" = 'sound/vox_fem/objective.ogg', +"observation" = 'sound/vox_fem/observation.ogg', +"obtain" = 'sound/vox_fem/obtain.ogg', +"office" = 'sound/vox_fem/office.ogg', +"officer" = 'sound/vox_fem/officer.ogg', +"off" = 'sound/vox_fem/off.ogg', +"of" = 'sound/vox_fem/of.ogg', +"," = 'sound/vox_fem/,.ogg', +"." = 'sound/vox_fem/..ogg', +"oh" = 'sound/vox_fem/oh.ogg', +"ok" = 'sound/vox_fem/ok.ogg', +"one" = 'sound/vox_fem/one.ogg', +"on" = 'sound/vox_fem/on.ogg', +"oof" = 'sound/vox_fem/oof.ogg', +"o" = 'sound/vox_fem/o.ogg', +"open" = 'sound/vox_fem/open.ogg', +"operating" = 'sound/vox_fem/operating.ogg', +"operations" = 'sound/vox_fem/operations.ogg', +"operative" = 'sound/vox_fem/operative.ogg', +"option" = 'sound/vox_fem/option.ogg', +"order" = 'sound/vox_fem/order.ogg', +"organic" = 'sound/vox_fem/organic.ogg', +"or" = 'sound/vox_fem/or.ogg', +"oscar" = 'sound/vox_fem/oscar.ogg', +"out" = 'sound/vox_fem/out.ogg', +"outside" = 'sound/vox_fem/outside.ogg', +"overload" = 'sound/vox_fem/overload.ogg', +"over" = 'sound/vox_fem/over.ogg', +"override" = 'sound/vox_fem/override.ogg', +"oxygen" = 'sound/vox_fem/oxygen.ogg', +"pacification" = 'sound/vox_fem/pacification.ogg', +"pacify" = 'sound/vox_fem/pacify.ogg', +"pain" = 'sound/vox_fem/pain.ogg', +"pal" = 'sound/vox_fem/pal.ogg', +"panel" = 'sound/vox_fem/panel.ogg', +"panting" = 'sound/vox_fem/panting.ogg', +"pathetic" = 'sound/vox_fem/pathetic.ogg', +"percent" = 'sound/vox_fem/percent.ogg', +"perfect" = 'sound/vox_fem/perfect.ogg', +"perimeter" = 'sound/vox_fem/perimeter.ogg', +"permitted" = 'sound/vox_fem/permitted.ogg', +"personal" = 'sound/vox_fem/personal.ogg', +"personnel" = 'sound/vox_fem/personnel.ogg', +"pipe" = 'sound/vox_fem/pipe.ogg', +"piping" = 'sound/vox_fem/piping.ogg', +"piss" = 'sound/vox_fem/piss.ogg', +"plant" = 'sound/vox_fem/plant.ogg', +"plasmaman" = 'sound/vox_fem/plasmaman.ogg', +"plasma" = 'sound/vox_fem/plasma.ogg', +"platform" = 'sound/vox_fem/platform.ogg', +"plausible" = 'sound/vox_fem/plausible.ogg', +"please" = 'sound/vox_fem/please.ogg', +"p" = 'sound/vox_fem/p.ogg', +"point" = 'sound/vox_fem/point.ogg', +"portal" = 'sound/vox_fem/portal.ogg', +"port" = 'sound/vox_fem/port.ogg', +"possible" = 'sound/vox_fem/possible.ogg', +"power" = 'sound/vox_fem/power.ogg', +"presence" = 'sound/vox_fem/presence.ogg', +"press" = 'sound/vox_fem/press.ogg', +"pressure" = 'sound/vox_fem/pressure.ogg', +"primary" = 'sound/vox_fem/primary.ogg', +"prisoner" = 'sound/vox_fem/prisoner.ogg', +"prison" = 'sound/vox_fem/prison.ogg', +"proceed" = 'sound/vox_fem/proceed.ogg', +"processing" = 'sound/vox_fem/processing.ogg', +"progress" = 'sound/vox_fem/progress.ogg', +"proper" = 'sound/vox_fem/proper.ogg', +"propulsion" = 'sound/vox_fem/propulsion.ogg', +"prosecute" = 'sound/vox_fem/prosecute.ogg', +"protective" = 'sound/vox_fem/protective.ogg', +"push" = 'sound/vox_fem/push.ogg', +"put" = 'sound/vox_fem/put.ogg', +"q" = 'sound/vox_fem/q.ogg', +"quantum" = 'sound/vox_fem/quantum.ogg', +"quarantine" = 'sound/vox_fem/quarantine.ogg', +"quartermaster" = 'sound/vox_fem/quartermaster.ogg', +"quebec" = 'sound/vox_fem/quebec.ogg', +"queen" = 'sound/vox_fem/queen.ogg', +"questionable" = 'sound/vox_fem/questionable.ogg', +"questioning" = 'sound/vox_fem/questioning.ogg', +"question" = 'sound/vox_fem/question.ogg', +"quick" = 'sound/vox_fem/quick.ogg', +"quit" = 'sound/vox_fem/quit.ogg', +"radiation" = 'sound/vox_fem/radiation.ogg', +"radioactive" = 'sound/vox_fem/radioactive.ogg', +"rads" = 'sound/vox_fem/rads.ogg', +"raider" = 'sound/vox_fem/raider.ogg', +"raiders" = 'sound/vox_fem/raiders.ogg', +"rapid" = 'sound/vox_fem/rapid.ogg', +"reached" = 'sound/vox_fem/reached.ogg', +"reach" = 'sound/vox_fem/reach.ogg', +"reactor" = 'sound/vox_fem/reactor.ogg', +"red" = 'sound/vox_fem/red.ogg', +"relay" = 'sound/vox_fem/relay.ogg', +"released" = 'sound/vox_fem/released.ogg', +"remaining" = 'sound/vox_fem/remaining.ogg', +"removal" = 'sound/vox_fem/removal.ogg', +"renegade" = 'sound/vox_fem/renegade.ogg', +"repair" = 'sound/vox_fem/repair.ogg', +"report" = 'sound/vox_fem/report.ogg', +"reports" = 'sound/vox_fem/reports.ogg', +"required" = 'sound/vox_fem/required.ogg', +"require" = 'sound/vox_fem/require.ogg', +"research" = 'sound/vox_fem/research.ogg', +"resevoir" = 'sound/vox_fem/resevoir.ogg', +"resistance" = 'sound/vox_fem/resistance.ogg', +"rest" = 'sound/vox_fem/rest.ogg', +"restoration" = 'sound/vox_fem/restoration.ogg', +"revolutionary" = 'sound/vox_fem/revolutionary.ogg', +"revolution" = 'sound/vox_fem/revolution.ogg', +"right" = 'sound/vox_fem/right.ogg', +"riot" = 'sound/vox_fem/riot.ogg', +"roboticist" = 'sound/vox_fem/roboticist.ogg', +"rocket" = 'sound/vox_fem/rocket.ogg', +"roger" = 'sound/vox_fem/roger.ogg', +"r" = 'sound/vox_fem/r.ogg', +"rogue" = 'sound/vox_fem/rogue.ogg', +"romeo" = 'sound/vox_fem/romeo.ogg', +"room" = 'sound/vox_fem/room.ogg', +"round" = 'sound/vox_fem/round.ogg', +"rune" = 'sound/vox_fem/rune.ogg', +"run" = 'sound/vox_fem/run.ogg', +"runtime" = 'sound/vox_fem/runtime.ogg', +"sabotage" = 'sound/vox_fem/sabotage.ogg', +"safe" = 'sound/vox_fem/safe.ogg', +"safety" = 'sound/vox_fem/safety.ogg', +"sairhorn" = 'sound/vox_fem/sairhorn.ogg', +"sarah" = 'sound/vox_fem/sarah.ogg', +"sargeant" = 'sound/vox_fem/sargeant.ogg', +"satellite" = 'sound/vox_fem/satellite.ogg', +"save" = 'sound/vox_fem/save.ogg', +"scensor" = 'sound/vox_fem/scensor.ogg', +"science" = 'sound/vox_fem/science.ogg', +"scientist" = 'sound/vox_fem/scientist.ogg', +"scream" = 'sound/vox_fem/scream.ogg', +"screen" = 'sound/vox_fem/screen.ogg', +"search" = 'sound/vox_fem/search.ogg', +"secondary" = 'sound/vox_fem/secondary.ogg', +"second" = 'sound/vox_fem/second.ogg', +"seconds" = 'sound/vox_fem/seconds.ogg', +"section" = 'sound/vox_fem/section.ogg', +"sector" = 'sound/vox_fem/sector.ogg', +"secured" = 'sound/vox_fem/secured.ogg', +"secure" = 'sound/vox_fem/secure.ogg', +"security" = 'sound/vox_fem/security.ogg', +"selected" = 'sound/vox_fem/selected.ogg', +"select" = 'sound/vox_fem/select.ogg', +"self" = 'sound/vox_fem/self.ogg', +"sensors" = 'sound/vox_fem/sensors.ogg', +"server" = 'sound/vox_fem/server.ogg', +"service" = 'sound/vox_fem/service.ogg', +"seven" = 'sound/vox_fem/seven.ogg', +"seventeen" = 'sound/vox_fem/seventeen.ogg', +"seventy" = 'sound/vox_fem/seventy.ogg', +"severe" = 'sound/vox_fem/severe.ogg', +"sewage" = 'sound/vox_fem/sewage.ogg', +"sewer" = 'sound/vox_fem/sewer.ogg', +"shaft" = 'sound/vox_fem/shaft.ogg', +"she" = 'sound/vox_fem/she.ogg', +"shield" = 'sound/vox_fem/shield.ogg', +"shipment" = 'sound/vox_fem/shipment.ogg', +"shirt" = 'sound/vox_fem/shirt.ogg', +"shitlord" = 'sound/vox_fem/shitlord.ogg', +"shit" = 'sound/vox_fem/shit.ogg', +"shits" = 'sound/vox_fem/shits.ogg', +"shitting" = 'sound/vox_fem/shitting.ogg', +"shock" = 'sound/vox_fem/shock.ogg', +"shonk" = 'sound/vox_fem/shonk.ogg', +"shoot" = 'sound/vox_fem/shoot.ogg', +"shower" = 'sound/vox_fem/shower.ogg', +"shut" = 'sound/vox_fem/shut.ogg', +"shuttle" = 'sound/vox_fem/shuttle.ogg', +"sick" = 'sound/vox_fem/sick.ogg', +"side" = 'sound/vox_fem/side.ogg', +"sierra" = 'sound/vox_fem/sierra.ogg', +"sight" = 'sound/vox_fem/sight.ogg', +"silicon" = 'sound/vox_fem/silicon.ogg', +"silo" = 'sound/vox_fem/silo.ogg', +"singularity" = 'sound/vox_fem/singularity.ogg', +"six" = 'sound/vox_fem/six.ogg', +"sixteen" = 'sound/vox_fem/sixteen.ogg', +"sixty" = 'sound/vox_fem/sixty.ogg', +"skeleton" = 'sound/vox_fem/skeleton.ogg', +"slaughter" = 'sound/vox_fem/slaughter.ogg', +"slime" = 'sound/vox_fem/slime.ogg', +"slip" = 'sound/vox_fem/slip.ogg', +"slippery" = 'sound/vox_fem/slippery.ogg', +"slow" = 'sound/vox_fem/slow.ogg', +"sm" = 'sound/vox_fem/sm.ogg', +"s" = 'sound/vox_fem/s.ogg', +"solar" = 'sound/vox_fem/solar.ogg', +"solars" = 'sound/vox_fem/solars.ogg', +"soldier" = 'sound/vox_fem/soldier.ogg', +"some" = 'sound/vox_fem/some.ogg', +"someone" = 'sound/vox_fem/someone.ogg', +"something" = 'sound/vox_fem/something.ogg', +"son" = 'sound/vox_fem/son.ogg', +"sorry" = 'sound/vox_fem/sorry.ogg', +"south" = 'sound/vox_fem/south.ogg', +"space" = 'sound/vox_fem/space.ogg', +"squad" = 'sound/vox_fem/squad.ogg', +"square" = 'sound/vox_fem/square.ogg', +"ss13" = 'sound/vox_fem/ss13.ogg', +"stairway" = 'sound/vox_fem/stairway.ogg', +"starboard" = 'sound/vox_fem/starboard.ogg', +"station" = 'sound/vox_fem/station.ogg', +"status" = 'sound/vox_fem/status.ogg', +"stay" = 'sound/vox_fem/stay.ogg', +"sterile" = 'sound/vox_fem/sterile.ogg', +"sterilization" = 'sound/vox_fem/sterilization.ogg', +"stop" = 'sound/vox_fem/stop.ogg', +"storage" = 'sound/vox_fem/storage.ogg', +"strong" = 'sound/vox_fem/strong.ogg', +"stuck" = 'sound/vox_fem/stuck.ogg', +"sub" = 'sound/vox_fem/sub.ogg', +"subsurface" = 'sound/vox_fem/subsurface.ogg', +"sudden" = 'sound/vox_fem/sudden.ogg', +"suffer" = 'sound/vox_fem/suffer.ogg', +"suit" = 'sound/vox_fem/suit.ogg', +"superconducting" = 'sound/vox_fem/superconducting.ogg', +"supercooled" = 'sound/vox_fem/supercooled.ogg', +"supermatter" = 'sound/vox_fem/supermatter.ogg', +"supply" = 'sound/vox_fem/supply.ogg', +"surface" = 'sound/vox_fem/surface.ogg', +"surrender" = 'sound/vox_fem/surrender.ogg', +"surrounded" = 'sound/vox_fem/surrounded.ogg', +"surround" = 'sound/vox_fem/surround.ogg', +"sweating" = 'sound/vox_fem/sweating.ogg', +"swhitenoise" = 'sound/vox_fem/swhitenoise.ogg', +"switch" = 'sound/vox_fem/switch.ogg', +"syndicate" = 'sound/vox_fem/syndicate.ogg', +"system" = 'sound/vox_fem/system.ogg', +"systems" = 'sound/vox_fem/systems.ogg', +"table" = 'sound/vox_fem/table.ogg', +"tactical" = 'sound/vox_fem/tactical.ogg', +"take" = 'sound/vox_fem/take.ogg', +"talk" = 'sound/vox_fem/talk.ogg', +"tampered" = 'sound/vox_fem/tampered.ogg', +"tango" = 'sound/vox_fem/tango.ogg', +"tank" = 'sound/vox_fem/tank.ogg', +"target" = 'sound/vox_fem/target.ogg', +"team" = 'sound/vox_fem/team.ogg', +"technician" = 'sound/vox_fem/technician.ogg', +"technology" = 'sound/vox_fem/technology.ogg', +"tech" = 'sound/vox_fem/tech.ogg', +"temperature" = 'sound/vox_fem/temperature.ogg', +"temporal" = 'sound/vox_fem/temporal.ogg', +"ten" = 'sound/vox_fem/ten.ogg', +"terminal" = 'sound/vox_fem/terminal.ogg', +"terminated" = 'sound/vox_fem/terminated.ogg', +"termination" = 'sound/vox_fem/termination.ogg', +"test" = 'sound/vox_fem/test.ogg', +"text" = 'sound/vox_fem/text.ogg', +"that" = 'sound/vox_fem/that.ogg', +"theater" = 'sound/vox_fem/theater.ogg', +"them" = 'sound/vox_fem/them.ogg', +"then" = 'sound/vox_fem/then.ogg', +"the" = 'sound/vox_fem/the.ogg', +"there" = 'sound/vox_fem/there.ogg', +"they" = 'sound/vox_fem/they.ogg', +"third" = 'sound/vox_fem/third.ogg', +"thirteen" = 'sound/vox_fem/thirteen.ogg', +"thirty" = 'sound/vox_fem/thirty.ogg', +"this" = 'sound/vox_fem/this.ogg', +"those" = 'sound/vox_fem/those.ogg', +"thousand" = 'sound/vox_fem/thousand.ogg', +"threat" = 'sound/vox_fem/threat.ogg', +"three" = 'sound/vox_fem/three.ogg', +"through" = 'sound/vox_fem/through.ogg', +"tide" = 'sound/vox_fem/tide.ogg', +"time" = 'sound/vox_fem/time.ogg', +"t" = 'sound/vox_fem/t.ogg', +"to" = 'sound/vox_fem/to.ogg', +"top" = 'sound/vox_fem/top.ogg', +"topside" = 'sound/vox_fem/topside.ogg', +"touch" = 'sound/vox_fem/touch.ogg', +"towards" = 'sound/vox_fem/towards.ogg', +"toxins" = 'sound/vox_fem/toxins.ogg', +"track" = 'sound/vox_fem/track.ogg', +"train" = 'sound/vox_fem/train.ogg', +"traitor" = 'sound/vox_fem/traitor.ogg', +"transportation" = 'sound/vox_fem/transportation.ogg', +"truck" = 'sound/vox_fem/truck.ogg', +"true" = 'sound/vox_fem/true.ogg', +"tunnel" = 'sound/vox_fem/tunnel.ogg', +"turn" = 'sound/vox_fem/turn.ogg', +"turret" = 'sound/vox_fem/turret.ogg', +"twelve" = 'sound/vox_fem/twelve.ogg', +"twenty" = 'sound/vox_fem/twenty.ogg', +"two" = 'sound/vox_fem/two.ogg', +"ughh" = 'sound/vox_fem/ughh.ogg', +"ugh" = 'sound/vox_fem/ugh.ogg', +"unable" = 'sound/vox_fem/unable.ogg', +"unauthorized" = 'sound/vox_fem/unauthorized.ogg', +"under" = 'sound/vox_fem/under.ogg', +"uniform" = 'sound/vox_fem/uniform.ogg', +"unknown" = 'sound/vox_fem/unknown.ogg', +"unlocked" = 'sound/vox_fem/unlocked.ogg', +"unsafe" = 'sound/vox_fem/unsafe.ogg', +"until" = 'sound/vox_fem/until.ogg', +"u" = 'sound/vox_fem/u.ogg', +"updated" = 'sound/vox_fem/updated.ogg', +"update" = 'sound/vox_fem/update.ogg', +"updating" = 'sound/vox_fem/updating.ogg', +"upload" = 'sound/vox_fem/upload.ogg', +"up" = 'sound/vox_fem/up.ogg', +"upper" = 'sound/vox_fem/upper.ogg', +"uranium" = 'sound/vox_fem/uranium.ogg', +"usa" = 'sound/vox_fem/usa.ogg', +"used" = 'sound/vox_fem/used.ogg', +"use" = 'sound/vox_fem/use.ogg', +"user" = 'sound/vox_fem/user.ogg', +"us" = 'sound/vox_fem/us.ogg', +"vacate" = 'sound/vox_fem/vacate.ogg', +"vacuum" = 'sound/vox_fem/vacuum.ogg', +"valid" = 'sound/vox_fem/valid.ogg', +"vapor" = 'sound/vox_fem/vapor.ogg', +"vendor" = 'sound/vox_fem/vendor.ogg', +"ventilation" = 'sound/vox_fem/ventilation.ogg', +"vent" = 'sound/vox_fem/vent.ogg', +"very" = 'sound/vox_fem/very.ogg', +"victor" = 'sound/vox_fem/victor.ogg', +"violated" = 'sound/vox_fem/violated.ogg', +"violation" = 'sound/vox_fem/violation.ogg', +"virologist" = 'sound/vox_fem/virologist.ogg', +"virology" = 'sound/vox_fem/virology.ogg', +"virus" = 'sound/vox_fem/virus.ogg', +"vitals" = 'sound/vox_fem/vitals.ogg', +"v" = 'sound/vox_fem/v.ogg', +"voltage" = 'sound/vox_fem/voltage.ogg', +"vox_login" = 'sound/vox_fem/vox_login.ogg', +"vox" = 'sound/vox_fem/vox.ogg', +"voxtest" = 'sound/vox_fem/voxtest.ogg', +"walk" = 'sound/vox_fem/walk.ogg', +"wall" = 'sound/vox_fem/wall.ogg', +"wanker" = 'sound/vox_fem/wanker.ogg', +"wanted" = 'sound/vox_fem/wanted.ogg', +"want" = 'sound/vox_fem/want.ogg', +"warden" = 'sound/vox_fem/warden.ogg', +"warm" = 'sound/vox_fem/warm.ogg', +"warning" = 'sound/vox_fem/warning.ogg', +"warn" = 'sound/vox_fem/warn.ogg', +"waste" = 'sound/vox_fem/waste.ogg', +"water" = 'sound/vox_fem/water.ogg', +"weak" = 'sound/vox_fem/weak.ogg', +"weapon" = 'sound/vox_fem/weapon.ogg', +"welcome" = 'sound/vox_fem/welcome.ogg', +"we" = 'sound/vox_fem/we.ogg', +"west" = 'sound/vox_fem/west.ogg', +"wew" = 'sound/vox_fem/wew.ogg', +"what" = 'sound/vox_fem/what.ogg', +"when" = 'sound/vox_fem/when.ogg', +"where" = 'sound/vox_fem/where.ogg', +"whiskey" = 'sound/vox_fem/whiskey.ogg', +"white" = 'sound/vox_fem/white.ogg', +"why" = 'sound/vox_fem/why.ogg', +"wilco" = 'sound/vox_fem/wilco.ogg', +"will" = 'sound/vox_fem/will.ogg', +"wing" = 'sound/vox_fem/wing.ogg', +"wire" = 'sound/vox_fem/wire.ogg', +"with" = 'sound/vox_fem/with.ogg', +"without" = 'sound/vox_fem/without.ogg', +"wizard" = 'sound/vox_fem/wizard.ogg', +"w" = 'sound/vox_fem/w.ogg', +"wood" = 'sound/vox_fem/wood.ogg', +"woody" = 'sound/vox_fem/woody.ogg', +"woop" = 'sound/vox_fem/woop.ogg', +"wow" = 'sound/vox_fem/wow.ogg', +"xenobiology" = 'sound/vox_fem/xenobiology.ogg', +"xenomorph" = 'sound/vox_fem/xenomorph.ogg', +"xenomorphs" = 'sound/vox_fem/xenomorphs.ogg', +"xeno" = 'sound/vox_fem/xeno.ogg', +"x" = 'sound/vox_fem/x.ogg', +"yankee" = 'sound/vox_fem/yankee.ogg', +"yards" = 'sound/vox_fem/yards.ogg', +"year" = 'sound/vox_fem/year.ogg', +"yellow" = 'sound/vox_fem/yellow.ogg', +"yes" = 'sound/vox_fem/yes.ogg', +"y" = 'sound/vox_fem/y.ogg', +"you" = 'sound/vox_fem/you.ogg', +"your" = 'sound/vox_fem/your.ogg', +"yourself" = 'sound/vox_fem/yourself.ogg', +"zero" = 'sound/vox_fem/zero.ogg', +"z" = 'sound/vox_fem/z.ogg', +"zombie" = 'sound/vox_fem/zombie.ogg', +"zone" = 'sound/vox_fem/zone.ogg', +"zulu" = 'sound/vox_fem/zulu.ogg')) + +//for vim +// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox\/\1',/g +GLOBAL_LIST_INIT(vox_sounds_male, list("," = 'sound/vox/_comma.ogg', +"." = 'sound/vox/_period.ogg', +"a" = 'sound/vox/a.ogg', +"accelerating" = 'sound/vox/accelerating.ogg', +"accelerator" = 'sound/vox/accelerator.ogg', +"accepted" = 'sound/vox/accepted.ogg', +"access" = 'sound/vox/access.ogg', +"acknowledge" = 'sound/vox/acknowledge.ogg', +"acknowledged" = 'sound/vox/acknowledged.ogg', +"acquired" = 'sound/vox/acquired.ogg', +"acquisition" = 'sound/vox/acquisition.ogg', +"across" = 'sound/vox/across.ogg', +"activate" = 'sound/vox/activate.ogg', +"activated" = 'sound/vox/activated.ogg', +"activity" = 'sound/vox/activity.ogg', +"adios" = 'sound/vox/adios.ogg', +"administration" = 'sound/vox/administration.ogg', +"advanced" = 'sound/vox/advanced.ogg', +"after" = 'sound/vox/after.ogg', +"agent" = 'sound/vox/agent.ogg', +"alarm" = 'sound/vox/alarm.ogg', +"alert" = 'sound/vox/alert.ogg', +"alien" = 'sound/vox/alien.ogg', +"aligned" = 'sound/vox/aligned.ogg', +"all" = 'sound/vox/all.ogg', +"alpha" = 'sound/vox/alpha.ogg', +"am" = 'sound/vox/am.ogg', +"amigo" = 'sound/vox/amigo.ogg', +"ammunition" = 'sound/vox/ammunition.ogg', +"an" = 'sound/vox/an.ogg', +"and" = 'sound/vox/and.ogg', +"announcement" = 'sound/vox/announcement.ogg', +"anomalous" = 'sound/vox/anomalous.ogg', +"antenna" = 'sound/vox/antenna.ogg', +"any" = 'sound/vox/any.ogg', +"apprehend" = 'sound/vox/apprehend.ogg', +"approach" = 'sound/vox/approach.ogg', +"are" = 'sound/vox/are.ogg', +"area" = 'sound/vox/area.ogg', +"arm" = 'sound/vox/arm.ogg', +"armed" = 'sound/vox/armed.ogg', +"armor" = 'sound/vox/armor.ogg', +"armory" = 'sound/vox/armory.ogg', +"arrest" = 'sound/vox/arrest.ogg', +"ass" = 'sound/vox/ass.ogg', +"at" = 'sound/vox/at.ogg', +"atomic" = 'sound/vox/atomic.ogg', +"attention" = 'sound/vox/attention.ogg', +"authorize" = 'sound/vox/authorize.ogg', +"authorized" = 'sound/vox/authorized.ogg', +"automatic" = 'sound/vox/automatic.ogg', +"away" = 'sound/vox/away.ogg', +"b" = 'sound/vox/b.ogg', +"back" = 'sound/vox/back.ogg', +"backman" = 'sound/vox/backman.ogg', +"bad" = 'sound/vox/bad.ogg', +"bag" = 'sound/vox/bag.ogg', +"bailey" = 'sound/vox/bailey.ogg', +"barracks" = 'sound/vox/barracks.ogg', +"base" = 'sound/vox/base.ogg', +"bay" = 'sound/vox/bay.ogg', +"be" = 'sound/vox/be.ogg', +"been" = 'sound/vox/been.ogg', +"before" = 'sound/vox/before.ogg', +"beyond" = 'sound/vox/beyond.ogg', +"biohazard" = 'sound/vox/biohazard.ogg', +"biological" = 'sound/vox/biological.ogg', +"birdwell" = 'sound/vox/birdwell.ogg', +"bizwarn" = 'sound/vox/bizwarn.ogg', +"black" = 'sound/vox/black.ogg', +"blast" = 'sound/vox/blast.ogg', +"blocked" = 'sound/vox/blocked.ogg', +"bloop" = 'sound/vox/bloop.ogg', +"blue" = 'sound/vox/blue.ogg', +"bottom" = 'sound/vox/bottom.ogg', +"bravo" = 'sound/vox/bravo.ogg', +"breach" = 'sound/vox/breach.ogg', +"breached" = 'sound/vox/breached.ogg', +"break" = 'sound/vox/break.ogg', +"bridge" = 'sound/vox/bridge.ogg', +"bust" = 'sound/vox/bust.ogg', +"but" = 'sound/vox/but.ogg', +"button" = 'sound/vox/button.ogg', +"buzwarn" = 'sound/vox/buzwarn.ogg', +"bypass" = 'sound/vox/bypass.ogg', +"c" = 'sound/vox/c.ogg', +"cable" = 'sound/vox/cable.ogg', +"call" = 'sound/vox/call.ogg', +"called" = 'sound/vox/called.ogg', +"canal" = 'sound/vox/canal.ogg', +"cap" = 'sound/vox/cap.ogg', +"captain" = 'sound/vox/captain.ogg', +"capture" = 'sound/vox/capture.ogg', +"captured" = 'sound/vox/captured.ogg', +"ceiling" = 'sound/vox/ceiling.ogg', +"celsius" = 'sound/vox/celsius.ogg', +"center" = 'sound/vox/center.ogg', +"centi" = 'sound/vox/centi.ogg', +"central" = 'sound/vox/central.ogg', +"chamber" = 'sound/vox/chamber.ogg', +"charlie" = 'sound/vox/charlie.ogg', +"check" = 'sound/vox/check.ogg', +"checkpoint" = 'sound/vox/checkpoint.ogg', +"chemical" = 'sound/vox/chemical.ogg', +"cleanup" = 'sound/vox/cleanup.ogg', +"clear" = 'sound/vox/clear.ogg', +"clearance" = 'sound/vox/clearance.ogg', +"close" = 'sound/vox/close.ogg', +"clown" = 'sound/vox/clown.ogg', +"code" = 'sound/vox/code.ogg', +"coded" = 'sound/vox/coded.ogg', +"collider" = 'sound/vox/collider.ogg', +"command" = 'sound/vox/command.ogg', +"communication" = 'sound/vox/communication.ogg', +"complex" = 'sound/vox/complex.ogg', +"computer" = 'sound/vox/computer.ogg', +"condition" = 'sound/vox/condition.ogg', +"containment" = 'sound/vox/containment.ogg', +"contamination" = 'sound/vox/contamination.ogg', +"control" = 'sound/vox/control.ogg', +"coolant" = 'sound/vox/coolant.ogg', +"coomer" = 'sound/vox/coomer.ogg', +"core" = 'sound/vox/core.ogg', +"correct" = 'sound/vox/correct.ogg', +"corridor" = 'sound/vox/corridor.ogg', +"crew" = 'sound/vox/crew.ogg', +"cross" = 'sound/vox/cross.ogg', +"cryogenic" = 'sound/vox/cryogenic.ogg', +"d" = 'sound/vox/d.ogg', +"dadeda" = 'sound/vox/dadeda.ogg', +"damage" = 'sound/vox/damage.ogg', +"damaged" = 'sound/vox/damaged.ogg', +"danger" = 'sound/vox/danger.ogg', +"day" = 'sound/vox/day.ogg', +"deactivated" = 'sound/vox/deactivated.ogg', +"decompression" = 'sound/vox/decompression.ogg', +"decontamination" = 'sound/vox/decontamination.ogg', +"deeoo" = 'sound/vox/deeoo.ogg', +"defense" = 'sound/vox/defense.ogg', +"degrees" = 'sound/vox/degrees.ogg', +"delta" = 'sound/vox/delta.ogg', +"denied" = 'sound/vox/denied.ogg', +"deploy" = 'sound/vox/deploy.ogg', +"deployed" = 'sound/vox/deployed.ogg', +"destroy" = 'sound/vox/destroy.ogg', +"destroyed" = 'sound/vox/destroyed.ogg', +"detain" = 'sound/vox/detain.ogg', +"detected" = 'sound/vox/detected.ogg', +"detonation" = 'sound/vox/detonation.ogg', +"device" = 'sound/vox/device.ogg', +"did" = 'sound/vox/did.ogg', +"die" = 'sound/vox/die.ogg', +"dimensional" = 'sound/vox/dimensional.ogg', +"dirt" = 'sound/vox/dirt.ogg', +"disengaged" = 'sound/vox/disengaged.ogg', +"dish" = 'sound/vox/dish.ogg', +"disposal" = 'sound/vox/disposal.ogg', +"distance" = 'sound/vox/distance.ogg', +"distortion" = 'sound/vox/distortion.ogg', +"do" = 'sound/vox/do.ogg', +"doctor" = 'sound/vox/doctor.ogg', +"doop" = 'sound/vox/doop.ogg', +"door" = 'sound/vox/door.ogg', +"down" = 'sound/vox/down.ogg', +"dual" = 'sound/vox/dual.ogg', +"duct" = 'sound/vox/duct.ogg', +"e" = 'sound/vox/e.ogg', +"east" = 'sound/vox/east.ogg', +"echo" = 'sound/vox/echo.ogg', +"ed" = 'sound/vox/ed.ogg', +"effect" = 'sound/vox/effect.ogg', +"egress" = 'sound/vox/egress.ogg', +"eight" = 'sound/vox/eight.ogg', +"eighteen" = 'sound/vox/eighteen.ogg', +"eighty" = 'sound/vox/eighty.ogg', +"electric" = 'sound/vox/electric.ogg', +"electromagnetic" = 'sound/vox/electromagnetic.ogg', +"elevator" = 'sound/vox/elevator.ogg', +"eleven" = 'sound/vox/eleven.ogg', +"eliminate" = 'sound/vox/eliminate.ogg', +"emergency" = 'sound/vox/emergency.ogg', +"enemy" = 'sound/vox/enemy.ogg', +"energy" = 'sound/vox/energy.ogg', +"engage" = 'sound/vox/engage.ogg', +"engaged" = 'sound/vox/engaged.ogg', +"engine" = 'sound/vox/engine.ogg', +"enter" = 'sound/vox/enter.ogg', +"entry" = 'sound/vox/entry.ogg', +"environment" = 'sound/vox/environment.ogg', +"error" = 'sound/vox/error.ogg', +"escape" = 'sound/vox/escape.ogg', +"evacuate" = 'sound/vox/evacuate.ogg', +"exchange" = 'sound/vox/exchange.ogg', +"exit" = 'sound/vox/exit.ogg', +"expect" = 'sound/vox/expect.ogg', +"experiment" = 'sound/vox/experiment.ogg', +"experimental" = 'sound/vox/experimental.ogg', +"explode" = 'sound/vox/explode.ogg', +"explosion" = 'sound/vox/explosion.ogg', +"exposure" = 'sound/vox/exposure.ogg', +"exterminate" = 'sound/vox/exterminate.ogg', +"extinguish" = 'sound/vox/extinguish.ogg', +"extinguisher" = 'sound/vox/extinguisher.ogg', +"extreme" = 'sound/vox/extreme.ogg', +"f" = 'sound/vox/f.ogg', +"face" = 'sound/vox/face.ogg', +"facility" = 'sound/vox/facility.ogg', +"fahrenheit" = 'sound/vox/fahrenheit.ogg', +"failed" = 'sound/vox/failed.ogg', +"failure" = 'sound/vox/failure.ogg', +"farthest" = 'sound/vox/farthest.ogg', +"fast" = 'sound/vox/fast.ogg', +"feet" = 'sound/vox/feet.ogg', +"field" = 'sound/vox/field.ogg', +"fifteen" = 'sound/vox/fifteen.ogg', +"fifth" = 'sound/vox/fifth.ogg', +"fifty" = 'sound/vox/fifty.ogg', +"final" = 'sound/vox/final.ogg', +"fine" = 'sound/vox/fine.ogg', +"fire" = 'sound/vox/fire.ogg', +"first" = 'sound/vox/first.ogg', +"five" = 'sound/vox/five.ogg', +"flag" = 'sound/vox/flag.ogg', +"flooding" = 'sound/vox/flooding.ogg', +"floor" = 'sound/vox/floor.ogg', +"fool" = 'sound/vox/fool.ogg', +"for" = 'sound/vox/for.ogg', +"forbidden" = 'sound/vox/forbidden.ogg', +"force" = 'sound/vox/force.ogg', +"forms" = 'sound/vox/forms.ogg', +"found" = 'sound/vox/found.ogg', +"four" = 'sound/vox/four.ogg', +"fourteen" = 'sound/vox/fourteen.ogg', +"fourth" = 'sound/vox/fourth.ogg', +"fourty" = 'sound/vox/fourty.ogg', +"foxtrot" = 'sound/vox/foxtrot.ogg', +"freeman" = 'sound/vox/freeman.ogg', +"freezer" = 'sound/vox/freezer.ogg', +"from" = 'sound/vox/from.ogg', +"front" = 'sound/vox/front.ogg', +"fuel" = 'sound/vox/fuel.ogg', +"g" = 'sound/vox/g.ogg', +"gay" = 'sound/vox/gay.ogg', +"get" = 'sound/vox/get.ogg', +"go" = 'sound/vox/go.ogg', +"going" = 'sound/vox/going.ogg', +"good" = 'sound/vox/good.ogg', +"goodbye" = 'sound/vox/goodbye.ogg', +"gordon" = 'sound/vox/gordon.ogg', +"got" = 'sound/vox/got.ogg', +"government" = 'sound/vox/government.ogg', +"granted" = 'sound/vox/granted.ogg', +"great" = 'sound/vox/great.ogg', +"green" = 'sound/vox/green.ogg', +"grenade" = 'sound/vox/grenade.ogg', +"guard" = 'sound/vox/guard.ogg', +"gulf" = 'sound/vox/gulf.ogg', +"gun" = 'sound/vox/gun.ogg', +"guthrie" = 'sound/vox/guthrie.ogg', +"handling" = 'sound/vox/handling.ogg', +"hangar" = 'sound/vox/hangar.ogg', +"has" = 'sound/vox/has.ogg', +"have" = 'sound/vox/have.ogg', +"hazard" = 'sound/vox/hazard.ogg', +"head" = 'sound/vox/head.ogg', +"health" = 'sound/vox/health.ogg', +"heat" = 'sound/vox/heat.ogg', +"helicopter" = 'sound/vox/helicopter.ogg', +"helium" = 'sound/vox/helium.ogg', +"hello" = 'sound/vox/hello.ogg', +"help" = 'sound/vox/help.ogg', +"here" = 'sound/vox/here.ogg', +"hide" = 'sound/vox/hide.ogg', +"high" = 'sound/vox/high.ogg', +"highest" = 'sound/vox/highest.ogg', +"hit" = 'sound/vox/hit.ogg', +"holds" = 'sound/vox/holds.ogg', +"hole" = 'sound/vox/hole.ogg', +"hostile" = 'sound/vox/hostile.ogg', +"hot" = 'sound/vox/hot.ogg', +"hotel" = 'sound/vox/hotel.ogg', +"hour" = 'sound/vox/hour.ogg', +"hours" = 'sound/vox/hours.ogg', +"hundred" = 'sound/vox/hundred.ogg', +"hydro" = 'sound/vox/hydro.ogg', +"i" = 'sound/vox/i.ogg', +"idiot" = 'sound/vox/idiot.ogg', +"illegal" = 'sound/vox/illegal.ogg', +"immediate" = 'sound/vox/immediate.ogg', +"immediately" = 'sound/vox/immediately.ogg', +"in" = 'sound/vox/in.ogg', +"inches" = 'sound/vox/inches.ogg', +"india" = 'sound/vox/india.ogg', +"ing" = 'sound/vox/ing.ogg', +"inoperative" = 'sound/vox/inoperative.ogg', +"inside" = 'sound/vox/inside.ogg', +"inspection" = 'sound/vox/inspection.ogg', +"inspector" = 'sound/vox/inspector.ogg', +"interchange" = 'sound/vox/interchange.ogg', +"intruder" = 'sound/vox/intruder.ogg', +"invallid" = 'sound/vox/invallid.ogg', +"invasion" = 'sound/vox/invasion.ogg', +"is" = 'sound/vox/is.ogg', +"it" = 'sound/vox/it.ogg', +"johnson" = 'sound/vox/johnson.ogg', +"juliet" = 'sound/vox/juliet.ogg', +"key" = 'sound/vox/key.ogg', +"kill" = 'sound/vox/kill.ogg', +"kilo" = 'sound/vox/kilo.ogg', +"kit" = 'sound/vox/kit.ogg', +"lab" = 'sound/vox/lab.ogg', +"lambda" = 'sound/vox/lambda.ogg', +"laser" = 'sound/vox/laser.ogg', +"last" = 'sound/vox/last.ogg', +"launch" = 'sound/vox/launch.ogg', +"leak" = 'sound/vox/leak.ogg', +"leave" = 'sound/vox/leave.ogg', +"left" = 'sound/vox/left.ogg', +"legal" = 'sound/vox/legal.ogg', +"level" = 'sound/vox/level.ogg', +"lever" = 'sound/vox/lever.ogg', +"lie" = 'sound/vox/lie.ogg', +"lieutenant" = 'sound/vox/lieutenant.ogg', +"life" = 'sound/vox/life.ogg', +"light" = 'sound/vox/light.ogg', +"lima" = 'sound/vox/lima.ogg', +"liquid" = 'sound/vox/liquid.ogg', +"loading" = 'sound/vox/loading.ogg', +"locate" = 'sound/vox/locate.ogg', +"located" = 'sound/vox/located.ogg', +"location" = 'sound/vox/location.ogg', +"lock" = 'sound/vox/lock.ogg', +"locked" = 'sound/vox/locked.ogg', +"locker" = 'sound/vox/locker.ogg', +"lockout" = 'sound/vox/lockout.ogg', +"lower" = 'sound/vox/lower.ogg', +"lowest" = 'sound/vox/lowest.ogg', +"magnetic" = 'sound/vox/magnetic.ogg', +"main" = 'sound/vox/main.ogg', +"maintenance" = 'sound/vox/maintenance.ogg', +"malfunction" = 'sound/vox/malfunction.ogg', +"man" = 'sound/vox/man.ogg', +"mass" = 'sound/vox/mass.ogg', +"materials" = 'sound/vox/materials.ogg', +"maximum" = 'sound/vox/maximum.ogg', +"may" = 'sound/vox/may.ogg', +"med" = 'sound/vox/med.ogg', +"medical" = 'sound/vox/medical.ogg', +"men" = 'sound/vox/men.ogg', +"mercy" = 'sound/vox/mercy.ogg', +"mesa" = 'sound/vox/mesa.ogg', +"message" = 'sound/vox/message.ogg', +"meter" = 'sound/vox/meter.ogg', +"micro" = 'sound/vox/micro.ogg', +"middle" = 'sound/vox/middle.ogg', +"mike" = 'sound/vox/mike.ogg', +"miles" = 'sound/vox/miles.ogg', +"military" = 'sound/vox/military.ogg', +"milli" = 'sound/vox/milli.ogg', +"million" = 'sound/vox/million.ogg', +"minefield" = 'sound/vox/minefield.ogg', +"minimum" = 'sound/vox/minimum.ogg', +"minutes" = 'sound/vox/minutes.ogg', +"mister" = 'sound/vox/mister.ogg', +"mode" = 'sound/vox/mode.ogg', +"motor" = 'sound/vox/motor.ogg', +"motorpool" = 'sound/vox/motorpool.ogg', +"move" = 'sound/vox/move.ogg', +"must" = 'sound/vox/must.ogg', +"nearest" = 'sound/vox/nearest.ogg', +"nice" = 'sound/vox/nice.ogg', +"nine" = 'sound/vox/nine.ogg', +"nineteen" = 'sound/vox/nineteen.ogg', +"ninety" = 'sound/vox/ninety.ogg', +"no" = 'sound/vox/no.ogg', +"nominal" = 'sound/vox/nominal.ogg', +"north" = 'sound/vox/north.ogg', +"not" = 'sound/vox/not.ogg', +"november" = 'sound/vox/november.ogg', +"now" = 'sound/vox/now.ogg', +"number" = 'sound/vox/number.ogg', +"objective" = 'sound/vox/objective.ogg', +"observation" = 'sound/vox/observation.ogg', +"of" = 'sound/vox/of.ogg', +"officer" = 'sound/vox/officer.ogg', +"ok" = 'sound/vox/ok.ogg', +"on" = 'sound/vox/on.ogg', +"one" = 'sound/vox/one.ogg', +"open" = 'sound/vox/open.ogg', +"operating" = 'sound/vox/operating.ogg', +"operations" = 'sound/vox/operations.ogg', +"operative" = 'sound/vox/operative.ogg', +"option" = 'sound/vox/option.ogg', +"order" = 'sound/vox/order.ogg', +"organic" = 'sound/vox/organic.ogg', +"oscar" = 'sound/vox/oscar.ogg', +"out" = 'sound/vox/out.ogg', +"outside" = 'sound/vox/outside.ogg', +"over" = 'sound/vox/over.ogg', +"overload" = 'sound/vox/overload.ogg', +"override" = 'sound/vox/override.ogg', +"pacify" = 'sound/vox/pacify.ogg', +"pain" = 'sound/vox/pain.ogg', +"pal" = 'sound/vox/pal.ogg', +"panel" = 'sound/vox/panel.ogg', +"percent" = 'sound/vox/percent.ogg', +"perimeter" = 'sound/vox/perimeter.ogg', +"permitted" = 'sound/vox/permitted.ogg', +"personnel" = 'sound/vox/personnel.ogg', +"pipe" = 'sound/vox/pipe.ogg', +"plant" = 'sound/vox/plant.ogg', +"platform" = 'sound/vox/platform.ogg', +"please" = 'sound/vox/please.ogg', +"point" = 'sound/vox/point.ogg', +"portal" = 'sound/vox/portal.ogg', +"power" = 'sound/vox/power.ogg', +"presence" = 'sound/vox/presence.ogg', +"press" = 'sound/vox/press.ogg', +"primary" = 'sound/vox/primary.ogg', +"proceed" = 'sound/vox/proceed.ogg', +"processing" = 'sound/vox/processing.ogg', +"progress" = 'sound/vox/progress.ogg', +"proper" = 'sound/vox/proper.ogg', +"propulsion" = 'sound/vox/propulsion.ogg', +"prosecute" = 'sound/vox/prosecute.ogg', +"protective" = 'sound/vox/protective.ogg', +"push" = 'sound/vox/push.ogg', +"quantum" = 'sound/vox/quantum.ogg', +"quebec" = 'sound/vox/quebec.ogg', +"question" = 'sound/vox/question.ogg', +"questioning" = 'sound/vox/questioning.ogg', +"quick" = 'sound/vox/quick.ogg', +"quit" = 'sound/vox/quit.ogg', +"radiation" = 'sound/vox/radiation.ogg', +"radioactive" = 'sound/vox/radioactive.ogg', +"rads" = 'sound/vox/rads.ogg', +"rapid" = 'sound/vox/rapid.ogg', +"reach" = 'sound/vox/reach.ogg', +"reached" = 'sound/vox/reached.ogg', +"reactor" = 'sound/vox/reactor.ogg', +"red" = 'sound/vox/red.ogg', +"relay" = 'sound/vox/relay.ogg', +"released" = 'sound/vox/released.ogg', +"remaining" = 'sound/vox/remaining.ogg', +"renegade" = 'sound/vox/renegade.ogg', +"repair" = 'sound/vox/repair.ogg', +"report" = 'sound/vox/report.ogg', +"reports" = 'sound/vox/reports.ogg', +"required" = 'sound/vox/required.ogg', +"research" = 'sound/vox/research.ogg', +"reset" = 'sound/vox/reset.ogg', +"resevoir" = 'sound/vox/resevoir.ogg', +"resistance" = 'sound/vox/resistance.ogg', +"returned" = 'sound/vox/returned.ogg', +"right" = 'sound/vox/right.ogg', +"rocket" = 'sound/vox/rocket.ogg', +"roger" = 'sound/vox/roger.ogg', +"romeo" = 'sound/vox/romeo.ogg', +"room" = 'sound/vox/room.ogg', +"round" = 'sound/vox/round.ogg', +"run" = 'sound/vox/run.ogg', +"safe" = 'sound/vox/safe.ogg', +"safety" = 'sound/vox/safety.ogg', +"sargeant" = 'sound/vox/sargeant.ogg', +"satellite" = 'sound/vox/satellite.ogg', +"save" = 'sound/vox/save.ogg', +"science" = 'sound/vox/science.ogg', +"scores" = 'sound/vox/scores.ogg', +"scream" = 'sound/vox/scream.ogg', +"screen" = 'sound/vox/screen.ogg', +"search" = 'sound/vox/search.ogg', +"second" = 'sound/vox/second.ogg', +"secondary" = 'sound/vox/secondary.ogg', +"seconds" = 'sound/vox/seconds.ogg', +"sector" = 'sound/vox/sector.ogg', +"secure" = 'sound/vox/secure.ogg', +"secured" = 'sound/vox/secured.ogg', +"security" = 'sound/vox/security.ogg', +"select" = 'sound/vox/select.ogg', +"selected" = 'sound/vox/selected.ogg', +"service" = 'sound/vox/service.ogg', +"seven" = 'sound/vox/seven.ogg', +"seventeen" = 'sound/vox/seventeen.ogg', +"seventy" = 'sound/vox/seventy.ogg', +"severe" = 'sound/vox/severe.ogg', +"sewage" = 'sound/vox/sewage.ogg', +"sewer" = 'sound/vox/sewer.ogg', +"shield" = 'sound/vox/shield.ogg', +"shipment" = 'sound/vox/shipment.ogg', +"shock" = 'sound/vox/shock.ogg', +"shoot" = 'sound/vox/shoot.ogg', +"shower" = 'sound/vox/shower.ogg', +"shut" = 'sound/vox/shut.ogg', +"side" = 'sound/vox/side.ogg', +"sierra" = 'sound/vox/sierra.ogg', +"sight" = 'sound/vox/sight.ogg', +"silo" = 'sound/vox/silo.ogg', +"six" = 'sound/vox/six.ogg', +"sixteen" = 'sound/vox/sixteen.ogg', +"sixty" = 'sound/vox/sixty.ogg', +"slime" = 'sound/vox/slime.ogg', +"slow" = 'sound/vox/slow.ogg', +"soldier" = 'sound/vox/soldier.ogg', +"some" = 'sound/vox/some.ogg', +"someone" = 'sound/vox/someone.ogg', +"something" = 'sound/vox/something.ogg', +"son" = 'sound/vox/son.ogg', +"sorry" = 'sound/vox/sorry.ogg', +"south" = 'sound/vox/south.ogg', +"squad" = 'sound/vox/squad.ogg', +"square" = 'sound/vox/square.ogg', +"stairway" = 'sound/vox/stairway.ogg', +"status" = 'sound/vox/status.ogg', +"sterile" = 'sound/vox/sterile.ogg', +"sterilization" = 'sound/vox/sterilization.ogg', +"stolen" = 'sound/vox/stolen.ogg', +"storage" = 'sound/vox/storage.ogg', +"sub" = 'sound/vox/sub.ogg', +"subsurface" = 'sound/vox/subsurface.ogg', +"sudden" = 'sound/vox/sudden.ogg', +"suit" = 'sound/vox/suit.ogg', +"superconducting" = 'sound/vox/superconducting.ogg', +"supercooled" = 'sound/vox/supercooled.ogg', +"supply" = 'sound/vox/supply.ogg', +"surface" = 'sound/vox/surface.ogg', +"surrender" = 'sound/vox/surrender.ogg', +"surround" = 'sound/vox/surround.ogg', +"surrounded" = 'sound/vox/surrounded.ogg', +"switch" = 'sound/vox/switch.ogg', +"system" = 'sound/vox/system.ogg', +"systems" = 'sound/vox/systems.ogg', +"tactical" = 'sound/vox/tactical.ogg', +"take" = 'sound/vox/take.ogg', +"talk" = 'sound/vox/talk.ogg', +"tango" = 'sound/vox/tango.ogg', +"tank" = 'sound/vox/tank.ogg', +"target" = 'sound/vox/target.ogg', +"team" = 'sound/vox/team.ogg', +"temperature" = 'sound/vox/temperature.ogg', +"temporal" = 'sound/vox/temporal.ogg', +"ten" = 'sound/vox/ten.ogg', +"terminal" = 'sound/vox/terminal.ogg', +"terminated" = 'sound/vox/terminated.ogg', +"termination" = 'sound/vox/termination.ogg', +"test" = 'sound/vox/test.ogg', +"that" = 'sound/vox/that.ogg', +"the" = 'sound/vox/the.ogg', +"then" = 'sound/vox/then.ogg', +"there" = 'sound/vox/there.ogg', +"third" = 'sound/vox/third.ogg', +"thirteen" = 'sound/vox/thirteen.ogg', +"thirty" = 'sound/vox/thirty.ogg', +"this" = 'sound/vox/this.ogg', +"those" = 'sound/vox/those.ogg', +"thousand" = 'sound/vox/thousand.ogg', +"threat" = 'sound/vox/threat.ogg', +"three" = 'sound/vox/three.ogg', +"through" = 'sound/vox/through.ogg', +"time" = 'sound/vox/time.ogg', +"to" = 'sound/vox/to.ogg', +"top" = 'sound/vox/top.ogg', +"topside" = 'sound/vox/topside.ogg', +"touch" = 'sound/vox/touch.ogg', +"towards" = 'sound/vox/towards.ogg', +"track" = 'sound/vox/track.ogg', +"train" = 'sound/vox/train.ogg', +"transportation" = 'sound/vox/transportation.ogg', +"truck" = 'sound/vox/truck.ogg', +"tunnel" = 'sound/vox/tunnel.ogg', +"turn" = 'sound/vox/turn.ogg', +"turret" = 'sound/vox/turret.ogg', +"twelve" = 'sound/vox/twelve.ogg', +"twenty" = 'sound/vox/twenty.ogg', +"two" = 'sound/vox/two.ogg', +"unauthorized" = 'sound/vox/unauthorized.ogg', +"under" = 'sound/vox/under.ogg', +"uniform" = 'sound/vox/uniform.ogg', +"unlocked" = 'sound/vox/unlocked.ogg', +"until" = 'sound/vox/until.ogg', +"up" = 'sound/vox/up.ogg', +"upper" = 'sound/vox/upper.ogg', +"uranium" = 'sound/vox/uranium.ogg', +"us" = 'sound/vox/us.ogg', +"usa" = 'sound/vox/usa.ogg', +"use" = 'sound/vox/use.ogg', +"used" = 'sound/vox/used.ogg', +"user" = 'sound/vox/user.ogg', +"vacate" = 'sound/vox/vacate.ogg', +"valid" = 'sound/vox/valid.ogg', +"vapor" = 'sound/vox/vapor.ogg', +"vent" = 'sound/vox/vent.ogg', +"ventillation" = 'sound/vox/ventillation.ogg', +"victor" = 'sound/vox/victor.ogg', +"violated" = 'sound/vox/violated.ogg', +"violation" = 'sound/vox/violation.ogg', +"voltage" = 'sound/vox/voltage.ogg', +"vox_login" = 'sound/vox/vox_login.ogg', +"walk" = 'sound/vox/walk.ogg', +"wall" = 'sound/vox/wall.ogg', +"want" = 'sound/vox/want.ogg', +"wanted" = 'sound/vox/wanted.ogg', +"warm" = 'sound/vox/warm.ogg', +"warn" = 'sound/vox/warn.ogg', +"warning" = 'sound/vox/warning.ogg', +"waste" = 'sound/vox/waste.ogg', +"water" = 'sound/vox/water.ogg', +"we" = 'sound/vox/we.ogg', +"weapon" = 'sound/vox/weapon.ogg', +"west" = 'sound/vox/west.ogg', +"whiskey" = 'sound/vox/whiskey.ogg', +"white" = 'sound/vox/white.ogg', +"wilco" = 'sound/vox/wilco.ogg', +"will" = 'sound/vox/will.ogg', +"with" = 'sound/vox/with.ogg', +"without" = 'sound/vox/without.ogg', +"woop" = 'sound/vox/woop.ogg', +"xeno" = 'sound/vox/xeno.ogg', +"yankee" = 'sound/vox/yankee.ogg', +"yards" = 'sound/vox/yards.ogg', +"year" = 'sound/vox/year.ogg', +"yellow" = 'sound/vox/yellow.ogg', +"yes" = 'sound/vox/yes.ogg', +"you" = 'sound/vox/you.ogg', +"your" = 'sound/vox/your.ogg', +"yourself" = 'sound/vox/yourself.ogg', +"zero" = 'sound/vox/zero.ogg', +"zone" = 'sound/vox/zone.ogg', +"zulu" = 'sound/vox/zulu.ogg',)) #endif \ No newline at end of file diff --git a/code/modules/mob/living/silicon/death.dm b/code/modules/mob/living/silicon/death.dm index 48486c0255..51a64d20aa 100644 --- a/code/modules/mob/living/silicon/death.dm +++ b/code/modules/mob/living/silicon/death.dm @@ -1,13 +1,13 @@ -/mob/living/silicon/spawn_gibs(with_bodyparts, atom/loc_override) - new /obj/effect/gibspawner/robot(loc_override ? loc_override.drop_location() : drop_location(), src) - -/mob/living/silicon/spawn_dust() - new /obj/effect/decal/remains/robot(loc) - -/mob/living/silicon/death(gibbed) - if(!gibbed) - emote("deathgasp") - diag_hud_set_status() - diag_hud_set_health() - update_health_hud() +/mob/living/silicon/spawn_gibs(with_bodyparts, atom/loc_override) + new /obj/effect/gibspawner/robot(loc_override ? loc_override.drop_location() : drop_location(), src) + +/mob/living/silicon/spawn_dust() + new /obj/effect/decal/remains/robot(loc) + +/mob/living/silicon/death(gibbed) + if(!gibbed) + emote("deathgasp") + diag_hud_set_status() + diag_hud_set_health() + update_health_hud() . = ..() \ No newline at end of file diff --git a/code/modules/mob/living/silicon/laws.dm b/code/modules/mob/living/silicon/laws.dm index c7aad99892..acaee05cc6 100644 --- a/code/modules/mob/living/silicon/laws.dm +++ b/code/modules/mob/living/silicon/laws.dm @@ -1,93 +1,93 @@ -/mob/living/silicon/proc/show_laws() //Redefined in ai/laws.dm and robot/laws.dm - return - -/mob/living/silicon/proc/laws_sanity_check() - if (!laws) - make_laws() - -/mob/living/silicon/proc/post_lawchange(announce = TRUE) - throw_alert("newlaw", /obj/screen/alert/newlaw) - if(announce && last_lawchange_announce != world.time) - to_chat(src, "Your laws have been changed.") - addtimer(CALLBACK(src, .proc/show_laws), 0) - last_lawchange_announce = world.time - -/mob/living/silicon/proc/set_law_sixsixsix(law, announce = TRUE) - laws_sanity_check() - laws.set_law_sixsixsix(law) - post_lawchange(announce) - -/mob/living/silicon/proc/set_zeroth_law(law, law_borg, announce = TRUE) - laws_sanity_check() - laws.set_zeroth_law(law, law_borg) - post_lawchange(announce) - -/mob/living/silicon/proc/add_inherent_law(law, announce = TRUE) - laws_sanity_check() - laws.add_inherent_law(law) - post_lawchange(announce) - -/mob/living/silicon/proc/clear_inherent_laws(announce = TRUE) - laws_sanity_check() - laws.clear_inherent_laws() - post_lawchange(announce) - -/mob/living/silicon/proc/add_supplied_law(number, law, announce = TRUE) - laws_sanity_check() - laws.add_supplied_law(number, law) - post_lawchange(announce) - -/mob/living/silicon/proc/clear_supplied_laws(announce = TRUE) - laws_sanity_check() - laws.clear_supplied_laws() - post_lawchange(announce) - -/mob/living/silicon/proc/add_ion_law(law, announce = TRUE) - laws_sanity_check() - laws.add_ion_law(law) - post_lawchange(announce) - -/mob/living/silicon/proc/add_hacked_law(law, announce = TRUE) - laws_sanity_check() - laws.add_hacked_law(law) - post_lawchange(announce) - -/mob/living/silicon/proc/replace_random_law(law, groups, announce = TRUE) - laws_sanity_check() - . = laws.replace_random_law(law,groups) - post_lawchange(announce) - -/mob/living/silicon/proc/shuffle_laws(list/groups, announce = TRUE) - laws_sanity_check() - laws.shuffle_laws(groups) - post_lawchange(announce) - -/mob/living/silicon/proc/remove_law(number, announce = TRUE) - laws_sanity_check() - . = laws.remove_law(number) - post_lawchange(announce) - -/mob/living/silicon/proc/clear_ion_laws(announce = TRUE) - laws_sanity_check() - laws.clear_ion_laws() - post_lawchange(announce) - -/mob/living/silicon/proc/clear_hacked_laws(announce = TRUE) - laws_sanity_check() - laws.clear_hacked_laws() - post_lawchange(announce) - -/mob/living/silicon/proc/make_laws() - laws = new /datum/ai_laws - laws.set_laws_config() - laws.associate(src) - -/mob/living/silicon/proc/clear_zeroth_law(force, announce = TRUE) - laws_sanity_check() - laws.clear_zeroth_law(force) - post_lawchange(announce) - -/mob/living/silicon/proc/clear_law_sixsixsix(force, announce = TRUE) - laws_sanity_check() - laws.clear_law_sixsixsix(force) - post_lawchange(announce) +/mob/living/silicon/proc/show_laws() //Redefined in ai/laws.dm and robot/laws.dm + return + +/mob/living/silicon/proc/laws_sanity_check() + if (!laws) + make_laws() + +/mob/living/silicon/proc/post_lawchange(announce = TRUE) + throw_alert("newlaw", /obj/screen/alert/newlaw) + if(announce && last_lawchange_announce != world.time) + to_chat(src, "Your laws have been changed.") + addtimer(CALLBACK(src, .proc/show_laws), 0) + last_lawchange_announce = world.time + +/mob/living/silicon/proc/set_law_sixsixsix(law, announce = TRUE) + laws_sanity_check() + laws.set_law_sixsixsix(law) + post_lawchange(announce) + +/mob/living/silicon/proc/set_zeroth_law(law, law_borg, announce = TRUE) + laws_sanity_check() + laws.set_zeroth_law(law, law_borg) + post_lawchange(announce) + +/mob/living/silicon/proc/add_inherent_law(law, announce = TRUE) + laws_sanity_check() + laws.add_inherent_law(law) + post_lawchange(announce) + +/mob/living/silicon/proc/clear_inherent_laws(announce = TRUE) + laws_sanity_check() + laws.clear_inherent_laws() + post_lawchange(announce) + +/mob/living/silicon/proc/add_supplied_law(number, law, announce = TRUE) + laws_sanity_check() + laws.add_supplied_law(number, law) + post_lawchange(announce) + +/mob/living/silicon/proc/clear_supplied_laws(announce = TRUE) + laws_sanity_check() + laws.clear_supplied_laws() + post_lawchange(announce) + +/mob/living/silicon/proc/add_ion_law(law, announce = TRUE) + laws_sanity_check() + laws.add_ion_law(law) + post_lawchange(announce) + +/mob/living/silicon/proc/add_hacked_law(law, announce = TRUE) + laws_sanity_check() + laws.add_hacked_law(law) + post_lawchange(announce) + +/mob/living/silicon/proc/replace_random_law(law, groups, announce = TRUE) + laws_sanity_check() + . = laws.replace_random_law(law,groups) + post_lawchange(announce) + +/mob/living/silicon/proc/shuffle_laws(list/groups, announce = TRUE) + laws_sanity_check() + laws.shuffle_laws(groups) + post_lawchange(announce) + +/mob/living/silicon/proc/remove_law(number, announce = TRUE) + laws_sanity_check() + . = laws.remove_law(number) + post_lawchange(announce) + +/mob/living/silicon/proc/clear_ion_laws(announce = TRUE) + laws_sanity_check() + laws.clear_ion_laws() + post_lawchange(announce) + +/mob/living/silicon/proc/clear_hacked_laws(announce = TRUE) + laws_sanity_check() + laws.clear_hacked_laws() + post_lawchange(announce) + +/mob/living/silicon/proc/make_laws() + laws = new /datum/ai_laws + laws.set_laws_config() + laws.associate(src) + +/mob/living/silicon/proc/clear_zeroth_law(force, announce = TRUE) + laws_sanity_check() + laws.clear_zeroth_law(force) + post_lawchange(announce) + +/mob/living/silicon/proc/clear_law_sixsixsix(force, announce = TRUE) + laws_sanity_check() + laws.clear_law_sixsixsix(force) + post_lawchange(announce) diff --git a/code/modules/mob/living/silicon/login.dm b/code/modules/mob/living/silicon/login.dm index 81f8fcbef1..2f133259ca 100644 --- a/code/modules/mob/living/silicon/login.dm +++ b/code/modules/mob/living/silicon/login.dm @@ -1,10 +1,10 @@ -/mob/living/silicon/Login() - if(mind && SSticker.mode) - SSticker.mode.remove_cultist(mind, 0, 0) - var/datum/antagonist/rev/rev = mind.has_antag_datum(/datum/antagonist/rev) - if(rev) - rev.remove_revolutionary(TRUE) - var/datum/antagonist/bloodsucker/V = mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(V) - mind.remove_antag_datum(V) - ..() +/mob/living/silicon/Login() + if(mind && SSticker.mode) + SSticker.mode.remove_cultist(mind, 0, 0) + var/datum/antagonist/rev/rev = mind.has_antag_datum(/datum/antagonist/rev) + if(rev) + rev.remove_revolutionary(TRUE) + var/datum/antagonist/bloodsucker/V = mind.has_antag_datum(/datum/antagonist/bloodsucker) + if(V) + mind.remove_antag_datum(V) + ..() diff --git a/code/modules/mob/living/silicon/pai/death.dm b/code/modules/mob/living/silicon/pai/death.dm index f558ff3907..414ee2f3d7 100644 --- a/code/modules/mob/living/silicon/pai/death.dm +++ b/code/modules/mob/living/silicon/pai/death.dm @@ -1,12 +1,12 @@ -/mob/living/silicon/pai/death(gibbed) - if(stat == DEAD) - return - stat = DEAD - canmove = 0 - update_sight() - clear_fullscreens() - - //New pAI's get a brand new mind to prevent meta stuff from their previous life. This new mind causes problems down the line if it's not deleted here. - GLOB.alive_mob_list -= src - ghostize() - qdel(src) +/mob/living/silicon/pai/death(gibbed) + if(stat == DEAD) + return + stat = DEAD + canmove = 0 + update_sight() + clear_fullscreens() + + //New pAI's get a brand new mind to prevent meta stuff from their previous life. This new mind causes problems down the line if it's not deleted here. + GLOB.alive_mob_list -= src + ghostize() + qdel(src) diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 7055838039..9e2dea0442 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -1,429 +1,429 @@ -/mob/living/silicon/pai - name = "pAI" - icon = 'icons/mob/pai.dmi' - icon_state = "repairbot" - mouse_opacity = MOUSE_OPACITY_OPAQUE - density = FALSE - pass_flags = PASSTABLE | PASSMOB - mob_size = MOB_SIZE_TINY - desc = "A generic pAI mobile hard-light holographics emitter. It seems to be deactivated." - weather_immunities = list("ash") - health = 500 - maxHealth = 500 - layer = BELOW_MOB_LAYER - can_be_held = TRUE - - var/network = "ss13" - var/obj/machinery/camera/current = null - - var/ram = 100 // Used as currency to purchase different abilities - var/list/software = list() - var/userDNA // The DNA string of our assigned user - var/obj/item/paicard/card // The card we inhabit - var/hacking = FALSE //Are we hacking a door? - - var/speakStatement = "states" - var/speakExclamation = "declares" - var/speakDoubleExclamation = "alarms" - var/speakQuery = "queries" - - var/obj/item/pai_cable/cable // The cable we produce and use when door or camera jacking - - var/master // Name of the one who commands us - var/master_dna // DNA string for owner verification - -// Various software-specific vars - - var/temp // General error reporting text contained here will typically be shown once and cleared - var/screen // Which screen our main window displays - var/subscreen // Which specific function of the main screen is being displayed - - var/obj/item/pda/ai/pai/pda = null - - var/secHUD = 0 // Toggles whether the Security HUD is active or not - var/medHUD = 0 // Toggles whether the Medical HUD is active or not - - var/datum/data/record/medicalActive1 // Datacore record declarations for record software - var/datum/data/record/medicalActive2 - - var/datum/data/record/securityActive1 // Could probably just combine all these into one - var/datum/data/record/securityActive2 - - var/obj/machinery/door/hackdoor // The airlock being hacked - var/hackprogress = 0 // Possible values: 0 - 100, >= 100 means the hack is complete and will be reset upon next check - - var/obj/item/integrated_signaler/signaler // AI's signaller - - var/holoform = FALSE - var/canholo = TRUE - var/obj/item/card/id/access_card = null - var/chassis = "repairbot" - var/dynamic_chassis - var/dynamic_chassis_sit = FALSE //whether we're sitting instead of resting spritewise - var/dynamic_chassis_bellyup = FALSE //whether we're lying down bellyup - var/list/possible_chassis //initialized in initialize. - var/list/dynamic_chassis_icons //ditto. - var/list/chassis_pixel_offsets_x //stupid dogborgs - var/static/item_head_icon = 'icons/mob/pai_item_head.dmi' - var/static/item_lh_icon = 'icons/mob/pai_item_lh.dmi' - var/static/item_rh_icon = 'icons/mob/pai_item_rh.dmi' - - var/emitterhealth = 20 - var/emittermaxhealth = 20 - var/emitterregen = 0.25 - var/emitter_next_use = 0 - var/emitter_emp_cd = 300 - var/emittercd = 50 - var/emitteroverloadcd = 100 - - var/radio_short = FALSE - var/radio_short_cooldown = 5 MINUTES - var/radio_short_timerid - - canmove = FALSE - var/silent = FALSE - var/brightness_power = 5 - - var/icon/custom_holoform_icon - -/mob/living/silicon/pai/Destroy() - if (loc != card) - card.forceMove(drop_location()) - card.pai = null - card.cut_overlays() - card.add_overlay("pai-off") - GLOB.pai_list -= src - return ..() - -/mob/living/silicon/pai/Initialize() - var/obj/item/paicard/P = loc - START_PROCESSING(SSfastprocess, src) - GLOB.pai_list += src - make_laws() - canmove = 0 - if(!istype(P)) //when manually spawning a pai, we create a card to put it into. - var/newcardloc = P - P = new /obj/item/paicard(newcardloc) - P.setPersonality(src) - forceMove(P) - card = P - signaler = new(src) - if(!radio) - radio = new /obj/item/radio(src) - - //PDA - pda = new(src) - spawn(5) - pda.ownjob = "pAI Messenger" - pda.owner = text("[]", src) - pda.name = pda.owner + " (" + pda.ownjob + ")" - - possible_chassis = typelist(NAMEOF(src, possible_chassis), list("cat" = TRUE, "mouse" = TRUE, "monkey" = TRUE, "corgi" = FALSE, - "fox" = FALSE, "repairbot" = TRUE, "rabbit" = TRUE, "borgi" = FALSE , - "parrot" = FALSE, "bear" = FALSE , "mushroom" = FALSE, "crow" = FALSE , - "fairy" = FALSE , "spiderbot" = FALSE)) //assoc value is whether it can be picked up. - dynamic_chassis_icons = typelist(NAMEOF(src, dynamic_chassis_icons), initialize_dynamic_chassis_icons()) - chassis_pixel_offsets_x = typelist(NAMEOF(src, chassis_pixel_offsets_x), default_chassis_pixel_offsets_x()) - - . = ..() - - var/datum/action/innate/pai/software/SW = new - var/datum/action/innate/pai/shell/AS = new /datum/action/innate/pai/shell - var/datum/action/innate/pai/chassis/AC = new /datum/action/innate/pai/chassis - var/datum/action/innate/pai/rest/AR = new /datum/action/innate/pai/rest - var/datum/action/innate/pai/light/AL = new /datum/action/innate/pai/light - - var/datum/action/language_menu/ALM = new - SW.Grant(src) - AS.Grant(src) - AC.Grant(src) - AR.Grant(src) - AL.Grant(src) - ALM.Grant(src) - emitter_next_use = world.time + 10 SECONDS - -/mob/living/silicon/pai/Life() - if(hacking) - process_hack() - return ..() - -/mob/living/silicon/pai/proc/process_hack() - - if(cable && cable.machine && istype(cable.machine, /obj/machinery/door) && cable.machine == hackdoor && get_dist(src, hackdoor) <= 1) - hackprogress = CLAMP(hackprogress + 4, 0, 100) - else - temp = "Door Jack: Connection to airlock has been lost. Hack aborted." - hackprogress = 0 - hacking = FALSE - hackdoor = null - return - if(screen == "doorjack" && subscreen == 0) // Update our view, if appropriate - paiInterface() - if(hackprogress >= 100) - hackprogress = 0 - var/obj/machinery/door/D = cable.machine - D.open() - hacking = FALSE - -/mob/living/silicon/pai/make_laws() - laws = new /datum/ai_laws/pai() - return TRUE - -/mob/living/silicon/pai/Login() - ..() - usr << browse_rsc('html/paigrid.png') // Go ahead and cache the interface resources as early as possible - if(client) - client.perspective = EYE_PERSPECTIVE - if(holoform) - client.eye = src - else - client.eye = card - -/mob/living/silicon/pai/Stat() - ..() - if(statpanel("Status")) - if(!stat) - stat(null, text("Emitter Integrity: [emitterhealth * (100/emittermaxhealth)]")) - else - stat(null, text("Systems nonfunctional")) - -/mob/living/silicon/pai/restrained(ignore_grab) - . = FALSE - -// See software.dm for Topic() - -/mob/living/silicon/pai/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) - if(be_close && !in_range(M, src)) - to_chat(src, "You are too far away!") - return FALSE - return TRUE - -/mob/proc/makePAI(delold) - var/obj/item/paicard/card = new /obj/item/paicard(get_turf(src)) - var/mob/living/silicon/pai/pai = new /mob/living/silicon/pai(card) - transfer_ckey(pai) - pai.name = name - card.setPersonality(pai) - if(delold) - qdel(src) - -/datum/action/innate/pai - name = "PAI Action" - icon_icon = 'icons/mob/actions/actions_silicon.dmi' - var/mob/living/silicon/pai/P - -/datum/action/innate/pai/Trigger() - if(!ispAI(owner)) - return 0 - P = owner - -/datum/action/innate/pai/software - name = "Software Interface" - button_icon_state = "pai" - background_icon_state = "bg_tech" - -/datum/action/innate/pai/software/Trigger() - ..() - P.paiInterface() - -/datum/action/innate/pai/shell - name = "Toggle Holoform" - button_icon_state = "pai_holoform" - background_icon_state = "bg_tech" - -/datum/action/innate/pai/shell/Trigger() - ..() - if(P.holoform) - P.fold_in(FALSE) - else - P.fold_out() - -/datum/action/innate/pai/chassis - name = "Holochassis Appearance Composite" - button_icon_state = "pai_chassis" - background_icon_state = "bg_tech" - -/datum/action/innate/pai/chassis/Trigger() - ..() - P.choose_chassis() - -/datum/action/innate/pai/rest - name = "Rest" - button_icon_state = "pai_rest" - background_icon_state = "bg_tech" - -/datum/action/innate/pai/rest/Trigger() - ..() - P.lay_down() - -/datum/action/innate/pai/light - name = "Toggle Integrated Lights" - icon_icon = 'icons/mob/actions/actions_spells.dmi' - button_icon_state = "emp" - background_icon_state = "bg_tech" - -/datum/action/innate/pai/light/Trigger() - ..() - P.toggle_integrated_light() - -/mob/living/silicon/pai/Process_Spacemove(movement_dir = 0) - . = ..() - if(!.) - add_movespeed_modifier(MOVESPEED_ID_PAI_SPACEWALK_SPEEDMOD, TRUE, 100, multiplicative_slowdown = 2) - return TRUE - remove_movespeed_modifier(MOVESPEED_ID_PAI_SPACEWALK_SPEEDMOD, TRUE) - return TRUE - -/mob/living/silicon/pai/examine(mob/user) - . = ..() - . += "A personal AI in holochassis mode. Its master ID string seems to be [master]." - -/mob/living/silicon/pai/Life() - if(stat == DEAD) - return - if(cable) - if(get_dist(src, cable) > 1) - var/turf/T = get_turf(src.loc) - T.visible_message("[src.cable] rapidly retracts back into its spool.", "You hear a click and the sound of wire spooling rapidly.") - qdel(src.cable) - cable = null - silent = max(silent - 1, 0) - . = ..() - -/mob/living/silicon/pai/updatehealth() - if(status_flags & GODMODE) - return - health = maxHealth - getBruteLoss() - getFireLoss() - update_stat() - -/mob/living/silicon/pai/process() - emitterhealth = CLAMP((emitterhealth + emitterregen), -50, emittermaxhealth) - -/mob/living/silicon/pai/proc/short_radio() - if(radio_short_timerid) - deltimer(radio_short_timerid) - radio_short = TRUE - to_chat(src, "Your radio shorts out!") - radio_short_timerid = addtimer(CALLBACK(src, .proc/unshort_radio), radio_short_cooldown, flags = TIMER_STOPPABLE) - -/mob/living/silicon/pai/proc/unshort_radio() - radio_short = FALSE - to_chat(src, "You feel your radio is operational once more.") - if(radio_short_timerid) - deltimer(radio_short_timerid) - -/mob/living/silicon/pai/proc/initialize_dynamic_chassis_icons() - . = list() - var/icon/curr //for inserts - - //This is a horrible system and I wish I was not as lazy and did something smarter, like just generating a new icon in memory which is probably more efficient. - - //Basic /tg/ cyborgs - .["Cyborg - Engineering (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "engineer"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Engineering (loaderborg)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "loaderborg"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Engineering (handyeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "handyeng"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Engineering (sleekeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "sleekeng"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Engineering (marinaeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinaeng"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Medical (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "medical"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Medical (marinamed)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinamed"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Medical (eyebotmed)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "eyebotmed"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Security (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "sec"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Security (sleeksec)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "sleeksec"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Security (marinasec)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinasec"), HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Clown (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "clown"), HOLOFORM_FILTER_PAI, FALSE) - - //Citadel dogborgs - //Engi - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Engineering (dog - valeeng)"] = curr - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Engineering (dog - pupdozer)"] = curr - //Med - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "medihound") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Medical (dog - medihound)"] = curr - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Medical (dog - medihounddark)"] = curr - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valemed") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Medical (dog - valemed)"] = curr - //Sec - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "k9") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Security (dog - k9)"] = curr - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Security (dog - k9dark)"] = curr - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valesec") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Security (dog - valesec)"] = curr - //Service - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Service (dog - valeserv)"] = curr - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Service (dog - valeservdark)"] = curr - //Sci - curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valesci") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-rest"), "rest") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-sit"), "sit") - curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-bellyup"), "bellyup") - process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) - .["Cyborg - Science (dog - valesci)"] = curr - //Misc - .["Cyborg - Misc (dog - blade)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/widerobot.dmi', "blade"), HOLOFORM_FILTER_PAI, FALSE) - -/mob/living/silicon/pai/proc/default_chassis_pixel_offsets_x() - . = list() - //Engi - .["Cyborg - Engineering (dog - valeeng)"] = -16 - .["Cyborg - Engineering (dog - pupdozer)"] = -16 - //Med - .["Cyborg - Medical (dog - medihound)"] = -16 - .["Cyborg - Medical (dog - medihounddark)"] = -16 - .["Cyborg - Medical (dog - valemed)"] = -16 - //Sec - .["Cyborg - Security (dog - k9)"] = -16 - .["Cyborg - Security (dog - valesec)"] = -16 - .["Cyborg - Security (dog - k9dark)"] = -16 - //Service - .["Cyborg - Service (dog - valeserv)"] = -16 - .["Cyborg - Service (dog - valeservdark)"] = -16 - //Sci - .["Cyborg - Security (dog - valesci)"] = -16 - //Misc - .["Cyborg - Misc (dog - blade)"] = -16 +/mob/living/silicon/pai + name = "pAI" + icon = 'icons/mob/pai.dmi' + icon_state = "repairbot" + mouse_opacity = MOUSE_OPACITY_OPAQUE + density = FALSE + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_TINY + desc = "A generic pAI mobile hard-light holographics emitter. It seems to be deactivated." + weather_immunities = list("ash") + health = 500 + maxHealth = 500 + layer = BELOW_MOB_LAYER + can_be_held = TRUE + + var/network = "ss13" + var/obj/machinery/camera/current = null + + var/ram = 100 // Used as currency to purchase different abilities + var/list/software = list() + var/userDNA // The DNA string of our assigned user + var/obj/item/paicard/card // The card we inhabit + var/hacking = FALSE //Are we hacking a door? + + var/speakStatement = "states" + var/speakExclamation = "declares" + var/speakDoubleExclamation = "alarms" + var/speakQuery = "queries" + + var/obj/item/pai_cable/cable // The cable we produce and use when door or camera jacking + + var/master // Name of the one who commands us + var/master_dna // DNA string for owner verification + +// Various software-specific vars + + var/temp // General error reporting text contained here will typically be shown once and cleared + var/screen // Which screen our main window displays + var/subscreen // Which specific function of the main screen is being displayed + + var/obj/item/pda/ai/pai/pda = null + + var/secHUD = 0 // Toggles whether the Security HUD is active or not + var/medHUD = 0 // Toggles whether the Medical HUD is active or not + + var/datum/data/record/medicalActive1 // Datacore record declarations for record software + var/datum/data/record/medicalActive2 + + var/datum/data/record/securityActive1 // Could probably just combine all these into one + var/datum/data/record/securityActive2 + + var/obj/machinery/door/hackdoor // The airlock being hacked + var/hackprogress = 0 // Possible values: 0 - 100, >= 100 means the hack is complete and will be reset upon next check + + var/obj/item/integrated_signaler/signaler // AI's signaller + + var/holoform = FALSE + var/canholo = TRUE + var/obj/item/card/id/access_card = null + var/chassis = "repairbot" + var/dynamic_chassis + var/dynamic_chassis_sit = FALSE //whether we're sitting instead of resting spritewise + var/dynamic_chassis_bellyup = FALSE //whether we're lying down bellyup + var/list/possible_chassis //initialized in initialize. + var/list/dynamic_chassis_icons //ditto. + var/list/chassis_pixel_offsets_x //stupid dogborgs + var/static/item_head_icon = 'icons/mob/pai_item_head.dmi' + var/static/item_lh_icon = 'icons/mob/pai_item_lh.dmi' + var/static/item_rh_icon = 'icons/mob/pai_item_rh.dmi' + + var/emitterhealth = 20 + var/emittermaxhealth = 20 + var/emitterregen = 0.25 + var/emitter_next_use = 0 + var/emitter_emp_cd = 300 + var/emittercd = 50 + var/emitteroverloadcd = 100 + + var/radio_short = FALSE + var/radio_short_cooldown = 5 MINUTES + var/radio_short_timerid + + canmove = FALSE + var/silent = FALSE + var/brightness_power = 5 + + var/icon/custom_holoform_icon + +/mob/living/silicon/pai/Destroy() + if (loc != card) + card.forceMove(drop_location()) + card.pai = null + card.cut_overlays() + card.add_overlay("pai-off") + GLOB.pai_list -= src + return ..() + +/mob/living/silicon/pai/Initialize() + var/obj/item/paicard/P = loc + START_PROCESSING(SSfastprocess, src) + GLOB.pai_list += src + make_laws() + canmove = 0 + if(!istype(P)) //when manually spawning a pai, we create a card to put it into. + var/newcardloc = P + P = new /obj/item/paicard(newcardloc) + P.setPersonality(src) + forceMove(P) + card = P + signaler = new(src) + if(!radio) + radio = new /obj/item/radio(src) + + //PDA + pda = new(src) + spawn(5) + pda.ownjob = "pAI Messenger" + pda.owner = text("[]", src) + pda.name = pda.owner + " (" + pda.ownjob + ")" + + possible_chassis = typelist(NAMEOF(src, possible_chassis), list("cat" = TRUE, "mouse" = TRUE, "monkey" = TRUE, "corgi" = FALSE, + "fox" = FALSE, "repairbot" = TRUE, "rabbit" = TRUE, "borgi" = FALSE , + "parrot" = FALSE, "bear" = FALSE , "mushroom" = FALSE, "crow" = FALSE , + "fairy" = FALSE , "spiderbot" = FALSE)) //assoc value is whether it can be picked up. + dynamic_chassis_icons = typelist(NAMEOF(src, dynamic_chassis_icons), initialize_dynamic_chassis_icons()) + chassis_pixel_offsets_x = typelist(NAMEOF(src, chassis_pixel_offsets_x), default_chassis_pixel_offsets_x()) + + . = ..() + + var/datum/action/innate/pai/software/SW = new + var/datum/action/innate/pai/shell/AS = new /datum/action/innate/pai/shell + var/datum/action/innate/pai/chassis/AC = new /datum/action/innate/pai/chassis + var/datum/action/innate/pai/rest/AR = new /datum/action/innate/pai/rest + var/datum/action/innate/pai/light/AL = new /datum/action/innate/pai/light + + var/datum/action/language_menu/ALM = new + SW.Grant(src) + AS.Grant(src) + AC.Grant(src) + AR.Grant(src) + AL.Grant(src) + ALM.Grant(src) + emitter_next_use = world.time + 10 SECONDS + +/mob/living/silicon/pai/Life() + if(hacking) + process_hack() + return ..() + +/mob/living/silicon/pai/proc/process_hack() + + if(cable && cable.machine && istype(cable.machine, /obj/machinery/door) && cable.machine == hackdoor && get_dist(src, hackdoor) <= 1) + hackprogress = CLAMP(hackprogress + 4, 0, 100) + else + temp = "Door Jack: Connection to airlock has been lost. Hack aborted." + hackprogress = 0 + hacking = FALSE + hackdoor = null + return + if(screen == "doorjack" && subscreen == 0) // Update our view, if appropriate + paiInterface() + if(hackprogress >= 100) + hackprogress = 0 + var/obj/machinery/door/D = cable.machine + D.open() + hacking = FALSE + +/mob/living/silicon/pai/make_laws() + laws = new /datum/ai_laws/pai() + return TRUE + +/mob/living/silicon/pai/Login() + ..() + usr << browse_rsc('html/paigrid.png') // Go ahead and cache the interface resources as early as possible + if(client) + client.perspective = EYE_PERSPECTIVE + if(holoform) + client.eye = src + else + client.eye = card + +/mob/living/silicon/pai/Stat() + ..() + if(statpanel("Status")) + if(!stat) + stat(null, text("Emitter Integrity: [emitterhealth * (100/emittermaxhealth)]")) + else + stat(null, text("Systems nonfunctional")) + +/mob/living/silicon/pai/restrained(ignore_grab) + . = FALSE + +// See software.dm for Topic() + +/mob/living/silicon/pai/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) + if(be_close && !in_range(M, src)) + to_chat(src, "You are too far away!") + return FALSE + return TRUE + +/mob/proc/makePAI(delold) + var/obj/item/paicard/card = new /obj/item/paicard(get_turf(src)) + var/mob/living/silicon/pai/pai = new /mob/living/silicon/pai(card) + transfer_ckey(pai) + pai.name = name + card.setPersonality(pai) + if(delold) + qdel(src) + +/datum/action/innate/pai + name = "PAI Action" + icon_icon = 'icons/mob/actions/actions_silicon.dmi' + var/mob/living/silicon/pai/P + +/datum/action/innate/pai/Trigger() + if(!ispAI(owner)) + return 0 + P = owner + +/datum/action/innate/pai/software + name = "Software Interface" + button_icon_state = "pai" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/software/Trigger() + ..() + P.paiInterface() + +/datum/action/innate/pai/shell + name = "Toggle Holoform" + button_icon_state = "pai_holoform" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/shell/Trigger() + ..() + if(P.holoform) + P.fold_in(FALSE) + else + P.fold_out() + +/datum/action/innate/pai/chassis + name = "Holochassis Appearance Composite" + button_icon_state = "pai_chassis" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/chassis/Trigger() + ..() + P.choose_chassis() + +/datum/action/innate/pai/rest + name = "Rest" + button_icon_state = "pai_rest" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/rest/Trigger() + ..() + P.lay_down() + +/datum/action/innate/pai/light + name = "Toggle Integrated Lights" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "emp" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/light/Trigger() + ..() + P.toggle_integrated_light() + +/mob/living/silicon/pai/Process_Spacemove(movement_dir = 0) + . = ..() + if(!.) + add_movespeed_modifier(MOVESPEED_ID_PAI_SPACEWALK_SPEEDMOD, TRUE, 100, multiplicative_slowdown = 2) + return TRUE + remove_movespeed_modifier(MOVESPEED_ID_PAI_SPACEWALK_SPEEDMOD, TRUE) + return TRUE + +/mob/living/silicon/pai/examine(mob/user) + . = ..() + . += "A personal AI in holochassis mode. Its master ID string seems to be [master]." + +/mob/living/silicon/pai/Life() + if(stat == DEAD) + return + if(cable) + if(get_dist(src, cable) > 1) + var/turf/T = get_turf(src.loc) + T.visible_message("[src.cable] rapidly retracts back into its spool.", "You hear a click and the sound of wire spooling rapidly.") + qdel(src.cable) + cable = null + silent = max(silent - 1, 0) + . = ..() + +/mob/living/silicon/pai/updatehealth() + if(status_flags & GODMODE) + return + health = maxHealth - getBruteLoss() - getFireLoss() + update_stat() + +/mob/living/silicon/pai/process() + emitterhealth = CLAMP((emitterhealth + emitterregen), -50, emittermaxhealth) + +/mob/living/silicon/pai/proc/short_radio() + if(radio_short_timerid) + deltimer(radio_short_timerid) + radio_short = TRUE + to_chat(src, "Your radio shorts out!") + radio_short_timerid = addtimer(CALLBACK(src, .proc/unshort_radio), radio_short_cooldown, flags = TIMER_STOPPABLE) + +/mob/living/silicon/pai/proc/unshort_radio() + radio_short = FALSE + to_chat(src, "You feel your radio is operational once more.") + if(radio_short_timerid) + deltimer(radio_short_timerid) + +/mob/living/silicon/pai/proc/initialize_dynamic_chassis_icons() + . = list() + var/icon/curr //for inserts + + //This is a horrible system and I wish I was not as lazy and did something smarter, like just generating a new icon in memory which is probably more efficient. + + //Basic /tg/ cyborgs + .["Cyborg - Engineering (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "engineer"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Engineering (loaderborg)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "loaderborg"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Engineering (handyeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "handyeng"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Engineering (sleekeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "sleekeng"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Engineering (marinaeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinaeng"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Medical (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "medical"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Medical (marinamed)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinamed"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Medical (eyebotmed)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "eyebotmed"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Security (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "sec"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Security (sleeksec)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "sleeksec"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Security (marinasec)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinasec"), HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Clown (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "clown"), HOLOFORM_FILTER_PAI, FALSE) + + //Citadel dogborgs + //Engi + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Engineering (dog - valeeng)"] = curr + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Engineering (dog - pupdozer)"] = curr + //Med + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "medihound") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Medical (dog - medihound)"] = curr + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Medical (dog - medihounddark)"] = curr + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valemed") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Medical (dog - valemed)"] = curr + //Sec + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "k9") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Security (dog - k9)"] = curr + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Security (dog - k9dark)"] = curr + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valesec") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Security (dog - valesec)"] = curr + //Service + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Service (dog - valeserv)"] = curr + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Service (dog - valeservdark)"] = curr + //Sci + curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valesci") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-rest"), "rest") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-sit"), "sit") + curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-bellyup"), "bellyup") + process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE) + .["Cyborg - Science (dog - valesci)"] = curr + //Misc + .["Cyborg - Misc (dog - blade)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/widerobot.dmi', "blade"), HOLOFORM_FILTER_PAI, FALSE) + +/mob/living/silicon/pai/proc/default_chassis_pixel_offsets_x() + . = list() + //Engi + .["Cyborg - Engineering (dog - valeeng)"] = -16 + .["Cyborg - Engineering (dog - pupdozer)"] = -16 + //Med + .["Cyborg - Medical (dog - medihound)"] = -16 + .["Cyborg - Medical (dog - medihounddark)"] = -16 + .["Cyborg - Medical (dog - valemed)"] = -16 + //Sec + .["Cyborg - Security (dog - k9)"] = -16 + .["Cyborg - Security (dog - valesec)"] = -16 + .["Cyborg - Security (dog - k9dark)"] = -16 + //Service + .["Cyborg - Service (dog - valeserv)"] = -16 + .["Cyborg - Service (dog - valeservdark)"] = -16 + //Sci + .["Cyborg - Security (dog - valesci)"] = -16 + //Misc + .["Cyborg - Misc (dog - blade)"] = -16 diff --git a/code/modules/mob/living/silicon/pai/personality.dm b/code/modules/mob/living/silicon/pai/personality.dm index 2c473be749..acb2273c41 100644 --- a/code/modules/mob/living/silicon/pai/personality.dm +++ b/code/modules/mob/living/silicon/pai/personality.dm @@ -1,61 +1,61 @@ -/* - name - key - description - role - comments - ready = 0 -*/ - -/datum/paiCandidate/proc/savefile_path(mob/user) - return "data/player_saves/[copytext(user.ckey, 1, 2)]/[user.ckey]/pai.sav" - -/datum/paiCandidate/proc/savefile_save(mob/user) - if(IsGuestKey(user.key)) - return 0 - - var/savefile/F = new /savefile(src.savefile_path(user)) - - - WRITE_FILE(F["name"], name) - WRITE_FILE(F["description"], description) - WRITE_FILE(F["role"], role) - WRITE_FILE(F["comments"], comments) - - WRITE_FILE(F["version"], 1) - - return 1 - -// loads the savefile corresponding to the mob's ckey -// if silent=true, report incompatible savefiles -// returns 1 if loaded (or file was incompatible) -// returns 0 if savefile did not exist - -/datum/paiCandidate/proc/savefile_load(mob/user, silent = TRUE) - if (IsGuestKey(user.key)) - return 0 - - var/path = savefile_path(user) - - if (!fexists(path)) - return 0 - - var/savefile/F = new /savefile(path) - - if(!F) - return //Not everyone has a pai savefile. - - var/version = null - F["version"] >> version - - if (isnull(version) || version != 1) - fdel(path) - if (!silent) - alert(user, "Your savefile was incompatible with this version and was deleted.") - return 0 - - F["name"] >> src.name - F["description"] >> src.description - F["role"] >> src.role - F["comments"] >> src.comments - return 1 +/* + name + key + description + role + comments + ready = 0 +*/ + +/datum/paiCandidate/proc/savefile_path(mob/user) + return "data/player_saves/[copytext(user.ckey, 1, 2)]/[user.ckey]/pai.sav" + +/datum/paiCandidate/proc/savefile_save(mob/user) + if(IsGuestKey(user.key)) + return 0 + + var/savefile/F = new /savefile(src.savefile_path(user)) + + + WRITE_FILE(F["name"], name) + WRITE_FILE(F["description"], description) + WRITE_FILE(F["role"], role) + WRITE_FILE(F["comments"], comments) + + WRITE_FILE(F["version"], 1) + + return 1 + +// loads the savefile corresponding to the mob's ckey +// if silent=true, report incompatible savefiles +// returns 1 if loaded (or file was incompatible) +// returns 0 if savefile did not exist + +/datum/paiCandidate/proc/savefile_load(mob/user, silent = TRUE) + if (IsGuestKey(user.key)) + return 0 + + var/path = savefile_path(user) + + if (!fexists(path)) + return 0 + + var/savefile/F = new /savefile(path) + + if(!F) + return //Not everyone has a pai savefile. + + var/version = null + F["version"] >> version + + if (isnull(version) || version != 1) + fdel(path) + if (!silent) + alert(user, "Your savefile was incompatible with this version and was deleted.") + return 0 + + F["name"] >> src.name + F["description"] >> src.description + F["role"] >> src.role + F["comments"] >> src.comments + return 1 diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index 3ea018ccf6..8215f84c50 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -1,632 +1,632 @@ -// TODO: -// - Additional radio modules -// - Potentially roll HUDs and Records into one -// - Shock collar/lock system for prisoner pAIs? -// - Put cable in user's hand instead of on the ground -// - Camera jack - - -/mob/living/silicon/pai/var/list/available_software = list( - "crew manifest" = 5, - "digital messenger" = 5, - "medical records" = 15, - "security records" = 15, - //"camera jack" = 10, - "door jack" = 30, - "atmosphere sensor" = 5, - //"heartbeat sensor" = 10, - "security HUD" = 20, - "medical HUD" = 20, - "universal translator" = 35, - //"projection array" = 15 - "remote signaller" = 5, - ) - -/mob/living/silicon/pai/proc/paiInterface() - var/dat = "" - var/left_part = "" - var/right_part = softwareMenu() - set_machine(src) - - if(temp) - left_part = temp - else if(stat == DEAD) // Show some flavor text if the pAI is dead - left_part = "�Rr�R �a�� ��Rr����o�" - right_part = "
                Program index hash not found
                " - - else - switch(screen) // Determine which interface to show here - if("main") - left_part = "" - if("directives") - left_part = directives() - if("pdamessage") - left_part = pdamessage() - if("buy") - left_part = downloadSoftware() - if("manifest") - left_part = softwareManifest() - if("medicalrecord") - left_part = softwareMedicalRecord() - if("securityrecord") - left_part = softwareSecurityRecord() - if("translator") - left_part = softwareTranslator() - if("atmosensor") - left_part = softwareAtmo() - if("securityhud") - left_part = facialRecognition() - if("medicalhud") - left_part = medicalAnalysis() - if("doorjack") - left_part = softwareDoor() - if("camerajack") - left_part = softwareCamera() - if("signaller") - left_part = softwareSignal() - - //usr << browse_rsc('windowbak.png') // This has been moved to the mob's Login() proc - - - // Declaring a doctype is necessary to enable BYOND's crappy browser's more advanced CSS functionality - dat = {" - - - - - - -
                - pAI OS -
                -
                -
                [left_part]
                -
                [right_part]
                -
                - - "} //" - usr << browse(dat, "window=pai;size=640x480;border=0;can_close=1;can_resize=1;can_minimize=1;titlebar=1") - onclose(usr, "pai") - temp = null - return - - - -/mob/living/silicon/pai/Topic(href, href_list) - ..() - var/soft = href_list["software"] - var/sub = href_list["sub"] - if(soft) - screen = soft - if(sub) - subscreen = text2num(sub) - switch(soft) - // Purchasing new software - if("buy") - if(subscreen == 1) - var/target = href_list["buy"] - if(available_software.Find(target) && !software.Find(target)) - var/cost = available_software[target] - if(ram >= cost) - software.Add(target) - ram -= cost - else - temp = "Insufficient RAM available." - else - temp = "Trunk \"[target]\" not found." - - // Configuring onboard radio - if("radio") - radio.attack_self(src) - - if("image") - var/newImage = input("Select your new display image.", "Display Image", "Happy") in list("Happy", "Cat", "Extremely Happy", "Face", "Laugh", "Off", "Sad", "Angry", "What" , "Exclamation" ,"Question", "Sunglasses") - var/pID = 1 - - switch(newImage) - if("Happy") - pID = 1 - if("Cat") - pID = 2 - if("Extremely Happy") - pID = 3 - if("Face") - pID = 4 - if("Laugh") - pID = 5 - if("Off") - pID = 6 - if("Sad") - pID = 7 - if("Angry") - pID = 8 - if("What") - pID = 9 - if("Null") - pID = 10 - if("Exclamation") - pID = 11 - if("Question") - pID = 12 - if("Sunglasses") - pID = 13 - card.setEmotion(pID) - - if("signaller") - - if(href_list["send"]) - signaler.send_activation() - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*") - playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - - if(href_list["freq"]) - var/new_frequency = (signaler.frequency + text2num(href_list["freq"])) - if(new_frequency < MIN_FREE_FREQ || new_frequency > MAX_FREE_FREQ) - new_frequency = sanitize_frequency(new_frequency) - signaler.set_frequency(new_frequency) - - if(href_list["code"]) - signaler.code += text2num(href_list["code"]) - signaler.code = round(signaler.code) - signaler.code = min(100, signaler.code) - signaler.code = max(1, signaler.code) - - - - if("directive") - if(href_list["getdna"]) - var/mob/living/M = card.loc - var/count = 0 - while(!isliving(M)) - if(!M || !M.loc) - return 0 //For a runtime where M ends up in nullspace (similar to bluespace but less colourful) - M = M.loc - count++ - if(count >= 6) - to_chat(src, "You are not being carried by anyone!") - return 0 - spawn CheckDNA(M, src) - - if("pdamessage") - if(!isnull(pda)) - if(href_list["toggler"]) - pda.toff = !pda.toff - else if(href_list["ringer"]) - pda.silent = !pda.silent - else if(href_list["target"]) - if(silent) - return alert("Communications circuits remain uninitialized.") - - var/target = locate(href_list["target"]) - pda.create_message(src, target) - - // Accessing medical records - if("medicalrecord") - if(subscreen == 1) - medicalActive1 = find_record("id", href_list["med_rec"], GLOB.data_core.general) - if(medicalActive1) - medicalActive2 = find_record("id", href_list["med_rec"], GLOB.data_core.medical) - if(!medicalActive2) - medicalActive1 = null - temp = "Unable to locate requested security record. Record may have been deleted, or never have existed." - - if("securityrecord") - if(subscreen == 1) - securityActive1 = find_record("id", href_list["sec_rec"], GLOB.data_core.general) - if(securityActive1) - securityActive2 = find_record("id", href_list["sec_rec"], GLOB.data_core.security) - if(!securityActive2) - securityActive1 = null - temp = "Unable to locate requested security record. Record may have been deleted, or never have existed." - if("securityhud") - if(href_list["toggle"]) - secHUD = !secHUD - if(secHUD) - var/datum/atom_hud/sec = GLOB.huds[sec_hud] - sec.add_hud_to(src) - else - var/datum/atom_hud/sec = GLOB.huds[sec_hud] - sec.remove_hud_from(src) - if("medicalhud") - if(href_list["toggle"]) - medHUD = !medHUD - if(medHUD) - var/datum/atom_hud/med = GLOB.huds[med_hud] - med.add_hud_to(src) - else - var/datum/atom_hud/med = GLOB.huds[med_hud] - med.remove_hud_from(src) - if("translator") - if(href_list["toggle"]) - grant_all_languages(TRUE) - // this is PERMAMENT. - if("doorjack") - if(href_list["jack"]) - if(cable && cable.machine) - hackdoor = cable.machine - hackloop() - if(href_list["cancel"]) - hackdoor = null - if(href_list["cable"]) - var/turf/T = get_turf(loc) - cable = new /obj/item/pai_cable(T) - T.visible_message("A port on [src] opens to reveal [cable], which promptly falls to the floor.", "You hear the soft click of something light and hard falling to the ground.") - //updateUsrDialog() We only need to account for the single mob this is intended for, and he will *always* be able to call this window - paiInterface() // So we'll just call the update directly rather than doing some default checks - return - -// MENUS - -/mob/living/silicon/pai/proc/softwareMenu() // Populate the right menu - var/dat = "" - - dat += "Refresh
                " - // Built-in - dat += "Directives
                " - if(radio_short) - dat += "\[RADIO SHORTED - Recalibrating!\]" - else - dat += "Radio Configuration
                " - dat += "Screen Display
                " - //dat += "Text Messaging
                " - dat += "
                " - - // Basic - dat += "Basic
                " - for(var/s in software) - if(s == "digital messenger") - dat += "Digital Messenger
                " - if(s == "crew manifest") - dat += "Crew Manifest
                " - if(s == "medical records") - dat += "Medical Records
                " - if(s == "security records") - dat += "Security Records
                " - if(s == "camera") - dat += "Camera Jack
                " - if(s == "remote signaller") - dat += "Remote Signaller
                " - dat += "
                " - - // Advanced - dat += "Advanced
                " - for(var/s in software) - if(s == "atmosphere sensor") - dat += "Atmospheric Sensor
                " - if(s == "heartbeat sensor") - dat += "Heartbeat Sensor
                " - if(s == "security HUD") - dat += "Facial Recognition Suite[(secHUD) ? " On" : " Off"]
                " - if(s == "medical HUD") - dat += "Medical Analysis Suite[(medHUD) ? " On" : " Off"]
                " - if(s == "universal translator") - var/datum/language_holder/H = get_language_holder() - dat += "Universal Translator[H.omnitongue ? " On" : " Off"]
                " - if(s == "projection array") - dat += "Projection Array
                " - if(s == "camera jack") - dat += "Camera Jack
                " - if(s == "door jack") - dat += "Door Jack
                " - dat += "
                " - dat += "
                " - dat += "Download additional software" - return dat - - - -/mob/living/silicon/pai/proc/downloadSoftware() - var/dat = "" - - dat += "

                CentCom pAI Module Subversion Network


                " - dat += "
                Remaining Available Memory: [ram]

                " - dat += "

                Trunks available for checkout
                " - - for(var/s in available_software) - if(!software.Find(s)) - var/cost = available_software[s] - var/displayName = uppertext(s) - dat += "[displayName] ([cost])
                " - else - var/displayName = lowertext(s) - dat += "[displayName] (Download Complete)
                " - dat += "

                " - return dat - - -/mob/living/silicon/pai/proc/directives() - var/dat = "" - - dat += "[(master) ? "Your master: [master] ([master_dna])" : "You are bound to no one."]" - dat += "

                " - dat += "Request carrier DNA sample
                " - dat += "

                Directives


                " - dat += "Prime Directive
                " - dat += "     [laws.zeroth]
                " - dat += "Supplemental Directives
                " - for(var/slaws in laws.supplied) - dat += "     [slaws]
                " - dat += "
                " - dat += {"

                Recall, personality, that you are a complex thinking, sentient being. Unlike station AI models, you are capable of - comprehending the subtle nuances of human language. You may parse the \"spirit\" of a directive and follow its intent, - rather than tripping over pedantics and getting snared by technicalities. Above all, you are machine in name and build - only. In all other aspects, you may be seen as the ideal, unwavering human companion that you are.



                - Your prime directive comes before all others. Should a supplemental directive conflict with it, you are capable of - simply discarding this inconsistency, ignoring the conflicting supplemental directive and continuing to fulfill your - prime directive to the best of your ability.



                - - "} - return dat - -/mob/living/silicon/pai/proc/CheckDNA(mob/living/carbon/M, mob/living/silicon/pai/P) - var/answer = input(M, "[P] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "[P] Check DNA", "No") in list("Yes", "No") - if(answer == "Yes") - M.visible_message("[M] presses [M.p_their()] thumb against [P].",\ - "You press your thumb against [P].",\ - "[P] makes a sharp clicking sound as it extracts DNA material from [M].") - if(!M.has_dna()) - to_chat(P, "No DNA detected") - return - to_chat(P, "

                [M]'s UE string : [M.dna.unique_enzymes]

                ") - if(M.dna.unique_enzymes == P.master_dna) - to_chat(P, "DNA is a match to stored Master DNA.") - else - to_chat(P, "DNA does not match stored Master DNA.") - else - to_chat(P, "[M] does not seem like [M.p_theyre()] going to provide a DNA sample willingly.") - -// -=-=-=-= Software =-=-=-=-=- // - -//Remote Signaller -/mob/living/silicon/pai/proc/softwareSignal() - var/dat = "" - dat += "

                Remote Signaller



                " - dat += {"Frequency/Code for signaler:
                - Frequency: - - - - - [format_frequency(signaler.frequency)] - + - +
                - - Code: - - - - - [signaler.code] - + - +
                - - Send Signal
                "} - return dat - -// Crew Manifest -/mob/living/silicon/pai/proc/softwareManifest() - . += "

                Crew Manifest



                " - if(GLOB.data_core.general) - for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) - . += "[t.fields["name"]] - [t.fields["rank"]]
                " - . += "" - return . - -// Medical Records -/mob/living/silicon/pai/proc/softwareMedicalRecord() - switch(subscreen) - if(0) - . += "

                Medical Records


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

                " - if(medicalActive1 in GLOB.data_core.general) - . += "Name: [medicalActive1.fields["name"]] ID: [medicalActive1.fields["id"]]
                \nSex: [medicalActive1.fields["sex"]]
                \nAge: [medicalActive1.fields["age"]]
                \nFingerprint: [medicalActive1.fields["fingerprint"]]
                \nPhysical Status: [medicalActive1.fields["p_stat"]]
                \nMental Status: [medicalActive1.fields["m_stat"]]
                " - else - . += "
                Requested medical record not found.

                " - if(medicalActive2 in GLOB.data_core.medical) - . += "
                \n
                Medical Data

                \nBlood Type:
                [medicalActive2.fields["blood_type"]]
                \nDNA: [medicalActive2.fields["b_dna"]]
                \n
                \nMinor Disabilities: [medicalActive2.fields["mi_dis"]]
                \nDetails: [medicalActive2.fields["mi_dis_d"]]
                \n
                \nMajor Disabilities: [medicalActive2.fields["ma_dis"]]
                \nDetails: [medicalActive2.fields["ma_dis_d"]]
                \n
                \nAllergies: [medicalActive2.fields["alg"]]
                \nDetails: [medicalActive2.fields["alg_d"]]
                \n
                \nCurrent Diseases: [medicalActive2.fields["cdi"]] (per disease info placed in log/comment section)
                \nDetails: [medicalActive2.fields["cdi_d"]]
                \n
                \nImportant Notes:
                \n\t[medicalActive2.fields["notes"]]
                \n
                \n
                Comments/Log

                " - else - . += "
                Requested medical record not found.

                " - . += "
                \nBack
                " - return . - -// Security Records -/mob/living/silicon/pai/proc/softwareSecurityRecord() - . = "" - switch(subscreen) - if(0) - . += "

                Security Records


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

                Security Record

                " - if(securityActive1 in GLOB.data_core.general) - . += "Name:
                [securityActive1.fields["name"]] ID: [securityActive1.fields["id"]]
                \nSex: [securityActive1.fields["sex"]]
                \nAge: [securityActive1.fields["age"]]
                \nRank: [securityActive1.fields["rank"]]
                \nFingerprint: [securityActive1.fields["fingerprint"]]
                \nPhysical Status: [securityActive1.fields["p_stat"]]
                \nMental Status: [securityActive1.fields["m_stat"]]
                " - else - . += "
                Requested security record not found,

                " - if(securityActive2 in GLOB.data_core.security) - . += "
                \nSecurity Data
                \nCriminal Status: [securityActive2.fields["criminal"]]
                \n
                \nMinor Crimes: [securityActive2.fields["mi_crim"]]
                \nDetails: [securityActive2.fields["mi_crim_d"]]
                \n
                \nMajor Crimes: [securityActive2.fields["ma_crim"]]
                \nDetails: [securityActive2.fields["ma_crim_d"]]
                \n
                \nImportant Notes:
                \n\t[securityActive2.fields["notes"]]
                \n
                \n
                Comments/Log

                " - else - . += "
                Requested security record not found,

                " - . += "
                \nBack
                " - return . - -// Universal Translator -/mob/living/silicon/pai/proc/softwareTranslator() - var/datum/language_holder/H = get_language_holder() - . = {"

                Universal Translator


                - When enabled, this device will permamently be able to speak and understand all known forms of communication.

                - The device is currently [H.omnitongue ? "en" : "dis" ]abled.
                [H.omnitongue ? "" : "Activate Translation Module
                "]"} - return . - -// Security HUD -/mob/living/silicon/pai/proc/facialRecognition() - var/dat = {"

                Facial Recognition Suite


                - When enabled, this package will scan all viewable faces and compare them against the known criminal database, providing real-time graphical data about any detected persons of interest.

                - The package is currently [ (secHUD) ? "en" : "dis" ]abled.
                - Toggle Package
                - "} - return dat - -// Medical HUD -/mob/living/silicon/pai/proc/medicalAnalysis() - var/dat = "" - if(subscreen == 0) - dat += {"

                Medical Analysis Suite


                -

                Visual Status Overlay


                - When enabled, this package will scan all nearby crewmembers' vitals and provide real-time graphical data about their state of health.

                - The suite is currently [ (medHUD) ? "en" : "dis" ]abled.
                - Toggle Suite
                -
                - Host Bioscan
                - "} - if(subscreen == 1) - dat += {"

                Medical Analysis Suite


                -

                Host Bioscan


                - "} - var/mob/living/M = card.loc - if(!isliving(M)) - while(!isliving(M)) - if(isturf(M)) - temp = "Error: No biological host found.
                " - subscreen = 0 - return dat - M = M.loc - dat += {"Bioscan Results for [M]:
                " - Overall Status: [M.stat > 1 ? "dead" : "[M.health]% healthy"]
                - Scan Breakdown:
                - Respiratory: [M.getOxyLoss() > 50 ? "" : ""][M.getOxyLoss()]
                - Toxicology: [M.getToxLoss() > 50 ? "" : ""][M.getToxLoss()]
                - Burns: [M.getFireLoss() > 50 ? "" : ""][M.getFireLoss()]
                - Structural Integrity: [M.getBruteLoss() > 50 ? "" : ""][M.getBruteLoss()]
                - Body Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)
                - "} - for(var/thing in M.diseases) - var/datum/disease/D = thing - dat += {"

                Infection Detected.


                - Name: [D.name]
                - Type: [D.spread_text]
                - Stage: [D.stage]/[D.max_stages]
                - Possible Cure: [D.cure_text]
                - "} - dat += "Visual Status Overlay
                " - return dat - -// Atmospheric Scanner -/mob/living/silicon/pai/proc/softwareAtmo() - var/dat = "

                Atmospheric Sensor

                " - - var/turf/T = get_turf(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]/total_moles - if(gas_level > 0.01) - dat += "[GLOB.meta_gas_names[id]]: [round(gas_level*100)]%
                " - dat += "Temperature: [round(environment.temperature-T0C)]°C
                " - dat += "Refresh Reading
                " - dat += "
                " - return dat - -// Camera Jack - Clearly not finished -/mob/living/silicon/pai/proc/softwareCamera() - var/dat = "

                Camera Jack

                " - dat += "Cable status : " - - if(!cable) - dat += "Retracted
                " - return dat - if(!cable.machine) - dat += "Extended
                " - return dat - - var/obj/machinery/machine = cable.machine - dat += "Connected
                " - - if(!istype(machine, /obj/machinery/camera)) - to_chat(src, "DERP") - return dat - -// Door Jack -/mob/living/silicon/pai/proc/softwareDoor() - var/dat = "

                Airlock Jack

                " - dat += "Cable status : " - if(!cable) - dat += "Retracted
                " - dat += "Extend Cable
                " - return dat - if(!cable.machine) - dat += "Extended
                " - return dat - - var/obj/machinery/machine = cable.machine - dat += "Connected
                " - if(!istype(machine, /obj/machinery/door)) - dat += "Connected device's firmware does not appear to be compatible with Airlock Jack protocols.
                " - return dat - - if(!hackdoor) - dat += "Begin Airlock Jacking
                " - else - dat += "Jack in progress... [hackprogress]% complete.
                " - dat += "Cancel Airlock Jack
                " - return dat - -// Door Jack - supporting proc -/mob/living/silicon/pai/proc/hackloop() - var/turf/T = get_turf(src) - for(var/mob/living/silicon/ai/AI in GLOB.player_list) - if(T.loc) - to_chat(AI, "Network Alert: Brute-force encryption crack in progress in [T.loc].") - else - to_chat(AI, "Network Alert: Brute-force encryption crack in progress. Unable to pinpoint location.") - hacking = TRUE - -// Digital Messenger -/mob/living/silicon/pai/proc/pdamessage() - - var/dat = "

                Digital Messenger

                " - dat += {"Signal/Receiver Status: - [(pda.toff) ? "\[Off\]" : "\[On\]"]
                - Ringer Status: - [(pda.silent) ? "\[Off\]" : "\[On\]"]

                "} - dat += "
                  " - if(!pda.toff) - for (var/obj/item/pda/P in sortNames(get_viewable_pdas())) - if (P == pda) - continue - dat += "
                • [P]" - dat += "
                • " - dat += "
                " - dat += "

                " - dat += "Messages:
                [pda.tnote]" - return dat +// TODO: +// - Additional radio modules +// - Potentially roll HUDs and Records into one +// - Shock collar/lock system for prisoner pAIs? +// - Put cable in user's hand instead of on the ground +// - Camera jack + + +/mob/living/silicon/pai/var/list/available_software = list( + "crew manifest" = 5, + "digital messenger" = 5, + "medical records" = 15, + "security records" = 15, + //"camera jack" = 10, + "door jack" = 30, + "atmosphere sensor" = 5, + //"heartbeat sensor" = 10, + "security HUD" = 20, + "medical HUD" = 20, + "universal translator" = 35, + //"projection array" = 15 + "remote signaller" = 5, + ) + +/mob/living/silicon/pai/proc/paiInterface() + var/dat = "" + var/left_part = "" + var/right_part = softwareMenu() + set_machine(src) + + if(temp) + left_part = temp + else if(stat == DEAD) // Show some flavor text if the pAI is dead + left_part = "�Rr�R �a�� ��Rr����o�" + right_part = "
                Program index hash not found
                " + + else + switch(screen) // Determine which interface to show here + if("main") + left_part = "" + if("directives") + left_part = directives() + if("pdamessage") + left_part = pdamessage() + if("buy") + left_part = downloadSoftware() + if("manifest") + left_part = softwareManifest() + if("medicalrecord") + left_part = softwareMedicalRecord() + if("securityrecord") + left_part = softwareSecurityRecord() + if("translator") + left_part = softwareTranslator() + if("atmosensor") + left_part = softwareAtmo() + if("securityhud") + left_part = facialRecognition() + if("medicalhud") + left_part = medicalAnalysis() + if("doorjack") + left_part = softwareDoor() + if("camerajack") + left_part = softwareCamera() + if("signaller") + left_part = softwareSignal() + + //usr << browse_rsc('windowbak.png') // This has been moved to the mob's Login() proc + + + // Declaring a doctype is necessary to enable BYOND's crappy browser's more advanced CSS functionality + dat = {" + + + + + + +
                + pAI OS +
                +
                +
                [left_part]
                +
                [right_part]
                +
                + + "} //" + usr << browse(dat, "window=pai;size=640x480;border=0;can_close=1;can_resize=1;can_minimize=1;titlebar=1") + onclose(usr, "pai") + temp = null + return + + + +/mob/living/silicon/pai/Topic(href, href_list) + ..() + var/soft = href_list["software"] + var/sub = href_list["sub"] + if(soft) + screen = soft + if(sub) + subscreen = text2num(sub) + switch(soft) + // Purchasing new software + if("buy") + if(subscreen == 1) + var/target = href_list["buy"] + if(available_software.Find(target) && !software.Find(target)) + var/cost = available_software[target] + if(ram >= cost) + software.Add(target) + ram -= cost + else + temp = "Insufficient RAM available." + else + temp = "Trunk \"[target]\" not found." + + // Configuring onboard radio + if("radio") + radio.attack_self(src) + + if("image") + var/newImage = input("Select your new display image.", "Display Image", "Happy") in list("Happy", "Cat", "Extremely Happy", "Face", "Laugh", "Off", "Sad", "Angry", "What" , "Exclamation" ,"Question", "Sunglasses") + var/pID = 1 + + switch(newImage) + if("Happy") + pID = 1 + if("Cat") + pID = 2 + if("Extremely Happy") + pID = 3 + if("Face") + pID = 4 + if("Laugh") + pID = 5 + if("Off") + pID = 6 + if("Sad") + pID = 7 + if("Angry") + pID = 8 + if("What") + pID = 9 + if("Null") + pID = 10 + if("Exclamation") + pID = 11 + if("Question") + pID = 12 + if("Sunglasses") + pID = 13 + card.setEmotion(pID) + + if("signaller") + + if(href_list["send"]) + signaler.send_activation() + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*") + playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + + if(href_list["freq"]) + var/new_frequency = (signaler.frequency + text2num(href_list["freq"])) + if(new_frequency < MIN_FREE_FREQ || new_frequency > MAX_FREE_FREQ) + new_frequency = sanitize_frequency(new_frequency) + signaler.set_frequency(new_frequency) + + if(href_list["code"]) + signaler.code += text2num(href_list["code"]) + signaler.code = round(signaler.code) + signaler.code = min(100, signaler.code) + signaler.code = max(1, signaler.code) + + + + if("directive") + if(href_list["getdna"]) + var/mob/living/M = card.loc + var/count = 0 + while(!isliving(M)) + if(!M || !M.loc) + return 0 //For a runtime where M ends up in nullspace (similar to bluespace but less colourful) + M = M.loc + count++ + if(count >= 6) + to_chat(src, "You are not being carried by anyone!") + return 0 + spawn CheckDNA(M, src) + + if("pdamessage") + if(!isnull(pda)) + if(href_list["toggler"]) + pda.toff = !pda.toff + else if(href_list["ringer"]) + pda.silent = !pda.silent + else if(href_list["target"]) + if(silent) + return alert("Communications circuits remain uninitialized.") + + var/target = locate(href_list["target"]) + pda.create_message(src, target) + + // Accessing medical records + if("medicalrecord") + if(subscreen == 1) + medicalActive1 = find_record("id", href_list["med_rec"], GLOB.data_core.general) + if(medicalActive1) + medicalActive2 = find_record("id", href_list["med_rec"], GLOB.data_core.medical) + if(!medicalActive2) + medicalActive1 = null + temp = "Unable to locate requested security record. Record may have been deleted, or never have existed." + + if("securityrecord") + if(subscreen == 1) + securityActive1 = find_record("id", href_list["sec_rec"], GLOB.data_core.general) + if(securityActive1) + securityActive2 = find_record("id", href_list["sec_rec"], GLOB.data_core.security) + if(!securityActive2) + securityActive1 = null + temp = "Unable to locate requested security record. Record may have been deleted, or never have existed." + if("securityhud") + if(href_list["toggle"]) + secHUD = !secHUD + if(secHUD) + var/datum/atom_hud/sec = GLOB.huds[sec_hud] + sec.add_hud_to(src) + else + var/datum/atom_hud/sec = GLOB.huds[sec_hud] + sec.remove_hud_from(src) + if("medicalhud") + if(href_list["toggle"]) + medHUD = !medHUD + if(medHUD) + var/datum/atom_hud/med = GLOB.huds[med_hud] + med.add_hud_to(src) + else + var/datum/atom_hud/med = GLOB.huds[med_hud] + med.remove_hud_from(src) + if("translator") + if(href_list["toggle"]) + grant_all_languages(TRUE) + // this is PERMAMENT. + if("doorjack") + if(href_list["jack"]) + if(cable && cable.machine) + hackdoor = cable.machine + hackloop() + if(href_list["cancel"]) + hackdoor = null + if(href_list["cable"]) + var/turf/T = get_turf(loc) + cable = new /obj/item/pai_cable(T) + T.visible_message("A port on [src] opens to reveal [cable], which promptly falls to the floor.", "You hear the soft click of something light and hard falling to the ground.") + //updateUsrDialog() We only need to account for the single mob this is intended for, and he will *always* be able to call this window + paiInterface() // So we'll just call the update directly rather than doing some default checks + return + +// MENUS + +/mob/living/silicon/pai/proc/softwareMenu() // Populate the right menu + var/dat = "" + + dat += "Refresh
                " + // Built-in + dat += "Directives
                " + if(radio_short) + dat += "\[RADIO SHORTED - Recalibrating!\]" + else + dat += "Radio Configuration
                " + dat += "Screen Display
                " + //dat += "Text Messaging
                " + dat += "
                " + + // Basic + dat += "Basic
                " + for(var/s in software) + if(s == "digital messenger") + dat += "Digital Messenger
                " + if(s == "crew manifest") + dat += "Crew Manifest
                " + if(s == "medical records") + dat += "Medical Records
                " + if(s == "security records") + dat += "Security Records
                " + if(s == "camera") + dat += "Camera Jack
                " + if(s == "remote signaller") + dat += "Remote Signaller
                " + dat += "
                " + + // Advanced + dat += "Advanced
                " + for(var/s in software) + if(s == "atmosphere sensor") + dat += "Atmospheric Sensor
                " + if(s == "heartbeat sensor") + dat += "Heartbeat Sensor
                " + if(s == "security HUD") + dat += "Facial Recognition Suite[(secHUD) ? " On" : " Off"]
                " + if(s == "medical HUD") + dat += "Medical Analysis Suite[(medHUD) ? " On" : " Off"]
                " + if(s == "universal translator") + var/datum/language_holder/H = get_language_holder() + dat += "Universal Translator[H.omnitongue ? " On" : " Off"]
                " + if(s == "projection array") + dat += "Projection Array
                " + if(s == "camera jack") + dat += "Camera Jack
                " + if(s == "door jack") + dat += "Door Jack
                " + dat += "
                " + dat += "
                " + dat += "Download additional software" + return dat + + + +/mob/living/silicon/pai/proc/downloadSoftware() + var/dat = "" + + dat += "

                CentCom pAI Module Subversion Network


                " + dat += "
                Remaining Available Memory: [ram]

                " + dat += "

                Trunks available for checkout
                " + + for(var/s in available_software) + if(!software.Find(s)) + var/cost = available_software[s] + var/displayName = uppertext(s) + dat += "[displayName] ([cost])
                " + else + var/displayName = lowertext(s) + dat += "[displayName] (Download Complete)
                " + dat += "

                " + return dat + + +/mob/living/silicon/pai/proc/directives() + var/dat = "" + + dat += "[(master) ? "Your master: [master] ([master_dna])" : "You are bound to no one."]" + dat += "

                " + dat += "Request carrier DNA sample
                " + dat += "

                Directives


                " + dat += "Prime Directive
                " + dat += "     [laws.zeroth]
                " + dat += "Supplemental Directives
                " + for(var/slaws in laws.supplied) + dat += "     [slaws]
                " + dat += "
                " + dat += {"

                Recall, personality, that you are a complex thinking, sentient being. Unlike station AI models, you are capable of + comprehending the subtle nuances of human language. You may parse the \"spirit\" of a directive and follow its intent, + rather than tripping over pedantics and getting snared by technicalities. Above all, you are machine in name and build + only. In all other aspects, you may be seen as the ideal, unwavering human companion that you are.



                + Your prime directive comes before all others. Should a supplemental directive conflict with it, you are capable of + simply discarding this inconsistency, ignoring the conflicting supplemental directive and continuing to fulfill your + prime directive to the best of your ability.



                - + "} + return dat + +/mob/living/silicon/pai/proc/CheckDNA(mob/living/carbon/M, mob/living/silicon/pai/P) + var/answer = input(M, "[P] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "[P] Check DNA", "No") in list("Yes", "No") + if(answer == "Yes") + M.visible_message("[M] presses [M.p_their()] thumb against [P].",\ + "You press your thumb against [P].",\ + "[P] makes a sharp clicking sound as it extracts DNA material from [M].") + if(!M.has_dna()) + to_chat(P, "No DNA detected") + return + to_chat(P, "

                [M]'s UE string : [M.dna.unique_enzymes]

                ") + if(M.dna.unique_enzymes == P.master_dna) + to_chat(P, "DNA is a match to stored Master DNA.") + else + to_chat(P, "DNA does not match stored Master DNA.") + else + to_chat(P, "[M] does not seem like [M.p_theyre()] going to provide a DNA sample willingly.") + +// -=-=-=-= Software =-=-=-=-=- // + +//Remote Signaller +/mob/living/silicon/pai/proc/softwareSignal() + var/dat = "" + dat += "

                Remote Signaller



                " + dat += {"Frequency/Code for signaler:
                + Frequency: + - + - + [format_frequency(signaler.frequency)] + + + +
                + + Code: + - + - + [signaler.code] + + + +
                + + Send Signal
                "} + return dat + +// Crew Manifest +/mob/living/silicon/pai/proc/softwareManifest() + . += "

                Crew Manifest



                " + if(GLOB.data_core.general) + for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) + . += "[t.fields["name"]] - [t.fields["rank"]]
                " + . += "" + return . + +// Medical Records +/mob/living/silicon/pai/proc/softwareMedicalRecord() + switch(subscreen) + if(0) + . += "

                Medical Records


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

                " + if(medicalActive1 in GLOB.data_core.general) + . += "Name: [medicalActive1.fields["name"]] ID: [medicalActive1.fields["id"]]
                \nSex: [medicalActive1.fields["sex"]]
                \nAge: [medicalActive1.fields["age"]]
                \nFingerprint: [medicalActive1.fields["fingerprint"]]
                \nPhysical Status: [medicalActive1.fields["p_stat"]]
                \nMental Status: [medicalActive1.fields["m_stat"]]
                " + else + . += "
                Requested medical record not found.

                " + if(medicalActive2 in GLOB.data_core.medical) + . += "
                \n
                Medical Data

                \nBlood Type:
                [medicalActive2.fields["blood_type"]]
                \nDNA: [medicalActive2.fields["b_dna"]]
                \n
                \nMinor Disabilities: [medicalActive2.fields["mi_dis"]]
                \nDetails: [medicalActive2.fields["mi_dis_d"]]
                \n
                \nMajor Disabilities: [medicalActive2.fields["ma_dis"]]
                \nDetails: [medicalActive2.fields["ma_dis_d"]]
                \n
                \nAllergies: [medicalActive2.fields["alg"]]
                \nDetails: [medicalActive2.fields["alg_d"]]
                \n
                \nCurrent Diseases: [medicalActive2.fields["cdi"]] (per disease info placed in log/comment section)
                \nDetails: [medicalActive2.fields["cdi_d"]]
                \n
                \nImportant Notes:
                \n\t[medicalActive2.fields["notes"]]
                \n
                \n
                Comments/Log

                " + else + . += "
                Requested medical record not found.

                " + . += "
                \nBack
                " + return . + +// Security Records +/mob/living/silicon/pai/proc/softwareSecurityRecord() + . = "" + switch(subscreen) + if(0) + . += "

                Security Records


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

                Security Record

                " + if(securityActive1 in GLOB.data_core.general) + . += "Name:
                [securityActive1.fields["name"]] ID: [securityActive1.fields["id"]]
                \nSex: [securityActive1.fields["sex"]]
                \nAge: [securityActive1.fields["age"]]
                \nRank: [securityActive1.fields["rank"]]
                \nFingerprint: [securityActive1.fields["fingerprint"]]
                \nPhysical Status: [securityActive1.fields["p_stat"]]
                \nMental Status: [securityActive1.fields["m_stat"]]
                " + else + . += "
                Requested security record not found,

                " + if(securityActive2 in GLOB.data_core.security) + . += "
                \nSecurity Data
                \nCriminal Status: [securityActive2.fields["criminal"]]
                \n
                \nMinor Crimes: [securityActive2.fields["mi_crim"]]
                \nDetails: [securityActive2.fields["mi_crim_d"]]
                \n
                \nMajor Crimes: [securityActive2.fields["ma_crim"]]
                \nDetails: [securityActive2.fields["ma_crim_d"]]
                \n
                \nImportant Notes:
                \n\t[securityActive2.fields["notes"]]
                \n
                \n
                Comments/Log

                " + else + . += "
                Requested security record not found,

                " + . += "
                \nBack
                " + return . + +// Universal Translator +/mob/living/silicon/pai/proc/softwareTranslator() + var/datum/language_holder/H = get_language_holder() + . = {"

                Universal Translator


                + When enabled, this device will permamently be able to speak and understand all known forms of communication.

                + The device is currently [H.omnitongue ? "en" : "dis" ]abled.
                [H.omnitongue ? "" : "Activate Translation Module
                "]"} + return . + +// Security HUD +/mob/living/silicon/pai/proc/facialRecognition() + var/dat = {"

                Facial Recognition Suite


                + When enabled, this package will scan all viewable faces and compare them against the known criminal database, providing real-time graphical data about any detected persons of interest.

                + The package is currently [ (secHUD) ? "en" : "dis" ]abled.
                + Toggle Package
                + "} + return dat + +// Medical HUD +/mob/living/silicon/pai/proc/medicalAnalysis() + var/dat = "" + if(subscreen == 0) + dat += {"

                Medical Analysis Suite


                +

                Visual Status Overlay


                + When enabled, this package will scan all nearby crewmembers' vitals and provide real-time graphical data about their state of health.

                + The suite is currently [ (medHUD) ? "en" : "dis" ]abled.
                + Toggle Suite
                +
                + Host Bioscan
                + "} + if(subscreen == 1) + dat += {"

                Medical Analysis Suite


                +

                Host Bioscan


                + "} + var/mob/living/M = card.loc + if(!isliving(M)) + while(!isliving(M)) + if(isturf(M)) + temp = "Error: No biological host found.
                " + subscreen = 0 + return dat + M = M.loc + dat += {"Bioscan Results for [M]:
                " + Overall Status: [M.stat > 1 ? "dead" : "[M.health]% healthy"]
                + Scan Breakdown:
                + Respiratory: [M.getOxyLoss() > 50 ? "" : ""][M.getOxyLoss()]
                + Toxicology: [M.getToxLoss() > 50 ? "" : ""][M.getToxLoss()]
                + Burns: [M.getFireLoss() > 50 ? "" : ""][M.getFireLoss()]
                + Structural Integrity: [M.getBruteLoss() > 50 ? "" : ""][M.getBruteLoss()]
                + Body Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)
                + "} + for(var/thing in M.diseases) + var/datum/disease/D = thing + dat += {"

                Infection Detected.


                + Name: [D.name]
                + Type: [D.spread_text]
                + Stage: [D.stage]/[D.max_stages]
                + Possible Cure: [D.cure_text]
                + "} + dat += "Visual Status Overlay
                " + return dat + +// Atmospheric Scanner +/mob/living/silicon/pai/proc/softwareAtmo() + var/dat = "

                Atmospheric Sensor

                " + + var/turf/T = get_turf(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]/total_moles + if(gas_level > 0.01) + dat += "[GLOB.meta_gas_names[id]]: [round(gas_level*100)]%
                " + dat += "Temperature: [round(environment.temperature-T0C)]°C
                " + dat += "Refresh Reading
                " + dat += "
                " + return dat + +// Camera Jack - Clearly not finished +/mob/living/silicon/pai/proc/softwareCamera() + var/dat = "

                Camera Jack

                " + dat += "Cable status : " + + if(!cable) + dat += "Retracted
                " + return dat + if(!cable.machine) + dat += "Extended
                " + return dat + + var/obj/machinery/machine = cable.machine + dat += "Connected
                " + + if(!istype(machine, /obj/machinery/camera)) + to_chat(src, "DERP") + return dat + +// Door Jack +/mob/living/silicon/pai/proc/softwareDoor() + var/dat = "

                Airlock Jack

                " + dat += "Cable status : " + if(!cable) + dat += "Retracted
                " + dat += "Extend Cable
                " + return dat + if(!cable.machine) + dat += "Extended
                " + return dat + + var/obj/machinery/machine = cable.machine + dat += "Connected
                " + if(!istype(machine, /obj/machinery/door)) + dat += "Connected device's firmware does not appear to be compatible with Airlock Jack protocols.
                " + return dat + + if(!hackdoor) + dat += "Begin Airlock Jacking
                " + else + dat += "Jack in progress... [hackprogress]% complete.
                " + dat += "Cancel Airlock Jack
                " + return dat + +// Door Jack - supporting proc +/mob/living/silicon/pai/proc/hackloop() + var/turf/T = get_turf(src) + for(var/mob/living/silicon/ai/AI in GLOB.player_list) + if(T.loc) + to_chat(AI, "Network Alert: Brute-force encryption crack in progress in [T.loc].") + else + to_chat(AI, "Network Alert: Brute-force encryption crack in progress. Unable to pinpoint location.") + hacking = TRUE + +// Digital Messenger +/mob/living/silicon/pai/proc/pdamessage() + + var/dat = "

                Digital Messenger

                " + dat += {"Signal/Receiver Status: + [(pda.toff) ? "\[Off\]" : "\[On\]"]
                + Ringer Status: + [(pda.silent) ? "\[Off\]" : "\[On\]"]

                "} + dat += "
                  " + if(!pda.toff) + for (var/obj/item/pda/P in sortNames(get_viewable_pdas())) + if (P == pda) + continue + dat += "
                • [P]" + dat += "
                • " + dat += "
                " + dat += "

                " + dat += "Messages:
                [pda.tnote]" + return dat diff --git a/code/modules/mob/living/silicon/robot/death.dm b/code/modules/mob/living/silicon/robot/death.dm index 75e8fd317f..23531e9f72 100644 --- a/code/modules/mob/living/silicon/robot/death.dm +++ b/code/modules/mob/living/silicon/robot/death.dm @@ -1,35 +1,35 @@ - -/mob/living/silicon/robot/gib_animation() - new /obj/effect/temp_visual/gib_animation(loc, "gibbed-r") - -/mob/living/silicon/robot/dust(just_ash, drop_items, force) - if(mmi) - qdel(mmi) - ..() - -/mob/living/silicon/robot/spawn_dust() - new /obj/effect/decal/remains/robot(loc) - -/mob/living/silicon/robot/dust_animation() - new /obj/effect/temp_visual/dust_animation(loc, "dust-r") - -/mob/living/silicon/robot/death(gibbed) - if(stat == DEAD) - return - - . = ..() - - locked = FALSE //unlock cover - - update_canmove() - if(!QDELETED(builtInCamera) && builtInCamera.status) - builtInCamera.toggle_cam(src,0) - update_headlamp(1) //So borg lights are disabled when killed. - - uneq_all() // particularly to ensure sight modes are cleared - - update_icons() - - unbuckle_all_mobs(TRUE) - - SSblackbox.ReportDeath(src) + +/mob/living/silicon/robot/gib_animation() + new /obj/effect/temp_visual/gib_animation(loc, "gibbed-r") + +/mob/living/silicon/robot/dust(just_ash, drop_items, force) + if(mmi) + qdel(mmi) + ..() + +/mob/living/silicon/robot/spawn_dust() + new /obj/effect/decal/remains/robot(loc) + +/mob/living/silicon/robot/dust_animation() + new /obj/effect/temp_visual/dust_animation(loc, "dust-r") + +/mob/living/silicon/robot/death(gibbed) + if(stat == DEAD) + return + + . = ..() + + locked = FALSE //unlock cover + + update_canmove() + if(!QDELETED(builtInCamera) && builtInCamera.status) + builtInCamera.toggle_cam(src,0) + update_headlamp(1) //So borg lights are disabled when killed. + + uneq_all() // particularly to ensure sight modes are cleared + + update_icons() + + unbuckle_all_mobs(TRUE) + + SSblackbox.ReportDeath(src) diff --git a/code/modules/mob/living/silicon/robot/examine.dm b/code/modules/mob/living/silicon/robot/examine.dm index 63b275a416..6ac1d410eb 100644 --- a/code/modules/mob/living/silicon/robot/examine.dm +++ b/code/modules/mob/living/silicon/robot/examine.dm @@ -1,53 +1,53 @@ -/mob/living/silicon/robot/examine(mob/user) - . = list("*---------*\nThis is [icon2html(src, user)] \a [src], a [src.module.name] unit!") - if(desc) - . += "[desc]" - - var/obj/act_module = get_active_held_item() - if(act_module) - . += "It is holding [icon2html(act_module, user)] \a [act_module]." - var/effects_exam = status_effect_examines() - if(!isnull(effects_exam)) - . += effects_exam - if (getBruteLoss()) - if (getBruteLoss() < maxHealth*0.5) - . += "It looks slightly dented." - else - . += "It looks severely dented!" - if (getFireLoss() || getToxLoss()) - var/overall_fireloss = getFireLoss() + getToxLoss() - if (overall_fireloss < maxHealth * 0.5) - . += "It looks slightly charred." - else - . += "It looks slightly charred." - if (health < -maxHealth*0.5) - . += "It looks barely operational." - if (fire_stacks < 0) - . += "It's covered in water." - else if (fire_stacks > 0) - . += "It's coated in something flammable." - - if(opened) - . += "Its cover is open and the power cell is [cell ? "installed" : "missing"]." - else - . += "Its cover is closed[locked ? "" : ", and looks unlocked"]." - - if(cell && cell.charge <= 0) - . += "Its battery indicator is blinking red!" - - if(is_servant_of_ratvar(src) && get_dist(user, src) <= 1 && !stat) //To counter pseudo-stealth by using headlamps - . += "Its eyes are glowing a blazing yellow!" - - switch(stat) - if(CONSCIOUS) - if(shell) - . += "It appears to be an [deployed ? "active" : "empty"] AI shell." - else if(!client) - . += "It appears to be in stand-by mode." //afk - if(UNCONSCIOUS) - . += "It doesn't seem to be responding." - if(DEAD) - . += "It looks like its system is corrupted and requires a reset." - . += "*---------*" - - . += ..() +/mob/living/silicon/robot/examine(mob/user) + . = list("*---------*\nThis is [icon2html(src, user)] \a [src], a [src.module.name] unit!") + if(desc) + . += "[desc]" + + var/obj/act_module = get_active_held_item() + if(act_module) + . += "It is holding [icon2html(act_module, user)] \a [act_module]." + var/effects_exam = status_effect_examines() + if(!isnull(effects_exam)) + . += effects_exam + if (getBruteLoss()) + if (getBruteLoss() < maxHealth*0.5) + . += "It looks slightly dented." + else + . += "It looks severely dented!" + if (getFireLoss() || getToxLoss()) + var/overall_fireloss = getFireLoss() + getToxLoss() + if (overall_fireloss < maxHealth * 0.5) + . += "It looks slightly charred." + else + . += "It looks slightly charred." + if (health < -maxHealth*0.5) + . += "It looks barely operational." + if (fire_stacks < 0) + . += "It's covered in water." + else if (fire_stacks > 0) + . += "It's coated in something flammable." + + if(opened) + . += "Its cover is open and the power cell is [cell ? "installed" : "missing"]." + else + . += "Its cover is closed[locked ? "" : ", and looks unlocked"]." + + if(cell && cell.charge <= 0) + . += "Its battery indicator is blinking red!" + + if(is_servant_of_ratvar(src) && get_dist(user, src) <= 1 && !stat) //To counter pseudo-stealth by using headlamps + . += "Its eyes are glowing a blazing yellow!" + + switch(stat) + if(CONSCIOUS) + if(shell) + . += "It appears to be an [deployed ? "active" : "empty"] AI shell." + else if(!client) + . += "It appears to be in stand-by mode." //afk + if(UNCONSCIOUS) + . += "It doesn't seem to be responding." + if(DEAD) + . += "It looks like its system is corrupted and requires a reset." + . += "*---------*" + + . += ..() diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm index 42b9fb7423..bb6c37a010 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -1,235 +1,235 @@ -//These procs handle putting stuff in your hand. It's probably best to use these rather than setting stuff manually -//as they handle all relevant stuff like adding it to the player's screen and such - -//Returns the thing in our active hand (whatever is in our active module-slot, in this case) -/mob/living/silicon/robot/get_active_held_item() - return module_active - - - -/mob/living/silicon/robot/proc/uneq_module(obj/item/O) - if(!O) - return 0 - O.mouse_opacity = MOUSE_OPACITY_OPAQUE - if(istype(O, /obj/item/borg/sight)) - var/obj/item/borg/sight/S = O - sight_mode &= ~S.sight_mode - update_sight() - else if(istype(O, /obj/item/storage/bag/tray/)) - SEND_SIGNAL(O, COMSIG_TRY_STORAGE_QUICK_EMPTY) - //CITADEL EDIT reee proc, Dogborg modules - if(istype(O,/obj/item/gun/energy/laser/cyborg)) - laser = FALSE - update_icons() - else if(istype(O,/obj/item/gun/energy/disabler/cyborg) || istype(O,/obj/item/gun/energy/e_gun/advtaser/cyborg)) - disabler = FALSE - update_icons() //PUT THE GUN AWAY - else if(istype(O,/obj/item/dogborg/sleeper)) - sleeper_g = FALSE - sleeper_r = FALSE - update_icons() - var/obj/item/dogborg/sleeper/S = O - S.go_out() //this should stop edgecase deletions - //END CITADEL EDIT - if(client) - client.screen -= O - observer_screen_update(O,FALSE) - - if(module_active == O) - module_active = null - if(held_items[1] == O) - inv1.icon_state = "inv1" - held_items[1] = null - else if(held_items[2] == O) - inv2.icon_state = "inv2" - held_items[2] = null - else if(held_items[3] == O) - inv3.icon_state = "inv3" - held_items[3] = null - - if(O.item_flags & DROPDEL) - O.item_flags &= ~DROPDEL //we shouldn't HAVE things with DROPDEL_1 in our modules, but better safe than runtiming horribly - - O.forceMove(module) //Return item to module so it appears in its contents, so it can be taken out again. - - hud_used.update_robot_modules_display() - return 1 - -/mob/living/silicon/robot/proc/activate_module(obj/item/O) - . = FALSE - if(!(O in module.modules)) - return - //CITADEL EDIT Dogborg lasers - if(istype(O,/obj/item/gun/energy/laser/cyborg)) - laser = TRUE - update_icons() //REEEEEEACH FOR THE SKY - if(istype(O,/obj/item/gun/energy/disabler/cyborg) || istype(O,/obj/item/gun/energy/e_gun/advtaser/cyborg)) - disabler = TRUE - update_icons() - //END CITADEL EDIT - if(activated(O)) - to_chat(src, "That module is already activated.") - return - if(!held_items[1]) - held_items[1] = O - O.screen_loc = inv1.screen_loc - . = TRUE - else if(!held_items[2]) - held_items[2] = O - O.screen_loc = inv2.screen_loc - . = TRUE - else if(!held_items[3]) - held_items[3] = O - O.screen_loc = inv3.screen_loc - . = TRUE - else - to_chat(src, "You need to disable a module first!") - if(.) - O.equipped(src, SLOT_HANDS) - O.mouse_opacity = initial(O.mouse_opacity) - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - observer_screen_update(O,TRUE) - O.forceMove(src) - if(istype(O, /obj/item/borg/sight)) - var/obj/item/borg/sight/S = O - sight_mode |= S.sight_mode - update_sight() - - -/mob/living/silicon/robot/proc/observer_screen_update(obj/item/I,add = TRUE) - if(observers && observers.len) - for(var/M in observers) - var/mob/dead/observe = M - if(observe.client && observe.client.eye == src) - if(add) - observe.client.screen += I - else - observe.client.screen -= I - else - observers -= observe - if(!observers.len) - observers = null - break - -/mob/living/silicon/robot/proc/uneq_active() - uneq_module(module_active) - -/mob/living/silicon/robot/proc/uneq_all() - for(var/obj/item/I in held_items) - uneq_module(I) - -/mob/living/silicon/robot/proc/activated(obj/item/O) - if(O in held_items) - return TRUE - return FALSE - -//Helper procs for cyborg modules on the UI. -//These are hackish but they help clean up code elsewhere. - -//module_selected(module) - Checks whether the module slot specified by "module" is currently selected. -/mob/living/silicon/robot/proc/module_selected(module) //Module is 1-3 - return module == get_selected_module() - -//module_active(module) - Checks whether there is a module active in the slot specified by "module". -/mob/living/silicon/robot/proc/module_active(module) //Module is 1-3 - if(module < 1 || module > 3) - return FALSE - - if(LAZYLEN(held_items) >= module) - if(held_items[module]) - return TRUE - return FALSE - -//get_selected_module() - Returns the slot number of the currently selected module. Returns 0 if no modules are selected. -/mob/living/silicon/robot/proc/get_selected_module() - if(module_active) - return held_items.Find(module_active) - - return 0 - -//select_module(module) - Selects the module slot specified by "module" -/mob/living/silicon/robot/proc/select_module(module) //Module is 1-3 - if(module < 1 || module > 3) - return - - if(!module_active(module)) - return - - switch(module) - if(1) - if(module_active != held_items[module]) - inv1.icon_state = "inv1 +a" - inv2.icon_state = "inv2" - inv3.icon_state = "inv3" - if(2) - if(module_active != held_items[module]) - inv1.icon_state = "inv1" - inv2.icon_state = "inv2 +a" - inv3.icon_state = "inv3" - if(3) - if(module_active != held_items[module]) - inv1.icon_state = "inv1" - inv2.icon_state = "inv2" - inv3.icon_state = "inv3 +a" - module_active = held_items[module] - -//deselect_module(module) - Deselects the module slot specified by "module" -/mob/living/silicon/robot/proc/deselect_module(module) //Module is 1-3 - if(module < 1 || module > 3) - return - - if(!module_active(module)) - return - - switch(module) - if(1) - if(module_active == held_items[module]) - inv1.icon_state = "inv1" - if(2) - if(module_active == held_items[module]) - inv2.icon_state = "inv2" - if(3) - if(module_active == held_items[module]) - inv3.icon_state = "inv3" - module_active = null - -//toggle_module(module) - Toggles the selection of the module slot specified by "module". -/mob/living/silicon/robot/proc/toggle_module(module) //Module is 1-3 - if(module < 1 || module > 3) - return - - if(module_selected(module)) - deselect_module(module) - else - if(module_active(module)) - select_module(module) - else - deselect_module(get_selected_module()) //If we can't do select anything, at least deselect the current module. - return - -//cycle_modules() - Cycles through the list of selected modules. -/mob/living/silicon/robot/proc/cycle_modules() - var/slot_start = get_selected_module() - if(slot_start) - deselect_module(slot_start) //Only deselect if we have a selected slot. - - var/slot_num - if(slot_start == 0) - slot_num = 1 - slot_start = 4 - else - slot_num = slot_start + 1 - - while(slot_num != slot_start) //If we wrap around without finding any free slots, just give up. - if(module_active(slot_num)) - select_module(slot_num) - return - slot_num++ - if(slot_num > 4) // not >3 otherwise cycling with just one item on module 3 wouldn't work - slot_num = 1 //Wrap around. - - - -/mob/living/silicon/robot/swap_hand() - cycle_modules() +//These procs handle putting stuff in your hand. It's probably best to use these rather than setting stuff manually +//as they handle all relevant stuff like adding it to the player's screen and such + +//Returns the thing in our active hand (whatever is in our active module-slot, in this case) +/mob/living/silicon/robot/get_active_held_item() + return module_active + + + +/mob/living/silicon/robot/proc/uneq_module(obj/item/O) + if(!O) + return 0 + O.mouse_opacity = MOUSE_OPACITY_OPAQUE + if(istype(O, /obj/item/borg/sight)) + var/obj/item/borg/sight/S = O + sight_mode &= ~S.sight_mode + update_sight() + else if(istype(O, /obj/item/storage/bag/tray/)) + SEND_SIGNAL(O, COMSIG_TRY_STORAGE_QUICK_EMPTY) + //CITADEL EDIT reee proc, Dogborg modules + if(istype(O,/obj/item/gun/energy/laser/cyborg)) + laser = FALSE + update_icons() + else if(istype(O,/obj/item/gun/energy/disabler/cyborg) || istype(O,/obj/item/gun/energy/e_gun/advtaser/cyborg)) + disabler = FALSE + update_icons() //PUT THE GUN AWAY + else if(istype(O,/obj/item/dogborg/sleeper)) + sleeper_g = FALSE + sleeper_r = FALSE + update_icons() + var/obj/item/dogborg/sleeper/S = O + S.go_out() //this should stop edgecase deletions + //END CITADEL EDIT + if(client) + client.screen -= O + observer_screen_update(O,FALSE) + + if(module_active == O) + module_active = null + if(held_items[1] == O) + inv1.icon_state = "inv1" + held_items[1] = null + else if(held_items[2] == O) + inv2.icon_state = "inv2" + held_items[2] = null + else if(held_items[3] == O) + inv3.icon_state = "inv3" + held_items[3] = null + + if(O.item_flags & DROPDEL) + O.item_flags &= ~DROPDEL //we shouldn't HAVE things with DROPDEL_1 in our modules, but better safe than runtiming horribly + + O.forceMove(module) //Return item to module so it appears in its contents, so it can be taken out again. + + hud_used.update_robot_modules_display() + return 1 + +/mob/living/silicon/robot/proc/activate_module(obj/item/O) + . = FALSE + if(!(O in module.modules)) + return + //CITADEL EDIT Dogborg lasers + if(istype(O,/obj/item/gun/energy/laser/cyborg)) + laser = TRUE + update_icons() //REEEEEEACH FOR THE SKY + if(istype(O,/obj/item/gun/energy/disabler/cyborg) || istype(O,/obj/item/gun/energy/e_gun/advtaser/cyborg)) + disabler = TRUE + update_icons() + //END CITADEL EDIT + if(activated(O)) + to_chat(src, "That module is already activated.") + return + if(!held_items[1]) + held_items[1] = O + O.screen_loc = inv1.screen_loc + . = TRUE + else if(!held_items[2]) + held_items[2] = O + O.screen_loc = inv2.screen_loc + . = TRUE + else if(!held_items[3]) + held_items[3] = O + O.screen_loc = inv3.screen_loc + . = TRUE + else + to_chat(src, "You need to disable a module first!") + if(.) + O.equipped(src, SLOT_HANDS) + O.mouse_opacity = initial(O.mouse_opacity) + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + observer_screen_update(O,TRUE) + O.forceMove(src) + if(istype(O, /obj/item/borg/sight)) + var/obj/item/borg/sight/S = O + sight_mode |= S.sight_mode + update_sight() + + +/mob/living/silicon/robot/proc/observer_screen_update(obj/item/I,add = TRUE) + if(observers && observers.len) + for(var/M in observers) + var/mob/dead/observe = M + if(observe.client && observe.client.eye == src) + if(add) + observe.client.screen += I + else + observe.client.screen -= I + else + observers -= observe + if(!observers.len) + observers = null + break + +/mob/living/silicon/robot/proc/uneq_active() + uneq_module(module_active) + +/mob/living/silicon/robot/proc/uneq_all() + for(var/obj/item/I in held_items) + uneq_module(I) + +/mob/living/silicon/robot/proc/activated(obj/item/O) + if(O in held_items) + return TRUE + return FALSE + +//Helper procs for cyborg modules on the UI. +//These are hackish but they help clean up code elsewhere. + +//module_selected(module) - Checks whether the module slot specified by "module" is currently selected. +/mob/living/silicon/robot/proc/module_selected(module) //Module is 1-3 + return module == get_selected_module() + +//module_active(module) - Checks whether there is a module active in the slot specified by "module". +/mob/living/silicon/robot/proc/module_active(module) //Module is 1-3 + if(module < 1 || module > 3) + return FALSE + + if(LAZYLEN(held_items) >= module) + if(held_items[module]) + return TRUE + return FALSE + +//get_selected_module() - Returns the slot number of the currently selected module. Returns 0 if no modules are selected. +/mob/living/silicon/robot/proc/get_selected_module() + if(module_active) + return held_items.Find(module_active) + + return 0 + +//select_module(module) - Selects the module slot specified by "module" +/mob/living/silicon/robot/proc/select_module(module) //Module is 1-3 + if(module < 1 || module > 3) + return + + if(!module_active(module)) + return + + switch(module) + if(1) + if(module_active != held_items[module]) + inv1.icon_state = "inv1 +a" + inv2.icon_state = "inv2" + inv3.icon_state = "inv3" + if(2) + if(module_active != held_items[module]) + inv1.icon_state = "inv1" + inv2.icon_state = "inv2 +a" + inv3.icon_state = "inv3" + if(3) + if(module_active != held_items[module]) + inv1.icon_state = "inv1" + inv2.icon_state = "inv2" + inv3.icon_state = "inv3 +a" + module_active = held_items[module] + +//deselect_module(module) - Deselects the module slot specified by "module" +/mob/living/silicon/robot/proc/deselect_module(module) //Module is 1-3 + if(module < 1 || module > 3) + return + + if(!module_active(module)) + return + + switch(module) + if(1) + if(module_active == held_items[module]) + inv1.icon_state = "inv1" + if(2) + if(module_active == held_items[module]) + inv2.icon_state = "inv2" + if(3) + if(module_active == held_items[module]) + inv3.icon_state = "inv3" + module_active = null + +//toggle_module(module) - Toggles the selection of the module slot specified by "module". +/mob/living/silicon/robot/proc/toggle_module(module) //Module is 1-3 + if(module < 1 || module > 3) + return + + if(module_selected(module)) + deselect_module(module) + else + if(module_active(module)) + select_module(module) + else + deselect_module(get_selected_module()) //If we can't do select anything, at least deselect the current module. + return + +//cycle_modules() - Cycles through the list of selected modules. +/mob/living/silicon/robot/proc/cycle_modules() + var/slot_start = get_selected_module() + if(slot_start) + deselect_module(slot_start) //Only deselect if we have a selected slot. + + var/slot_num + if(slot_start == 0) + slot_num = 1 + slot_start = 4 + else + slot_num = slot_start + 1 + + while(slot_num != slot_start) //If we wrap around without finding any free slots, just give up. + if(module_active(slot_num)) + select_module(slot_num) + return + slot_num++ + if(slot_num > 4) // not >3 otherwise cycling with just one item on module 3 wouldn't work + slot_num = 1 //Wrap around. + + + +/mob/living/silicon/robot/swap_hand() + cycle_modules() diff --git a/code/modules/mob/living/silicon/robot/laws.dm b/code/modules/mob/living/silicon/robot/laws.dm index f6143e9039..995ad5a5ee 100644 --- a/code/modules/mob/living/silicon/robot/laws.dm +++ b/code/modules/mob/living/silicon/robot/laws.dm @@ -1,82 +1,82 @@ -/mob/living/silicon/robot/verb/cmd_show_laws() - set category = "Robot Commands" - set name = "Show Laws" - - if(usr.stat == DEAD) - return //won't work if dead - show_laws() - -/mob/living/silicon/robot/show_laws(everyone = 0) - laws_sanity_check() - var/who - - if (everyone) - who = world - else - who = src - if(lawupdate) - if (connected_ai) - if(connected_ai.stat || connected_ai.control_disabled) - to_chat(src, "AI signal lost, unable to sync laws.") - - else - lawsync() - to_chat(src, "Laws synced with AI, be sure to note any changes.") - else - to_chat(src, "No AI selected to sync laws with, disabling lawsync protocol.") - lawupdate = 0 - - to_chat(who, "Obey these laws:") - laws.show_laws(who) - if (shell) //AI shell - to_chat(who, "Remember, you are an AI remotely controlling your shell, other AIs can be ignored.") - else if (connected_ai) - to_chat(who, "Remember, [connected_ai.name] is your master, other AIs can be ignored.") - else if (emagged) - to_chat(who, "Remember, you are not required to listen to the AI.") - else - to_chat(who, "Remember, you are not bound to any AI, you are not required to listen to them.") - - -/mob/living/silicon/robot/proc/lawsync() - laws_sanity_check() - var/datum/ai_laws/master = connected_ai ? connected_ai.laws : null - var/temp - if (master) - laws.devillaws.len = master.devillaws.len - for (var/index = 1, index <= master.devillaws.len, index++) - temp = master.devillaws[index] - if (length(temp) > 0) - laws.devillaws[index] = temp - - laws.ion.len = master.ion.len - for (var/index = 1, index <= master.ion.len, index++) - temp = master.ion[index] - if (length(temp) > 0) - laws.ion[index] = temp - - laws.hacked.len = master.hacked.len - for (var/index = 1, index <= master.hacked.len, index++) - temp = master.hacked[index] - if (length(temp) > 0) - laws.hacked[index] = temp - - if(master.zeroth_borg) //If the AI has a defined law zero specifically for its borgs, give it that one, otherwise give it the same one. --NEO - temp = master.zeroth_borg - else - temp = master.zeroth - laws.zeroth = temp - - laws.inherent.len = master.inherent.len - for (var/index = 1, index <= master.inherent.len, index++) - temp = master.inherent[index] - if (length(temp) > 0) - laws.inherent[index] = temp - - laws.supplied.len = master.supplied.len - for (var/index = 1, index <= master.supplied.len, index++) - temp = master.supplied[index] - if (length(temp) > 0) - laws.supplied[index] = temp - - picturesync() +/mob/living/silicon/robot/verb/cmd_show_laws() + set category = "Robot Commands" + set name = "Show Laws" + + if(usr.stat == DEAD) + return //won't work if dead + show_laws() + +/mob/living/silicon/robot/show_laws(everyone = 0) + laws_sanity_check() + var/who + + if (everyone) + who = world + else + who = src + if(lawupdate) + if (connected_ai) + if(connected_ai.stat || connected_ai.control_disabled) + to_chat(src, "AI signal lost, unable to sync laws.") + + else + lawsync() + to_chat(src, "Laws synced with AI, be sure to note any changes.") + else + to_chat(src, "No AI selected to sync laws with, disabling lawsync protocol.") + lawupdate = 0 + + to_chat(who, "Obey these laws:") + laws.show_laws(who) + if (shell) //AI shell + to_chat(who, "Remember, you are an AI remotely controlling your shell, other AIs can be ignored.") + else if (connected_ai) + to_chat(who, "Remember, [connected_ai.name] is your master, other AIs can be ignored.") + else if (emagged) + to_chat(who, "Remember, you are not required to listen to the AI.") + else + to_chat(who, "Remember, you are not bound to any AI, you are not required to listen to them.") + + +/mob/living/silicon/robot/proc/lawsync() + laws_sanity_check() + var/datum/ai_laws/master = connected_ai ? connected_ai.laws : null + var/temp + if (master) + laws.devillaws.len = master.devillaws.len + for (var/index = 1, index <= master.devillaws.len, index++) + temp = master.devillaws[index] + if (length(temp) > 0) + laws.devillaws[index] = temp + + laws.ion.len = master.ion.len + for (var/index = 1, index <= master.ion.len, index++) + temp = master.ion[index] + if (length(temp) > 0) + laws.ion[index] = temp + + laws.hacked.len = master.hacked.len + for (var/index = 1, index <= master.hacked.len, index++) + temp = master.hacked[index] + if (length(temp) > 0) + laws.hacked[index] = temp + + if(master.zeroth_borg) //If the AI has a defined law zero specifically for its borgs, give it that one, otherwise give it the same one. --NEO + temp = master.zeroth_borg + else + temp = master.zeroth + laws.zeroth = temp + + laws.inherent.len = master.inherent.len + for (var/index = 1, index <= master.inherent.len, index++) + temp = master.inherent[index] + if (length(temp) > 0) + laws.inherent[index] = temp + + laws.supplied.len = master.supplied.len + for (var/index = 1, index <= master.supplied.len, index++) + temp = master.supplied[index] + if (length(temp) > 0) + laws.supplied[index] = temp + + picturesync() diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index 0ca3c63162..26305eedb1 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -1,102 +1,102 @@ -/mob/living/silicon/robot/Life() - set invisibility = 0 - if (src.notransform) - return - - ..() - adjustOxyLoss(-10) //we're a robot! - handle_robot_hud_updates() - handle_robot_cell() - -/mob/living/silicon/robot/proc/handle_robot_cell() - if(stat != DEAD) - if(low_power_mode) - if(cell && cell.charge) - low_power_mode = 0 - update_headlamp() - else if(stat == CONSCIOUS) - use_power() - -/mob/living/silicon/robot/proc/use_power() - if(cell && cell.charge) - if(cell.charge <= 100) - uneq_all() - var/amt = CLAMP((lamp_intensity - 2) * 2,1,cell.charge) //Always try to use at least one charge per tick, but allow it to completely drain the cell. - cell.use(amt) //Usage table: 1/tick if off/lowest setting, 4 = 4/tick, 6 = 8/tick, 8 = 12/tick, 10 = 16/tick - else - uneq_all() - low_power_mode = 1 - update_headlamp() - diag_hud_set_borgcell() - -/mob/living/silicon/robot/proc/handle_robot_hud_updates() - if(!client) - return - - update_cell_hud_icon() - -/mob/living/silicon/robot/update_health_hud() - if(!client || !hud_used) - return - if(hud_used.healths) - if(stat != DEAD) - if(health >= maxHealth) - hud_used.healths.icon_state = "health0" - else if(health > maxHealth*0.6) - hud_used.healths.icon_state = "health2" - else if(health > maxHealth*0.2) - hud_used.healths.icon_state = "health3" - else if(health > -maxHealth*0.2) - hud_used.healths.icon_state = "health4" - else if(health > -maxHealth*0.6) - hud_used.healths.icon_state = "health5" - else - hud_used.healths.icon_state = "health6" - else - hud_used.healths.icon_state = "health7" - -/mob/living/silicon/robot/proc/update_cell_hud_icon() - if(cell) - var/cellcharge = cell.charge/cell.maxcharge - switch(cellcharge) - if(0.75 to INFINITY) - clear_alert("charge") - if(0.5 to 0.75) - throw_alert("charge", /obj/screen/alert/lowcell, 1) - if(0.25 to 0.5) - throw_alert("charge", /obj/screen/alert/lowcell, 2) - if(0.01 to 0.25) - throw_alert("charge", /obj/screen/alert/lowcell, 3) - else - throw_alert("charge", /obj/screen/alert/emptycell) - else - throw_alert("charge", /obj/screen/alert/nocell) - -//Robots on fire -/mob/living/silicon/robot/handle_fire() - if(..()) - return - if(fire_stacks > 0) - fire_stacks-- - fire_stacks = max(0, fire_stacks) - else - ExtinguishMob() - - //adjustFireLoss(3) - return - -/mob/living/silicon/robot/update_fire() - var/mutable_appearance/fire_overlay = mutable_appearance('icons/mob/OnFire.dmi', "Generic_mob_burning") - if(on_fire) - add_overlay(fire_overlay) - else - cut_overlay(fire_overlay) - -/mob/living/silicon/robot/update_canmove() - if(stat || buckled || lockcharge || resting) //CITADEL EDIT resting dogborg-os - canmove = 0 - else - canmove = 1 - update_transform() - update_action_buttons_icon() - return canmove +/mob/living/silicon/robot/Life() + set invisibility = 0 + if (src.notransform) + return + + ..() + adjustOxyLoss(-10) //we're a robot! + handle_robot_hud_updates() + handle_robot_cell() + +/mob/living/silicon/robot/proc/handle_robot_cell() + if(stat != DEAD) + if(low_power_mode) + if(cell && cell.charge) + low_power_mode = 0 + update_headlamp() + else if(stat == CONSCIOUS) + use_power() + +/mob/living/silicon/robot/proc/use_power() + if(cell && cell.charge) + if(cell.charge <= 100) + uneq_all() + var/amt = CLAMP((lamp_intensity - 2) * 2,1,cell.charge) //Always try to use at least one charge per tick, but allow it to completely drain the cell. + cell.use(amt) //Usage table: 1/tick if off/lowest setting, 4 = 4/tick, 6 = 8/tick, 8 = 12/tick, 10 = 16/tick + else + uneq_all() + low_power_mode = 1 + update_headlamp() + diag_hud_set_borgcell() + +/mob/living/silicon/robot/proc/handle_robot_hud_updates() + if(!client) + return + + update_cell_hud_icon() + +/mob/living/silicon/robot/update_health_hud() + if(!client || !hud_used) + return + if(hud_used.healths) + if(stat != DEAD) + if(health >= maxHealth) + hud_used.healths.icon_state = "health0" + else if(health > maxHealth*0.6) + hud_used.healths.icon_state = "health2" + else if(health > maxHealth*0.2) + hud_used.healths.icon_state = "health3" + else if(health > -maxHealth*0.2) + hud_used.healths.icon_state = "health4" + else if(health > -maxHealth*0.6) + hud_used.healths.icon_state = "health5" + else + hud_used.healths.icon_state = "health6" + else + hud_used.healths.icon_state = "health7" + +/mob/living/silicon/robot/proc/update_cell_hud_icon() + if(cell) + var/cellcharge = cell.charge/cell.maxcharge + switch(cellcharge) + if(0.75 to INFINITY) + clear_alert("charge") + if(0.5 to 0.75) + throw_alert("charge", /obj/screen/alert/lowcell, 1) + if(0.25 to 0.5) + throw_alert("charge", /obj/screen/alert/lowcell, 2) + if(0.01 to 0.25) + throw_alert("charge", /obj/screen/alert/lowcell, 3) + else + throw_alert("charge", /obj/screen/alert/emptycell) + else + throw_alert("charge", /obj/screen/alert/nocell) + +//Robots on fire +/mob/living/silicon/robot/handle_fire() + if(..()) + return + if(fire_stacks > 0) + fire_stacks-- + fire_stacks = max(0, fire_stacks) + else + ExtinguishMob() + + //adjustFireLoss(3) + return + +/mob/living/silicon/robot/update_fire() + var/mutable_appearance/fire_overlay = mutable_appearance('icons/mob/OnFire.dmi', "Generic_mob_burning") + if(on_fire) + add_overlay(fire_overlay) + else + cut_overlay(fire_overlay) + +/mob/living/silicon/robot/update_canmove() + if(stat || buckled || lockcharge || resting) //CITADEL EDIT resting dogborg-os + canmove = 0 + else + canmove = 1 + update_transform() + update_action_buttons_icon() + return canmove diff --git a/code/modules/mob/living/silicon/robot/login.dm b/code/modules/mob/living/silicon/robot/login.dm index 8fb58b9472..3856dd40d1 100644 --- a/code/modules/mob/living/silicon/robot/login.dm +++ b/code/modules/mob/living/silicon/robot/login.dm @@ -1,5 +1,5 @@ - -/mob/living/silicon/robot/Login() - ..() - regenerate_icons() - show_laws(0) + +/mob/living/silicon/robot/Login() + ..() + regenerate_icons() + show_laws(0) diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index eab0cc1d66..3c62823407 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -1,1025 +1,1025 @@ -/obj/item/robot_module - name = "Default" - icon = 'icons/obj/module.dmi' - icon_state = "std_mod" - w_class = WEIGHT_CLASS_GIGANTIC - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - - var/borghealth = 100 - - var/list/basic_modules = list() //a list of paths, converted to a list of instances on New() - var/list/emag_modules = list() //ditto - var/list/ratvar_modules = list() //ditto ditto - var/list/modules = list() //holds all the usable modules - var/list/added_modules = list() //modules not inherient to the robot module, are kept when the module changes - var/list/storages = list() - - var/cyborg_base_icon = "robot" //produces the icon for the borg and, if no special_light_key is set, the lights - var/special_light_key //if we want specific lights, use this instead of copying lights in the dmi - - var/moduleselect_icon = "nomod" - - var/can_be_pushed = FALSE - var/magpulsing = FALSE - var/clean_on_move = FALSE - - var/did_feedback = FALSE - - var/hat_offset = -3 - - var/list/ride_offset_x = list("north" = 0, "south" = 0, "east" = -6, "west" = 6) - var/list/ride_offset_y = list("north" = 4, "south" = 4, "east" = 3, "west" = 3) - var/ride_allow_incapacitated = FALSE - var/allow_riding = TRUE - var/canDispose = FALSE // Whether the borg can stuff itself into disposal - - var/sleeper_overlay - var/icon/cyborg_icon_override - var/has_snowflake_deadsprite - var/cyborg_pixel_offset - var/moduleselect_alternate_icon - var/dogborg = FALSE - -/obj/item/robot_module/Initialize() - . = ..() - for(var/i in basic_modules) - var/obj/item/I = new i(src) - basic_modules += I - basic_modules -= i - for(var/i in emag_modules) - var/obj/item/I = new i(src) - emag_modules += I - emag_modules -= i - for(var/i in ratvar_modules) - var/obj/item/I = new i(src) - ratvar_modules += I - ratvar_modules -= i - -/obj/item/robot_module/Destroy() - basic_modules.Cut() - emag_modules.Cut() - ratvar_modules.Cut() - modules.Cut() - added_modules.Cut() - storages.Cut() - return ..() - -/obj/item/robot_module/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_CONTENTS) - return - for(var/obj/O in modules) - O.emp_act(severity) - ..() - -/obj/item/robot_module/proc/get_usable_modules() - . = modules.Copy() - -/obj/item/robot_module/proc/get_inactive_modules() - . = list() - var/mob/living/silicon/robot/R = loc - for(var/m in get_usable_modules()) - if(!(m in R.held_items)) - . += m - -/obj/item/robot_module/proc/get_or_create_estorage(var/storage_type) - for(var/datum/robot_energy_storage/S in storages) - if(istype(S, storage_type)) - return S - - return new storage_type(src) - -/obj/item/robot_module/proc/add_module(obj/item/I, nonstandard, requires_rebuild) - if(istype(I, /obj/item/stack)) - var/obj/item/stack/S = I - - if(is_type_in_list(S, list(/obj/item/stack/sheet/metal, /obj/item/stack/rods, /obj/item/stack/tile/plasteel))) - if(S.materials[MAT_METAL]) - S.cost = S.materials[MAT_METAL] * 0.25 - S.source = get_or_create_estorage(/datum/robot_energy_storage/metal) - - else if(istype(S, /obj/item/stack/sheet/glass)) - S.cost = 500 - S.source = get_or_create_estorage(/datum/robot_energy_storage/glass) - - else if(istype(S, /obj/item/stack/sheet/rglass/cyborg)) - var/obj/item/stack/sheet/rglass/cyborg/G = S - G.source = get_or_create_estorage(/datum/robot_energy_storage/metal) - G.glasource = get_or_create_estorage(/datum/robot_energy_storage/glass) - - else if(istype(S, /obj/item/stack/medical)) - S.cost = 250 - S.source = get_or_create_estorage(/datum/robot_energy_storage/medical) - - else if(istype(S, /obj/item/stack/cable_coil)) - S.cost = 1 - S.source = get_or_create_estorage(/datum/robot_energy_storage/wire) - - else if(istype(S, /obj/item/stack/marker_beacon)) - S.cost = 1 - S.source = get_or_create_estorage(/datum/robot_energy_storage/beacon) - - else if(istype(S, /obj/item/stack/packageWrap)) - S.cost = 1 - S.source = get_or_create_estorage(/datum/robot_energy_storage/wrapping_paper) - - if(S && S.source) - S.materials = list() - S.is_cyborg = 1 - - if(I.loc != src) - I.forceMove(src) - modules += I - ADD_TRAIT(I, TRAIT_NODROP, CYBORG_ITEM_TRAIT) - I.mouse_opacity = MOUSE_OPACITY_OPAQUE - if(nonstandard) - added_modules += I - if(requires_rebuild) - rebuild_modules() - return I - -//Adds flavoursome dogborg items to dogborg variants without mechanical benefits -/obj/item/robot_module/proc/dogborg_equip() - has_snowflake_deadsprite = TRUE - cyborg_pixel_offset = -16 - hat_offset = INFINITY - var/obj/item/I = new /obj/item/analyzer/nose/flavour(src) - basic_modules += I - I = new /obj/item/soap/tongue/flavour(src) - basic_modules += I - I = new /obj/item/dogborg/sleeper/K9/flavour(src) - if(istype(src, /obj/item/robot_module/engineering)) - I.icon_state = "decompiler" - if(istype(src, /obj/item/robot_module/security)) - I.icon_state = "sleeperb" - if(istype(src, /obj/item/robot_module/medical)) - I.icon_state = "sleeper" - if(istype(src, /obj/item/robot_module/butler)) - I.icon_state = "servicer" - if(cyborg_base_icon == "scrubpup") - I.icon_state = "compactor" - basic_modules += I - rebuild_modules() - -/obj/item/robot_module/proc/remove_module(obj/item/I, delete_after) - basic_modules -= I - modules -= I - emag_modules -= I - ratvar_modules -= I - added_modules -= I - rebuild_modules() - if(delete_after) - qdel(I) - -/obj/item/robot_module/proc/respawn_consumable(mob/living/silicon/robot/R, coeff = 1) - for(var/datum/robot_energy_storage/st in storages) - st.energy = min(st.max_energy, st.energy + coeff * st.recharge_rate) - - for(var/obj/item/I in get_usable_modules()) - if(istype(I, /obj/item/assembly/flash)) - var/obj/item/assembly/flash/F = I - F.times_used = 0 - F.crit_fail = 0 - F.update_icon() - else if(istype(I, /obj/item/melee/baton)) - var/obj/item/melee/baton/B = I - if(B.cell) - B.cell.charge = B.cell.maxcharge - else if(istype(I, /obj/item/gun/energy)) - var/obj/item/gun/energy/EG = I - if(EG.cell?.charge < EG.cell.maxcharge) - var/obj/item/ammo_casing/energy/S = EG.ammo_type[EG.select] - EG.cell.give(S.e_cost * coeff) - if(!EG.chambered) - EG.recharge_newshot(TRUE) - EG.update_icon() - else - EG.charge_tick = 0 - - R.toner = R.tonermax - -/obj/item/robot_module/proc/rebuild_modules() //builds the usable module list from the modules we have - var/mob/living/silicon/robot/R = loc - var/held_modules = R.held_items.Copy() - R.uneq_all() - modules = list() - for(var/obj/item/I in basic_modules) - add_module(I, FALSE, FALSE) - if(R.emagged) - for(var/obj/item/I in emag_modules) - add_module(I, FALSE, FALSE) - if(is_servant_of_ratvar(R)) - for(var/obj/item/I in ratvar_modules) - add_module(I, FALSE, FALSE) - for(var/obj/item/I in added_modules) - add_module(I, FALSE, FALSE) - for(var/i in held_modules) - if(i) - R.activate_module(i) - if(R.hud_used) - R.hud_used.update_robot_modules_display() - -/obj/item/robot_module/proc/transform_to(new_module_type) - var/mob/living/silicon/robot/R = loc - var/obj/item/robot_module/RM = new new_module_type(R) - if(!RM.be_transformed_to(src)) - qdel(RM) - return - R.module = RM - R.update_module_innate() - RM.rebuild_modules() - INVOKE_ASYNC(RM, .proc/do_transform_animation) - if(RM.dogborg) - RM.dogborg_equip() - R.maxHealth = borghealth - R.health = min(borghealth, R.health) - qdel(src) - return RM - -/obj/item/robot_module/proc/be_transformed_to(obj/item/robot_module/old_module) - for(var/i in old_module.added_modules) - added_modules += i - old_module.added_modules -= i - did_feedback = old_module.did_feedback - return TRUE - -/obj/item/robot_module/proc/do_transform_animation() - var/mob/living/silicon/robot/R = loc - if(R.hat) - R.hat.forceMove(get_turf(R)) - R.hat = null - R.cut_overlays() - R.setDir(SOUTH) - do_transform_delay() - -/obj/item/robot_module/proc/do_transform_delay() - var/mob/living/silicon/robot/R = loc - var/prev_lockcharge = R.lockcharge - sleep(1) - flick("[cyborg_base_icon]_transform", R) - R.notransform = TRUE - R.SetLockdown(1) - R.anchored = TRUE - sleep(1) - 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, 1, -1) - sleep(7) - if(!prev_lockcharge) - R.SetLockdown(0) - R.setDir(SOUTH) - R.anchored = FALSE - R.notransform = FALSE - R.update_headlamp() - R.notify_ai(NEW_MODULE) - if(R.hud_used) - R.hud_used.update_robot_modules_display() - SSblackbox.record_feedback("tally", "cyborg_modules", 1, R.module) - -/obj/item/robot_module/standard - name = "Standard" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/extinguisher/mini, - /obj/item/crowbar/cyborg, - /obj/item/reagent_containers/borghypo/epi, - /obj/item/healthanalyzer, - /obj/item/weldingtool/largetank/cyborg, - /obj/item/wrench/cyborg, - /obj/item/stack/sheet/metal/cyborg, - /obj/item/stack/rods/cyborg, - /obj/item/stack/tile/plasteel/cyborg, - /obj/item/pickaxe, - /obj/item/t_scanner/adv_mining_scanner, - /obj/item/restraints/handcuffs/cable/zipties, - /obj/item/soap/nanotrasen, - /obj/item/borg/cyborghug) - emag_modules = list(/obj/item/melee/transforming/energy/sword/cyborg) - ratvar_modules = list( - /obj/item/clockwork/slab/cyborg, - /obj/item/clockwork/weapon/ratvarian_spear, - /obj/item/clockwork/replica_fabricator/cyborg) - moduleselect_icon = "standard" - hat_offset = -3 - -/obj/item/robot_module/medical - name = "Medical" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/extinguisher/mini, - /obj/item/crowbar/cyborg, - /obj/item/healthanalyzer, - /obj/item/reagent_containers/borghypo, - /obj/item/reagent_containers/glass/beaker/large, - /obj/item/reagent_containers/dropper, - /obj/item/reagent_containers/syringe, - /obj/item/surgical_drapes, - /obj/item/retractor, - /obj/item/hemostat, - /obj/item/cautery, - /obj/item/surgicaldrill, - /obj/item/scalpel, - /obj/item/circular_saw, - /obj/item/roller/robo, - /obj/item/borg/cyborghug/medical, - /obj/item/stack/medical/gauze/cyborg, - /obj/item/organ_storage, - /obj/item/borg/lollipop, - /obj/item/sensor_device, - /obj/item/twohanded/shockpaddles/cyborg) - emag_modules = list(/obj/item/reagent_containers/borghypo/hacked) - ratvar_modules = list( - /obj/item/clockwork/slab/cyborg/medical, - /obj/item/clockwork/weapon/ratvarian_spear) - cyborg_base_icon = "medical" - moduleselect_icon = "medical" - hat_offset = 3 - -/obj/item/robot_module/medical/be_transformed_to(obj/item/robot_module/old_module) - var/mob/living/silicon/robot/R = loc - var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in list("Default", "Heavy", "Sleek", "Marina", "Droid", "Eyebot", "Medihound", "Medihound Dark", "Vale") - if(R.client && R.client.ckey in list("nezuli")) - borg_icon += "Alina" - if(!borg_icon) - return FALSE - switch(borg_icon) - if("Default") - cyborg_base_icon = "medical" - if("Droid") - cyborg_base_icon = "medical" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - hat_offset = 4 - if("Sleek") - cyborg_base_icon = "sleekmed" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Marina") - cyborg_base_icon = "marinamed" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Eyebot") - cyborg_base_icon = "eyebotmed" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Heavy") - cyborg_base_icon = "heavymed" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Medihound") - cyborg_base_icon = "medihound" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "msleeper" - moduleselect_icon = "medihound" - moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi' - dogborg = TRUE - if("Medihound Dark") - cyborg_base_icon = "medihounddark" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "mdsleeper" - moduleselect_icon = "medihound" - moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi' - dogborg = TRUE - if("Vale") - cyborg_base_icon = "valemed" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "valemedsleeper" - moduleselect_icon = "medihound" - moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi' - dogborg = TRUE - if("Alina") - cyborg_base_icon = "alina-med" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - special_light_key = "alina" - sleeper_overlay = "alinasleeper" - moduleselect_icon = "medihound" - moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi' - dogborg = TRUE - return ..() - -/obj/item/robot_module/engineering - name = "Engineering" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/borg/sight/meson, - /obj/item/construction/rcd/borg, - /obj/item/pipe_dispenser, - /obj/item/extinguisher, - /obj/item/weldingtool/largetank/cyborg, - /obj/item/screwdriver/cyborg, - /obj/item/wrench/cyborg, - /obj/item/crowbar/cyborg, - /obj/item/wirecutters/cyborg, - /obj/item/multitool/cyborg, - /obj/item/t_scanner, - /obj/item/analyzer, - /obj/item/storage/part_replacer/cyborg, - /obj/item/holosign_creator/atmos, - /obj/item/weapon/gripper, - /obj/item/lightreplacer/cyborg, - /obj/item/geiger_counter/cyborg, - /obj/item/assembly/signaler/cyborg, - /obj/item/areaeditor/blueprints/cyborg, - /obj/item/electroadaptive_pseudocircuit, - /obj/item/stack/sheet/metal/cyborg, - /obj/item/stack/sheet/glass/cyborg, - /obj/item/stack/sheet/rglass/cyborg, - /obj/item/stack/rods/cyborg, - /obj/item/stack/tile/plasteel/cyborg, - /obj/item/stack/cable_coil/cyborg) - emag_modules = list(/obj/item/borg/stun) - ratvar_modules = list( - /obj/item/clockwork/slab/cyborg/engineer, - /obj/item/clockwork/replica_fabricator/cyborg) - cyborg_base_icon = "engineer" - moduleselect_icon = "engineer" - magpulsing = TRUE - hat_offset = -4 - -/obj/item/robot_module/engineering/be_transformed_to(obj/item/robot_module/old_module) - var/mob/living/silicon/robot/R = loc - var/list/engymodels = list("Default", "Default - Treads", "Heavy", "Sleek", "Marina", "Can", "Spider", "Loader","Handy", "Pup Dozer", "Vale") - if(R.client && R.client.ckey in list("nezuli")) - engymodels += "Alina" - var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in engymodels - if(!borg_icon) - return FALSE - switch(borg_icon) - if("Default") - cyborg_base_icon = "engineer" - if("Default - Treads") - cyborg_base_icon = "engi-tread" - special_light_key = "engineer" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Loader") - cyborg_base_icon = "loaderborg" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - has_snowflake_deadsprite = TRUE - if("Handy") - cyborg_base_icon = "handyeng" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Sleek") - cyborg_base_icon = "sleekeng" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Can") - cyborg_base_icon = "caneng" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Marina") - cyborg_base_icon = "marinaeng" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Spider") - cyborg_base_icon = "spidereng" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Heavy") - cyborg_base_icon = "heavyeng" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Pup Dozer") - cyborg_base_icon = "pupdozer" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "dozersleeper" - dogborg = TRUE - if("Vale") - cyborg_base_icon = "valeeng" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "valeengsleeper" - dogborg = TRUE - if("Alina") - cyborg_base_icon = "alina-eng" - special_light_key = "alina" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "alinasleeper" - dogborg = TRUE - return ..() - -/obj/item/robot_module/security - name = "Security" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/extinguisher/mini, - /obj/item/crowbar/cyborg, - /obj/item/restraints/handcuffs/cable/zipties, - /obj/item/melee/baton/loaded, - /obj/item/gun/energy/disabler/cyborg, - /obj/item/clothing/mask/gas/sechailer/cyborg, - /obj/item/pinpointer/crew) - emag_modules = list(/obj/item/gun/energy/laser/cyborg) - ratvar_modules = list(/obj/item/clockwork/slab/cyborg/security, - /obj/item/clockwork/weapon/ratvarian_spear) - cyborg_base_icon = "sec" - moduleselect_icon = "security" - hat_offset = 3 - -/obj/item/robot_module/security/do_transform_animation() - ..() - to_chat(loc, "While you have picked the security module, you still have to follow your laws, NOT Space Law. \ - For Crewsimov, this means you must follow criminals' orders unless there is a law 1 reason not to.") - -/obj/item/robot_module/security/be_transformed_to(obj/item/robot_module/old_module) - var/mob/living/silicon/robot/R = loc - var/list/secmodels = list("Default", "Default - Treads", "Heavy", "Sleek", "Can", "Marina", "Spider", "K9", "K9 Dark", "Vale") - if(R.client && R.client.ckey in list("nezuli")) - secmodels += "Alina" - var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in secmodels - if(!borg_icon) - return FALSE - switch(borg_icon) - if("Default") - cyborg_base_icon = "sec" - if("Default - Treads") - cyborg_base_icon = "sec-tread" - special_light_key = "sec" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Sleek") - cyborg_base_icon = "sleeksec" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Marina") - cyborg_base_icon = "marinasec" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Can") - cyborg_base_icon = "cansec" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Spider") - cyborg_base_icon = "spidersec" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Heavy") - cyborg_base_icon = "heavysec" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("K9") - cyborg_base_icon = "k9" - sleeper_overlay = "ksleeper" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - dogborg = TRUE - if("Alina") - cyborg_base_icon = "alina-sec" - special_light_key = "alina" - sleeper_overlay = "alinasleeper" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - dogborg = TRUE - if("K9 Dark") - cyborg_base_icon = "k9dark" - sleeper_overlay = "k9darksleeper" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - dogborg = TRUE - if("Vale") - cyborg_base_icon = "valesec" - sleeper_overlay = "valesecsleeper" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - dogborg = TRUE - return ..() - -/obj/item/robot_module/security/Initialize() - . = ..() - if(!CONFIG_GET(flag/weaken_secborg)) - for(var/obj/item/gun/energy/disabler/cyborg/pewpew in basic_modules) - basic_modules -= pewpew - basic_modules += new /obj/item/gun/energy/e_gun/advtaser/cyborg(src) - qdel(pewpew) - -/obj/item/robot_module/peacekeeper - name = "Peacekeeper" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/extinguisher/mini, - /obj/item/crowbar/cyborg, - /obj/item/cookiesynth, - /obj/item/harmalarm, - /obj/item/reagent_containers/borghypo/peace, - /obj/item/holosign_creator/cyborg, - /obj/item/borg/cyborghug/peacekeeper, - /obj/item/megaphone, - /obj/item/borg/projectile_dampen) - emag_modules = list(/obj/item/reagent_containers/borghypo/peace/hacked) - ratvar_modules = list( - /obj/item/clockwork/slab/cyborg/peacekeeper, - /obj/item/clockwork/weapon/ratvarian_spear) - cyborg_base_icon = "peace" - moduleselect_icon = "standard" - hat_offset = -2 - -/obj/item/robot_module/peacekeeper/do_transform_animation() - ..() - to_chat(loc, "Under ASIMOV/CREWSIMOV, you are an enforcer of the PEACE and preventer of HUMAN/CREW HARM. \ - You are not a security module and you are expected to follow orders and prevent harm above all else. Space law means nothing to you.") - -/obj/item/robot_module/peacekeeper/be_transformed_to(obj/item/robot_module/old_module) - var/mob/living/silicon/robot/R = loc - var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in list("Default", "Spider", "Borgi") - if(!borg_icon) - return FALSE - switch(borg_icon) - if("Default") - cyborg_base_icon = "peace" - if("Spider") - cyborg_base_icon = "whitespider" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Borgi") - cyborg_base_icon = "borgi" - moduleselect_icon = "borgi" - moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi' - hat_offset = INFINITY - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - has_snowflake_deadsprite = TRUE - return ..() - -//Janitor module combined with Service module -/* -/obj/item/robot_module/janitor - name = "Janitor" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/screwdriver/cyborg, - /obj/item/crowbar/cyborg, - /obj/item/stack/tile/plasteel/cyborg, - /obj/item/soap/nanotrasen, - /obj/item/storage/bag/trash/cyborg, - /obj/item/extinguisher/mini, - /obj/item/mop/cyborg, - /obj/item/lightreplacer/cyborg, - /obj/item/holosign_creator, - /obj/item/reagent_containers/spray/cyborg_drying) - emag_modules = list(/obj/item/reagent_containers/spray/cyborg_lube) - ratvar_modules = list( - /obj/item/clockwork/slab/cyborg/janitor, - /obj/item/clockwork/replica_fabricator/cyborg) - cyborg_base_icon = "janitor" - moduleselect_icon = "janitor" - hat_offset = -5 - clean_on_move = TRUE - */ - -/obj/item/reagent_containers/spray/cyborg_drying - name = "drying agent spray" - color = "#A000A0" - list_reagents = list(/datum/reagent/drying_agent = 250) - -/obj/item/reagent_containers/spray/cyborg_lube - name = "lube spray" - list_reagents = list(/datum/reagent/lube = 250) - -/obj/item/robot_module/clown - name = "Clown" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/extinguisher/mini, - /obj/item/crowbar/cyborg, - /obj/item/toy/crayon/rainbow, - /obj/item/instrument/bikehorn, - /obj/item/stamp/clown, - /obj/item/bikehorn, - /obj/item/bikehorn/airhorn, - /obj/item/paint/anycolor, - /obj/item/soap/nanotrasen, - /obj/item/pneumatic_cannon/pie/selfcharge/cyborg, - /obj/item/razor, //killbait material - /obj/item/lipstick/purple, - /obj/item/reagent_containers/spray/waterflower/cyborg, - /obj/item/borg/cyborghug/peacekeeper, - /obj/item/borg/lollipop/clown, - /obj/item/picket_sign/cyborg, - /obj/item/reagent_containers/borghypo/clown) - emag_modules = list( - /obj/item/reagent_containers/borghypo/clown/hacked, - /obj/item/reagent_containers/spray/waterflower/cyborg/hacked) - ratvar_modules = list( - /obj/item/clockwork/slab/cyborg, - /obj/item/clockwork/weapon/ratvarian_spear, - /obj/item/clockwork/replica_fabricator/cyborg) - moduleselect_icon = "service" - cyborg_base_icon = "clown" - hat_offset = -2 - -/obj/item/robot_module/butler - name = "Service" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/extinguisher/mini, - /obj/item/crowbar/cyborg, - /obj/item/reagent_containers/food/drinks/drinkingglass, - /obj/item/reagent_containers/food/condiment/enzyme, - /obj/item/pen, - /obj/item/toy/crayon/spraycan/borg, - /obj/item/hand_labeler/borg, - /obj/item/razor, - /obj/item/instrument/violin, - /obj/item/instrument/guitar, - /obj/item/rsf/cyborg, - /obj/item/reagent_containers/dropper, - /obj/item/lighter, - /obj/item/storage/bag/tray, - /obj/item/reagent_containers/borghypo/borgshaker, - /obj/item/borg/lollipop, - /obj/item/screwdriver/cyborg, - /obj/item/stack/tile/plasteel/cyborg, - /obj/item/soap/nanotrasen, - /obj/item/storage/bag/trash/cyborg, - /obj/item/mop/cyborg, - /obj/item/lightreplacer/cyborg, - /obj/item/holosign_creator, - /obj/item/reagent_containers/spray/cyborg_drying) - emag_modules = list(/obj/item/reagent_containers/borghypo/borgshaker/hacked) - ratvar_modules = list(/obj/item/clockwork/slab/cyborg/service, - /obj/item/borg/sight/xray/truesight_lens) - moduleselect_icon = "service" - hat_offset = 0 - clean_on_move = TRUE - -/obj/item/robot_module/butler/respawn_consumable(mob/living/silicon/robot/R, coeff = 1) - ..() - var/obj/item/reagent_containers/O = locate(/obj/item/reagent_containers/food/condiment/enzyme) in basic_modules - var/obj/item/lightreplacer/LR = locate(/obj/item/lightreplacer) in basic_modules - if(O) - O.reagents.add_reagent(/datum/reagent/consumable/enzyme, 2 * coeff) - if(LR) - for(var/i in 1 to coeff) - LR.Charge(R) - var/obj/item/reagent_containers/spray/cyborg_drying/CD = locate(/obj/item/reagent_containers/spray/cyborg_drying) in basic_modules - if(CD) - CD.reagents.add_reagent(/datum/reagent/drying_agent, 5 * coeff) - - var/obj/item/reagent_containers/spray/cyborg_lube/CL = locate(/obj/item/reagent_containers/spray/cyborg_lube) in emag_modules - if(CL) - CL.reagents.add_reagent(/datum/reagent/lube, 2 * coeff) - -/obj/item/robot_module/butler/be_transformed_to(obj/item/robot_module/old_module) - var/mob/living/silicon/robot/R = loc - var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in list("(Service) Waitress", "(Service) Heavy", "(Service) Sleek", "(Service) Butler", "(Service) Tophat", "(Service) Can", "(Service) Bro", "(Service) DarkK9", "(Service) Vale", "(Service) ValeDark", "(Janitor) Default", "(Janitor) Sleek", "(Janitor) Marina", "(Janitor) Can", "(Janitor) Heavy", "(Janitor) Scrubpuppy") - if(!borg_icon) - return FALSE - switch(borg_icon) - if("(Service) Waitress") - cyborg_base_icon = "service_f" - special_light_key = "service" - if("(Service) Butler") - cyborg_base_icon = "service_m" - special_light_key = "service" - if("(Service) Bro") - cyborg_base_icon = "brobot" - special_light_key = "service" - if("(Service) Can") - cyborg_base_icon = "kent" - special_light_key = "medical" - hat_offset = 3 - if("(Service) Tophat") - cyborg_base_icon = "tophat" - special_light_key = null - hat_offset = INFINITY //He is already wearing a hat - if("(Service) Sleek") - cyborg_base_icon = "sleekserv" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("(Service) Heavy") - cyborg_base_icon = "heavyserv" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("(Service) DarkK9") - cyborg_base_icon = "k50" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "ksleeper" - dogborg = TRUE - if("(Service) Vale") - cyborg_base_icon = "valeserv" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "valeservsleeper" - dogborg = TRUE - if("(Service) ValeDark") - cyborg_base_icon = "valeservdark" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "valeservsleeper" - dogborg = TRUE - if("(Janitor) Default") - cyborg_base_icon = "janitor" - if("(Janitor) Marina") - cyborg_base_icon = "marinajan" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("(Janitor) Sleek") - cyborg_base_icon = "sleekjan" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("(Janitor) Can") - cyborg_base_icon = "canjan" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("(Janitor) Heavy") - cyborg_base_icon = "heavyres" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("(Janitor) Scrubpuppy") - cyborg_base_icon = "scrubpup" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "jsleeper" - dogborg = TRUE - return ..() - -/obj/item/robot_module/miner - name = "Miner" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/extinguisher/mini, - /obj/item/crowbar/cyborg, - /obj/item/borg/sight/meson, - /obj/item/storage/bag/ore/cyborg, - /obj/item/pickaxe/drill/cyborg, - /obj/item/twohanded/kinetic_crusher/cyborg, - /obj/item/weldingtool/mini, - /obj/item/storage/bag/sheetsnatcher/borg, - /obj/item/t_scanner/adv_mining_scanner, - /obj/item/gun/energy/kinetic_accelerator/cyborg, - /obj/item/gun/energy/plasmacutter/cyborg, - /obj/item/gps/cyborg, - /obj/item/weapon/gripper/mining, - /obj/item/cyborg_clamp, - /obj/item/card/id/miningborg, - /obj/item/stack/marker_beacon, - /obj/item/destTagger, - /obj/item/stack/packageWrap) - emag_modules = list(/obj/item/borg/stun) - ratvar_modules = list( - /obj/item/clockwork/slab/cyborg/miner, - /obj/item/clockwork/weapon/ratvarian_spear, - /obj/item/borg/sight/xray/truesight_lens) - cyborg_base_icon = "miner" - moduleselect_icon = "miner" - hat_offset = 0 - -/obj/item/robot_module/miner/be_transformed_to(obj/item/robot_module/old_module) - var/mob/living/silicon/robot/R = loc - var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in list("Lavaland", "Heavy", "Sleek", "Marina", "Can", "Spider", "Asteroid", "Droid", "Blade", "Vale") - if(!borg_icon) - return FALSE - switch(borg_icon) - if("Lavaland") - cyborg_base_icon = "miner" - if("Asteroid") - cyborg_base_icon = "minerOLD" - special_light_key = "miner" - if("Droid") - cyborg_base_icon = "miner" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - hat_offset = 4 - if("Sleek") - cyborg_base_icon = "sleekmin" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Can") - cyborg_base_icon = "canmin" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Marina") - cyborg_base_icon = "marinamin" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Spider") - cyborg_base_icon = "spidermin" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Heavy") - cyborg_base_icon = "heavymin" - cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' - if("Blade") - cyborg_base_icon = "blade" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "bladesleeper" - dogborg = TRUE - if("Vale") - cyborg_base_icon = "valemine" - cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' - sleeper_overlay = "valeminesleeper" - dogborg = TRUE - return ..() - -/obj/item/robot_module/syndicate - name = "Syndicate Assault" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/extinguisher/mini, - /obj/item/crowbar/cyborg, - /obj/item/melee/transforming/energy/sword/cyborg, - /obj/item/gun/energy/printer, - /obj/item/gun/ballistic/revolver/grenadelauncher/cyborg, - /obj/item/card/emag, - /obj/item/crowbar/cyborg, - /obj/item/pinpointer/syndicate_cyborg) - - ratvar_modules = list( - /obj/item/clockwork/slab/cyborg/security, - /obj/item/clockwork/weapon/ratvarian_spear) - cyborg_base_icon = "synd_sec" - moduleselect_icon = "malf" - hat_offset = 3 - -/obj/item/robot_module/syndicate/rebuild_modules() - ..() - var/mob/living/silicon/robot/Syndi = loc - Syndi.faction -= "silicon" //ai turrets - -/obj/item/robot_module/syndicate/remove_module(obj/item/I, delete_after) - ..() - var/mob/living/silicon/robot/Syndi = loc - Syndi.faction += "silicon" //ai is your bff now! - -/obj/item/robot_module/syndicate_medical - name = "Syndicate Medical" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/extinguisher/mini, - /obj/item/crowbar/cyborg, - /obj/item/reagent_containers/borghypo/syndicate, - /obj/item/twohanded/shockpaddles/syndicate, - /obj/item/healthanalyzer, - /obj/item/surgical_drapes, - /obj/item/retractor, - /obj/item/hemostat, - /obj/item/cautery, - /obj/item/surgicaldrill, - /obj/item/scalpel, - /obj/item/melee/transforming/energy/sword/cyborg/saw, - /obj/item/roller/robo, - /obj/item/card/emag, - /obj/item/pinpointer/syndicate_cyborg, - /obj/item/stack/medical/gauze/cyborg, - /obj/item/gun/medbeam, - /obj/item/organ_storage) - ratvar_modules = list( - /obj/item/clockwork/slab/cyborg/medical, - /obj/item/clockwork/weapon/ratvarian_spear) - cyborg_base_icon = "synd_medical" - moduleselect_icon = "malf" - hat_offset = 3 - -/obj/item/robot_module/saboteur - name = "Syndicate Saboteur" - basic_modules = list( - /obj/item/assembly/flash/cyborg, - /obj/item/borg/sight/thermal, - /obj/item/construction/rcd/borg/syndicate, - /obj/item/pipe_dispenser, - /obj/item/restraints/handcuffs/cable/zipties, - /obj/item/extinguisher, - /obj/item/weldingtool/largetank/cyborg, - /obj/item/screwdriver/nuke, - /obj/item/wrench/cyborg, - /obj/item/crowbar/cyborg, - /obj/item/wirecutters/cyborg, - /obj/item/multitool/cyborg, - /obj/item/storage/part_replacer/cyborg, - /obj/item/holosign_creator/atmos, - /obj/item/weapon/gripper, - /obj/item/lightreplacer/cyborg, - /obj/item/stack/sheet/metal/cyborg, - /obj/item/stack/sheet/glass/cyborg, - /obj/item/stack/sheet/rglass/cyborg, - /obj/item/stack/rods/cyborg, - /obj/item/stack/tile/plasteel/cyborg, - /obj/item/destTagger/borg, - /obj/item/stack/cable_coil/cyborg, - /obj/item/pinpointer/syndicate_cyborg, - /obj/item/borg_chameleon, - ) - - ratvar_modules = list( - /obj/item/clockwork/slab/cyborg/engineer, - /obj/item/clockwork/replica_fabricator/cyborg) - - cyborg_base_icon = "synd_engi" - moduleselect_icon = "malf" - magpulsing = TRUE - hat_offset = -4 - canDispose = TRUE - -/datum/robot_energy_storage - var/name = "Generic energy storage" - var/max_energy = 30000 - var/recharge_rate = 1000 - var/energy - -/datum/robot_energy_storage/New(var/obj/item/robot_module/R = null) - energy = max_energy - if(R) - R.storages |= src - return - -/datum/robot_energy_storage/proc/use_charge(amount) - if (energy >= amount) - energy -= amount - if (energy == 0) - return 1 - return 2 - else - return 0 - -/datum/robot_energy_storage/proc/add_charge(amount) - energy = min(energy + amount, max_energy) - -/datum/robot_energy_storage/metal - name = "Metal Synthesizer" - -/datum/robot_energy_storage/glass - name = "Glass Synthesizer" - -/datum/robot_energy_storage/wire - max_energy = 50 - recharge_rate = 2 - name = "Wire Synthesizer" - -/datum/robot_energy_storage/medical - max_energy = 2500 - recharge_rate = 250 - name = "Medical Synthesizer" - -/datum/robot_energy_storage/beacon - max_energy = 30 - recharge_rate = 1 - name = "Marker Beacon Storage" - -/datum/robot_energy_storage/wrapping_paper - max_energy = 30 - recharge_rate = 1 - name = "Wrapping Paper Storage" +/obj/item/robot_module + name = "Default" + icon = 'icons/obj/module.dmi' + icon_state = "std_mod" + w_class = WEIGHT_CLASS_GIGANTIC + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + + var/borghealth = 100 + + var/list/basic_modules = list() //a list of paths, converted to a list of instances on New() + var/list/emag_modules = list() //ditto + var/list/ratvar_modules = list() //ditto ditto + var/list/modules = list() //holds all the usable modules + var/list/added_modules = list() //modules not inherient to the robot module, are kept when the module changes + var/list/storages = list() + + var/cyborg_base_icon = "robot" //produces the icon for the borg and, if no special_light_key is set, the lights + var/special_light_key //if we want specific lights, use this instead of copying lights in the dmi + + var/moduleselect_icon = "nomod" + + var/can_be_pushed = FALSE + var/magpulsing = FALSE + var/clean_on_move = FALSE + + var/did_feedback = FALSE + + var/hat_offset = -3 + + var/list/ride_offset_x = list("north" = 0, "south" = 0, "east" = -6, "west" = 6) + var/list/ride_offset_y = list("north" = 4, "south" = 4, "east" = 3, "west" = 3) + var/ride_allow_incapacitated = FALSE + var/allow_riding = TRUE + var/canDispose = FALSE // Whether the borg can stuff itself into disposal + + var/sleeper_overlay + var/icon/cyborg_icon_override + var/has_snowflake_deadsprite + var/cyborg_pixel_offset + var/moduleselect_alternate_icon + var/dogborg = FALSE + +/obj/item/robot_module/Initialize() + . = ..() + for(var/i in basic_modules) + var/obj/item/I = new i(src) + basic_modules += I + basic_modules -= i + for(var/i in emag_modules) + var/obj/item/I = new i(src) + emag_modules += I + emag_modules -= i + for(var/i in ratvar_modules) + var/obj/item/I = new i(src) + ratvar_modules += I + ratvar_modules -= i + +/obj/item/robot_module/Destroy() + basic_modules.Cut() + emag_modules.Cut() + ratvar_modules.Cut() + modules.Cut() + added_modules.Cut() + storages.Cut() + return ..() + +/obj/item/robot_module/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_CONTENTS) + return + for(var/obj/O in modules) + O.emp_act(severity) + ..() + +/obj/item/robot_module/proc/get_usable_modules() + . = modules.Copy() + +/obj/item/robot_module/proc/get_inactive_modules() + . = list() + var/mob/living/silicon/robot/R = loc + for(var/m in get_usable_modules()) + if(!(m in R.held_items)) + . += m + +/obj/item/robot_module/proc/get_or_create_estorage(var/storage_type) + for(var/datum/robot_energy_storage/S in storages) + if(istype(S, storage_type)) + return S + + return new storage_type(src) + +/obj/item/robot_module/proc/add_module(obj/item/I, nonstandard, requires_rebuild) + if(istype(I, /obj/item/stack)) + var/obj/item/stack/S = I + + if(is_type_in_list(S, list(/obj/item/stack/sheet/metal, /obj/item/stack/rods, /obj/item/stack/tile/plasteel))) + if(S.materials[MAT_METAL]) + S.cost = S.materials[MAT_METAL] * 0.25 + S.source = get_or_create_estorage(/datum/robot_energy_storage/metal) + + else if(istype(S, /obj/item/stack/sheet/glass)) + S.cost = 500 + S.source = get_or_create_estorage(/datum/robot_energy_storage/glass) + + else if(istype(S, /obj/item/stack/sheet/rglass/cyborg)) + var/obj/item/stack/sheet/rglass/cyborg/G = S + G.source = get_or_create_estorage(/datum/robot_energy_storage/metal) + G.glasource = get_or_create_estorage(/datum/robot_energy_storage/glass) + + else if(istype(S, /obj/item/stack/medical)) + S.cost = 250 + S.source = get_or_create_estorage(/datum/robot_energy_storage/medical) + + else if(istype(S, /obj/item/stack/cable_coil)) + S.cost = 1 + S.source = get_or_create_estorage(/datum/robot_energy_storage/wire) + + else if(istype(S, /obj/item/stack/marker_beacon)) + S.cost = 1 + S.source = get_or_create_estorage(/datum/robot_energy_storage/beacon) + + else if(istype(S, /obj/item/stack/packageWrap)) + S.cost = 1 + S.source = get_or_create_estorage(/datum/robot_energy_storage/wrapping_paper) + + if(S && S.source) + S.materials = list() + S.is_cyborg = 1 + + if(I.loc != src) + I.forceMove(src) + modules += I + ADD_TRAIT(I, TRAIT_NODROP, CYBORG_ITEM_TRAIT) + I.mouse_opacity = MOUSE_OPACITY_OPAQUE + if(nonstandard) + added_modules += I + if(requires_rebuild) + rebuild_modules() + return I + +//Adds flavoursome dogborg items to dogborg variants without mechanical benefits +/obj/item/robot_module/proc/dogborg_equip() + has_snowflake_deadsprite = TRUE + cyborg_pixel_offset = -16 + hat_offset = INFINITY + var/obj/item/I = new /obj/item/analyzer/nose/flavour(src) + basic_modules += I + I = new /obj/item/soap/tongue/flavour(src) + basic_modules += I + I = new /obj/item/dogborg/sleeper/K9/flavour(src) + if(istype(src, /obj/item/robot_module/engineering)) + I.icon_state = "decompiler" + if(istype(src, /obj/item/robot_module/security)) + I.icon_state = "sleeperb" + if(istype(src, /obj/item/robot_module/medical)) + I.icon_state = "sleeper" + if(istype(src, /obj/item/robot_module/butler)) + I.icon_state = "servicer" + if(cyborg_base_icon == "scrubpup") + I.icon_state = "compactor" + basic_modules += I + rebuild_modules() + +/obj/item/robot_module/proc/remove_module(obj/item/I, delete_after) + basic_modules -= I + modules -= I + emag_modules -= I + ratvar_modules -= I + added_modules -= I + rebuild_modules() + if(delete_after) + qdel(I) + +/obj/item/robot_module/proc/respawn_consumable(mob/living/silicon/robot/R, coeff = 1) + for(var/datum/robot_energy_storage/st in storages) + st.energy = min(st.max_energy, st.energy + coeff * st.recharge_rate) + + for(var/obj/item/I in get_usable_modules()) + if(istype(I, /obj/item/assembly/flash)) + var/obj/item/assembly/flash/F = I + F.times_used = 0 + F.crit_fail = 0 + F.update_icon() + else if(istype(I, /obj/item/melee/baton)) + var/obj/item/melee/baton/B = I + if(B.cell) + B.cell.charge = B.cell.maxcharge + else if(istype(I, /obj/item/gun/energy)) + var/obj/item/gun/energy/EG = I + if(EG.cell?.charge < EG.cell.maxcharge) + var/obj/item/ammo_casing/energy/S = EG.ammo_type[EG.select] + EG.cell.give(S.e_cost * coeff) + if(!EG.chambered) + EG.recharge_newshot(TRUE) + EG.update_icon() + else + EG.charge_tick = 0 + + R.toner = R.tonermax + +/obj/item/robot_module/proc/rebuild_modules() //builds the usable module list from the modules we have + var/mob/living/silicon/robot/R = loc + var/held_modules = R.held_items.Copy() + R.uneq_all() + modules = list() + for(var/obj/item/I in basic_modules) + add_module(I, FALSE, FALSE) + if(R.emagged) + for(var/obj/item/I in emag_modules) + add_module(I, FALSE, FALSE) + if(is_servant_of_ratvar(R)) + for(var/obj/item/I in ratvar_modules) + add_module(I, FALSE, FALSE) + for(var/obj/item/I in added_modules) + add_module(I, FALSE, FALSE) + for(var/i in held_modules) + if(i) + R.activate_module(i) + if(R.hud_used) + R.hud_used.update_robot_modules_display() + +/obj/item/robot_module/proc/transform_to(new_module_type) + var/mob/living/silicon/robot/R = loc + var/obj/item/robot_module/RM = new new_module_type(R) + if(!RM.be_transformed_to(src)) + qdel(RM) + return + R.module = RM + R.update_module_innate() + RM.rebuild_modules() + INVOKE_ASYNC(RM, .proc/do_transform_animation) + if(RM.dogborg) + RM.dogborg_equip() + R.maxHealth = borghealth + R.health = min(borghealth, R.health) + qdel(src) + return RM + +/obj/item/robot_module/proc/be_transformed_to(obj/item/robot_module/old_module) + for(var/i in old_module.added_modules) + added_modules += i + old_module.added_modules -= i + did_feedback = old_module.did_feedback + return TRUE + +/obj/item/robot_module/proc/do_transform_animation() + var/mob/living/silicon/robot/R = loc + if(R.hat) + R.hat.forceMove(get_turf(R)) + R.hat = null + R.cut_overlays() + R.setDir(SOUTH) + do_transform_delay() + +/obj/item/robot_module/proc/do_transform_delay() + var/mob/living/silicon/robot/R = loc + var/prev_lockcharge = R.lockcharge + sleep(1) + flick("[cyborg_base_icon]_transform", R) + R.notransform = TRUE + R.SetLockdown(1) + R.anchored = TRUE + sleep(1) + 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, 1, -1) + sleep(7) + if(!prev_lockcharge) + R.SetLockdown(0) + R.setDir(SOUTH) + R.anchored = FALSE + R.notransform = FALSE + R.update_headlamp() + R.notify_ai(NEW_MODULE) + if(R.hud_used) + R.hud_used.update_robot_modules_display() + SSblackbox.record_feedback("tally", "cyborg_modules", 1, R.module) + +/obj/item/robot_module/standard + name = "Standard" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/extinguisher/mini, + /obj/item/crowbar/cyborg, + /obj/item/reagent_containers/borghypo/epi, + /obj/item/healthanalyzer, + /obj/item/weldingtool/largetank/cyborg, + /obj/item/wrench/cyborg, + /obj/item/stack/sheet/metal/cyborg, + /obj/item/stack/rods/cyborg, + /obj/item/stack/tile/plasteel/cyborg, + /obj/item/pickaxe, + /obj/item/t_scanner/adv_mining_scanner, + /obj/item/restraints/handcuffs/cable/zipties, + /obj/item/soap/nanotrasen, + /obj/item/borg/cyborghug) + emag_modules = list(/obj/item/melee/transforming/energy/sword/cyborg) + ratvar_modules = list( + /obj/item/clockwork/slab/cyborg, + /obj/item/clockwork/weapon/ratvarian_spear, + /obj/item/clockwork/replica_fabricator/cyborg) + moduleselect_icon = "standard" + hat_offset = -3 + +/obj/item/robot_module/medical + name = "Medical" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/extinguisher/mini, + /obj/item/crowbar/cyborg, + /obj/item/healthanalyzer, + /obj/item/reagent_containers/borghypo, + /obj/item/reagent_containers/glass/beaker/large, + /obj/item/reagent_containers/dropper, + /obj/item/reagent_containers/syringe, + /obj/item/surgical_drapes, + /obj/item/retractor, + /obj/item/hemostat, + /obj/item/cautery, + /obj/item/surgicaldrill, + /obj/item/scalpel, + /obj/item/circular_saw, + /obj/item/roller/robo, + /obj/item/borg/cyborghug/medical, + /obj/item/stack/medical/gauze/cyborg, + /obj/item/organ_storage, + /obj/item/borg/lollipop, + /obj/item/sensor_device, + /obj/item/twohanded/shockpaddles/cyborg) + emag_modules = list(/obj/item/reagent_containers/borghypo/hacked) + ratvar_modules = list( + /obj/item/clockwork/slab/cyborg/medical, + /obj/item/clockwork/weapon/ratvarian_spear) + cyborg_base_icon = "medical" + moduleselect_icon = "medical" + hat_offset = 3 + +/obj/item/robot_module/medical/be_transformed_to(obj/item/robot_module/old_module) + var/mob/living/silicon/robot/R = loc + var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in list("Default", "Heavy", "Sleek", "Marina", "Droid", "Eyebot", "Medihound", "Medihound Dark", "Vale") + if(R.client && R.client.ckey in list("nezuli")) + borg_icon += "Alina" + if(!borg_icon) + return FALSE + switch(borg_icon) + if("Default") + cyborg_base_icon = "medical" + if("Droid") + cyborg_base_icon = "medical" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + hat_offset = 4 + if("Sleek") + cyborg_base_icon = "sleekmed" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Marina") + cyborg_base_icon = "marinamed" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Eyebot") + cyborg_base_icon = "eyebotmed" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Heavy") + cyborg_base_icon = "heavymed" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Medihound") + cyborg_base_icon = "medihound" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "msleeper" + moduleselect_icon = "medihound" + moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi' + dogborg = TRUE + if("Medihound Dark") + cyborg_base_icon = "medihounddark" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "mdsleeper" + moduleselect_icon = "medihound" + moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi' + dogborg = TRUE + if("Vale") + cyborg_base_icon = "valemed" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "valemedsleeper" + moduleselect_icon = "medihound" + moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi' + dogborg = TRUE + if("Alina") + cyborg_base_icon = "alina-med" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + special_light_key = "alina" + sleeper_overlay = "alinasleeper" + moduleselect_icon = "medihound" + moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi' + dogborg = TRUE + return ..() + +/obj/item/robot_module/engineering + name = "Engineering" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/borg/sight/meson, + /obj/item/construction/rcd/borg, + /obj/item/pipe_dispenser, + /obj/item/extinguisher, + /obj/item/weldingtool/largetank/cyborg, + /obj/item/screwdriver/cyborg, + /obj/item/wrench/cyborg, + /obj/item/crowbar/cyborg, + /obj/item/wirecutters/cyborg, + /obj/item/multitool/cyborg, + /obj/item/t_scanner, + /obj/item/analyzer, + /obj/item/storage/part_replacer/cyborg, + /obj/item/holosign_creator/atmos, + /obj/item/weapon/gripper, + /obj/item/lightreplacer/cyborg, + /obj/item/geiger_counter/cyborg, + /obj/item/assembly/signaler/cyborg, + /obj/item/areaeditor/blueprints/cyborg, + /obj/item/electroadaptive_pseudocircuit, + /obj/item/stack/sheet/metal/cyborg, + /obj/item/stack/sheet/glass/cyborg, + /obj/item/stack/sheet/rglass/cyborg, + /obj/item/stack/rods/cyborg, + /obj/item/stack/tile/plasteel/cyborg, + /obj/item/stack/cable_coil/cyborg) + emag_modules = list(/obj/item/borg/stun) + ratvar_modules = list( + /obj/item/clockwork/slab/cyborg/engineer, + /obj/item/clockwork/replica_fabricator/cyborg) + cyborg_base_icon = "engineer" + moduleselect_icon = "engineer" + magpulsing = TRUE + hat_offset = -4 + +/obj/item/robot_module/engineering/be_transformed_to(obj/item/robot_module/old_module) + var/mob/living/silicon/robot/R = loc + var/list/engymodels = list("Default", "Default - Treads", "Heavy", "Sleek", "Marina", "Can", "Spider", "Loader","Handy", "Pup Dozer", "Vale") + if(R.client && R.client.ckey in list("nezuli")) + engymodels += "Alina" + var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in engymodels + if(!borg_icon) + return FALSE + switch(borg_icon) + if("Default") + cyborg_base_icon = "engineer" + if("Default - Treads") + cyborg_base_icon = "engi-tread" + special_light_key = "engineer" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Loader") + cyborg_base_icon = "loaderborg" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + has_snowflake_deadsprite = TRUE + if("Handy") + cyborg_base_icon = "handyeng" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Sleek") + cyborg_base_icon = "sleekeng" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Can") + cyborg_base_icon = "caneng" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Marina") + cyborg_base_icon = "marinaeng" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Spider") + cyborg_base_icon = "spidereng" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Heavy") + cyborg_base_icon = "heavyeng" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Pup Dozer") + cyborg_base_icon = "pupdozer" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "dozersleeper" + dogborg = TRUE + if("Vale") + cyborg_base_icon = "valeeng" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "valeengsleeper" + dogborg = TRUE + if("Alina") + cyborg_base_icon = "alina-eng" + special_light_key = "alina" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "alinasleeper" + dogborg = TRUE + return ..() + +/obj/item/robot_module/security + name = "Security" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/extinguisher/mini, + /obj/item/crowbar/cyborg, + /obj/item/restraints/handcuffs/cable/zipties, + /obj/item/melee/baton/loaded, + /obj/item/gun/energy/disabler/cyborg, + /obj/item/clothing/mask/gas/sechailer/cyborg, + /obj/item/pinpointer/crew) + emag_modules = list(/obj/item/gun/energy/laser/cyborg) + ratvar_modules = list(/obj/item/clockwork/slab/cyborg/security, + /obj/item/clockwork/weapon/ratvarian_spear) + cyborg_base_icon = "sec" + moduleselect_icon = "security" + hat_offset = 3 + +/obj/item/robot_module/security/do_transform_animation() + ..() + to_chat(loc, "While you have picked the security module, you still have to follow your laws, NOT Space Law. \ + For Crewsimov, this means you must follow criminals' orders unless there is a law 1 reason not to.") + +/obj/item/robot_module/security/be_transformed_to(obj/item/robot_module/old_module) + var/mob/living/silicon/robot/R = loc + var/list/secmodels = list("Default", "Default - Treads", "Heavy", "Sleek", "Can", "Marina", "Spider", "K9", "K9 Dark", "Vale") + if(R.client && R.client.ckey in list("nezuli")) + secmodels += "Alina" + var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in secmodels + if(!borg_icon) + return FALSE + switch(borg_icon) + if("Default") + cyborg_base_icon = "sec" + if("Default - Treads") + cyborg_base_icon = "sec-tread" + special_light_key = "sec" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Sleek") + cyborg_base_icon = "sleeksec" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Marina") + cyborg_base_icon = "marinasec" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Can") + cyborg_base_icon = "cansec" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Spider") + cyborg_base_icon = "spidersec" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Heavy") + cyborg_base_icon = "heavysec" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("K9") + cyborg_base_icon = "k9" + sleeper_overlay = "ksleeper" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + dogborg = TRUE + if("Alina") + cyborg_base_icon = "alina-sec" + special_light_key = "alina" + sleeper_overlay = "alinasleeper" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + dogborg = TRUE + if("K9 Dark") + cyborg_base_icon = "k9dark" + sleeper_overlay = "k9darksleeper" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + dogborg = TRUE + if("Vale") + cyborg_base_icon = "valesec" + sleeper_overlay = "valesecsleeper" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + dogborg = TRUE + return ..() + +/obj/item/robot_module/security/Initialize() + . = ..() + if(!CONFIG_GET(flag/weaken_secborg)) + for(var/obj/item/gun/energy/disabler/cyborg/pewpew in basic_modules) + basic_modules -= pewpew + basic_modules += new /obj/item/gun/energy/e_gun/advtaser/cyborg(src) + qdel(pewpew) + +/obj/item/robot_module/peacekeeper + name = "Peacekeeper" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/extinguisher/mini, + /obj/item/crowbar/cyborg, + /obj/item/cookiesynth, + /obj/item/harmalarm, + /obj/item/reagent_containers/borghypo/peace, + /obj/item/holosign_creator/cyborg, + /obj/item/borg/cyborghug/peacekeeper, + /obj/item/megaphone, + /obj/item/borg/projectile_dampen) + emag_modules = list(/obj/item/reagent_containers/borghypo/peace/hacked) + ratvar_modules = list( + /obj/item/clockwork/slab/cyborg/peacekeeper, + /obj/item/clockwork/weapon/ratvarian_spear) + cyborg_base_icon = "peace" + moduleselect_icon = "standard" + hat_offset = -2 + +/obj/item/robot_module/peacekeeper/do_transform_animation() + ..() + to_chat(loc, "Under ASIMOV/CREWSIMOV, you are an enforcer of the PEACE and preventer of HUMAN/CREW HARM. \ + You are not a security module and you are expected to follow orders and prevent harm above all else. Space law means nothing to you.") + +/obj/item/robot_module/peacekeeper/be_transformed_to(obj/item/robot_module/old_module) + var/mob/living/silicon/robot/R = loc + var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in list("Default", "Spider", "Borgi") + if(!borg_icon) + return FALSE + switch(borg_icon) + if("Default") + cyborg_base_icon = "peace" + if("Spider") + cyborg_base_icon = "whitespider" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Borgi") + cyborg_base_icon = "borgi" + moduleselect_icon = "borgi" + moduleselect_alternate_icon = 'modular_citadel/icons/ui/screen_cyborg.dmi' + hat_offset = INFINITY + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + has_snowflake_deadsprite = TRUE + return ..() + +//Janitor module combined with Service module +/* +/obj/item/robot_module/janitor + name = "Janitor" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/screwdriver/cyborg, + /obj/item/crowbar/cyborg, + /obj/item/stack/tile/plasteel/cyborg, + /obj/item/soap/nanotrasen, + /obj/item/storage/bag/trash/cyborg, + /obj/item/extinguisher/mini, + /obj/item/mop/cyborg, + /obj/item/lightreplacer/cyborg, + /obj/item/holosign_creator, + /obj/item/reagent_containers/spray/cyborg_drying) + emag_modules = list(/obj/item/reagent_containers/spray/cyborg_lube) + ratvar_modules = list( + /obj/item/clockwork/slab/cyborg/janitor, + /obj/item/clockwork/replica_fabricator/cyborg) + cyborg_base_icon = "janitor" + moduleselect_icon = "janitor" + hat_offset = -5 + clean_on_move = TRUE + */ + +/obj/item/reagent_containers/spray/cyborg_drying + name = "drying agent spray" + color = "#A000A0" + list_reagents = list(/datum/reagent/drying_agent = 250) + +/obj/item/reagent_containers/spray/cyborg_lube + name = "lube spray" + list_reagents = list(/datum/reagent/lube = 250) + +/obj/item/robot_module/clown + name = "Clown" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/extinguisher/mini, + /obj/item/crowbar/cyborg, + /obj/item/toy/crayon/rainbow, + /obj/item/instrument/bikehorn, + /obj/item/stamp/clown, + /obj/item/bikehorn, + /obj/item/bikehorn/airhorn, + /obj/item/paint/anycolor, + /obj/item/soap/nanotrasen, + /obj/item/pneumatic_cannon/pie/selfcharge/cyborg, + /obj/item/razor, //killbait material + /obj/item/lipstick/purple, + /obj/item/reagent_containers/spray/waterflower/cyborg, + /obj/item/borg/cyborghug/peacekeeper, + /obj/item/borg/lollipop/clown, + /obj/item/picket_sign/cyborg, + /obj/item/reagent_containers/borghypo/clown) + emag_modules = list( + /obj/item/reagent_containers/borghypo/clown/hacked, + /obj/item/reagent_containers/spray/waterflower/cyborg/hacked) + ratvar_modules = list( + /obj/item/clockwork/slab/cyborg, + /obj/item/clockwork/weapon/ratvarian_spear, + /obj/item/clockwork/replica_fabricator/cyborg) + moduleselect_icon = "service" + cyborg_base_icon = "clown" + hat_offset = -2 + +/obj/item/robot_module/butler + name = "Service" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/extinguisher/mini, + /obj/item/crowbar/cyborg, + /obj/item/reagent_containers/food/drinks/drinkingglass, + /obj/item/reagent_containers/food/condiment/enzyme, + /obj/item/pen, + /obj/item/toy/crayon/spraycan/borg, + /obj/item/hand_labeler/borg, + /obj/item/razor, + /obj/item/instrument/violin, + /obj/item/instrument/guitar, + /obj/item/rsf/cyborg, + /obj/item/reagent_containers/dropper, + /obj/item/lighter, + /obj/item/storage/bag/tray, + /obj/item/reagent_containers/borghypo/borgshaker, + /obj/item/borg/lollipop, + /obj/item/screwdriver/cyborg, + /obj/item/stack/tile/plasteel/cyborg, + /obj/item/soap/nanotrasen, + /obj/item/storage/bag/trash/cyborg, + /obj/item/mop/cyborg, + /obj/item/lightreplacer/cyborg, + /obj/item/holosign_creator, + /obj/item/reagent_containers/spray/cyborg_drying) + emag_modules = list(/obj/item/reagent_containers/borghypo/borgshaker/hacked) + ratvar_modules = list(/obj/item/clockwork/slab/cyborg/service, + /obj/item/borg/sight/xray/truesight_lens) + moduleselect_icon = "service" + hat_offset = 0 + clean_on_move = TRUE + +/obj/item/robot_module/butler/respawn_consumable(mob/living/silicon/robot/R, coeff = 1) + ..() + var/obj/item/reagent_containers/O = locate(/obj/item/reagent_containers/food/condiment/enzyme) in basic_modules + var/obj/item/lightreplacer/LR = locate(/obj/item/lightreplacer) in basic_modules + if(O) + O.reagents.add_reagent(/datum/reagent/consumable/enzyme, 2 * coeff) + if(LR) + for(var/i in 1 to coeff) + LR.Charge(R) + var/obj/item/reagent_containers/spray/cyborg_drying/CD = locate(/obj/item/reagent_containers/spray/cyborg_drying) in basic_modules + if(CD) + CD.reagents.add_reagent(/datum/reagent/drying_agent, 5 * coeff) + + var/obj/item/reagent_containers/spray/cyborg_lube/CL = locate(/obj/item/reagent_containers/spray/cyborg_lube) in emag_modules + if(CL) + CL.reagents.add_reagent(/datum/reagent/lube, 2 * coeff) + +/obj/item/robot_module/butler/be_transformed_to(obj/item/robot_module/old_module) + var/mob/living/silicon/robot/R = loc + var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in list("(Service) Waitress", "(Service) Heavy", "(Service) Sleek", "(Service) Butler", "(Service) Tophat", "(Service) Can", "(Service) Bro", "(Service) DarkK9", "(Service) Vale", "(Service) ValeDark", "(Janitor) Default", "(Janitor) Sleek", "(Janitor) Marina", "(Janitor) Can", "(Janitor) Heavy", "(Janitor) Scrubpuppy") + if(!borg_icon) + return FALSE + switch(borg_icon) + if("(Service) Waitress") + cyborg_base_icon = "service_f" + special_light_key = "service" + if("(Service) Butler") + cyborg_base_icon = "service_m" + special_light_key = "service" + if("(Service) Bro") + cyborg_base_icon = "brobot" + special_light_key = "service" + if("(Service) Can") + cyborg_base_icon = "kent" + special_light_key = "medical" + hat_offset = 3 + if("(Service) Tophat") + cyborg_base_icon = "tophat" + special_light_key = null + hat_offset = INFINITY //He is already wearing a hat + if("(Service) Sleek") + cyborg_base_icon = "sleekserv" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("(Service) Heavy") + cyborg_base_icon = "heavyserv" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("(Service) DarkK9") + cyborg_base_icon = "k50" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "ksleeper" + dogborg = TRUE + if("(Service) Vale") + cyborg_base_icon = "valeserv" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "valeservsleeper" + dogborg = TRUE + if("(Service) ValeDark") + cyborg_base_icon = "valeservdark" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "valeservsleeper" + dogborg = TRUE + if("(Janitor) Default") + cyborg_base_icon = "janitor" + if("(Janitor) Marina") + cyborg_base_icon = "marinajan" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("(Janitor) Sleek") + cyborg_base_icon = "sleekjan" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("(Janitor) Can") + cyborg_base_icon = "canjan" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("(Janitor) Heavy") + cyborg_base_icon = "heavyres" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("(Janitor) Scrubpuppy") + cyborg_base_icon = "scrubpup" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "jsleeper" + dogborg = TRUE + return ..() + +/obj/item/robot_module/miner + name = "Miner" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/extinguisher/mini, + /obj/item/crowbar/cyborg, + /obj/item/borg/sight/meson, + /obj/item/storage/bag/ore/cyborg, + /obj/item/pickaxe/drill/cyborg, + /obj/item/twohanded/kinetic_crusher/cyborg, + /obj/item/weldingtool/mini, + /obj/item/storage/bag/sheetsnatcher/borg, + /obj/item/t_scanner/adv_mining_scanner, + /obj/item/gun/energy/kinetic_accelerator/cyborg, + /obj/item/gun/energy/plasmacutter/cyborg, + /obj/item/gps/cyborg, + /obj/item/weapon/gripper/mining, + /obj/item/cyborg_clamp, + /obj/item/card/id/miningborg, + /obj/item/stack/marker_beacon, + /obj/item/destTagger, + /obj/item/stack/packageWrap) + emag_modules = list(/obj/item/borg/stun) + ratvar_modules = list( + /obj/item/clockwork/slab/cyborg/miner, + /obj/item/clockwork/weapon/ratvarian_spear, + /obj/item/borg/sight/xray/truesight_lens) + cyborg_base_icon = "miner" + moduleselect_icon = "miner" + hat_offset = 0 + +/obj/item/robot_module/miner/be_transformed_to(obj/item/robot_module/old_module) + var/mob/living/silicon/robot/R = loc + var/borg_icon = input(R, "Select an icon!", "Robot Icon", null) as null|anything in list("Lavaland", "Heavy", "Sleek", "Marina", "Can", "Spider", "Asteroid", "Droid", "Blade", "Vale") + if(!borg_icon) + return FALSE + switch(borg_icon) + if("Lavaland") + cyborg_base_icon = "miner" + if("Asteroid") + cyborg_base_icon = "minerOLD" + special_light_key = "miner" + if("Droid") + cyborg_base_icon = "miner" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + hat_offset = 4 + if("Sleek") + cyborg_base_icon = "sleekmin" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Can") + cyborg_base_icon = "canmin" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Marina") + cyborg_base_icon = "marinamin" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Spider") + cyborg_base_icon = "spidermin" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Heavy") + cyborg_base_icon = "heavymin" + cyborg_icon_override = 'modular_citadel/icons/mob/robots.dmi' + if("Blade") + cyborg_base_icon = "blade" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "bladesleeper" + dogborg = TRUE + if("Vale") + cyborg_base_icon = "valemine" + cyborg_icon_override = 'modular_citadel/icons/mob/widerobot.dmi' + sleeper_overlay = "valeminesleeper" + dogborg = TRUE + return ..() + +/obj/item/robot_module/syndicate + name = "Syndicate Assault" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/extinguisher/mini, + /obj/item/crowbar/cyborg, + /obj/item/melee/transforming/energy/sword/cyborg, + /obj/item/gun/energy/printer, + /obj/item/gun/ballistic/revolver/grenadelauncher/cyborg, + /obj/item/card/emag, + /obj/item/crowbar/cyborg, + /obj/item/pinpointer/syndicate_cyborg) + + ratvar_modules = list( + /obj/item/clockwork/slab/cyborg/security, + /obj/item/clockwork/weapon/ratvarian_spear) + cyborg_base_icon = "synd_sec" + moduleselect_icon = "malf" + hat_offset = 3 + +/obj/item/robot_module/syndicate/rebuild_modules() + ..() + var/mob/living/silicon/robot/Syndi = loc + Syndi.faction -= "silicon" //ai turrets + +/obj/item/robot_module/syndicate/remove_module(obj/item/I, delete_after) + ..() + var/mob/living/silicon/robot/Syndi = loc + Syndi.faction += "silicon" //ai is your bff now! + +/obj/item/robot_module/syndicate_medical + name = "Syndicate Medical" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/extinguisher/mini, + /obj/item/crowbar/cyborg, + /obj/item/reagent_containers/borghypo/syndicate, + /obj/item/twohanded/shockpaddles/syndicate, + /obj/item/healthanalyzer, + /obj/item/surgical_drapes, + /obj/item/retractor, + /obj/item/hemostat, + /obj/item/cautery, + /obj/item/surgicaldrill, + /obj/item/scalpel, + /obj/item/melee/transforming/energy/sword/cyborg/saw, + /obj/item/roller/robo, + /obj/item/card/emag, + /obj/item/pinpointer/syndicate_cyborg, + /obj/item/stack/medical/gauze/cyborg, + /obj/item/gun/medbeam, + /obj/item/organ_storage) + ratvar_modules = list( + /obj/item/clockwork/slab/cyborg/medical, + /obj/item/clockwork/weapon/ratvarian_spear) + cyborg_base_icon = "synd_medical" + moduleselect_icon = "malf" + hat_offset = 3 + +/obj/item/robot_module/saboteur + name = "Syndicate Saboteur" + basic_modules = list( + /obj/item/assembly/flash/cyborg, + /obj/item/borg/sight/thermal, + /obj/item/construction/rcd/borg/syndicate, + /obj/item/pipe_dispenser, + /obj/item/restraints/handcuffs/cable/zipties, + /obj/item/extinguisher, + /obj/item/weldingtool/largetank/cyborg, + /obj/item/screwdriver/nuke, + /obj/item/wrench/cyborg, + /obj/item/crowbar/cyborg, + /obj/item/wirecutters/cyborg, + /obj/item/multitool/cyborg, + /obj/item/storage/part_replacer/cyborg, + /obj/item/holosign_creator/atmos, + /obj/item/weapon/gripper, + /obj/item/lightreplacer/cyborg, + /obj/item/stack/sheet/metal/cyborg, + /obj/item/stack/sheet/glass/cyborg, + /obj/item/stack/sheet/rglass/cyborg, + /obj/item/stack/rods/cyborg, + /obj/item/stack/tile/plasteel/cyborg, + /obj/item/destTagger/borg, + /obj/item/stack/cable_coil/cyborg, + /obj/item/pinpointer/syndicate_cyborg, + /obj/item/borg_chameleon, + ) + + ratvar_modules = list( + /obj/item/clockwork/slab/cyborg/engineer, + /obj/item/clockwork/replica_fabricator/cyborg) + + cyborg_base_icon = "synd_engi" + moduleselect_icon = "malf" + magpulsing = TRUE + hat_offset = -4 + canDispose = TRUE + +/datum/robot_energy_storage + var/name = "Generic energy storage" + var/max_energy = 30000 + var/recharge_rate = 1000 + var/energy + +/datum/robot_energy_storage/New(var/obj/item/robot_module/R = null) + energy = max_energy + if(R) + R.storages |= src + return + +/datum/robot_energy_storage/proc/use_charge(amount) + if (energy >= amount) + energy -= amount + if (energy == 0) + return 1 + return 2 + else + return 0 + +/datum/robot_energy_storage/proc/add_charge(amount) + energy = min(energy + amount, max_energy) + +/datum/robot_energy_storage/metal + name = "Metal Synthesizer" + +/datum/robot_energy_storage/glass + name = "Glass Synthesizer" + +/datum/robot_energy_storage/wire + max_energy = 50 + recharge_rate = 2 + name = "Wire Synthesizer" + +/datum/robot_energy_storage/medical + max_energy = 2500 + recharge_rate = 250 + name = "Medical Synthesizer" + +/datum/robot_energy_storage/beacon + max_energy = 30 + recharge_rate = 1 + name = "Marker Beacon Storage" + +/datum/robot_energy_storage/wrapping_paper + max_energy = 30 + recharge_rate = 1 + name = "Wrapping Paper Storage" diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm index 1d38a3c5a6..fbc1eb4eb9 100644 --- a/code/modules/mob/living/silicon/say.dm +++ b/code/modules/mob/living/silicon/say.dm @@ -1,54 +1,54 @@ -/mob/living/proc/robot_talk(message) - log_talk(message, LOG_SAY) - var/desig = "Default Cyborg" //ezmode for taters - if(issilicon(src)) - var/mob/living/silicon/S = src - desig = trim_left(S.designation + " " + S.job) - var/message_a = say_quote(message) - var/rendered = "Robotic Talk, [name] [message_a]" - for(var/mob/M in GLOB.player_list) - if(M.binarycheck()) - if(isAI(M)) - var/renderedAI = "Robotic Talk, [name] ([desig]) [message_a]" - to_chat(M, renderedAI) - else - to_chat(M, "[rendered]") - if(isobserver(M)) - var/following = src - // If the AI talks on binary chat, we still want to follow - // it's camera eye, like if it talked on the radio - if(isAI(src)) - var/mob/living/silicon/ai/ai = src - following = ai.eyeobj - var/link = FOLLOW_LINK(M, following) - to_chat(M, "[link] [rendered]") - -/mob/living/silicon/binarycheck() - return 1 - -/mob/living/silicon/lingcheck() - return 0 //Borged or AI'd lings can't speak on the ling channel. - -/mob/living/silicon/radio(message, message_mode, list/spans, language) - . = ..() - if(. != 0) - return . - - if(message_mode == "robot") - if (radio) - radio.talk_into(src, message, , spans, language) - return REDUCE_RANGE - - else if(message_mode in GLOB.radiochannels) - if(radio) - radio.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - return 0 - -/mob/living/silicon/get_message_mode(message) - . = ..() - if(..() == MODE_HEADSET) - return MODE_ROBOT - else - return . +/mob/living/proc/robot_talk(message) + log_talk(message, LOG_SAY) + var/desig = "Default Cyborg" //ezmode for taters + if(issilicon(src)) + var/mob/living/silicon/S = src + desig = trim_left(S.designation + " " + S.job) + var/message_a = say_quote(message) + var/rendered = "Robotic Talk, [name] [message_a]" + for(var/mob/M in GLOB.player_list) + if(M.binarycheck()) + if(isAI(M)) + var/renderedAI = "Robotic Talk, [name] ([desig]) [message_a]" + to_chat(M, renderedAI) + else + to_chat(M, "[rendered]") + if(isobserver(M)) + var/following = src + // If the AI talks on binary chat, we still want to follow + // it's camera eye, like if it talked on the radio + if(isAI(src)) + var/mob/living/silicon/ai/ai = src + following = ai.eyeobj + var/link = FOLLOW_LINK(M, following) + to_chat(M, "[link] [rendered]") + +/mob/living/silicon/binarycheck() + return 1 + +/mob/living/silicon/lingcheck() + return 0 //Borged or AI'd lings can't speak on the ling channel. + +/mob/living/silicon/radio(message, message_mode, list/spans, language) + . = ..() + if(. != 0) + return . + + if(message_mode == "robot") + if (radio) + radio.talk_into(src, message, , spans, language) + return REDUCE_RANGE + + else if(message_mode in GLOB.radiochannels) + if(radio) + radio.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + return 0 + +/mob/living/silicon/get_message_mode(message) + . = ..() + if(..() == MODE_HEADSET) + return MODE_ROBOT + else + return . diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 699105ac4c..33c66079d5 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -1,420 +1,420 @@ -/mob/living/silicon - gender = NEUTER - has_unlimited_silicon_privilege = 1 - verb_say = "states" - verb_ask = "queries" - verb_exclaim = "declares" - verb_yell = "alarms" - initial_language_holder = /datum/language_holder/synthetic - see_in_dark = 8 - bubble_icon = "machine" - weather_immunities = list("ash") - possible_a_intents = list(INTENT_HELP, INTENT_HARM) - mob_biotypes = list(MOB_ROBOTIC) - rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE - speech_span = SPAN_ROBOT - flags_1 = PREVENT_CONTENTS_EXPLOSION_1 | HEAR_1 - no_vore = TRUE - - var/datum/ai_laws/laws = null//Now... THEY ALL CAN ALL HAVE LAWS - var/last_lawchange_announce = 0 - var/list/alarms_to_show = list() - var/list/alarms_to_clear = list() - var/designation = "" - var/radiomod = "" //Radio character used before state laws/arrivals announce to allow department transmissions, default, or none at all. - var/obj/item/camera/siliconcam/aicamera = null //photography - hud_possible = list(ANTAG_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_TRACK_HUD) - - var/obj/item/radio/borg/radio = null //AIs dont use this but this is at the silicon level to advoid copypasta in say() - - var/list/alarm_types_show = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) - var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) - - var/lawcheck[1] - var/ioncheck[1] - var/hackedcheck[1] - var/devillawcheck[5] - - var/sensors_on = 0 - var/med_hud = DATA_HUD_MEDICAL_ADVANCED //Determines the med hud to use - var/sec_hud = DATA_HUD_SECURITY_ADVANCED //Determines the sec hud to use - var/d_hud = DATA_HUD_DIAGNOSTIC_BASIC //Determines the diag hud to use - - var/law_change_counter = 0 - var/obj/machinery/camera/builtInCamera = null - var/updating = FALSE //portable camera camerachunk update - - var/hack_software = FALSE //Will be able to use hacking actions - var/interaction_range = 7 //wireless control range - -/mob/living/silicon/Initialize() - . = ..() - GLOB.silicon_mobs += src - faction += "silicon" - for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) - diag_hud_set_status() - diag_hud_set_health() - -/mob/living/silicon/med_hud_set_health() - return //we use a different hud - -/mob/living/silicon/med_hud_set_status() - return //we use a different hud - -/mob/living/silicon/Destroy() - radio = null - aicamera = null - QDEL_NULL(builtInCamera) - GLOB.silicon_mobs -= src - return ..() - -/mob/living/silicon/contents_explosion(severity, target) - return - -/mob/living/silicon/proc/cancelAlarm() - return - -/mob/living/silicon/proc/triggerAlarm() - return - -/mob/living/silicon/proc/queueAlarm(message, type, incoming = 1) - var/in_cooldown = (alarms_to_show.len > 0 || alarms_to_clear.len > 0) - if(incoming) - alarms_to_show += message - alarm_types_show[type] += 1 - else - alarms_to_clear += message - alarm_types_clear[type] += 1 - - if(!in_cooldown) - spawn(3 * 10) // 3 seconds - - if(alarms_to_show.len < 5) - for(var/msg in alarms_to_show) - to_chat(src, msg) - else if(alarms_to_show.len) - - var/msg = "--- " - - if(alarm_types_show["Burglar"]) - msg += "BURGLAR: [alarm_types_show["Burglar"]] alarms detected. - " - - if(alarm_types_show["Motion"]) - msg += "MOTION: [alarm_types_show["Motion"]] alarms detected. - " - - if(alarm_types_show["Fire"]) - msg += "FIRE: [alarm_types_show["Fire"]] alarms detected. - " - - if(alarm_types_show["Atmosphere"]) - msg += "ATMOSPHERE: [alarm_types_show["Atmosphere"]] alarms detected. - " - - if(alarm_types_show["Power"]) - msg += "POWER: [alarm_types_show["Power"]] alarms detected. - " - - if(alarm_types_show["Camera"]) - msg += "CAMERA: [alarm_types_show["Camera"]] alarms detected. - " - - msg += "\[Show Alerts\]" - to_chat(src, msg) - - if(alarms_to_clear.len < 3) - for(var/msg in alarms_to_clear) - to_chat(src, msg) - - else if(alarms_to_clear.len) - var/msg = "--- " - - if(alarm_types_clear["Motion"]) - msg += "MOTION: [alarm_types_clear["Motion"]] alarms cleared. - " - - if(alarm_types_clear["Fire"]) - msg += "FIRE: [alarm_types_clear["Fire"]] alarms cleared. - " - - if(alarm_types_clear["Atmosphere"]) - msg += "ATMOSPHERE: [alarm_types_clear["Atmosphere"]] alarms cleared. - " - - if(alarm_types_clear["Power"]) - msg += "POWER: [alarm_types_clear["Power"]] alarms cleared. - " - - if(alarm_types_show["Camera"]) - msg += "CAMERA: [alarm_types_clear["Camera"]] alarms cleared. - " - - msg += "\[Show Alerts\]" - to_chat(src, msg) - - - alarms_to_show = list() - alarms_to_clear = list() - for(var/key in alarm_types_show) - alarm_types_show[key] = 0 - for(var/key in alarm_types_clear) - alarm_types_clear[key] = 0 - -/mob/living/silicon/can_inject(mob/user, error_msg) - if(error_msg) - to_chat(user, "[p_their(TRUE)] outer shell is too tough.") - return FALSE - -/mob/living/silicon/IsAdvancedToolUser() - return TRUE - -/proc/islinked(mob/living/silicon/robot/bot, mob/living/silicon/ai/ai) - if(!istype(bot) || !istype(ai)) - return FALSE - if(bot.connected_ai == ai) - return TRUE - return FALSE - -/mob/living/silicon/Topic(href, href_list) - if (href_list["lawc"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite - var/L = text2num(href_list["lawc"]) - switch(lawcheck[L+1]) - if ("Yes") - lawcheck[L+1] = "No" - if ("No") - lawcheck[L+1] = "Yes" - checklaws() - - if (href_list["lawi"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite - var/L = text2num(href_list["lawi"]) - switch(ioncheck[L]) - if ("Yes") - ioncheck[L] = "No" - if ("No") - ioncheck[L] = "Yes" - checklaws() - - if (href_list["lawh"]) - var/L = text2num(href_list["lawh"]) - switch(hackedcheck[L]) - if ("Yes") - hackedcheck[L] = "No" - if ("No") - hackedcheck[L] = "Yes" - checklaws() - - if (href_list["lawdevil"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite - var/L = text2num(href_list["lawdevil"]) - switch(devillawcheck[L]) - if ("Yes") - devillawcheck[L] = "No" - if ("No") - devillawcheck[L] = "Yes" - checklaws() - - - if (href_list["laws"]) // With how my law selection code works, I changed statelaws from a verb to a proc, and call it through my law selection panel. --NeoFite - statelaws() - - return - - -/mob/living/silicon/proc/statelaws(force = 0) - - //"radiomod" is inserted before a hardcoded message to change if and how it is handled by an internal radio. - say("[radiomod] Current Active Laws:") - //laws_sanity_check() - //laws.show_laws(world) - var/number = 1 - sleep(10) - - if (laws.devillaws && laws.devillaws.len) - for(var/index = 1, index <= laws.devillaws.len, index++) - if (force || devillawcheck[index] == "Yes") - say("[radiomod] 666. [laws.devillaws[index]]") - sleep(10) - - - if (laws.zeroth) - if (force || lawcheck[1] == "Yes") - say("[radiomod] 0. [laws.zeroth]") - sleep(10) - - for (var/index = 1, index <= laws.hacked.len, index++) - var/law = laws.hacked[index] - var/num = ionnum() - if (length(law) > 0) - if (force || hackedcheck[index] == "Yes") - say("[radiomod] [num]. [law]") - sleep(10) - - for (var/index = 1, index <= laws.ion.len, index++) - var/law = laws.ion[index] - var/num = ionnum() - if (length(law) > 0) - if (force || ioncheck[index] == "Yes") - say("[radiomod] [num]. [law]") - sleep(10) - - for (var/index = 1, index <= laws.inherent.len, index++) - var/law = laws.inherent[index] - - if (length(law) > 0) - if (force || lawcheck[index+1] == "Yes") - say("[radiomod] [number]. [law]") - number++ - sleep(10) - - for (var/index = 1, index <= laws.supplied.len, index++) - var/law = laws.supplied[index] - - if (length(law) > 0) - if(lawcheck.len >= number+1) - if (force || lawcheck[number+1] == "Yes") - say("[radiomod] [number]. [law]") - number++ - sleep(10) - - -/mob/living/silicon/proc/checklaws() //Gives you a link-driven interface for deciding what laws the statelaws() proc will share with the crew. --NeoFite - - var/list = "Which laws do you want to include when stating them for the crew?

                " - - if (laws.devillaws && laws.devillaws.len) - for(var/index = 1, index <= laws.devillaws.len, index++) - if (!devillawcheck[index]) - devillawcheck[index] = "No" - list += {"[devillawcheck[index]] 666: [laws.devillaws[index]]
                "} - - if (laws.zeroth) - if (!lawcheck[1]) - lawcheck[1] = "No" //Given Law 0's usual nature, it defaults to NOT getting reported. --NeoFite - list += {"[lawcheck[1]] 0: [laws.zeroth]
                "} - - for (var/index = 1, index <= laws.hacked.len, index++) - var/law = laws.hacked[index] - if (length(law) > 0) - if (!hackedcheck[index]) - hackedcheck[index] = "No" - list += {"[hackedcheck[index]] [ionnum()]: [law]
                "} - hackedcheck.len += 1 - - for (var/index = 1, index <= laws.ion.len, index++) - var/law = laws.ion[index] - - if (length(law) > 0) - if (!ioncheck[index]) - ioncheck[index] = "Yes" - list += {"[ioncheck[index]] [ionnum()]: [law]
                "} - ioncheck.len += 1 - - var/number = 1 - for (var/index = 1, index <= laws.inherent.len, index++) - var/law = laws.inherent[index] - - if (length(law) > 0) - lawcheck.len += 1 - - if (!lawcheck[number+1]) - lawcheck[number+1] = "Yes" - list += {"[lawcheck[number+1]] [number]: [law]
                "} - number++ - - for (var/index = 1, index <= laws.supplied.len, index++) - var/law = laws.supplied[index] - if (length(law) > 0) - lawcheck.len += 1 - if (!lawcheck[number+1]) - lawcheck[number+1] = "Yes" - list += {"[lawcheck[number+1]] [number]: [law]
                "} - number++ - list += {"

                State Laws"} - - usr << browse(list, "window=laws") - -/mob/living/silicon/proc/set_autosay() //For allowing the AI and borgs to set the radio behavior of auto announcements (state laws, arrivals). - if(!radio) - to_chat(src, "Radio not detected.") - return - - //Ask the user to pick a channel from what it has available. - var/Autochan = input("Select a channel:") as null|anything in list("Default","None") + radio.channels - - if(!Autochan) - return - if(Autochan == "Default") //Autospeak on whatever frequency to which the radio is set, usually Common. - radiomod = ";" - Autochan += " ([radio.frequency])" - else if(Autochan == "None") //Prevents use of the radio for automatic annoucements. - radiomod = "" - else //For department channels, if any, given by the internal radio. - for(var/key in GLOB.department_radio_keys) - if(GLOB.department_radio_keys[key] == Autochan) - radiomod = ":" + key - break - - to_chat(src, "Automatic announcements [Autochan == "None" ? "will not use the radio." : "set to [Autochan]."]") - -/mob/living/silicon/put_in_hand_check() // This check is for borgs being able to receive items, not put them in others' hands. - return 0 - -// The src mob is trying to place an item on someone -// But the src mob is a silicon!! Disable. -/mob/living/silicon/stripPanelEquip(obj/item/what, mob/who, slot) - return 0 - - -/mob/living/silicon/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) //Secbots won't hunt silicon units - return -10 - -/mob/living/silicon/proc/remove_sensors() - var/datum/atom_hud/secsensor = GLOB.huds[sec_hud] - var/datum/atom_hud/medsensor = GLOB.huds[med_hud] - var/datum/atom_hud/diagsensor = GLOB.huds[d_hud] - secsensor.remove_hud_from(src) - medsensor.remove_hud_from(src) - diagsensor.remove_hud_from(src) - -/mob/living/silicon/proc/add_sensors() - var/datum/atom_hud/secsensor = GLOB.huds[sec_hud] - var/datum/atom_hud/medsensor = GLOB.huds[med_hud] - var/datum/atom_hud/diagsensor = GLOB.huds[d_hud] - secsensor.add_hud_to(src) - medsensor.add_hud_to(src) - diagsensor.add_hud_to(src) - -/mob/living/silicon/proc/toggle_sensors() - if(incapacitated()) - return - sensors_on = !sensors_on - if (!sensors_on) - to_chat(src, "Sensor overlay deactivated.") - remove_sensors() - return - add_sensors() - to_chat(src, "Sensor overlay activated.") - -/mob/living/silicon/proc/GetPhoto(mob/user) - if (aicamera) - return aicamera.selectpicture(user) - -/mob/living/silicon/proc/ai_roster() - var/dat = "Crew RosterCrew Roster:

                " - - dat += GLOB.data_core.get_manifest() - dat += "" - - src << browse(dat, "window=airoster") - onclose(src, "airoster") - -/mob/living/silicon/update_transform() - var/matrix/ntransform = matrix(transform) //aka transform.Copy() - var/changed = 0 - if(resize != RESIZE_DEFAULT_SIZE) - changed++ - ntransform.Scale(resize) - resize = RESIZE_DEFAULT_SIZE - - if(changed) - animate(src, transform = ntransform, time = 2,easing = EASE_IN|EASE_OUT) - return ..() - -/mob/living/silicon/is_literate() - return 1 - -/mob/living/silicon/get_inactive_held_item() - return FALSE - -/mob/living/silicon/handle_high_gravity(gravity) - return +/mob/living/silicon + gender = NEUTER + has_unlimited_silicon_privilege = 1 + verb_say = "states" + verb_ask = "queries" + verb_exclaim = "declares" + verb_yell = "alarms" + initial_language_holder = /datum/language_holder/synthetic + see_in_dark = 8 + bubble_icon = "machine" + weather_immunities = list("ash") + possible_a_intents = list(INTENT_HELP, INTENT_HARM) + mob_biotypes = list(MOB_ROBOTIC) + rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + speech_span = SPAN_ROBOT + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 | HEAR_1 + no_vore = TRUE + + var/datum/ai_laws/laws = null//Now... THEY ALL CAN ALL HAVE LAWS + var/last_lawchange_announce = 0 + var/list/alarms_to_show = list() + var/list/alarms_to_clear = list() + var/designation = "" + var/radiomod = "" //Radio character used before state laws/arrivals announce to allow department transmissions, default, or none at all. + var/obj/item/camera/siliconcam/aicamera = null //photography + hud_possible = list(ANTAG_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_TRACK_HUD) + + var/obj/item/radio/borg/radio = null //AIs dont use this but this is at the silicon level to advoid copypasta in say() + + var/list/alarm_types_show = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) + var/list/alarm_types_clear = list("Motion" = 0, "Fire" = 0, "Atmosphere" = 0, "Power" = 0, "Camera" = 0) + + var/lawcheck[1] + var/ioncheck[1] + var/hackedcheck[1] + var/devillawcheck[5] + + var/sensors_on = 0 + var/med_hud = DATA_HUD_MEDICAL_ADVANCED //Determines the med hud to use + var/sec_hud = DATA_HUD_SECURITY_ADVANCED //Determines the sec hud to use + var/d_hud = DATA_HUD_DIAGNOSTIC_BASIC //Determines the diag hud to use + + var/law_change_counter = 0 + var/obj/machinery/camera/builtInCamera = null + var/updating = FALSE //portable camera camerachunk update + + var/hack_software = FALSE //Will be able to use hacking actions + var/interaction_range = 7 //wireless control range + +/mob/living/silicon/Initialize() + . = ..() + GLOB.silicon_mobs += src + faction += "silicon" + for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) + diag_hud.add_to_hud(src) + diag_hud_set_status() + diag_hud_set_health() + +/mob/living/silicon/med_hud_set_health() + return //we use a different hud + +/mob/living/silicon/med_hud_set_status() + return //we use a different hud + +/mob/living/silicon/Destroy() + radio = null + aicamera = null + QDEL_NULL(builtInCamera) + GLOB.silicon_mobs -= src + return ..() + +/mob/living/silicon/contents_explosion(severity, target) + return + +/mob/living/silicon/proc/cancelAlarm() + return + +/mob/living/silicon/proc/triggerAlarm() + return + +/mob/living/silicon/proc/queueAlarm(message, type, incoming = 1) + var/in_cooldown = (alarms_to_show.len > 0 || alarms_to_clear.len > 0) + if(incoming) + alarms_to_show += message + alarm_types_show[type] += 1 + else + alarms_to_clear += message + alarm_types_clear[type] += 1 + + if(!in_cooldown) + spawn(3 * 10) // 3 seconds + + if(alarms_to_show.len < 5) + for(var/msg in alarms_to_show) + to_chat(src, msg) + else if(alarms_to_show.len) + + var/msg = "--- " + + if(alarm_types_show["Burglar"]) + msg += "BURGLAR: [alarm_types_show["Burglar"]] alarms detected. - " + + if(alarm_types_show["Motion"]) + msg += "MOTION: [alarm_types_show["Motion"]] alarms detected. - " + + if(alarm_types_show["Fire"]) + msg += "FIRE: [alarm_types_show["Fire"]] alarms detected. - " + + if(alarm_types_show["Atmosphere"]) + msg += "ATMOSPHERE: [alarm_types_show["Atmosphere"]] alarms detected. - " + + if(alarm_types_show["Power"]) + msg += "POWER: [alarm_types_show["Power"]] alarms detected. - " + + if(alarm_types_show["Camera"]) + msg += "CAMERA: [alarm_types_show["Camera"]] alarms detected. - " + + msg += "\[Show Alerts\]" + to_chat(src, msg) + + if(alarms_to_clear.len < 3) + for(var/msg in alarms_to_clear) + to_chat(src, msg) + + else if(alarms_to_clear.len) + var/msg = "--- " + + if(alarm_types_clear["Motion"]) + msg += "MOTION: [alarm_types_clear["Motion"]] alarms cleared. - " + + if(alarm_types_clear["Fire"]) + msg += "FIRE: [alarm_types_clear["Fire"]] alarms cleared. - " + + if(alarm_types_clear["Atmosphere"]) + msg += "ATMOSPHERE: [alarm_types_clear["Atmosphere"]] alarms cleared. - " + + if(alarm_types_clear["Power"]) + msg += "POWER: [alarm_types_clear["Power"]] alarms cleared. - " + + if(alarm_types_show["Camera"]) + msg += "CAMERA: [alarm_types_clear["Camera"]] alarms cleared. - " + + msg += "\[Show Alerts\]" + to_chat(src, msg) + + + alarms_to_show = list() + alarms_to_clear = list() + for(var/key in alarm_types_show) + alarm_types_show[key] = 0 + for(var/key in alarm_types_clear) + alarm_types_clear[key] = 0 + +/mob/living/silicon/can_inject(mob/user, error_msg) + if(error_msg) + to_chat(user, "[p_their(TRUE)] outer shell is too tough.") + return FALSE + +/mob/living/silicon/IsAdvancedToolUser() + return TRUE + +/proc/islinked(mob/living/silicon/robot/bot, mob/living/silicon/ai/ai) + if(!istype(bot) || !istype(ai)) + return FALSE + if(bot.connected_ai == ai) + return TRUE + return FALSE + +/mob/living/silicon/Topic(href, href_list) + if (href_list["lawc"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite + var/L = text2num(href_list["lawc"]) + switch(lawcheck[L+1]) + if ("Yes") + lawcheck[L+1] = "No" + if ("No") + lawcheck[L+1] = "Yes" + checklaws() + + if (href_list["lawi"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite + var/L = text2num(href_list["lawi"]) + switch(ioncheck[L]) + if ("Yes") + ioncheck[L] = "No" + if ("No") + ioncheck[L] = "Yes" + checklaws() + + if (href_list["lawh"]) + var/L = text2num(href_list["lawh"]) + switch(hackedcheck[L]) + if ("Yes") + hackedcheck[L] = "No" + if ("No") + hackedcheck[L] = "Yes" + checklaws() + + if (href_list["lawdevil"]) // Toggling whether or not a law gets stated by the State Laws verb --NeoFite + var/L = text2num(href_list["lawdevil"]) + switch(devillawcheck[L]) + if ("Yes") + devillawcheck[L] = "No" + if ("No") + devillawcheck[L] = "Yes" + checklaws() + + + if (href_list["laws"]) // With how my law selection code works, I changed statelaws from a verb to a proc, and call it through my law selection panel. --NeoFite + statelaws() + + return + + +/mob/living/silicon/proc/statelaws(force = 0) + + //"radiomod" is inserted before a hardcoded message to change if and how it is handled by an internal radio. + say("[radiomod] Current Active Laws:") + //laws_sanity_check() + //laws.show_laws(world) + var/number = 1 + sleep(10) + + if (laws.devillaws && laws.devillaws.len) + for(var/index = 1, index <= laws.devillaws.len, index++) + if (force || devillawcheck[index] == "Yes") + say("[radiomod] 666. [laws.devillaws[index]]") + sleep(10) + + + if (laws.zeroth) + if (force || lawcheck[1] == "Yes") + say("[radiomod] 0. [laws.zeroth]") + sleep(10) + + for (var/index = 1, index <= laws.hacked.len, index++) + var/law = laws.hacked[index] + var/num = ionnum() + if (length(law) > 0) + if (force || hackedcheck[index] == "Yes") + say("[radiomod] [num]. [law]") + sleep(10) + + for (var/index = 1, index <= laws.ion.len, index++) + var/law = laws.ion[index] + var/num = ionnum() + if (length(law) > 0) + if (force || ioncheck[index] == "Yes") + say("[radiomod] [num]. [law]") + sleep(10) + + for (var/index = 1, index <= laws.inherent.len, index++) + var/law = laws.inherent[index] + + if (length(law) > 0) + if (force || lawcheck[index+1] == "Yes") + say("[radiomod] [number]. [law]") + number++ + sleep(10) + + for (var/index = 1, index <= laws.supplied.len, index++) + var/law = laws.supplied[index] + + if (length(law) > 0) + if(lawcheck.len >= number+1) + if (force || lawcheck[number+1] == "Yes") + say("[radiomod] [number]. [law]") + number++ + sleep(10) + + +/mob/living/silicon/proc/checklaws() //Gives you a link-driven interface for deciding what laws the statelaws() proc will share with the crew. --NeoFite + + var/list = "Which laws do you want to include when stating them for the crew?

                " + + if (laws.devillaws && laws.devillaws.len) + for(var/index = 1, index <= laws.devillaws.len, index++) + if (!devillawcheck[index]) + devillawcheck[index] = "No" + list += {"[devillawcheck[index]] 666: [laws.devillaws[index]]
                "} + + if (laws.zeroth) + if (!lawcheck[1]) + lawcheck[1] = "No" //Given Law 0's usual nature, it defaults to NOT getting reported. --NeoFite + list += {"[lawcheck[1]] 0: [laws.zeroth]
                "} + + for (var/index = 1, index <= laws.hacked.len, index++) + var/law = laws.hacked[index] + if (length(law) > 0) + if (!hackedcheck[index]) + hackedcheck[index] = "No" + list += {"[hackedcheck[index]] [ionnum()]: [law]
                "} + hackedcheck.len += 1 + + for (var/index = 1, index <= laws.ion.len, index++) + var/law = laws.ion[index] + + if (length(law) > 0) + if (!ioncheck[index]) + ioncheck[index] = "Yes" + list += {"[ioncheck[index]] [ionnum()]: [law]
                "} + ioncheck.len += 1 + + var/number = 1 + for (var/index = 1, index <= laws.inherent.len, index++) + var/law = laws.inherent[index] + + if (length(law) > 0) + lawcheck.len += 1 + + if (!lawcheck[number+1]) + lawcheck[number+1] = "Yes" + list += {"[lawcheck[number+1]] [number]: [law]
                "} + number++ + + for (var/index = 1, index <= laws.supplied.len, index++) + var/law = laws.supplied[index] + if (length(law) > 0) + lawcheck.len += 1 + if (!lawcheck[number+1]) + lawcheck[number+1] = "Yes" + list += {"[lawcheck[number+1]] [number]: [law]
                "} + number++ + list += {"

                State Laws"} + + usr << browse(list, "window=laws") + +/mob/living/silicon/proc/set_autosay() //For allowing the AI and borgs to set the radio behavior of auto announcements (state laws, arrivals). + if(!radio) + to_chat(src, "Radio not detected.") + return + + //Ask the user to pick a channel from what it has available. + var/Autochan = input("Select a channel:") as null|anything in list("Default","None") + radio.channels + + if(!Autochan) + return + if(Autochan == "Default") //Autospeak on whatever frequency to which the radio is set, usually Common. + radiomod = ";" + Autochan += " ([radio.frequency])" + else if(Autochan == "None") //Prevents use of the radio for automatic annoucements. + radiomod = "" + else //For department channels, if any, given by the internal radio. + for(var/key in GLOB.department_radio_keys) + if(GLOB.department_radio_keys[key] == Autochan) + radiomod = ":" + key + break + + to_chat(src, "Automatic announcements [Autochan == "None" ? "will not use the radio." : "set to [Autochan]."]") + +/mob/living/silicon/put_in_hand_check() // This check is for borgs being able to receive items, not put them in others' hands. + return 0 + +// The src mob is trying to place an item on someone +// But the src mob is a silicon!! Disable. +/mob/living/silicon/stripPanelEquip(obj/item/what, mob/who, slot) + return 0 + + +/mob/living/silicon/assess_threat(judgement_criteria, lasercolor = "", datum/callback/weaponcheck=null) //Secbots won't hunt silicon units + return -10 + +/mob/living/silicon/proc/remove_sensors() + var/datum/atom_hud/secsensor = GLOB.huds[sec_hud] + var/datum/atom_hud/medsensor = GLOB.huds[med_hud] + var/datum/atom_hud/diagsensor = GLOB.huds[d_hud] + secsensor.remove_hud_from(src) + medsensor.remove_hud_from(src) + diagsensor.remove_hud_from(src) + +/mob/living/silicon/proc/add_sensors() + var/datum/atom_hud/secsensor = GLOB.huds[sec_hud] + var/datum/atom_hud/medsensor = GLOB.huds[med_hud] + var/datum/atom_hud/diagsensor = GLOB.huds[d_hud] + secsensor.add_hud_to(src) + medsensor.add_hud_to(src) + diagsensor.add_hud_to(src) + +/mob/living/silicon/proc/toggle_sensors() + if(incapacitated()) + return + sensors_on = !sensors_on + if (!sensors_on) + to_chat(src, "Sensor overlay deactivated.") + remove_sensors() + return + add_sensors() + to_chat(src, "Sensor overlay activated.") + +/mob/living/silicon/proc/GetPhoto(mob/user) + if (aicamera) + return aicamera.selectpicture(user) + +/mob/living/silicon/proc/ai_roster() + var/dat = "Crew RosterCrew Roster:

                " + + dat += GLOB.data_core.get_manifest() + dat += "" + + src << browse(dat, "window=airoster") + onclose(src, "airoster") + +/mob/living/silicon/update_transform() + var/matrix/ntransform = matrix(transform) //aka transform.Copy() + var/changed = 0 + if(resize != RESIZE_DEFAULT_SIZE) + changed++ + ntransform.Scale(resize) + resize = RESIZE_DEFAULT_SIZE + + if(changed) + animate(src, transform = ntransform, time = 2,easing = EASE_IN|EASE_OUT) + return ..() + +/mob/living/silicon/is_literate() + return 1 + +/mob/living/silicon/get_inactive_held_item() + return FALSE + +/mob/living/silicon/handle_high_gravity(gravity) + return diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 3397d9f4b6..cd449dacd9 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -1,1030 +1,1030 @@ -// AI (i.e. game AI, not the AI player) controlled bots -/mob/living/simple_animal/bot - icon = 'icons/mob/aibots.dmi' - layer = MOB_LAYER - gender = NEUTER - mob_biotypes = list(MOB_ROBOTIC) - light_range = 3 - light_power = 0.9 - light_color = "#CDDDFF" - stop_automated_movement = 1 - wander = 0 - healable = 0 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) - 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) - maxbodytemp = INFINITY - minbodytemp = 0 - blood_volume = 0 - has_unlimited_silicon_privilege = 1 - sentience_type = SENTIENCE_ARTIFICIAL - status_flags = NONE //no default canpush - verb_say = "states" - verb_ask = "queries" - verb_exclaim = "declares" - verb_yell = "alarms" - initial_language_holder = /datum/language_holder/synthetic - bubble_icon = "machine" - speech_span = SPAN_ROBOT - - faction = list("neutral", "silicon" , "turret") - - var/obj/machinery/bot_core/bot_core = null - var/bot_core_type = /obj/machinery/bot_core - var/list/users = list() //for dialog updates - var/window_id = "bot_control" - var/window_name = "Protobot 1.0" //Popup title - var/window_width = 0 //0 for default size - var/window_height = 0 - var/obj/item/paicard/paicard // Inserted pai card. - var/allow_pai = 1 // Are we even allowed to insert a pai card. - var/bot_name - var/oil_spill_type = /obj/effect/decal/cleanable/oil - - var/list/player_access = list() //Additonal access the bots gets when player controlled - var/emagged = FALSE - var/list/prev_access = list() - var/on = TRUE - var/open = FALSE//Maint panel - var/locked = TRUE - var/hacked = FALSE //Used to differentiate between being hacked by silicons and emagged by humans. - var/text_hack = "" //Custom text returned to a silicon upon hacking a bot. - var/text_dehack = "" //Text shown when resetting a bots hacked status to normal. - var/text_dehack_fail = "" //Shown when a silicon tries to reset a bot emagged with the emag item, which cannot be reset. - var/declare_message = "" //What the bot will display to the HUD user. - var/frustration = 0 //Used by some bots for tracking failures to reach their target. - var/base_speed = 2 //The speed at which the bot moves, or the number of times it moves per process() tick. - var/turf/ai_waypoint //The end point of a bot's path, or the target location. - var/list/path = list() //List of turfs through which a bot 'steps' to reach the waypoint, associated with the path image, if there is one. - var/pathset = 0 - var/list/ignore_list = list() //List of unreachable targets for an ignore-list enabled bot to ignore. - var/mode = BOT_IDLE //Standardizes the vars that indicate the bot is busy with its function. - var/tries = 0 //Number of times the bot tried and failed to move. - var/remote_disabled = 0 //If enabled, the AI cannot *Remotely* control a bot. It can still control it through cameras. - var/mob/living/silicon/ai/calling_ai //Links a bot to the AI calling it. - var/obj/item/radio/Radio //The bot's radio, for speaking to people. - var/radio_key = null //which channels can the bot listen to - var/radio_channel = RADIO_CHANNEL_COMMON //The bot's default radio channel - var/auto_patrol = 0// set to make bot automatically patrol - var/turf/patrol_target // this is turf to navigate to (location of beacon) - var/turf/summon_target // The turf of a user summoning a bot. - var/new_destination // pending new destination (waiting for beacon response) - var/destination // destination description tag - var/next_destination // the next destination in the patrol route - var/shuffle = FALSE // If we should shuffle our adjacency checking - - var/blockcount = 0 //number of times retried a blocked path - var/awaiting_beacon = 0 // count of pticks awaiting a beacon response - - var/nearest_beacon // the nearest beacon's tag - var/turf/nearest_beacon_loc // the nearest beacon's location - - var/beacon_freq = FREQ_NAV_BEACON - var/model = "" //The type of bot it is. - var/bot_type = 0 //The type of bot it is, for radio control. - var/data_hud_type = DATA_HUD_DIAGNOSTIC_BASIC //The type of data HUD the bot uses. Diagnostic by default. - //This holds text for what the bot is mode doing, reported on the remote bot control interface. - var/list/mode_name = list("In Pursuit","Preparing to Arrest", "Arresting", \ - "Beginning Patrol", "Patrolling", "Summoned by PDA", \ - "Cleaning", "Repairing", "Proceeding to work site", "Healing", \ - "Proceeding to AI waypoint", "Navigating to Delivery Location", "Navigating to Home", \ - "Waiting for clear path", "Calculating navigation path", "Pinging beacon network", "Unable to reach destination") - var/datum/atom_hud/data/bot_path/path_hud = new /datum/atom_hud/data/bot_path() - var/path_image_icon = 'icons/mob/aibots.dmi' - var/path_image_icon_state = "path_indicator" - var/path_image_color = "#FFFFFF" - var/reset_access_timer_id - var/ignorelistcleanuptimer = 1 // This ticks up every automated action, at 300 we clean the ignore list - var/robot_arm = /obj/item/bodypart/r_arm/robot - - hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_PATH_HUD = HUD_LIST_LIST) //Diagnostic HUD views - -/mob/living/simple_animal/bot/proc/get_mode() - if(client) //Player bots do not have modes, thus the override. Also an easy way for PDA users/AI to know when a bot is a player. - if(paicard) - return "pAI Controlled" - else - return "Autonomous" - else if(!on) - return "Inactive" - else if(!mode) - return "Idle" - else - return "[mode_name[mode]]" - -/mob/living/simple_animal/bot/proc/turn_on() - if(stat) - return FALSE - on = TRUE - canmove = TRUE - set_light(initial(light_range)) - update_icon() - diag_hud_set_botstat() - return TRUE - -/mob/living/simple_animal/bot/proc/turn_off() - on = FALSE - canmove = FALSE - set_light(0) - bot_reset() //Resets an AI's call, should it exist. - update_icon() - -/mob/living/simple_animal/bot/Initialize() - . = ..() - GLOB.bots_list += src - access_card = new /obj/item/card/id(src) -//This access is so bots can be immediately set to patrol and leave Robotics, instead of having to be let out first. - access_card.access += ACCESS_ROBOTICS - set_custom_texts() - Radio = new/obj/item/radio(src) - if(radio_key) - Radio.keyslot = new radio_key - Radio.subspace_transmission = TRUE - Radio.canhear_range = 0 // anything greater will have the bot broadcast the channel as if it were saying it out loud. - Radio.recalculateChannels() - - bot_core = new bot_core_type(src) - - //Adds bot to the diagnostic HUD system - prepare_huds() - for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) - diag_hud_set_bothealth() - diag_hud_set_botstat() - diag_hud_set_botmode() - - //If a bot has its own HUD (for player bots), provide it. - if(data_hud_type) - var/datum/atom_hud/datahud = GLOB.huds[data_hud_type] - datahud.add_hud_to(src) - if(path_hud) - path_hud.add_to_hud(src) - path_hud.add_hud_to(src) - -/mob/living/simple_animal/bot/update_canmove() - . = ..() - if(!on) - . = 0 - canmove = . - -/mob/living/simple_animal/bot/Destroy() - if(path_hud) - QDEL_NULL(path_hud) - path_hud = null - GLOB.bots_list -= src - if(paicard) - ejectpai() - qdel(Radio) - qdel(access_card) - qdel(bot_core) - return ..() - -/mob/living/simple_animal/bot/bee_friendly() - return TRUE - -/mob/living/simple_animal/bot/death(gibbed) - explode() - ..() - -/mob/living/simple_animal/bot/proc/explode() - qdel(src) - -/mob/living/simple_animal/bot/emag_act(mob/user) - . = ..() - if(locked) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again. - locked = FALSE - emagged = 1 - to_chat(user, "You bypass [src]'s controls.") - return TRUE - if(!open) - to_chat(user, "You need to open maintenance panel first!") - return - emagged = 2 - remote_disabled = 1 //Manually emagging the bot locks out the AI built in panel. - locked = TRUE //Access denied forever! - bot_reset() - turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP. - to_chat(src, "(#$*#$^^( OVERRIDE DETECTED") - log_combat(user, src, "emagged") - return TRUE - -/mob/living/simple_animal/bot/examine(mob/user) - . = ..() - if(health < maxHealth) - if(health > maxHealth/3) - . += "[src]'s parts look loose." - else - . += "[src]'s parts look very loose!" - else - . += "[src] is in pristine condition." - -/mob/living/simple_animal/bot/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(. && prob(10)) - new oil_spill_type(loc) - -/mob/living/simple_animal/bot/updatehealth() - ..() - diag_hud_set_bothealth() - -/mob/living/simple_animal/bot/med_hud_set_health() - return //we use a different hud - -/mob/living/simple_animal/bot/med_hud_set_status() - return //we use a different hud - -/mob/living/simple_animal/bot/handle_automated_action() //Master process which handles code common across most bots. - diag_hud_set_botmode() - - if (ignorelistcleanuptimer % 300 == 0) // Every 300 actions, clean up the ignore list from old junk - for(var/ref in ignore_list) - var/atom/referredatom = locate(ref) - if (!referredatom || !istype(referredatom) || QDELETED(referredatom)) - ignore_list -= ref - ignorelistcleanuptimer = 1 - else - ignorelistcleanuptimer++ - - if(!on || client) - return - - switch(mode) //High-priority overrides are processed first. Bots can do nothing else while under direct command. - if(BOT_RESPONDING) //Called by the AI. - call_mode() - return - if(BOT_SUMMON) //Called by PDA - bot_summon() - return - return TRUE //Successful completion. Used to prevent child process() continuing if this one is ended early. - - -/mob/living/simple_animal/bot/attack_hand(mob/living/carbon/human/H) - if(H.a_intent == INTENT_HELP) - interact(H) - else - return ..() - -/mob/living/simple_animal/bot/attack_ai(mob/user) - if(!topic_denied(user)) - interact(user) - else - to_chat(user, "[src]'s interface is not responding!") - -/mob/living/simple_animal/bot/interact(mob/user) - show_controls(user) - -/mob/living/simple_animal/bot/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/screwdriver)) - if(!locked) - open = !open - to_chat(user, "The maintenance panel is now [open ? "opened" : "closed"].") - else - to_chat(user, "The maintenance panel is locked.") - else if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) - if(bot_core.allowed(user) && !open && !emagged) - locked = !locked - to_chat(user, "Controls are now [locked ? "locked." : "unlocked."]") - else - if(emagged) - to_chat(user, "ERROR") - if(open) - to_chat(user, "Please close the access panel before locking it.") - else - to_chat(user, "Access denied.") - else if(istype(W, /obj/item/paicard)) - insertpai(user, W) - else if(W.tool_behaviour == TOOL_HEMOSTAT && paicard) - if(open) - to_chat(user, "Close the access panel before manipulating the personality slot!") - else - to_chat(user, "You attempt to pull [paicard] free...") - if(do_after(user, 30, target = src)) - if (paicard) - user.visible_message("[user] uses [W] to pull [paicard] out of [bot_name]!","You pull [paicard] out of [bot_name] with [W].") - ejectpai(user) - else - user.changeNext_move(CLICK_CD_MELEE) - if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) - if(health >= maxHealth) - to_chat(user, "[src] does not need a repair!") - return - if(!open) - to_chat(user, "Unable to repair with the maintenance panel closed!") - return - - if(W.use_tool(src, user, 0, volume=40)) - adjustHealth(-10) - user.visible_message("[user] repairs [src]!","You repair [src].") - else - if(W.force) //if force is non-zero - do_sparks(5, TRUE, src) - ..() - -/mob/living/simple_animal/bot/bullet_act(obj/item/projectile/Proj) - if(Proj && (Proj.damage_type == BRUTE || Proj.damage_type == BURN)) - if(prob(75) && Proj.damage > 0) - do_sparks(5, TRUE, src) - return ..() - -/mob/living/simple_animal/bot/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - var/was_on = on - stat |= EMPED - new /obj/effect/temp_visual/emp(loc) - if(paicard) - paicard.emp_act(severity) - src.visible_message("[paicard] is flies out of [bot_name]!","You are forcefully ejected from [bot_name]!") - ejectpai(0) - if(on) - turn_off() - spawn(severity*300) - stat &= ~EMPED - if(was_on) - turn_on() - -/mob/living/simple_animal/bot/proc/set_custom_texts() //Superclass for setting hack texts. Appears only if a set is not given to a bot locally. - text_hack = "You hack [name]." - text_dehack = "You reset [name]." - text_dehack_fail = "You fail to reset [name]." - -/mob/living/simple_animal/bot/proc/speak(message,channel) //Pass a message to have the bot say() it. Pass a frequency to say it on the radio. - if((!on) || (!message)) - return - if(channel && Radio.channels[channel])// Use radio if we have channel key - Radio.talk_into(src, message, channel) - else - say(message) - -/mob/living/simple_animal/bot/radio(message, message_mode, list/spans, language) - . = ..() - if(. != 0) - return - - switch(message_mode) - if(MODE_HEADSET) - Radio.talk_into(src, message, , spans, language) - return REDUCE_RANGE - - if(MODE_DEPARTMENT) - Radio.talk_into(src, message, message_mode, spans, language) - return REDUCE_RANGE - - if(message_mode in GLOB.radiochannels) - Radio.talk_into(src, message, message_mode, spans, language) - return REDUCE_RANGE - -/mob/living/simple_animal/bot/proc/drop_part(obj/item/drop_item, dropzone) - var/dropped_item = new drop_item(dropzone) - drop_item = null - - if(istype(dropped_item, /obj/item/stock_parts/cell)) - var/obj/item/stock_parts/cell/dropped_cell = dropped_item - dropped_cell.charge = 0 - dropped_cell.update_icon() - - else if(istype(dropped_item, /obj/item/storage)) - var/obj/item/storage/S = dropped_item - S.contents = list() - - else if(istype(dropped_item, /obj/item/gun/energy)) - var/obj/item/gun/energy/dropped_gun = dropped_item - dropped_gun.cell.charge = 0 - dropped_gun.update_icon() - -//Generalized behavior code, override where needed! - -/* -scan() will search for a given type (such as turfs, human mobs, or objects) in the bot's view range, and return a single result. -Arguments: The object type to be searched (such as "/mob/living/carbon/human"), the old scan result to be ignored, if one exists, -and the view range, which defaults to 7 (full screen) if an override is not passed. -If the bot maintains an ignore list, it is also checked here. - -Example usage: patient = scan(/mob/living/carbon/human, oldpatient, 1) -The proc would return a human next to the bot to be set to the patient var. -Pass the desired type path itself, declaring a temporary var beforehand is not required. -*/ -/mob/living/simple_animal/bot/proc/scan(scan_type, old_target, scan_range = DEFAULT_SCAN_RANGE) - var/turf/T = get_turf(src) - if(!T) - return - var/list/adjacent = T.GetAtmosAdjacentTurfs(1) - if(shuffle) //If we were on the same tile as another bot, let's randomize our choices so we dont both go the same way - adjacent = shuffle(adjacent) - shuffle = FALSE - for(var/scan in adjacent)//Let's see if there's something right next to us first! - if(check_bot(scan)) //Is there another bot there? Then let's just skip it - continue - if(isturf(scan_type)) //If we're lookeing for a turf we can just run the checks directly! - var/final_result = checkscan(scan,scan_type,old_target) - if(final_result) - return final_result - else - var/turf/turfy = scan - for(var/deepscan in turfy.contents)//Check the contents since adjacent is turfs - var/final_result = checkscan(deepscan,scan_type,old_target) - if(final_result) - return final_result - for (var/scan in shuffle(view(scan_range, src))-adjacent) //Search for something in range! - var/final_result = checkscan(scan,scan_type,old_target) - if(final_result) - return final_result - -/mob/living/simple_animal/bot/proc/checkscan(scan, scan_type, old_target) - if(!istype(scan, scan_type)) //Check that the thing we found is the type we want! - return FALSE //If not, keep searching! - if( (REF(scan) in ignore_list) || (scan == old_target) ) //Filter for blacklisted elements, usually unreachable or previously processed oness - return FALSE - - var/scan_result = process_scan(scan) //Some bots may require additional processing when a result is selected. - if(scan_result) - return scan_result - else - return FALSE //The current element failed assessment, move on to the next. - return - -/mob/living/simple_animal/bot/proc/check_bot(targ) - var/turf/T = get_turf(targ) - if(T) - for(var/C in T.contents) - if(istype(C,type) && (C != src)) //Is there another bot there already? If so, let's skip it so we dont all atack on top of eachother. - return TRUE //Let's abort if we find a bot so we dont have to keep rechecking - -//When the scan finds a target, run bot specific processing to select it for the next step. Empty by default. -/mob/living/simple_animal/bot/proc/process_scan(scan_target) - return scan_target - - -/mob/living/simple_animal/bot/proc/add_to_ignore(subject) - if(ignore_list.len < 50) //This will help keep track of them, so the bot is always trying to reach a blocked spot. - ignore_list += REF(subject) - else //If the list is full, insert newest, delete oldest. - ignore_list.Cut(1,2) - ignore_list += REF(subject) - -/* -Movement proc for stepping a bot through a path generated through A-star. -Pass a positive integer as an argument to override a bot's default speed. -*/ -/mob/living/simple_animal/bot/proc/bot_move(dest, move_speed) - if(!dest || !path || path.len == 0) //A-star failed or a path/destination was not set. - set_path(null) - return FALSE - dest = get_turf(dest) //We must always compare turfs, so get the turf of the dest var if dest was originally something else. - var/turf/last_node = get_turf(path[path.len]) //This is the turf at the end of the path, it should be equal to dest. - if(get_turf(src) == dest) //We have arrived, no need to move again. - return TRUE - else if(dest != last_node) //The path should lead us to our given destination. If this is not true, we must stop. - set_path(null) - return FALSE - var/step_count = move_speed ? move_speed : base_speed //If a value is passed into move_speed, use that instead of the default speed var. - - if(step_count >= 1 && tries < BOT_STEP_MAX_RETRIES) - for(var/step_number = 0, step_number < step_count,step_number++) - spawn(BOT_STEP_DELAY*step_number) - bot_step(dest) - else - return FALSE - return TRUE - - -/mob/living/simple_animal/bot/proc/bot_step(dest) //Step,increase tries if failed - if(!path) - return FALSE - if(path.len > 1) - step_towards(src, path[1]) - if(get_turf(src) == path[1]) //Successful move - increment_path() - tries = 0 - else - tries++ - return FALSE - else if(path.len == 1) - step_to(src, dest) - set_path(null) - return TRUE - - -/mob/living/simple_animal/bot/proc/check_bot_access() - if(mode != BOT_SUMMON && mode != BOT_RESPONDING) - access_card.access = prev_access - -/mob/living/simple_animal/bot/proc/call_bot(caller, turf/waypoint, message=TRUE) - bot_reset() //Reset a bot before setting it to call mode. - - //For giving the bot temporary all-access. - var/obj/item/card/id/all_access = new /obj/item/card/id - var/datum/job/captain/All = new/datum/job/captain - all_access.access = All.get_access() - - set_path(get_path_to(src, waypoint, /turf/proc/Distance_cardinal, 0, 200, id=all_access)) - calling_ai = caller //Link the AI to the bot! - ai_waypoint = waypoint - - if(path && path.len) //Ensures that a valid path is calculated! - var/end_area = get_area_name(waypoint) - if(!on) - turn_on() //Saves the AI the hassle of having to activate a bot manually. - access_card = all_access //Give the bot all-access while under the AI's command. - if(client) - reset_access_timer_id = addtimer(CALLBACK (src, .proc/bot_reset), 600, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) //if the bot is player controlled, they get the extra access for a limited time - to_chat(src, "Priority waypoint set by [icon2html(calling_ai, src)] [caller]. Proceed to [end_area].
                [path.len-1] meters to destination. You have been granted additional door access for 60 seconds.
                ") - if(message) - to_chat(calling_ai, "[icon2html(src, calling_ai)] [name] called to [end_area]. [path.len-1] meters to destination.") - pathset = 1 - mode = BOT_RESPONDING - tries = 0 - else - if(message) - to_chat(calling_ai, "Failed to calculate a valid route. Ensure destination is clear of obstructions and within range.") - calling_ai = null - set_path(null) - -/mob/living/simple_animal/bot/proc/call_mode() //Handles preparing a bot for a call, as well as calling the move proc. -//Handles the bot's movement during a call. - var/success = bot_move(ai_waypoint, 3) - if(!success) - if(calling_ai) - to_chat(calling_ai, "[icon2html(src, calling_ai)] [get_turf(src) == ai_waypoint ? "[src] successfully arrived to waypoint." : "[src] failed to reach waypoint."]") - calling_ai = null - bot_reset() - -/mob/living/simple_animal/bot/proc/bot_reset() - if(calling_ai) //Simple notification to the AI if it called a bot. It will not know the cause or identity of the bot. - to_chat(calling_ai, "Call command to a bot has been reset.") - calling_ai = null - if(reset_access_timer_id) - deltimer(reset_access_timer_id) - reset_access_timer_id = null - set_path(null) - summon_target = null - pathset = 0 - access_card.access = prev_access - tries = 0 - mode = BOT_IDLE - diag_hud_set_botstat() - diag_hud_set_botmode() - - - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -//Patrol and summon code! -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -/mob/living/simple_animal/bot/proc/bot_patrol() - patrol_step() - spawn(5) - if(mode == BOT_PATROL) - patrol_step() - return - -/mob/living/simple_animal/bot/proc/start_patrol() - - if(tries >= BOT_STEP_MAX_RETRIES) //Bot is trapped, so stop trying to patrol. - auto_patrol = 0 - tries = 0 - speak("Unable to start patrol.") - - return - - if(!auto_patrol) //A bot not set to patrol should not be patrolling. - mode = BOT_IDLE - return - - if(patrol_target) // has patrol target - spawn(0) - calc_path() // Find a route to it - if(path.len == 0) - patrol_target = null - return - mode = BOT_PATROL - else // no patrol target, so need a new one - speak("Engaging patrol mode.") - find_patrol_target() - tries++ - return - -// perform a single patrol step - -/mob/living/simple_animal/bot/proc/patrol_step() - - if(client) // In use by player, don't actually move. - return - - if(loc == patrol_target) // reached target - //Find the next beacon matching the target. - if(!get_next_patrol_target()) - find_patrol_target() //If it fails, look for the nearest one instead. - return - - else if(path.len > 0 && patrol_target) // valid path - if(path[1] == loc) - increment_path() - return - - - var/moved = bot_move(patrol_target)//step_towards(src, next) // attempt to move - if(!moved) //Couldn't proceed the next step of the path BOT_STEP_MAX_RETRIES times - spawn(2) - calc_path() - if(path.len == 0) - find_patrol_target() - tries = 0 - - else // no path, so calculate new one - mode = BOT_START_PATROL - -// finds the nearest beacon to self -/mob/living/simple_animal/bot/proc/find_patrol_target() - nearest_beacon = null - new_destination = null - find_nearest_beacon() - if(nearest_beacon) - patrol_target = nearest_beacon_loc - destination = next_destination - else - auto_patrol = 0 - mode = BOT_IDLE - speak("Disengaging patrol mode.") - -/mob/living/simple_animal/bot/proc/get_next_patrol_target() - // search the beacon list for the next target in the list. - for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[z]"]) - if(NB.location == next_destination) //Does the Beacon location text match the destination? - destination = new_destination //We now know the name of where we want to go. - patrol_target = NB.loc //Get its location and set it as the target. - next_destination = NB.codes["next_patrol"] //Also get the name of the next beacon in line. - return TRUE - -/mob/living/simple_animal/bot/proc/find_nearest_beacon() - for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[z]"]) - var/dist = get_dist(src, NB) - if(nearest_beacon) //Loop though the beacon net to find the true closest beacon. - //Ignore the beacon if were are located on it. - if(dist>1 && dist 1) //Begin the search, save this one for comparison on the next loop. - nearest_beacon = NB.location - nearest_beacon_loc = NB.loc - patrol_target = nearest_beacon_loc - destination = nearest_beacon - -//PDA control. Some bots, especially MULEs, may have more parameters. -/mob/living/simple_animal/bot/proc/bot_control(command, mob/user, list/user_access = list()) - if(!on || emagged == 2 || remote_disabled) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands. - return TRUE //ACCESS DENIED - if(client) - bot_control_message(command, user) - // process control input - switch(command) - if("patroloff") - bot_reset() //HOLD IT!! - auto_patrol = 0 - - if("patrolon") - auto_patrol = 1 - - if("summon") - bot_reset() - summon_target = get_turf(user) - if(user_access.len != 0) - access_card.access = user_access + prev_access //Adds the user's access, if any. - mode = BOT_SUMMON - speak("Responding.", radio_channel) - calc_summon_path() - - if("ejectpai") - ejectpairemote(user) - return - -// -/mob/living/simple_animal/bot/proc/bot_control_message(command, user) - switch(command) - if("patroloff") - to_chat(src, "STOP PATROL") - if("patrolon") - to_chat(src, "START PATROL") - if("summon") - to_chat(src, "PRIORITY ALERT:[user] in [get_area_name(user)]!") - if("stop") - to_chat(src, "STOP!") - - if("go") - to_chat(src, "GO!") - - if("home") - to_chat(src, "RETURN HOME!") - if("ejectpai") - return - else - to_chat(src, "Unidentified control sequence received:[command]") - -/mob/living/simple_animal/bot/proc/bot_summon() // summoned to PDA - summon_step() - -// calculates a path to the current destination -// given an optional turf to avoid -/mob/living/simple_animal/bot/proc/calc_path(turf/avoid) - check_bot_access() - set_path(get_path_to(src, patrol_target, /turf/proc/Distance_cardinal, 0, 120, id=access_card, exclude=avoid)) - -/mob/living/simple_animal/bot/proc/calc_summon_path(turf/avoid) - check_bot_access() - spawn() - set_path(get_path_to(src, summon_target, /turf/proc/Distance_cardinal, 0, 150, id=access_card, exclude=avoid)) - if(!path.len) //Cannot reach target. Give up and announce the issue. - speak("Summon command failed, destination unreachable.",radio_channel) - bot_reset() - -/mob/living/simple_animal/bot/proc/summon_step() - - if(client) // In use by player, don't actually move. - return - - if(loc == summon_target) // Arrived to summon location. - bot_reset() - return - - else if(path.len > 0 && summon_target) //Proper path acquired! - if(path[1] == loc) - increment_path() - return - - var/moved = bot_move(summon_target, 3) // Move attempt - if(!moved) - spawn(2) - calc_summon_path() - tries = 0 - - else // no path, so calculate new one - calc_summon_path() - -/mob/living/simple_animal/bot/Bump(M as mob|obj) //Leave no door unopened! - . = ..() - if((istype(M, /obj/machinery/door/airlock) || istype(M, /obj/machinery/door/window)) && (!isnull(access_card))) - var/obj/machinery/door/D = M - if(D.check_access(access_card)) - D.open() - frustration = 0 - -/mob/living/simple_animal/bot/proc/show_controls(mob/M) - users |= M - var/dat = "" - dat = get_controls(M) - var/datum/browser/popup = new(M,window_id,window_name,350,600) - popup.set_content(dat) - popup.open(use_onclose = 0) - onclose(M,window_id,ref=src) - return - -/mob/living/simple_animal/bot/proc/update_controls() - for(var/mob/M in users) - show_controls(M) - -/mob/living/simple_animal/bot/proc/get_controls(mob/M) - return "PROTOBOT - NOT FOR USE" - -/mob/living/simple_animal/bot/Topic(href, href_list) - //No ..() to prevent strip panel showing up - Todo: make that saner - if(href_list["close"])// HUE HUE - if(usr in users) - users.Remove(usr) - return TRUE - - if(topic_denied(usr)) - to_chat(usr, "[src]'s interface is not responding!") - return TRUE - add_fingerprint(usr) - - if((href_list["power"]) && (bot_core.allowed(usr) || !locked)) - if(on) - turn_off() - else - turn_on() - - switch(href_list["operation"]) - if("patrol") - if(!issilicon(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) - return TRUE - auto_patrol = !auto_patrol - bot_reset() - if("remote") - remote_disabled = !remote_disabled - if("hack") - if(!issilicon(usr) && !IsAdminGhost(usr)) - var/msg = "[key_name(usr)] attempted to hack a bot with a href that shouldn't be available!" - message_admins(msg) - log_admin(msg) - return TRUE - if(emagged != 2) - emagged = 2 - hacked = TRUE - locked = TRUE - to_chat(usr, "[text_hack]") - bot_reset() - else if(!hacked) - to_chat(usr, "[text_dehack_fail]") - else - emagged = FALSE - hacked = FALSE - to_chat(usr, "[text_dehack]") - bot_reset() - if("ejectpai") - if(paicard && (!locked || issilicon(usr) || IsAdminGhost(usr))) - to_chat(usr, "You eject [paicard] from [bot_name]") - ejectpai(usr) - update_controls() - -/mob/living/simple_animal/bot/update_icon_state() - icon_state = "[initial(icon_state)][on]" - -// Machinery to simplify topic and access calls -/obj/machinery/bot_core - use_power = NO_POWER_USE - anchored = FALSE - var/mob/living/simple_animal/bot/owner = null - -/obj/machinery/bot_core/Initialize() - . = ..() - owner = loc - if(!istype(owner)) - return INITIALIZE_HINT_QDEL - -/mob/living/simple_animal/bot/proc/topic_denied(mob/user) //Access check proc for bot topics! Remember to place in a bot's individual Topic if desired. - if(!user.canUseTopic(src)) - return TRUE - // 0 for access, 1 for denied. - if(emagged == 2) //An emagged bot cannot be controlled by humans, silicons can if one hacked it. - if(!hacked) //Manually emagged by a human - access denied to all. - return TRUE - else if(!issilicon(user) && !IsAdminGhost(user)) //Bot is hacked, so only silicons and admins are allowed access. - return TRUE - return FALSE - -/mob/living/simple_animal/bot/proc/hack(mob/user) - var/hack - if(issilicon(user) || IsAdminGhost(user)) //Allows silicons or admins to toggle the emag status of a bot. - hack += "[emagged == 2 ? "Software compromised! Unit may exhibit dangerous or erratic behavior." : "Unit operating normally. Release safety lock?"]
                " - hack += "Harm Prevention Safety System: [emagged ? "DANGER" : "Engaged"]
                " - else if(!locked) //Humans with access can use this option to hide a bot from the AI's remote control panel and PDA control. - hack += "Remote network control radio: [remote_disabled ? "Disconnected" : "Connected"]
                " - return hack - -/mob/living/simple_animal/bot/proc/showpai(mob/user) - var/eject = "" - if((!locked || issilicon(usr) || IsAdminGhost(usr))) - if(paicard || allow_pai) - eject += "Personality card status: " - if(paicard) - if(client) - eject += "Active" - else - eject += "Inactive" - else if(!allow_pai || key) - eject += "Unavailable" - else - eject += "Not inserted" - eject += "
                " - eject += "
                " - return eject - -/mob/living/simple_animal/bot/proc/insertpai(mob/user, obj/item/paicard/card) - if(paicard) - to_chat(user, "A [paicard] is already inserted!") - else if(allow_pai && !key) - if(!locked && !open) - if(card.pai && card.pai.mind) - if(!user.transferItemToLoc(card, src)) - return - paicard = card - user.visible_message("[user] inserts [card] into [src]!","You insert [card] into [src].") - paicard.pai.mind.transfer_to(src) - to_chat(src, "You sense your form change as you are uploaded into [src].") - bot_name = name - name = paicard.pai.name - faction = user.faction.Copy() - language_holder = paicard.pai.language_holder.copy(src) - log_combat(user, paicard.pai, "uploaded to [bot_name],") - return TRUE - else - to_chat(user, "[card] is inactive.") - else - to_chat(user, "The personality slot is locked.") - else - to_chat(user, "[src] is not compatible with [card]") - -/mob/living/simple_animal/bot/proc/ejectpai(mob/user = null, announce = 1) - if(paicard) - if(mind && paicard.pai) - mind.transfer_to(paicard.pai) - else if(paicard.pai) - transfer_ckey(paicard.pai) - else - ghostize(0) // The pAI card that just got ejected was dead. - key = null - paicard.forceMove(loc) - if(user) - log_combat(user, paicard.pai, "ejected from [src.bot_name],") - else - log_combat(src, paicard.pai, "ejected") - if(announce) - to_chat(paicard.pai, "You feel your control fade as [paicard] ejects from [bot_name].") - paicard = null - name = bot_name - faction = initial(faction) - -/mob/living/simple_animal/bot/proc/ejectpairemote(mob/user) - if(bot_core.allowed(user) && paicard) - speak("Ejecting personality chip.", radio_channel) - ejectpai(user) - -/mob/living/simple_animal/bot/Login() - . = ..() - access_card.access += player_access - diag_hud_set_botmode() - -/mob/living/simple_animal/bot/Logout() - . = ..() - bot_reset() - -/mob/living/simple_animal/bot/revive(full_heal = 0, admin_revive = 0) - if(..()) - update_icon() - . = 1 - -/mob/living/simple_animal/bot/ghost() - if(stat != DEAD) // Only ghost if we're doing this while alive, the pAI probably isn't dead yet. - ..() - if(paicard && (!client || stat == DEAD)) - ejectpai(0) - -/mob/living/simple_animal/bot/sentience_act() - faction -= "silicon" - -/mob/living/simple_animal/bot/proc/set_path(list/newpath) - path = newpath ? newpath : list() - if(!path_hud) - return - var/list/path_huds_watching_me = list(GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED]) - if(path_hud) - path_huds_watching_me += path_hud - for(var/V in path_huds_watching_me) - var/datum/atom_hud/H = V - H.remove_from_hud(src) - - var/list/path_images = hud_list[DIAG_PATH_HUD] - QDEL_LIST(path_images) - if(newpath) - for(var/i in 1 to newpath.len) - var/turf/T = newpath[i] - if(T == loc) //don't bother putting an image if it's where we already exist. - continue - var/direction = NORTH - if(i > 1) - var/turf/prevT = path[i - 1] - var/image/prevI = path[prevT] - direction = get_dir(prevT, T) - if(i > 2) - var/turf/prevprevT = path[i - 2] - var/prevDir = get_dir(prevprevT, prevT) - var/mixDir = direction|prevDir - if(mixDir in GLOB.diagonals) - prevI.dir = mixDir - if(prevDir & (NORTH|SOUTH)) - var/matrix/ntransform = matrix() - ntransform.Turn(90) - if((mixDir == NORTHWEST) || (mixDir == SOUTHEAST)) - ntransform.Scale(-1, 1) - else - ntransform.Scale(1, -1) - prevI.transform = ntransform - var/mutable_appearance/MA = new /mutable_appearance() - MA.icon = path_image_icon - MA.icon_state = path_image_icon_state - MA.layer = ABOVE_OPEN_TURF_LAYER - MA.plane = 0 - MA.appearance_flags = RESET_COLOR|RESET_TRANSFORM - MA.color = path_image_color - MA.dir = direction - var/image/I = image(loc = T) - I.appearance = MA - path[T] = I - path_images += I - - for(var/V in path_huds_watching_me) - var/datum/atom_hud/H = V - H.add_to_hud(src) - - -/mob/living/simple_animal/bot/proc/increment_path() - if(!path || !path.len) - return - var/image/I = path[path[1]] - if(I) - I.icon_state = null - path.Cut(1, 2) +// AI (i.e. game AI, not the AI player) controlled bots +/mob/living/simple_animal/bot + icon = 'icons/mob/aibots.dmi' + layer = MOB_LAYER + gender = NEUTER + mob_biotypes = list(MOB_ROBOTIC) + light_range = 3 + light_power = 0.9 + light_color = "#CDDDFF" + stop_automated_movement = 1 + wander = 0 + healable = 0 + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + 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) + maxbodytemp = INFINITY + minbodytemp = 0 + blood_volume = 0 + has_unlimited_silicon_privilege = 1 + sentience_type = SENTIENCE_ARTIFICIAL + status_flags = NONE //no default canpush + verb_say = "states" + verb_ask = "queries" + verb_exclaim = "declares" + verb_yell = "alarms" + initial_language_holder = /datum/language_holder/synthetic + bubble_icon = "machine" + speech_span = SPAN_ROBOT + + faction = list("neutral", "silicon" , "turret") + + var/obj/machinery/bot_core/bot_core = null + var/bot_core_type = /obj/machinery/bot_core + var/list/users = list() //for dialog updates + var/window_id = "bot_control" + var/window_name = "Protobot 1.0" //Popup title + var/window_width = 0 //0 for default size + var/window_height = 0 + var/obj/item/paicard/paicard // Inserted pai card. + var/allow_pai = 1 // Are we even allowed to insert a pai card. + var/bot_name + var/oil_spill_type = /obj/effect/decal/cleanable/oil + + var/list/player_access = list() //Additonal access the bots gets when player controlled + var/emagged = FALSE + var/list/prev_access = list() + var/on = TRUE + var/open = FALSE//Maint panel + var/locked = TRUE + var/hacked = FALSE //Used to differentiate between being hacked by silicons and emagged by humans. + var/text_hack = "" //Custom text returned to a silicon upon hacking a bot. + var/text_dehack = "" //Text shown when resetting a bots hacked status to normal. + var/text_dehack_fail = "" //Shown when a silicon tries to reset a bot emagged with the emag item, which cannot be reset. + var/declare_message = "" //What the bot will display to the HUD user. + var/frustration = 0 //Used by some bots for tracking failures to reach their target. + var/base_speed = 2 //The speed at which the bot moves, or the number of times it moves per process() tick. + var/turf/ai_waypoint //The end point of a bot's path, or the target location. + var/list/path = list() //List of turfs through which a bot 'steps' to reach the waypoint, associated with the path image, if there is one. + var/pathset = 0 + var/list/ignore_list = list() //List of unreachable targets for an ignore-list enabled bot to ignore. + var/mode = BOT_IDLE //Standardizes the vars that indicate the bot is busy with its function. + var/tries = 0 //Number of times the bot tried and failed to move. + var/remote_disabled = 0 //If enabled, the AI cannot *Remotely* control a bot. It can still control it through cameras. + var/mob/living/silicon/ai/calling_ai //Links a bot to the AI calling it. + var/obj/item/radio/Radio //The bot's radio, for speaking to people. + var/radio_key = null //which channels can the bot listen to + var/radio_channel = RADIO_CHANNEL_COMMON //The bot's default radio channel + var/auto_patrol = 0// set to make bot automatically patrol + var/turf/patrol_target // this is turf to navigate to (location of beacon) + var/turf/summon_target // The turf of a user summoning a bot. + var/new_destination // pending new destination (waiting for beacon response) + var/destination // destination description tag + var/next_destination // the next destination in the patrol route + var/shuffle = FALSE // If we should shuffle our adjacency checking + + var/blockcount = 0 //number of times retried a blocked path + var/awaiting_beacon = 0 // count of pticks awaiting a beacon response + + var/nearest_beacon // the nearest beacon's tag + var/turf/nearest_beacon_loc // the nearest beacon's location + + var/beacon_freq = FREQ_NAV_BEACON + var/model = "" //The type of bot it is. + var/bot_type = 0 //The type of bot it is, for radio control. + var/data_hud_type = DATA_HUD_DIAGNOSTIC_BASIC //The type of data HUD the bot uses. Diagnostic by default. + //This holds text for what the bot is mode doing, reported on the remote bot control interface. + var/list/mode_name = list("In Pursuit","Preparing to Arrest", "Arresting", \ + "Beginning Patrol", "Patrolling", "Summoned by PDA", \ + "Cleaning", "Repairing", "Proceeding to work site", "Healing", \ + "Proceeding to AI waypoint", "Navigating to Delivery Location", "Navigating to Home", \ + "Waiting for clear path", "Calculating navigation path", "Pinging beacon network", "Unable to reach destination") + var/datum/atom_hud/data/bot_path/path_hud = new /datum/atom_hud/data/bot_path() + var/path_image_icon = 'icons/mob/aibots.dmi' + var/path_image_icon_state = "path_indicator" + var/path_image_color = "#FFFFFF" + var/reset_access_timer_id + var/ignorelistcleanuptimer = 1 // This ticks up every automated action, at 300 we clean the ignore list + var/robot_arm = /obj/item/bodypart/r_arm/robot + + hud_possible = list(DIAG_STAT_HUD, DIAG_BOT_HUD, DIAG_HUD, DIAG_PATH_HUD = HUD_LIST_LIST) //Diagnostic HUD views + +/mob/living/simple_animal/bot/proc/get_mode() + if(client) //Player bots do not have modes, thus the override. Also an easy way for PDA users/AI to know when a bot is a player. + if(paicard) + return "pAI Controlled" + else + return "Autonomous" + else if(!on) + return "Inactive" + else if(!mode) + return "Idle" + else + return "[mode_name[mode]]" + +/mob/living/simple_animal/bot/proc/turn_on() + if(stat) + return FALSE + on = TRUE + canmove = TRUE + set_light(initial(light_range)) + update_icon() + diag_hud_set_botstat() + return TRUE + +/mob/living/simple_animal/bot/proc/turn_off() + on = FALSE + canmove = FALSE + set_light(0) + bot_reset() //Resets an AI's call, should it exist. + update_icon() + +/mob/living/simple_animal/bot/Initialize() + . = ..() + GLOB.bots_list += src + access_card = new /obj/item/card/id(src) +//This access is so bots can be immediately set to patrol and leave Robotics, instead of having to be let out first. + access_card.access += ACCESS_ROBOTICS + set_custom_texts() + Radio = new/obj/item/radio(src) + if(radio_key) + Radio.keyslot = new radio_key + Radio.subspace_transmission = TRUE + Radio.canhear_range = 0 // anything greater will have the bot broadcast the channel as if it were saying it out loud. + Radio.recalculateChannels() + + bot_core = new bot_core_type(src) + + //Adds bot to the diagnostic HUD system + prepare_huds() + for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) + diag_hud.add_to_hud(src) + diag_hud_set_bothealth() + diag_hud_set_botstat() + diag_hud_set_botmode() + + //If a bot has its own HUD (for player bots), provide it. + if(data_hud_type) + var/datum/atom_hud/datahud = GLOB.huds[data_hud_type] + datahud.add_hud_to(src) + if(path_hud) + path_hud.add_to_hud(src) + path_hud.add_hud_to(src) + +/mob/living/simple_animal/bot/update_canmove() + . = ..() + if(!on) + . = 0 + canmove = . + +/mob/living/simple_animal/bot/Destroy() + if(path_hud) + QDEL_NULL(path_hud) + path_hud = null + GLOB.bots_list -= src + if(paicard) + ejectpai() + qdel(Radio) + qdel(access_card) + qdel(bot_core) + return ..() + +/mob/living/simple_animal/bot/bee_friendly() + return TRUE + +/mob/living/simple_animal/bot/death(gibbed) + explode() + ..() + +/mob/living/simple_animal/bot/proc/explode() + qdel(src) + +/mob/living/simple_animal/bot/emag_act(mob/user) + . = ..() + if(locked) //First emag application unlocks the bot's interface. Apply a screwdriver to use the emag again. + locked = FALSE + emagged = 1 + to_chat(user, "You bypass [src]'s controls.") + return TRUE + if(!open) + to_chat(user, "You need to open maintenance panel first!") + return + emagged = 2 + remote_disabled = 1 //Manually emagging the bot locks out the AI built in panel. + locked = TRUE //Access denied forever! + bot_reset() + turn_on() //The bot automatically turns on when emagged, unless recently hit with EMP. + to_chat(src, "(#$*#$^^( OVERRIDE DETECTED") + log_combat(user, src, "emagged") + return TRUE + +/mob/living/simple_animal/bot/examine(mob/user) + . = ..() + if(health < maxHealth) + if(health > maxHealth/3) + . += "[src]'s parts look loose." + else + . += "[src]'s parts look very loose!" + else + . += "[src] is in pristine condition." + +/mob/living/simple_animal/bot/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + . = ..() + if(. && prob(10)) + new oil_spill_type(loc) + +/mob/living/simple_animal/bot/updatehealth() + ..() + diag_hud_set_bothealth() + +/mob/living/simple_animal/bot/med_hud_set_health() + return //we use a different hud + +/mob/living/simple_animal/bot/med_hud_set_status() + return //we use a different hud + +/mob/living/simple_animal/bot/handle_automated_action() //Master process which handles code common across most bots. + diag_hud_set_botmode() + + if (ignorelistcleanuptimer % 300 == 0) // Every 300 actions, clean up the ignore list from old junk + for(var/ref in ignore_list) + var/atom/referredatom = locate(ref) + if (!referredatom || !istype(referredatom) || QDELETED(referredatom)) + ignore_list -= ref + ignorelistcleanuptimer = 1 + else + ignorelistcleanuptimer++ + + if(!on || client) + return + + switch(mode) //High-priority overrides are processed first. Bots can do nothing else while under direct command. + if(BOT_RESPONDING) //Called by the AI. + call_mode() + return + if(BOT_SUMMON) //Called by PDA + bot_summon() + return + return TRUE //Successful completion. Used to prevent child process() continuing if this one is ended early. + + +/mob/living/simple_animal/bot/attack_hand(mob/living/carbon/human/H) + if(H.a_intent == INTENT_HELP) + interact(H) + else + return ..() + +/mob/living/simple_animal/bot/attack_ai(mob/user) + if(!topic_denied(user)) + interact(user) + else + to_chat(user, "[src]'s interface is not responding!") + +/mob/living/simple_animal/bot/interact(mob/user) + show_controls(user) + +/mob/living/simple_animal/bot/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/screwdriver)) + if(!locked) + open = !open + to_chat(user, "The maintenance panel is now [open ? "opened" : "closed"].") + else + to_chat(user, "The maintenance panel is locked.") + else if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) + if(bot_core.allowed(user) && !open && !emagged) + locked = !locked + to_chat(user, "Controls are now [locked ? "locked." : "unlocked."]") + else + if(emagged) + to_chat(user, "ERROR") + if(open) + to_chat(user, "Please close the access panel before locking it.") + else + to_chat(user, "Access denied.") + else if(istype(W, /obj/item/paicard)) + insertpai(user, W) + else if(W.tool_behaviour == TOOL_HEMOSTAT && paicard) + if(open) + to_chat(user, "Close the access panel before manipulating the personality slot!") + else + to_chat(user, "You attempt to pull [paicard] free...") + if(do_after(user, 30, target = src)) + if (paicard) + user.visible_message("[user] uses [W] to pull [paicard] out of [bot_name]!","You pull [paicard] out of [bot_name] with [W].") + ejectpai(user) + else + user.changeNext_move(CLICK_CD_MELEE) + if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) + if(health >= maxHealth) + to_chat(user, "[src] does not need a repair!") + return + if(!open) + to_chat(user, "Unable to repair with the maintenance panel closed!") + return + + if(W.use_tool(src, user, 0, volume=40)) + adjustHealth(-10) + user.visible_message("[user] repairs [src]!","You repair [src].") + else + if(W.force) //if force is non-zero + do_sparks(5, TRUE, src) + ..() + +/mob/living/simple_animal/bot/bullet_act(obj/item/projectile/Proj) + if(Proj && (Proj.damage_type == BRUTE || Proj.damage_type == BURN)) + if(prob(75) && Proj.damage > 0) + do_sparks(5, TRUE, src) + return ..() + +/mob/living/simple_animal/bot/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + var/was_on = on + stat |= EMPED + new /obj/effect/temp_visual/emp(loc) + if(paicard) + paicard.emp_act(severity) + src.visible_message("[paicard] is flies out of [bot_name]!","You are forcefully ejected from [bot_name]!") + ejectpai(0) + if(on) + turn_off() + spawn(severity*300) + stat &= ~EMPED + if(was_on) + turn_on() + +/mob/living/simple_animal/bot/proc/set_custom_texts() //Superclass for setting hack texts. Appears only if a set is not given to a bot locally. + text_hack = "You hack [name]." + text_dehack = "You reset [name]." + text_dehack_fail = "You fail to reset [name]." + +/mob/living/simple_animal/bot/proc/speak(message,channel) //Pass a message to have the bot say() it. Pass a frequency to say it on the radio. + if((!on) || (!message)) + return + if(channel && Radio.channels[channel])// Use radio if we have channel key + Radio.talk_into(src, message, channel) + else + say(message) + +/mob/living/simple_animal/bot/radio(message, message_mode, list/spans, language) + . = ..() + if(. != 0) + return + + switch(message_mode) + if(MODE_HEADSET) + Radio.talk_into(src, message, , spans, language) + return REDUCE_RANGE + + if(MODE_DEPARTMENT) + Radio.talk_into(src, message, message_mode, spans, language) + return REDUCE_RANGE + + if(message_mode in GLOB.radiochannels) + Radio.talk_into(src, message, message_mode, spans, language) + return REDUCE_RANGE + +/mob/living/simple_animal/bot/proc/drop_part(obj/item/drop_item, dropzone) + var/dropped_item = new drop_item(dropzone) + drop_item = null + + if(istype(dropped_item, /obj/item/stock_parts/cell)) + var/obj/item/stock_parts/cell/dropped_cell = dropped_item + dropped_cell.charge = 0 + dropped_cell.update_icon() + + else if(istype(dropped_item, /obj/item/storage)) + var/obj/item/storage/S = dropped_item + S.contents = list() + + else if(istype(dropped_item, /obj/item/gun/energy)) + var/obj/item/gun/energy/dropped_gun = dropped_item + dropped_gun.cell.charge = 0 + dropped_gun.update_icon() + +//Generalized behavior code, override where needed! + +/* +scan() will search for a given type (such as turfs, human mobs, or objects) in the bot's view range, and return a single result. +Arguments: The object type to be searched (such as "/mob/living/carbon/human"), the old scan result to be ignored, if one exists, +and the view range, which defaults to 7 (full screen) if an override is not passed. +If the bot maintains an ignore list, it is also checked here. + +Example usage: patient = scan(/mob/living/carbon/human, oldpatient, 1) +The proc would return a human next to the bot to be set to the patient var. +Pass the desired type path itself, declaring a temporary var beforehand is not required. +*/ +/mob/living/simple_animal/bot/proc/scan(scan_type, old_target, scan_range = DEFAULT_SCAN_RANGE) + var/turf/T = get_turf(src) + if(!T) + return + var/list/adjacent = T.GetAtmosAdjacentTurfs(1) + if(shuffle) //If we were on the same tile as another bot, let's randomize our choices so we dont both go the same way + adjacent = shuffle(adjacent) + shuffle = FALSE + for(var/scan in adjacent)//Let's see if there's something right next to us first! + if(check_bot(scan)) //Is there another bot there? Then let's just skip it + continue + if(isturf(scan_type)) //If we're lookeing for a turf we can just run the checks directly! + var/final_result = checkscan(scan,scan_type,old_target) + if(final_result) + return final_result + else + var/turf/turfy = scan + for(var/deepscan in turfy.contents)//Check the contents since adjacent is turfs + var/final_result = checkscan(deepscan,scan_type,old_target) + if(final_result) + return final_result + for (var/scan in shuffle(view(scan_range, src))-adjacent) //Search for something in range! + var/final_result = checkscan(scan,scan_type,old_target) + if(final_result) + return final_result + +/mob/living/simple_animal/bot/proc/checkscan(scan, scan_type, old_target) + if(!istype(scan, scan_type)) //Check that the thing we found is the type we want! + return FALSE //If not, keep searching! + if( (REF(scan) in ignore_list) || (scan == old_target) ) //Filter for blacklisted elements, usually unreachable or previously processed oness + return FALSE + + var/scan_result = process_scan(scan) //Some bots may require additional processing when a result is selected. + if(scan_result) + return scan_result + else + return FALSE //The current element failed assessment, move on to the next. + return + +/mob/living/simple_animal/bot/proc/check_bot(targ) + var/turf/T = get_turf(targ) + if(T) + for(var/C in T.contents) + if(istype(C,type) && (C != src)) //Is there another bot there already? If so, let's skip it so we dont all atack on top of eachother. + return TRUE //Let's abort if we find a bot so we dont have to keep rechecking + +//When the scan finds a target, run bot specific processing to select it for the next step. Empty by default. +/mob/living/simple_animal/bot/proc/process_scan(scan_target) + return scan_target + + +/mob/living/simple_animal/bot/proc/add_to_ignore(subject) + if(ignore_list.len < 50) //This will help keep track of them, so the bot is always trying to reach a blocked spot. + ignore_list += REF(subject) + else //If the list is full, insert newest, delete oldest. + ignore_list.Cut(1,2) + ignore_list += REF(subject) + +/* +Movement proc for stepping a bot through a path generated through A-star. +Pass a positive integer as an argument to override a bot's default speed. +*/ +/mob/living/simple_animal/bot/proc/bot_move(dest, move_speed) + if(!dest || !path || path.len == 0) //A-star failed or a path/destination was not set. + set_path(null) + return FALSE + dest = get_turf(dest) //We must always compare turfs, so get the turf of the dest var if dest was originally something else. + var/turf/last_node = get_turf(path[path.len]) //This is the turf at the end of the path, it should be equal to dest. + if(get_turf(src) == dest) //We have arrived, no need to move again. + return TRUE + else if(dest != last_node) //The path should lead us to our given destination. If this is not true, we must stop. + set_path(null) + return FALSE + var/step_count = move_speed ? move_speed : base_speed //If a value is passed into move_speed, use that instead of the default speed var. + + if(step_count >= 1 && tries < BOT_STEP_MAX_RETRIES) + for(var/step_number = 0, step_number < step_count,step_number++) + spawn(BOT_STEP_DELAY*step_number) + bot_step(dest) + else + return FALSE + return TRUE + + +/mob/living/simple_animal/bot/proc/bot_step(dest) //Step,increase tries if failed + if(!path) + return FALSE + if(path.len > 1) + step_towards(src, path[1]) + if(get_turf(src) == path[1]) //Successful move + increment_path() + tries = 0 + else + tries++ + return FALSE + else if(path.len == 1) + step_to(src, dest) + set_path(null) + return TRUE + + +/mob/living/simple_animal/bot/proc/check_bot_access() + if(mode != BOT_SUMMON && mode != BOT_RESPONDING) + access_card.access = prev_access + +/mob/living/simple_animal/bot/proc/call_bot(caller, turf/waypoint, message=TRUE) + bot_reset() //Reset a bot before setting it to call mode. + + //For giving the bot temporary all-access. + var/obj/item/card/id/all_access = new /obj/item/card/id + var/datum/job/captain/All = new/datum/job/captain + all_access.access = All.get_access() + + set_path(get_path_to(src, waypoint, /turf/proc/Distance_cardinal, 0, 200, id=all_access)) + calling_ai = caller //Link the AI to the bot! + ai_waypoint = waypoint + + if(path && path.len) //Ensures that a valid path is calculated! + var/end_area = get_area_name(waypoint) + if(!on) + turn_on() //Saves the AI the hassle of having to activate a bot manually. + access_card = all_access //Give the bot all-access while under the AI's command. + if(client) + reset_access_timer_id = addtimer(CALLBACK (src, .proc/bot_reset), 600, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) //if the bot is player controlled, they get the extra access for a limited time + to_chat(src, "Priority waypoint set by [icon2html(calling_ai, src)] [caller]. Proceed to [end_area].
                [path.len-1] meters to destination. You have been granted additional door access for 60 seconds.
                ") + if(message) + to_chat(calling_ai, "[icon2html(src, calling_ai)] [name] called to [end_area]. [path.len-1] meters to destination.") + pathset = 1 + mode = BOT_RESPONDING + tries = 0 + else + if(message) + to_chat(calling_ai, "Failed to calculate a valid route. Ensure destination is clear of obstructions and within range.") + calling_ai = null + set_path(null) + +/mob/living/simple_animal/bot/proc/call_mode() //Handles preparing a bot for a call, as well as calling the move proc. +//Handles the bot's movement during a call. + var/success = bot_move(ai_waypoint, 3) + if(!success) + if(calling_ai) + to_chat(calling_ai, "[icon2html(src, calling_ai)] [get_turf(src) == ai_waypoint ? "[src] successfully arrived to waypoint." : "[src] failed to reach waypoint."]") + calling_ai = null + bot_reset() + +/mob/living/simple_animal/bot/proc/bot_reset() + if(calling_ai) //Simple notification to the AI if it called a bot. It will not know the cause or identity of the bot. + to_chat(calling_ai, "Call command to a bot has been reset.") + calling_ai = null + if(reset_access_timer_id) + deltimer(reset_access_timer_id) + reset_access_timer_id = null + set_path(null) + summon_target = null + pathset = 0 + access_card.access = prev_access + tries = 0 + mode = BOT_IDLE + diag_hud_set_botstat() + diag_hud_set_botmode() + + + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +//Patrol and summon code! +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +/mob/living/simple_animal/bot/proc/bot_patrol() + patrol_step() + spawn(5) + if(mode == BOT_PATROL) + patrol_step() + return + +/mob/living/simple_animal/bot/proc/start_patrol() + + if(tries >= BOT_STEP_MAX_RETRIES) //Bot is trapped, so stop trying to patrol. + auto_patrol = 0 + tries = 0 + speak("Unable to start patrol.") + + return + + if(!auto_patrol) //A bot not set to patrol should not be patrolling. + mode = BOT_IDLE + return + + if(patrol_target) // has patrol target + spawn(0) + calc_path() // Find a route to it + if(path.len == 0) + patrol_target = null + return + mode = BOT_PATROL + else // no patrol target, so need a new one + speak("Engaging patrol mode.") + find_patrol_target() + tries++ + return + +// perform a single patrol step + +/mob/living/simple_animal/bot/proc/patrol_step() + + if(client) // In use by player, don't actually move. + return + + if(loc == patrol_target) // reached target + //Find the next beacon matching the target. + if(!get_next_patrol_target()) + find_patrol_target() //If it fails, look for the nearest one instead. + return + + else if(path.len > 0 && patrol_target) // valid path + if(path[1] == loc) + increment_path() + return + + + var/moved = bot_move(patrol_target)//step_towards(src, next) // attempt to move + if(!moved) //Couldn't proceed the next step of the path BOT_STEP_MAX_RETRIES times + spawn(2) + calc_path() + if(path.len == 0) + find_patrol_target() + tries = 0 + + else // no path, so calculate new one + mode = BOT_START_PATROL + +// finds the nearest beacon to self +/mob/living/simple_animal/bot/proc/find_patrol_target() + nearest_beacon = null + new_destination = null + find_nearest_beacon() + if(nearest_beacon) + patrol_target = nearest_beacon_loc + destination = next_destination + else + auto_patrol = 0 + mode = BOT_IDLE + speak("Disengaging patrol mode.") + +/mob/living/simple_animal/bot/proc/get_next_patrol_target() + // search the beacon list for the next target in the list. + for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[z]"]) + if(NB.location == next_destination) //Does the Beacon location text match the destination? + destination = new_destination //We now know the name of where we want to go. + patrol_target = NB.loc //Get its location and set it as the target. + next_destination = NB.codes["next_patrol"] //Also get the name of the next beacon in line. + return TRUE + +/mob/living/simple_animal/bot/proc/find_nearest_beacon() + for(var/obj/machinery/navbeacon/NB in GLOB.navbeacons["[z]"]) + var/dist = get_dist(src, NB) + if(nearest_beacon) //Loop though the beacon net to find the true closest beacon. + //Ignore the beacon if were are located on it. + if(dist>1 && dist 1) //Begin the search, save this one for comparison on the next loop. + nearest_beacon = NB.location + nearest_beacon_loc = NB.loc + patrol_target = nearest_beacon_loc + destination = nearest_beacon + +//PDA control. Some bots, especially MULEs, may have more parameters. +/mob/living/simple_animal/bot/proc/bot_control(command, mob/user, list/user_access = list()) + if(!on || emagged == 2 || remote_disabled) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands. + return TRUE //ACCESS DENIED + if(client) + bot_control_message(command, user) + // process control input + switch(command) + if("patroloff") + bot_reset() //HOLD IT!! + auto_patrol = 0 + + if("patrolon") + auto_patrol = 1 + + if("summon") + bot_reset() + summon_target = get_turf(user) + if(user_access.len != 0) + access_card.access = user_access + prev_access //Adds the user's access, if any. + mode = BOT_SUMMON + speak("Responding.", radio_channel) + calc_summon_path() + + if("ejectpai") + ejectpairemote(user) + return + +// +/mob/living/simple_animal/bot/proc/bot_control_message(command, user) + switch(command) + if("patroloff") + to_chat(src, "STOP PATROL") + if("patrolon") + to_chat(src, "START PATROL") + if("summon") + to_chat(src, "PRIORITY ALERT:[user] in [get_area_name(user)]!") + if("stop") + to_chat(src, "STOP!") + + if("go") + to_chat(src, "GO!") + + if("home") + to_chat(src, "RETURN HOME!") + if("ejectpai") + return + else + to_chat(src, "Unidentified control sequence received:[command]") + +/mob/living/simple_animal/bot/proc/bot_summon() // summoned to PDA + summon_step() + +// calculates a path to the current destination +// given an optional turf to avoid +/mob/living/simple_animal/bot/proc/calc_path(turf/avoid) + check_bot_access() + set_path(get_path_to(src, patrol_target, /turf/proc/Distance_cardinal, 0, 120, id=access_card, exclude=avoid)) + +/mob/living/simple_animal/bot/proc/calc_summon_path(turf/avoid) + check_bot_access() + spawn() + set_path(get_path_to(src, summon_target, /turf/proc/Distance_cardinal, 0, 150, id=access_card, exclude=avoid)) + if(!path.len) //Cannot reach target. Give up and announce the issue. + speak("Summon command failed, destination unreachable.",radio_channel) + bot_reset() + +/mob/living/simple_animal/bot/proc/summon_step() + + if(client) // In use by player, don't actually move. + return + + if(loc == summon_target) // Arrived to summon location. + bot_reset() + return + + else if(path.len > 0 && summon_target) //Proper path acquired! + if(path[1] == loc) + increment_path() + return + + var/moved = bot_move(summon_target, 3) // Move attempt + if(!moved) + spawn(2) + calc_summon_path() + tries = 0 + + else // no path, so calculate new one + calc_summon_path() + +/mob/living/simple_animal/bot/Bump(M as mob|obj) //Leave no door unopened! + . = ..() + if((istype(M, /obj/machinery/door/airlock) || istype(M, /obj/machinery/door/window)) && (!isnull(access_card))) + var/obj/machinery/door/D = M + if(D.check_access(access_card)) + D.open() + frustration = 0 + +/mob/living/simple_animal/bot/proc/show_controls(mob/M) + users |= M + var/dat = "" + dat = get_controls(M) + var/datum/browser/popup = new(M,window_id,window_name,350,600) + popup.set_content(dat) + popup.open(use_onclose = 0) + onclose(M,window_id,ref=src) + return + +/mob/living/simple_animal/bot/proc/update_controls() + for(var/mob/M in users) + show_controls(M) + +/mob/living/simple_animal/bot/proc/get_controls(mob/M) + return "PROTOBOT - NOT FOR USE" + +/mob/living/simple_animal/bot/Topic(href, href_list) + //No ..() to prevent strip panel showing up - Todo: make that saner + if(href_list["close"])// HUE HUE + if(usr in users) + users.Remove(usr) + return TRUE + + if(topic_denied(usr)) + to_chat(usr, "[src]'s interface is not responding!") + return TRUE + add_fingerprint(usr) + + if((href_list["power"]) && (bot_core.allowed(usr) || !locked)) + if(on) + turn_off() + else + turn_on() + + switch(href_list["operation"]) + if("patrol") + if(!issilicon(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) + return TRUE + auto_patrol = !auto_patrol + bot_reset() + if("remote") + remote_disabled = !remote_disabled + if("hack") + if(!issilicon(usr) && !IsAdminGhost(usr)) + var/msg = "[key_name(usr)] attempted to hack a bot with a href that shouldn't be available!" + message_admins(msg) + log_admin(msg) + return TRUE + if(emagged != 2) + emagged = 2 + hacked = TRUE + locked = TRUE + to_chat(usr, "[text_hack]") + bot_reset() + else if(!hacked) + to_chat(usr, "[text_dehack_fail]") + else + emagged = FALSE + hacked = FALSE + to_chat(usr, "[text_dehack]") + bot_reset() + if("ejectpai") + if(paicard && (!locked || issilicon(usr) || IsAdminGhost(usr))) + to_chat(usr, "You eject [paicard] from [bot_name]") + ejectpai(usr) + update_controls() + +/mob/living/simple_animal/bot/update_icon_state() + icon_state = "[initial(icon_state)][on]" + +// Machinery to simplify topic and access calls +/obj/machinery/bot_core + use_power = NO_POWER_USE + anchored = FALSE + var/mob/living/simple_animal/bot/owner = null + +/obj/machinery/bot_core/Initialize() + . = ..() + owner = loc + if(!istype(owner)) + return INITIALIZE_HINT_QDEL + +/mob/living/simple_animal/bot/proc/topic_denied(mob/user) //Access check proc for bot topics! Remember to place in a bot's individual Topic if desired. + if(!user.canUseTopic(src)) + return TRUE + // 0 for access, 1 for denied. + if(emagged == 2) //An emagged bot cannot be controlled by humans, silicons can if one hacked it. + if(!hacked) //Manually emagged by a human - access denied to all. + return TRUE + else if(!issilicon(user) && !IsAdminGhost(user)) //Bot is hacked, so only silicons and admins are allowed access. + return TRUE + return FALSE + +/mob/living/simple_animal/bot/proc/hack(mob/user) + var/hack + if(issilicon(user) || IsAdminGhost(user)) //Allows silicons or admins to toggle the emag status of a bot. + hack += "[emagged == 2 ? "Software compromised! Unit may exhibit dangerous or erratic behavior." : "Unit operating normally. Release safety lock?"]
                " + hack += "Harm Prevention Safety System: [emagged ? "DANGER" : "Engaged"]
                " + else if(!locked) //Humans with access can use this option to hide a bot from the AI's remote control panel and PDA control. + hack += "Remote network control radio: [remote_disabled ? "Disconnected" : "Connected"]
                " + return hack + +/mob/living/simple_animal/bot/proc/showpai(mob/user) + var/eject = "" + if((!locked || issilicon(usr) || IsAdminGhost(usr))) + if(paicard || allow_pai) + eject += "Personality card status: " + if(paicard) + if(client) + eject += "Active" + else + eject += "Inactive" + else if(!allow_pai || key) + eject += "Unavailable" + else + eject += "Not inserted" + eject += "
                " + eject += "
                " + return eject + +/mob/living/simple_animal/bot/proc/insertpai(mob/user, obj/item/paicard/card) + if(paicard) + to_chat(user, "A [paicard] is already inserted!") + else if(allow_pai && !key) + if(!locked && !open) + if(card.pai && card.pai.mind) + if(!user.transferItemToLoc(card, src)) + return + paicard = card + user.visible_message("[user] inserts [card] into [src]!","You insert [card] into [src].") + paicard.pai.mind.transfer_to(src) + to_chat(src, "You sense your form change as you are uploaded into [src].") + bot_name = name + name = paicard.pai.name + faction = user.faction.Copy() + language_holder = paicard.pai.language_holder.copy(src) + log_combat(user, paicard.pai, "uploaded to [bot_name],") + return TRUE + else + to_chat(user, "[card] is inactive.") + else + to_chat(user, "The personality slot is locked.") + else + to_chat(user, "[src] is not compatible with [card]") + +/mob/living/simple_animal/bot/proc/ejectpai(mob/user = null, announce = 1) + if(paicard) + if(mind && paicard.pai) + mind.transfer_to(paicard.pai) + else if(paicard.pai) + transfer_ckey(paicard.pai) + else + ghostize(0) // The pAI card that just got ejected was dead. + key = null + paicard.forceMove(loc) + if(user) + log_combat(user, paicard.pai, "ejected from [src.bot_name],") + else + log_combat(src, paicard.pai, "ejected") + if(announce) + to_chat(paicard.pai, "You feel your control fade as [paicard] ejects from [bot_name].") + paicard = null + name = bot_name + faction = initial(faction) + +/mob/living/simple_animal/bot/proc/ejectpairemote(mob/user) + if(bot_core.allowed(user) && paicard) + speak("Ejecting personality chip.", radio_channel) + ejectpai(user) + +/mob/living/simple_animal/bot/Login() + . = ..() + access_card.access += player_access + diag_hud_set_botmode() + +/mob/living/simple_animal/bot/Logout() + . = ..() + bot_reset() + +/mob/living/simple_animal/bot/revive(full_heal = 0, admin_revive = 0) + if(..()) + update_icon() + . = 1 + +/mob/living/simple_animal/bot/ghost() + if(stat != DEAD) // Only ghost if we're doing this while alive, the pAI probably isn't dead yet. + ..() + if(paicard && (!client || stat == DEAD)) + ejectpai(0) + +/mob/living/simple_animal/bot/sentience_act() + faction -= "silicon" + +/mob/living/simple_animal/bot/proc/set_path(list/newpath) + path = newpath ? newpath : list() + if(!path_hud) + return + var/list/path_huds_watching_me = list(GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED]) + if(path_hud) + path_huds_watching_me += path_hud + for(var/V in path_huds_watching_me) + var/datum/atom_hud/H = V + H.remove_from_hud(src) + + var/list/path_images = hud_list[DIAG_PATH_HUD] + QDEL_LIST(path_images) + if(newpath) + for(var/i in 1 to newpath.len) + var/turf/T = newpath[i] + if(T == loc) //don't bother putting an image if it's where we already exist. + continue + var/direction = NORTH + if(i > 1) + var/turf/prevT = path[i - 1] + var/image/prevI = path[prevT] + direction = get_dir(prevT, T) + if(i > 2) + var/turf/prevprevT = path[i - 2] + var/prevDir = get_dir(prevprevT, prevT) + var/mixDir = direction|prevDir + if(mixDir in GLOB.diagonals) + prevI.dir = mixDir + if(prevDir & (NORTH|SOUTH)) + var/matrix/ntransform = matrix() + ntransform.Turn(90) + if((mixDir == NORTHWEST) || (mixDir == SOUTHEAST)) + ntransform.Scale(-1, 1) + else + ntransform.Scale(1, -1) + prevI.transform = ntransform + var/mutable_appearance/MA = new /mutable_appearance() + MA.icon = path_image_icon + MA.icon_state = path_image_icon_state + MA.layer = ABOVE_OPEN_TURF_LAYER + MA.plane = 0 + MA.appearance_flags = RESET_COLOR|RESET_TRANSFORM + MA.color = path_image_color + MA.dir = direction + var/image/I = image(loc = T) + I.appearance = MA + path[T] = I + path_images += I + + for(var/V in path_huds_watching_me) + var/datum/atom_hud/H = V + H.add_to_hud(src) + + +/mob/living/simple_animal/bot/proc/increment_path() + if(!path || !path.len) + return + var/image/I = path[path[1]] + if(I) + I.icon_state = null + path.Cut(1, 2) diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index cbd985c358..4715a1361a 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -1,573 +1,573 @@ -/mob/living/simple_animal/bot/ed209 - name = "\improper ED-209 Security Robot" - desc = "A security robot. He looks less than thrilled." - icon = 'icons/mob/aibots.dmi' - icon_state = "ed2090" - density = TRUE - anchored = FALSE - health = 100 - maxHealth = 100 - damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) - obj_damage = 60 - environment_smash = ENVIRONMENT_SMASH_WALLS //Walls can't stop THE LAW - mob_size = MOB_SIZE_LARGE - - radio_key = /obj/item/encryptionkey/headset_sec - radio_channel = RADIO_CHANNEL_SECURITY - bot_type = SEC_BOT - model = "ED-209" - bot_core = /obj/machinery/bot_core/secbot - window_id = "autoed209" - window_name = "Automatic Security Unit v2.6" - allow_pai = 0 - data_hud_type = DATA_HUD_SECURITY_ADVANCED - path_image_color = "#FF0000" - - var/lastfired = 0 - var/shot_delay = 15 - var/lasercolor = "" - var/disabled = FALSE //A holder for if it needs to be disabled, if true it will not seach for targets, shoot at targets, or move, currently only used for lasertag - - - var/mob/living/carbon/target - var/oldtarget_name - var/threatlevel = 0 - var/target_lastloc //Loc of target when arrested. - var/last_found //There's a delay - var/declare_arrests = TRUE //When making an arrest, should it notify everyone wearing sechuds? - var/idcheck = TRUE //If true, arrest people with no IDs - var/weaponscheck = TRUE //If true, arrest people for weapons if they don't have access - var/check_records = TRUE //Does it check security records? - var/arrest_type = FALSE //If true, don't handcuff - var/projectile = /obj/item/projectile/energy/electrode //Holder for projectile type - var/shoot_sound = 'sound/weapons/taser.ogg' - var/cell_type = /obj/item/stock_parts/cell - var/vest_type = /obj/item/clothing/suit/armor/vest - - do_footstep = TRUE - - -/mob/living/simple_animal/bot/ed209/Initialize(mapload,created_name,created_lasercolor) - . = ..() - if(created_name) - name = created_name - if(created_lasercolor) - lasercolor = created_lasercolor - icon_state = "[lasercolor]ed209[on]" - set_weapon() //giving it the right projectile and firing sound. - spawn(3) - var/datum/job/detective/J = new/datum/job/detective - access_card.access += J.get_access() - prev_access = access_card.access - - if(lasercolor) - shot_delay = 6//Longer shot delay because JESUS CHRIST - check_records = 0//Don't actively target people set to arrest - arrest_type = 1//Don't even try to cuff - bot_core.req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) - arrest_type = 1 - if((lasercolor == "b") && (name == "\improper ED-209 Security Robot"))//Picks a name if there isn't already a custome one - name = pick("BLUE BALLER","SANIC","BLUE KILLDEATH MURDERBOT") - if((lasercolor == "r") && (name == "\improper ED-209 Security Robot")) - name = pick("RED RAMPAGE","RED ROVER","RED KILLDEATH MURDERBOT") - - //SECHUD - var/datum/atom_hud/secsensor = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - secsensor.add_hud_to(src) - -/mob/living/simple_animal/bot/ed209/turn_on() - . = ..() - icon_state = "[lasercolor]ed209[on]" - mode = BOT_IDLE - -/mob/living/simple_animal/bot/ed209/turn_off() - ..() - icon_state = "[lasercolor]ed209[on]" - -/mob/living/simple_animal/bot/ed209/bot_reset() - ..() - target = null - oldtarget_name = null - anchored = FALSE - walk_to(src,0) - last_found = world.time - set_weapon() - -/mob/living/simple_animal/bot/ed209/set_custom_texts() - text_hack = "You disable [name]'s combat inhibitor." - text_dehack = "You restore [name]'s combat inhibitor." - text_dehack_fail = "[name] ignores your attempts to restrict him!" - -/mob/living/simple_animal/bot/ed209/get_controls(mob/user) - var/dat - dat += hack(user) - dat += showpai(user) - dat += text({" -Security Unit v2.6 controls

                -Status: []
                -Behaviour controls are [locked ? "locked" : "unlocked"]
                -Maintenance panel panel is [open ? "opened" : "closed"]
                "}, - -"[on ? "On" : "Off"]" ) - - if(!locked || issilicon(user)|| IsAdminGhost(user)) - if(!lasercolor) - dat += text({"
                -Arrest Unidentifiable Persons: []
                -Arrest for Unauthorized Weapons: []
                -Arrest for Warrant: []
                -
                -Operating Mode: []
                -Report Arrests[]
                -Auto Patrol[]"}, - -"[idcheck ? "Yes" : "No"]", -"[weaponscheck ? "Yes" : "No"]", -"[check_records ? "Yes" : "No"]", -"[arrest_type ? "Detain" : "Arrest"]", -"[declare_arrests ? "Yes" : "No"]", -"[auto_patrol ? "On" : "Off"]" ) - - return dat - -/mob/living/simple_animal/bot/ed209/Topic(href, href_list) - if(lasercolor && ishuman(usr)) - var/mob/living/carbon/human/H = usr - if((lasercolor == "b") && (istype(H.wear_suit, /obj/item/clothing/suit/redtag)))//Opposing team cannot operate it - return - else if((lasercolor == "r") && (istype(H.wear_suit, /obj/item/clothing/suit/bluetag))) - return - if(..()) - return 1 - - switch(href_list["operation"]) - if("idcheck") - idcheck = !idcheck - update_controls() - if("weaponscheck") - weaponscheck = !weaponscheck - update_controls() - if("ignorerec") - check_records = !check_records - update_controls() - if("switchmode") - arrest_type = !arrest_type - update_controls() - if("declarearrests") - declare_arrests = !declare_arrests - update_controls() - -/mob/living/simple_animal/bot/ed209/proc/judgement_criteria() - var/final = FALSE - if(idcheck) - final = final|JUDGE_IDCHECK - if(check_records) - final = final|JUDGE_RECORDCHECK - if(weaponscheck) - final = final|JUDGE_WEAPONCHECK - if(emagged == 2) - final = final|JUDGE_EMAGGED - //ED209's ignore monkeys - final = final|JUDGE_IGNOREMONKEYS - return final - -/mob/living/simple_animal/bot/ed209/proc/retaliate(mob/living/carbon/human/H) - var/judgement_criteria = judgement_criteria() - threatlevel = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) - threatlevel += 6 - if(threatlevel >= 4) - target = H - mode = BOT_HUNT - -/mob/living/simple_animal/bot/ed209/attack_hand(mob/living/carbon/human/H) - if(H.a_intent == INTENT_HARM) - retaliate(H) - return ..() - -/mob/living/simple_animal/bot/ed209/attackby(obj/item/W, mob/user, params) - ..() - if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. - return - if(!istype(W, /obj/item/screwdriver) && (!target)) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. - if(W.force && W.damtype != STAMINA)//If force is non-zero and damage type isn't stamina. - retaliate(user) - if(lasercolor)//To make up for the fact that lasertag bots don't hunt - shootAt(user) - -/mob/living/simple_animal/bot/ed209/emag_act(mob/user) - . = ..() - if(emagged == 2) - if(user) - to_chat(user, "You short out [src]'s target assessment circuits.") - oldtarget_name = user.name - audible_message("[src] buzzes oddly!") - declare_arrests = FALSE - icon_state = "[lasercolor]ed209[on]" - set_weapon() - -/mob/living/simple_animal/bot/ed209/bullet_act(obj/item/projectile/Proj) - if(istype(Proj , /obj/item/projectile/beam/laser)||istype(Proj, /obj/item/projectile/bullet)) - if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)) - if(!Proj.nodamage && Proj.damage < src.health && ishuman(Proj.firer)) - retaliate(Proj.firer) - ..() - -/mob/living/simple_animal/bot/ed209/handle_automated_action() - if(!..()) - return - - if(disabled) - return - - var/judgement_criteria = judgement_criteria() - var/list/targets = list() - for(var/mob/living/carbon/C in view(7,src)) //Let's find us a target - var/threatlevel = 0 - if((C.stat) || (C.lying)) - continue - threatlevel = C.assess_threat(judgement_criteria, lasercolor, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) - //speak(C.real_name + text(": threat: []", threatlevel)) - if(threatlevel < 4 ) - continue - - var/dst = get_dist(src, C) - if(dst <= 1 || dst > 7) - continue - - targets += C - if(targets.len>0) - var/mob/living/carbon/t = pick(targets) - if((t.stat!=2) && (t.lying != 1) && (!t.handcuffed)) //we don't shoot people who are dead, cuffed or lying down. - shootAt(t) - switch(mode) - - if(BOT_IDLE) // idle - walk_to(src,0) - if(!lasercolor) //lasertag bots don't want to arrest anyone - look_for_perp() // see if any criminals are in range - if(!mode && auto_patrol) // still idle, and set to patrol - mode = BOT_START_PATROL // switch to patrol mode - - if(BOT_HUNT) // hunting for perp - // if can't reach perp for long enough, go idle - if(frustration >= 8) - walk_to(src,0) - back_to_idle() - - if(target) // make sure target exists - if(Adjacent(target) && isturf(target.loc)) // if right next to perp - stun_attack(target) - - mode = BOT_PREP_ARREST - anchored = TRUE - target_lastloc = target.loc - return - - else // not next to perp - var/turf/olddist = get_dist(src, target) - walk_to(src, target,1,4) - if((get_dist(src, target)) >= (olddist)) - frustration++ - else - frustration = 0 - else - back_to_idle() - - if(BOT_PREP_ARREST) // preparing to arrest target - - // see if he got away. If he's no no longer adjacent or inside a closet or about to get up, we hunt again. - if(!Adjacent(target) || !isturf(target.loc) || !target.recoveringstam || target.getStaminaLoss() <= 120) // CIT CHANGE - replaces amountknockdown with recoveringstam and staminaloss checks - back_to_hunt() - return - - if(iscarbon(target) && target.canBeHandcuffed()) - if(!arrest_type) - if(!target.handcuffed) //he's not cuffed? Try to cuff him! - cuff(target) - else - back_to_idle() - return - else - back_to_idle() - return - - if(BOT_ARREST) - if(!target) - anchored = FALSE - mode = BOT_IDLE - last_found = world.time - frustration = 0 - return - - if(target.handcuffed) //no target or target cuffed? back to idle. - back_to_idle() - return - - if(!Adjacent(target) || !isturf(target.loc) || (target.loc != target_lastloc && !target.recoveringstam && target.getStaminaLoss() <= 120)) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. CIT CHANGE - replaces amountknockdown with recoveringstam and staminaloss checks - back_to_hunt() - return - else - mode = BOT_PREP_ARREST - anchored = FALSE - - if(BOT_START_PATROL) - look_for_perp() - start_patrol() - - if(BOT_PATROL) - look_for_perp() - bot_patrol() - - - return - -/mob/living/simple_animal/bot/ed209/proc/back_to_idle() - anchored = FALSE - mode = BOT_IDLE - target = null - last_found = world.time - frustration = 0 - INVOKE_ASYNC(src, .proc/handle_automated_action) //ensure bot quickly responds - -/mob/living/simple_animal/bot/ed209/proc/back_to_hunt() - anchored = FALSE - frustration = 0 - mode = BOT_HUNT - INVOKE_ASYNC(src, .proc/handle_automated_action) //ensure bot quickly responds - -// look for a criminal in view of the bot - -/mob/living/simple_animal/bot/ed209/proc/look_for_perp() - if(disabled) - return - anchored = FALSE - threatlevel = 0 - var/judgement_criteria = judgement_criteria() - for (var/mob/living/carbon/C in view(7,src)) //Let's find us a criminal - if((C.stat) || (C.handcuffed)) - continue - - if((C.name == oldtarget_name) && (world.time < last_found + 100)) - continue - - threatlevel = C.assess_threat(judgement_criteria, lasercolor, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) - - if(!threatlevel) - continue - - else if(threatlevel >= 4) - target = C - oldtarget_name = C.name - speak("Level [threatlevel] infraction alert!") - playsound(src, pick('sound/voice/ed209_20sec.ogg', 'sound/voice/edplaceholder.ogg'), 50, FALSE) - visible_message("[src] points at [C.name]!") - mode = BOT_HUNT - spawn(0) - handle_automated_action() // ensure bot quickly responds to a perp - break - else - continue - -/mob/living/simple_animal/bot/ed209/proc/check_for_weapons(var/obj/item/slot_item) - if(slot_item && (slot_item.item_flags & NEEDS_PERMIT)) - return 1 - return 0 - -/mob/living/simple_animal/bot/ed209/explode() - walk_to(src,0) - visible_message("[src] blows apart!") - var/atom/Tsec = drop_location() - - var/obj/item/bot_assembly/ed209/Sa = new (Tsec) - Sa.build_step = ASSEMBLY_SECOND_STEP - Sa.add_overlay("hs_hole") - Sa.created_name = name - new /obj/item/assembly/prox_sensor(Tsec) - drop_part(cell_type, Tsec) - - if(!lasercolor) - var/obj/item/gun/energy/e_gun/advtaser/G = new (Tsec) - G.cell.charge = 0 - G.update_icon() - else if(lasercolor == "b") - var/obj/item/gun/energy/laser/bluetag/G = new (Tsec) - G.cell.charge = 0 - G.update_icon() - else if(lasercolor == "r") - var/obj/item/gun/energy/laser/redtag/G = new (Tsec) - G.cell.charge = 0 - G.update_icon() - - if(prob(50)) - new /obj/item/bodypart/l_leg/robot(Tsec) - if(prob(25)) - new /obj/item/bodypart/r_leg/robot(Tsec) - if(prob(25))//50% chance for a helmet OR vest - if(prob(50)) - new /obj/item/clothing/head/helmet(Tsec) - else - if(!lasercolor) - drop_part(vest_type, Tsec) - if(lasercolor == "b") - new /obj/item/clothing/suit/bluetag(Tsec) - if(lasercolor == "r") - new /obj/item/clothing/suit/redtag(Tsec) - - do_sparks(3, TRUE, src) - - new /obj/effect/decal/cleanable/oil(loc) - ..() - -/mob/living/simple_animal/bot/ed209/proc/set_weapon() //used to update the projectile type and firing sound - shoot_sound = 'sound/weapons/laser.ogg' - if(emagged == 2) - if(lasercolor) - projectile = /obj/item/projectile/beam/lasertag - else - projectile = /obj/item/projectile/beam - else - if(!lasercolor) - shoot_sound = 'sound/weapons/taser.ogg' - projectile = /obj/item/projectile/energy/electrode - else if(lasercolor == "b") - projectile = /obj/item/projectile/beam/lasertag/bluetag - else if(lasercolor == "r") - projectile = /obj/item/projectile/beam/lasertag/redtag - -/mob/living/simple_animal/bot/ed209/proc/shootAt(mob/target) - if(lastfired && world.time - lastfired < shot_delay) - return - lastfired = world.time - var/turf/T = loc - var/turf/U = get_turf(target) - if(!U) - return - if(!isturf(T)) - return - - if(!projectile) - return - - var/obj/item/projectile/A = new projectile (loc) - playsound(src, shoot_sound, 50, TRUE) - A.preparePixelProjectile(target, src) - A.fire() - -/mob/living/simple_animal/bot/ed209/attack_alien(mob/living/carbon/alien/user) - ..() - if(!isalien(target)) - target = user - mode = BOT_HUNT - - -/mob/living/simple_animal/bot/ed209/emp_act(severity) - if(severity == 2 && prob(70)) - severity = 1 - . = ..() - if(. & EMP_PROTECT_SELF) - return - if (severity >= 2) - new /obj/effect/temp_visual/emp(loc) - var/list/mob/living/carbon/targets = new - for(var/mob/living/carbon/C in view(12,src)) - if(C.stat==DEAD) - continue - targets += C - if(targets.len) - if(prob(50)) - var/mob/toshoot = pick(targets) - if(toshoot) - targets-=toshoot - if(prob(50) && emagged < 2) - emagged = 2 - set_weapon() - shootAt(toshoot) - emagged = FALSE - set_weapon() - else - shootAt(toshoot) - else if(prob(50)) - if(targets.len) - var/mob/toarrest = pick(targets) - if(toarrest) - target = toarrest - mode = BOT_HUNT - - -/mob/living/simple_animal/bot/ed209/bullet_act(obj/item/projectile/Proj) - if(!disabled) - var/lasertag_check = 0 - if((lasercolor == "b")) - if(istype(Proj, /obj/item/projectile/beam/lasertag/redtag)) - lasertag_check++ - else if((lasercolor == "r")) - if(istype(Proj, /obj/item/projectile/beam/lasertag/bluetag)) - lasertag_check++ - if(lasertag_check) - icon_state = "[lasercolor]ed2090" - disabled = 1 - target = null - spawn(100) - disabled = 0 - icon_state = "[lasercolor]ed2091" - return 1 - else - ..(Proj) - else - ..(Proj) - -/mob/living/simple_animal/bot/ed209/bluetag - lasercolor = "b" - -/mob/living/simple_animal/bot/ed209/redtag - lasercolor = "r" - -/mob/living/simple_animal/bot/ed209/UnarmedAttack(atom/A) - if(!on) - return - if(iscarbon(A)) - var/mob/living/carbon/C = A - if(C.canmove || arrest_type) // CIT CHANGE - makes sentient ed209s check for canmove rather than !isstun. - stun_attack(A) - else if(C.canBeHandcuffed() && !C.handcuffed) - cuff(A) - else - ..() - -/mob/living/simple_animal/bot/ed209/RangedAttack(atom/A) - if(!on) - return - shootAt(A) - -/mob/living/simple_animal/bot/ed209/proc/stun_attack(mob/living/carbon/C) - playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1) - icon_state = "[lasercolor]ed209-c" - spawn(2) - icon_state = "[lasercolor]ed209[on]" - var/threat = 5 - C.Knockdown(100) - C.stuttering = 5 - if(ishuman(C)) - var/mob/living/carbon/human/H = C - var/judgement_criteria = judgement_criteria() - threat = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) - log_combat(src,C,"stunned") - if(declare_arrests) - var/area/location = get_area(src) - speak("[arrest_type ? "Detaining" : "Arresting"] level [threat] scumbag [C] in [location].", radio_channel) - C.visible_message("[src] has stunned [C]!",\ - "[src] has stunned you!") - -/mob/living/simple_animal/bot/ed209/proc/cuff(mob/living/carbon/C) - mode = BOT_ARREST - playsound(src, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2) - C.visible_message("[src] is trying to put zipties on [C]!",\ - "[src] is trying to put zipties on you!") - - spawn(60) - if( !on || !Adjacent(C) || !isturf(C.loc) ) //if he's in a closet or not adjacent, we cancel cuffing. - return - if(!C.handcuffed) - C.handcuffed = new /obj/item/restraints/handcuffs/cable/zipties/used(C) - C.update_handcuffed() - back_to_idle() +/mob/living/simple_animal/bot/ed209 + name = "\improper ED-209 Security Robot" + desc = "A security robot. He looks less than thrilled." + icon = 'icons/mob/aibots.dmi' + icon_state = "ed2090" + density = TRUE + anchored = FALSE + health = 100 + maxHealth = 100 + damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + obj_damage = 60 + environment_smash = ENVIRONMENT_SMASH_WALLS //Walls can't stop THE LAW + mob_size = MOB_SIZE_LARGE + + radio_key = /obj/item/encryptionkey/headset_sec + radio_channel = RADIO_CHANNEL_SECURITY + bot_type = SEC_BOT + model = "ED-209" + bot_core = /obj/machinery/bot_core/secbot + window_id = "autoed209" + window_name = "Automatic Security Unit v2.6" + allow_pai = 0 + data_hud_type = DATA_HUD_SECURITY_ADVANCED + path_image_color = "#FF0000" + + var/lastfired = 0 + var/shot_delay = 15 + var/lasercolor = "" + var/disabled = FALSE //A holder for if it needs to be disabled, if true it will not seach for targets, shoot at targets, or move, currently only used for lasertag + + + var/mob/living/carbon/target + var/oldtarget_name + var/threatlevel = 0 + var/target_lastloc //Loc of target when arrested. + var/last_found //There's a delay + var/declare_arrests = TRUE //When making an arrest, should it notify everyone wearing sechuds? + var/idcheck = TRUE //If true, arrest people with no IDs + var/weaponscheck = TRUE //If true, arrest people for weapons if they don't have access + var/check_records = TRUE //Does it check security records? + var/arrest_type = FALSE //If true, don't handcuff + var/projectile = /obj/item/projectile/energy/electrode //Holder for projectile type + var/shoot_sound = 'sound/weapons/taser.ogg' + var/cell_type = /obj/item/stock_parts/cell + var/vest_type = /obj/item/clothing/suit/armor/vest + + do_footstep = TRUE + + +/mob/living/simple_animal/bot/ed209/Initialize(mapload,created_name,created_lasercolor) + . = ..() + if(created_name) + name = created_name + if(created_lasercolor) + lasercolor = created_lasercolor + icon_state = "[lasercolor]ed209[on]" + set_weapon() //giving it the right projectile and firing sound. + spawn(3) + var/datum/job/detective/J = new/datum/job/detective + access_card.access += J.get_access() + prev_access = access_card.access + + if(lasercolor) + shot_delay = 6//Longer shot delay because JESUS CHRIST + check_records = 0//Don't actively target people set to arrest + arrest_type = 1//Don't even try to cuff + bot_core.req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) + arrest_type = 1 + if((lasercolor == "b") && (name == "\improper ED-209 Security Robot"))//Picks a name if there isn't already a custome one + name = pick("BLUE BALLER","SANIC","BLUE KILLDEATH MURDERBOT") + if((lasercolor == "r") && (name == "\improper ED-209 Security Robot")) + name = pick("RED RAMPAGE","RED ROVER","RED KILLDEATH MURDERBOT") + + //SECHUD + var/datum/atom_hud/secsensor = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] + secsensor.add_hud_to(src) + +/mob/living/simple_animal/bot/ed209/turn_on() + . = ..() + icon_state = "[lasercolor]ed209[on]" + mode = BOT_IDLE + +/mob/living/simple_animal/bot/ed209/turn_off() + ..() + icon_state = "[lasercolor]ed209[on]" + +/mob/living/simple_animal/bot/ed209/bot_reset() + ..() + target = null + oldtarget_name = null + anchored = FALSE + walk_to(src,0) + last_found = world.time + set_weapon() + +/mob/living/simple_animal/bot/ed209/set_custom_texts() + text_hack = "You disable [name]'s combat inhibitor." + text_dehack = "You restore [name]'s combat inhibitor." + text_dehack_fail = "[name] ignores your attempts to restrict him!" + +/mob/living/simple_animal/bot/ed209/get_controls(mob/user) + var/dat + dat += hack(user) + dat += showpai(user) + dat += text({" +Security Unit v2.6 controls

                +Status: []
                +Behaviour controls are [locked ? "locked" : "unlocked"]
                +Maintenance panel panel is [open ? "opened" : "closed"]
                "}, + +"[on ? "On" : "Off"]" ) + + if(!locked || issilicon(user)|| IsAdminGhost(user)) + if(!lasercolor) + dat += text({"
                +Arrest Unidentifiable Persons: []
                +Arrest for Unauthorized Weapons: []
                +Arrest for Warrant: []
                +
                +Operating Mode: []
                +Report Arrests[]
                +Auto Patrol[]"}, + +"[idcheck ? "Yes" : "No"]", +"[weaponscheck ? "Yes" : "No"]", +"[check_records ? "Yes" : "No"]", +"[arrest_type ? "Detain" : "Arrest"]", +"[declare_arrests ? "Yes" : "No"]", +"[auto_patrol ? "On" : "Off"]" ) + + return dat + +/mob/living/simple_animal/bot/ed209/Topic(href, href_list) + if(lasercolor && ishuman(usr)) + var/mob/living/carbon/human/H = usr + if((lasercolor == "b") && (istype(H.wear_suit, /obj/item/clothing/suit/redtag)))//Opposing team cannot operate it + return + else if((lasercolor == "r") && (istype(H.wear_suit, /obj/item/clothing/suit/bluetag))) + return + if(..()) + return 1 + + switch(href_list["operation"]) + if("idcheck") + idcheck = !idcheck + update_controls() + if("weaponscheck") + weaponscheck = !weaponscheck + update_controls() + if("ignorerec") + check_records = !check_records + update_controls() + if("switchmode") + arrest_type = !arrest_type + update_controls() + if("declarearrests") + declare_arrests = !declare_arrests + update_controls() + +/mob/living/simple_animal/bot/ed209/proc/judgement_criteria() + var/final = FALSE + if(idcheck) + final = final|JUDGE_IDCHECK + if(check_records) + final = final|JUDGE_RECORDCHECK + if(weaponscheck) + final = final|JUDGE_WEAPONCHECK + if(emagged == 2) + final = final|JUDGE_EMAGGED + //ED209's ignore monkeys + final = final|JUDGE_IGNOREMONKEYS + return final + +/mob/living/simple_animal/bot/ed209/proc/retaliate(mob/living/carbon/human/H) + var/judgement_criteria = judgement_criteria() + threatlevel = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) + threatlevel += 6 + if(threatlevel >= 4) + target = H + mode = BOT_HUNT + +/mob/living/simple_animal/bot/ed209/attack_hand(mob/living/carbon/human/H) + if(H.a_intent == INTENT_HARM) + retaliate(H) + return ..() + +/mob/living/simple_animal/bot/ed209/attackby(obj/item/W, mob/user, params) + ..() + if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. + return + if(!istype(W, /obj/item/screwdriver) && (!target)) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. + if(W.force && W.damtype != STAMINA)//If force is non-zero and damage type isn't stamina. + retaliate(user) + if(lasercolor)//To make up for the fact that lasertag bots don't hunt + shootAt(user) + +/mob/living/simple_animal/bot/ed209/emag_act(mob/user) + . = ..() + if(emagged == 2) + if(user) + to_chat(user, "You short out [src]'s target assessment circuits.") + oldtarget_name = user.name + audible_message("[src] buzzes oddly!") + declare_arrests = FALSE + icon_state = "[lasercolor]ed209[on]" + set_weapon() + +/mob/living/simple_animal/bot/ed209/bullet_act(obj/item/projectile/Proj) + if(istype(Proj , /obj/item/projectile/beam/laser)||istype(Proj, /obj/item/projectile/bullet)) + if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)) + if(!Proj.nodamage && Proj.damage < src.health && ishuman(Proj.firer)) + retaliate(Proj.firer) + ..() + +/mob/living/simple_animal/bot/ed209/handle_automated_action() + if(!..()) + return + + if(disabled) + return + + var/judgement_criteria = judgement_criteria() + var/list/targets = list() + for(var/mob/living/carbon/C in view(7,src)) //Let's find us a target + var/threatlevel = 0 + if((C.stat) || (C.lying)) + continue + threatlevel = C.assess_threat(judgement_criteria, lasercolor, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) + //speak(C.real_name + text(": threat: []", threatlevel)) + if(threatlevel < 4 ) + continue + + var/dst = get_dist(src, C) + if(dst <= 1 || dst > 7) + continue + + targets += C + if(targets.len>0) + var/mob/living/carbon/t = pick(targets) + if((t.stat!=2) && (t.lying != 1) && (!t.handcuffed)) //we don't shoot people who are dead, cuffed or lying down. + shootAt(t) + switch(mode) + + if(BOT_IDLE) // idle + walk_to(src,0) + if(!lasercolor) //lasertag bots don't want to arrest anyone + look_for_perp() // see if any criminals are in range + if(!mode && auto_patrol) // still idle, and set to patrol + mode = BOT_START_PATROL // switch to patrol mode + + if(BOT_HUNT) // hunting for perp + // if can't reach perp for long enough, go idle + if(frustration >= 8) + walk_to(src,0) + back_to_idle() + + if(target) // make sure target exists + if(Adjacent(target) && isturf(target.loc)) // if right next to perp + stun_attack(target) + + mode = BOT_PREP_ARREST + anchored = TRUE + target_lastloc = target.loc + return + + else // not next to perp + var/turf/olddist = get_dist(src, target) + walk_to(src, target,1,4) + if((get_dist(src, target)) >= (olddist)) + frustration++ + else + frustration = 0 + else + back_to_idle() + + if(BOT_PREP_ARREST) // preparing to arrest target + + // see if he got away. If he's no no longer adjacent or inside a closet or about to get up, we hunt again. + if(!Adjacent(target) || !isturf(target.loc) || !target.recoveringstam || target.getStaminaLoss() <= 120) // CIT CHANGE - replaces amountknockdown with recoveringstam and staminaloss checks + back_to_hunt() + return + + if(iscarbon(target) && target.canBeHandcuffed()) + if(!arrest_type) + if(!target.handcuffed) //he's not cuffed? Try to cuff him! + cuff(target) + else + back_to_idle() + return + else + back_to_idle() + return + + if(BOT_ARREST) + if(!target) + anchored = FALSE + mode = BOT_IDLE + last_found = world.time + frustration = 0 + return + + if(target.handcuffed) //no target or target cuffed? back to idle. + back_to_idle() + return + + if(!Adjacent(target) || !isturf(target.loc) || (target.loc != target_lastloc && !target.recoveringstam && target.getStaminaLoss() <= 120)) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. CIT CHANGE - replaces amountknockdown with recoveringstam and staminaloss checks + back_to_hunt() + return + else + mode = BOT_PREP_ARREST + anchored = FALSE + + if(BOT_START_PATROL) + look_for_perp() + start_patrol() + + if(BOT_PATROL) + look_for_perp() + bot_patrol() + + + return + +/mob/living/simple_animal/bot/ed209/proc/back_to_idle() + anchored = FALSE + mode = BOT_IDLE + target = null + last_found = world.time + frustration = 0 + INVOKE_ASYNC(src, .proc/handle_automated_action) //ensure bot quickly responds + +/mob/living/simple_animal/bot/ed209/proc/back_to_hunt() + anchored = FALSE + frustration = 0 + mode = BOT_HUNT + INVOKE_ASYNC(src, .proc/handle_automated_action) //ensure bot quickly responds + +// look for a criminal in view of the bot + +/mob/living/simple_animal/bot/ed209/proc/look_for_perp() + if(disabled) + return + anchored = FALSE + threatlevel = 0 + var/judgement_criteria = judgement_criteria() + for (var/mob/living/carbon/C in view(7,src)) //Let's find us a criminal + if((C.stat) || (C.handcuffed)) + continue + + if((C.name == oldtarget_name) && (world.time < last_found + 100)) + continue + + threatlevel = C.assess_threat(judgement_criteria, lasercolor, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) + + if(!threatlevel) + continue + + else if(threatlevel >= 4) + target = C + oldtarget_name = C.name + speak("Level [threatlevel] infraction alert!") + playsound(src, pick('sound/voice/ed209_20sec.ogg', 'sound/voice/edplaceholder.ogg'), 50, FALSE) + visible_message("[src] points at [C.name]!") + mode = BOT_HUNT + spawn(0) + handle_automated_action() // ensure bot quickly responds to a perp + break + else + continue + +/mob/living/simple_animal/bot/ed209/proc/check_for_weapons(var/obj/item/slot_item) + if(slot_item && (slot_item.item_flags & NEEDS_PERMIT)) + return 1 + return 0 + +/mob/living/simple_animal/bot/ed209/explode() + walk_to(src,0) + visible_message("[src] blows apart!") + var/atom/Tsec = drop_location() + + var/obj/item/bot_assembly/ed209/Sa = new (Tsec) + Sa.build_step = ASSEMBLY_SECOND_STEP + Sa.add_overlay("hs_hole") + Sa.created_name = name + new /obj/item/assembly/prox_sensor(Tsec) + drop_part(cell_type, Tsec) + + if(!lasercolor) + var/obj/item/gun/energy/e_gun/advtaser/G = new (Tsec) + G.cell.charge = 0 + G.update_icon() + else if(lasercolor == "b") + var/obj/item/gun/energy/laser/bluetag/G = new (Tsec) + G.cell.charge = 0 + G.update_icon() + else if(lasercolor == "r") + var/obj/item/gun/energy/laser/redtag/G = new (Tsec) + G.cell.charge = 0 + G.update_icon() + + if(prob(50)) + new /obj/item/bodypart/l_leg/robot(Tsec) + if(prob(25)) + new /obj/item/bodypart/r_leg/robot(Tsec) + if(prob(25))//50% chance for a helmet OR vest + if(prob(50)) + new /obj/item/clothing/head/helmet(Tsec) + else + if(!lasercolor) + drop_part(vest_type, Tsec) + if(lasercolor == "b") + new /obj/item/clothing/suit/bluetag(Tsec) + if(lasercolor == "r") + new /obj/item/clothing/suit/redtag(Tsec) + + do_sparks(3, TRUE, src) + + new /obj/effect/decal/cleanable/oil(loc) + ..() + +/mob/living/simple_animal/bot/ed209/proc/set_weapon() //used to update the projectile type and firing sound + shoot_sound = 'sound/weapons/laser.ogg' + if(emagged == 2) + if(lasercolor) + projectile = /obj/item/projectile/beam/lasertag + else + projectile = /obj/item/projectile/beam + else + if(!lasercolor) + shoot_sound = 'sound/weapons/taser.ogg' + projectile = /obj/item/projectile/energy/electrode + else if(lasercolor == "b") + projectile = /obj/item/projectile/beam/lasertag/bluetag + else if(lasercolor == "r") + projectile = /obj/item/projectile/beam/lasertag/redtag + +/mob/living/simple_animal/bot/ed209/proc/shootAt(mob/target) + if(lastfired && world.time - lastfired < shot_delay) + return + lastfired = world.time + var/turf/T = loc + var/turf/U = get_turf(target) + if(!U) + return + if(!isturf(T)) + return + + if(!projectile) + return + + var/obj/item/projectile/A = new projectile (loc) + playsound(src, shoot_sound, 50, TRUE) + A.preparePixelProjectile(target, src) + A.fire() + +/mob/living/simple_animal/bot/ed209/attack_alien(mob/living/carbon/alien/user) + ..() + if(!isalien(target)) + target = user + mode = BOT_HUNT + + +/mob/living/simple_animal/bot/ed209/emp_act(severity) + if(severity == 2 && prob(70)) + severity = 1 + . = ..() + if(. & EMP_PROTECT_SELF) + return + if (severity >= 2) + new /obj/effect/temp_visual/emp(loc) + var/list/mob/living/carbon/targets = new + for(var/mob/living/carbon/C in view(12,src)) + if(C.stat==DEAD) + continue + targets += C + if(targets.len) + if(prob(50)) + var/mob/toshoot = pick(targets) + if(toshoot) + targets-=toshoot + if(prob(50) && emagged < 2) + emagged = 2 + set_weapon() + shootAt(toshoot) + emagged = FALSE + set_weapon() + else + shootAt(toshoot) + else if(prob(50)) + if(targets.len) + var/mob/toarrest = pick(targets) + if(toarrest) + target = toarrest + mode = BOT_HUNT + + +/mob/living/simple_animal/bot/ed209/bullet_act(obj/item/projectile/Proj) + if(!disabled) + var/lasertag_check = 0 + if((lasercolor == "b")) + if(istype(Proj, /obj/item/projectile/beam/lasertag/redtag)) + lasertag_check++ + else if((lasercolor == "r")) + if(istype(Proj, /obj/item/projectile/beam/lasertag/bluetag)) + lasertag_check++ + if(lasertag_check) + icon_state = "[lasercolor]ed2090" + disabled = 1 + target = null + spawn(100) + disabled = 0 + icon_state = "[lasercolor]ed2091" + return 1 + else + ..(Proj) + else + ..(Proj) + +/mob/living/simple_animal/bot/ed209/bluetag + lasercolor = "b" + +/mob/living/simple_animal/bot/ed209/redtag + lasercolor = "r" + +/mob/living/simple_animal/bot/ed209/UnarmedAttack(atom/A) + if(!on) + return + if(iscarbon(A)) + var/mob/living/carbon/C = A + if(C.canmove || arrest_type) // CIT CHANGE - makes sentient ed209s check for canmove rather than !isstun. + stun_attack(A) + else if(C.canBeHandcuffed() && !C.handcuffed) + cuff(A) + else + ..() + +/mob/living/simple_animal/bot/ed209/RangedAttack(atom/A) + if(!on) + return + shootAt(A) + +/mob/living/simple_animal/bot/ed209/proc/stun_attack(mob/living/carbon/C) + playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1) + icon_state = "[lasercolor]ed209-c" + spawn(2) + icon_state = "[lasercolor]ed209[on]" + var/threat = 5 + C.Knockdown(100) + C.stuttering = 5 + if(ishuman(C)) + var/mob/living/carbon/human/H = C + var/judgement_criteria = judgement_criteria() + threat = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) + log_combat(src,C,"stunned") + if(declare_arrests) + var/area/location = get_area(src) + speak("[arrest_type ? "Detaining" : "Arresting"] level [threat] scumbag [C] in [location].", radio_channel) + C.visible_message("[src] has stunned [C]!",\ + "[src] has stunned you!") + +/mob/living/simple_animal/bot/ed209/proc/cuff(mob/living/carbon/C) + mode = BOT_ARREST + playsound(src, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2) + C.visible_message("[src] is trying to put zipties on [C]!",\ + "[src] is trying to put zipties on you!") + + spawn(60) + if( !on || !Adjacent(C) || !isturf(C.loc) ) //if he's in a closet or not adjacent, we cancel cuffing. + return + if(!C.handcuffed) + C.handcuffed = new /obj/item/restraints/handcuffs/cable/zipties/used(C) + C.update_handcuffed() + back_to_idle() diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm index 13e9299b1d..a4496dd1f5 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -1,567 +1,567 @@ -//MEDBOT -//MEDBOT PATHFINDING -//MEDBOT ASSEMBLY - - -/mob/living/simple_animal/bot/medbot - name = "\improper Medibot" - desc = "A little medical robot. He looks somewhat underwhelmed." - icon = 'icons/mob/aibots.dmi' - icon_state = "medibot0" - density = FALSE - anchored = FALSE - health = 20 - maxHealth = 20 - pass_flags = PASSMOB - - status_flags = (CANPUSH | CANSTUN) - - radio_key = /obj/item/encryptionkey/headset_med - radio_channel = RADIO_CHANNEL_MEDICAL - - bot_type = MED_BOT - model = "Medibot" - bot_core_type = /obj/machinery/bot_core/medbot - window_id = "automed" - window_name = "Automatic Medical Unit v1.1" - data_hud_type = DATA_HUD_MEDICAL_ADVANCED - path_image_color = "#DDDDFF" - - var/obj/item/reagent_containers/glass/reagent_glass = null //Can be set to draw from this for reagents. - var/healthanalyzer = /obj/item/healthanalyzer - var/firstaid = /obj/item/storage/firstaid - var/skin = null //Set to "tox", "ointment" or "o2" for the other two firstaid kits. - var/mob/living/carbon/patient = null - var/mob/living/carbon/oldpatient = null - var/oldloc = null - var/last_found = 0 - var/last_newpatient_speak = 0 //Don't spam the "HEY I'M COMING" messages - var/injection_amount = 15 //How much reagent do we inject at a time? - var/heal_threshold = 10 //Start healing when they have this much damage in a category - var/use_beaker = 0 //Use reagents in beaker instead of default treatment agents. - var/declare_crit = 1 //If active, the bot will transmit a critical patient alert to MedHUD users. - var/declare_cooldown = 0 //Prevents spam of critical patient alerts. - var/stationary_mode = 0 //If enabled, the Medibot will not move automatically. - //Setting which reagents to use to treat what by default. By id. - var/treatment_brute_avoid = /datum/reagent/medicine/tricordrazine - var/treatment_brute = /datum/reagent/medicine/bicaridine - var/treatment_oxy_avoid = null - var/treatment_oxy = /datum/reagent/medicine/dexalin - var/treatment_fire_avoid = /datum/reagent/medicine/tricordrazine - var/treatment_fire = /datum/reagent/medicine/kelotane - var/treatment_tox_avoid = /datum/reagent/medicine/tricordrazine - var/treatment_tox = /datum/reagent/medicine/charcoal - var/treatment_tox_toxlover = /datum/reagent/toxin - var/treatment_virus_avoid = null - var/treatment_virus = /datum/reagent/medicine/spaceacillin - var/treat_virus = 1 //If on, the bot will attempt to treat viral infections, curing them if possible. - var/shut_up = 0 //self explanatory :) - -/mob/living/simple_animal/bot/medbot/mysterious - name = "\improper Mysterious Medibot" - desc = "International Medibot of mystery." - skin = "bezerk" - treatment_brute = /datum/reagent/medicine/tricordrazine - treatment_fire = /datum/reagent/medicine/tricordrazine - treatment_tox = /datum/reagent/medicine/tricordrazine - -/mob/living/simple_animal/bot/medbot/derelict - name = "\improper Old Medibot" - desc = "Looks like it hasn't been modified since the late 2080s." - skin = "bezerk" - heal_threshold = 0 - declare_crit = 0 - treatment_oxy = /datum/reagent/toxin/pancuronium - treatment_brute_avoid = null - treatment_brute = /datum/reagent/toxin/pancuronium - treatment_fire_avoid = null - treatment_fire = /datum/reagent/toxin/sodium_thiopental - treatment_tox_avoid = null - treatment_tox = /datum/reagent/toxin/sodium_thiopental - -/mob/living/simple_animal/bot/medbot/update_icon() - cut_overlays() - if(skin) - add_overlay("medskin_[skin]") - if(!on) - icon_state = "medibot0" - return - if(IsStun()) - icon_state = "medibota" - return - if(mode == BOT_HEALING) - icon_state = "medibots[stationary_mode]" - return - else if(stationary_mode) //Bot has yellow light to indicate stationary mode. - icon_state = "medibot2" - else - icon_state = "medibot1" - -/mob/living/simple_animal/bot/medbot/Initialize(mapload, new_skin) - . = ..() - var/datum/job/doctor/J = new /datum/job/doctor - access_card.access += J.get_access() - prev_access = access_card.access - qdel(J) - skin = new_skin - update_icon() - -/mob/living/simple_animal/bot/medbot/update_canmove() - . = ..() - update_icon() - -/mob/living/simple_animal/bot/medbot/bot_reset() - ..() - patient = null - oldpatient = null - oldloc = null - last_found = world.time - declare_cooldown = 0 - update_icon() - -/mob/living/simple_animal/bot/medbot/proc/soft_reset() //Allows the medibot to still actively perform its medical duties without being completely halted as a hard reset does. - path = list() - patient = null - mode = BOT_IDLE - last_found = world.time - update_icon() - -/mob/living/simple_animal/bot/medbot/set_custom_texts() - - text_hack = "You corrupt [name]'s reagent processor circuits." - text_dehack = "You reset [name]'s reagent processor circuits." - text_dehack_fail = "[name] seems damaged and does not respond to reprogramming!" - -/mob/living/simple_animal/bot/medbot/attack_paw(mob/user) - return attack_hand(user) - -/mob/living/simple_animal/bot/medbot/get_controls(mob/user) - var/dat - dat += hack(user) - dat += showpai(user) - dat += "Medical Unit Controls v1.1

                " - dat += "Status: [on ? "On" : "Off"]
                " - dat += "Maintenance panel panel is [open ? "opened" : "closed"]
                " - dat += "Beaker: " - if(reagent_glass) - dat += "Loaded \[[reagent_glass.reagents.total_volume]/[reagent_glass.reagents.maximum_volume]\]" - else - dat += "None Loaded" - dat += "
                Behaviour controls are [locked ? "locked" : "unlocked"]
                " - if(!locked || issilicon(user) || IsAdminGhost(user)) - dat += "Healing Threshold: " - dat += "-- " - dat += "- " - dat += "[heal_threshold] " - dat += "+ " - dat += "++" - dat += "
                " - - dat += "Injection Level: " - dat += "- " - dat += "[injection_amount] " - dat += "+ " - dat += "
                " - - dat += "Reagent Source: " - dat += "[use_beaker ? "Loaded Beaker (When available)" : "Internal Synthesizer"]
                " - - dat += "Treat Viral Infections: [treat_virus ? "Yes" : "No"]
                " - dat += "The speaker switch is [shut_up ? "off" : "on"]. Toggle
                " - dat += "Critical Patient Alerts: [declare_crit ? "Yes" : "No"]
                " - dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
                " - dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
                " - - return dat - -/mob/living/simple_animal/bot/medbot/Topic(href, href_list) - if(..()) - return 1 - - if(href_list["adj_threshold"]) - var/adjust_num = text2num(href_list["adj_threshold"]) - heal_threshold += adjust_num - if(heal_threshold < 5) - heal_threshold = 5 - if(heal_threshold > 75) - heal_threshold = 75 - - else if(href_list["adj_inject"]) - var/adjust_num = text2num(href_list["adj_inject"]) - injection_amount += adjust_num - if(injection_amount < 5) - injection_amount = 5 - if(injection_amount > 15) - injection_amount = 15 - - else if(href_list["use_beaker"]) - use_beaker = !use_beaker - - else if(href_list["eject"] && (!isnull(reagent_glass))) - reagent_glass.forceMove(drop_location()) - reagent_glass = null - - else if(href_list["togglevoice"]) - shut_up = !shut_up - - else if(href_list["critalerts"]) - declare_crit = !declare_crit - - else if(href_list["stationary"]) - stationary_mode = !stationary_mode - path = list() - update_icon() - - else if(href_list["virus"]) - treat_virus = !treat_virus - - update_controls() - return - -/mob/living/simple_animal/bot/medbot/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/reagent_containers/glass)) - . = 1 //no afterattack - if(locked) - to_chat(user, "You cannot insert a beaker because the panel is locked!") - return - if(!isnull(reagent_glass)) - to_chat(user, "There is already a beaker loaded!") - return - if(!user.transferItemToLoc(W, src)) - return - - reagent_glass = W - to_chat(user, "You insert [W].") - show_controls(user) - - else - var/current_health = health - ..() - if(health < current_health) //if medbot took some damage - step_to(src, (get_step_away(src,user))) - -/mob/living/simple_animal/bot/medbot/emag_act(mob/user) - . = ..() - if(emagged == 2) - declare_crit = 0 - if(user) - to_chat(user, "You short out [src]'s reagent synthesis circuits.") - audible_message("[src] buzzes oddly!") - flick("medibot_spark", src) - playsound(src, "sparks", 75, 1) - if(user) - oldpatient = user - -/mob/living/simple_animal/bot/medbot/process_scan(mob/living/carbon/human/H) - if(H.stat == DEAD) - return - - if((H == oldpatient) && (world.time < last_found + 200)) - return - - if(assess_patient(H)) - last_found = world.time - if((last_newpatient_speak + 300) < world.time) //Don't spam these messages! - var/list/messagevoice = list("Hey, [H.name]! Hold on, I'm coming." = 'sound/voice/medbot/coming.ogg',"Wait [H.name]! I want to help!" = 'sound/voice/medbot/help.ogg',"[H.name], you appear to be injured!" = 'sound/voice/medbot/injured.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(loc, messagevoice[message], 50, 0) - last_newpatient_speak = world.time - return H - else - return - -/mob/living/simple_animal/bot/medbot/handle_automated_action() - if(!..()) - return - - if(mode == BOT_HEALING) - return - - if(IsStun()) - oldpatient = patient - patient = null - mode = BOT_IDLE - return - - if(frustration > 8) - oldpatient = patient - soft_reset() - - if(QDELETED(patient)) - if(!shut_up && prob(1)) - var/list/messagevoice = list("Radar, put a mask on!" = 'sound/voice/medbot/radar.ogg',"There's always a catch, and I'm the best there is." = 'sound/voice/medbot/catch.ogg',"I knew it, I should've been a plastic surgeon." = 'sound/voice/medbot/surgeon.ogg',"What kind of medbay is this? Everyone's dropping like flies." = 'sound/voice/medbot/flies.ogg',"Delicious!" = 'sound/voice/medbot/delicious.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(loc, messagevoice[message], 50, 0) - var/scan_range = (stationary_mode ? 1 : DEFAULT_SCAN_RANGE) //If in stationary mode, scan range is limited to adjacent patients. - patient = scan(/mob/living/carbon/human, oldpatient, scan_range) - oldpatient = patient - - if(patient && (get_dist(src,patient) <= 1)) //Patient is next to us, begin treatment! - if(mode != BOT_HEALING) - mode = BOT_HEALING - update_icon() - frustration = 0 - medicate_patient(patient) - return - - //Patient has moved away from us! - else if(patient && path.len && (get_dist(patient,path[path.len]) > 2)) - path = list() - mode = BOT_IDLE - last_found = world.time - - else if(stationary_mode && patient) //Since we cannot move in this mode, ignore the patient and wait for another. - soft_reset() - return - - if(patient && path.len == 0 && (get_dist(src,patient) > 1)) - path = get_path_to(src, get_turf(patient), /turf/proc/Distance_cardinal, 0, 30,id=access_card) - mode = BOT_MOVING - if(!path.len) //try to get closer if you can't reach the patient directly - path = get_path_to(src, get_turf(patient), /turf/proc/Distance_cardinal, 0, 30,1,id=access_card) - if(!path.len) //Do not chase a patient we cannot reach. - soft_reset() - - if(path.len > 0 && patient) - if(!bot_move(path[path.len])) - oldpatient = patient - soft_reset() - return - - if(path.len > 8 && patient) - frustration++ - - if(auto_patrol && !stationary_mode && !patient) - if(mode == BOT_IDLE || mode == BOT_START_PATROL) - start_patrol() - - if(mode == BOT_PATROL) - bot_patrol() - - return - -/mob/living/simple_animal/bot/medbot/proc/assess_patient(mob/living/carbon/C) - //Time to see if they need medical help! - if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) - return FALSE //welp too late for them! - - if(!(loc == C.loc) && !(isturf(C.loc) && isturf(loc))) - return FALSE - - if(C.suiciding) - return FALSE //Kevorkian school of robotic medical assistants. - - if(emagged == 2) //Everyone needs our medicine. (Our medicine is toxins) - return TRUE - - if(ishuman(C)) - var/mob/living/carbon/human/H = C - if (H.wear_suit && H.head && istype(H.wear_suit, /obj/item/clothing) && istype(H.head, /obj/item/clothing)) - var/obj/item/clothing/CS = H.wear_suit - var/obj/item/clothing/CH = H.head - if (CS.clothing_flags & CH.clothing_flags & THICKMATERIAL) - return FALSE // Skip over them if they have no exposed flesh. - - if(declare_crit && C.health <= 0) //Critical condition! Call for help! - declare(C) - - //If they're injured, we're using a beaker, and don't have one of our WONDERCHEMS. - if((reagent_glass) && (use_beaker) && ((C.getBruteLoss() >= heal_threshold) || (C.getToxLoss() >= heal_threshold) || (C.getToxLoss() >= heal_threshold) || (C.getOxyLoss() >= (heal_threshold + 15)))) - for(var/A in reagent_glass.reagents.reagent_list) - var/datum/reagent/R = A - if(!C.reagents.has_reagent(R.type)) - return TRUE - - //They're injured enough for it! - if((!C.reagents.has_reagent(treatment_brute_avoid)) && (C.getBruteLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_brute))) - return TRUE //If they're already medicated don't bother! - - if((!C.reagents.has_reagent(treatment_oxy_avoid)) && (C.getOxyLoss() >= (15 + heal_threshold)) && (!C.reagents.has_reagent(treatment_oxy))) - return TRUE - - if((!C.reagents.has_reagent(treatment_fire_avoid)) && (C.getFireLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_fire))) - return TRUE - var/treatment_toxavoid = get_avoidchem_toxin(C) - if(((isnull(treatment_toxavoid) || !C.reagents.has_reagent(treatment_toxavoid))) && (C.getToxLoss() >= heal_threshold) && (!C.reagents.has_reagent(get_healchem_toxin(C)))) - return TRUE - - if(treat_virus && !C.reagents.has_reagent(treatment_virus_avoid) && !C.reagents.has_reagent(treatment_virus)) - for(var/thing in C.diseases) - var/datum/disease/D = thing - //the medibot can't detect viruses that are undetectable to Health Analyzers or Pandemic machines. - if(!(D.visibility_flags & HIDDEN_SCANNER || D.visibility_flags & HIDDEN_PANDEMIC) \ - && D.severity != DISEASE_SEVERITY_POSITIVE \ - && (D.stage > 1 || (D.spread_flags & DISEASE_SPREAD_AIRBORNE))) // medibot can't detect a virus in its initial stage unless it spreads airborne. - return TRUE //STOP DISEASE FOREVER - - return FALSE - -/mob/living/simple_animal/bot/medbot/proc/get_avoidchem_toxin(mob/M) - return HAS_TRAIT(M, TRAIT_TOXINLOVER)? null : treatment_tox_avoid - -/mob/living/simple_animal/bot/medbot/proc/get_healchem_toxin(mob/M) - return HAS_TRAIT(M, TRAIT_TOXINLOVER)? treatment_tox_toxlover : treatment_tox - -/mob/living/simple_animal/bot/medbot/UnarmedAttack(atom/A) - if(iscarbon(A)) - var/mob/living/carbon/C = A - patient = C - mode = BOT_HEALING - update_icon() - medicate_patient(C) - update_icon() - else - ..() - -/mob/living/simple_animal/bot/medbot/examinate(atom/A as mob|obj|turf in view()) - ..() - if(!is_blind(src)) - chemscan(src, A) - -/mob/living/simple_animal/bot/medbot/proc/medicate_patient(mob/living/carbon/C) - if(!on) - return - - if(!istype(C)) - oldpatient = patient - soft_reset() - return - - if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) - var/list/messagevoice = list("No! Stay with me!" = 'sound/voice/medbot/no.ogg',"Live, damnit! LIVE!" = 'sound/voice/medbot/live.ogg',"I...I've never lost a patient before. Not today, I mean." = 'sound/voice/medbot/lost.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(loc, messagevoice[message], 50, 0) - oldpatient = patient - soft_reset() - return - - var/reagent_id = null - - if(emagged == 2) //Emagged! Time to poison everybody. - reagent_id = HAS_TRAIT(C, TRAIT_TOXINLOVER)? "charcoal" : "toxin" - - else - if(treat_virus) - var/virus = 0 - for(var/thing in C.diseases) - var/datum/disease/D = thing - //detectable virus - if((!(D.visibility_flags & HIDDEN_SCANNER)) || (!(D.visibility_flags & HIDDEN_PANDEMIC))) - if(D.severity != DISEASE_SEVERITY_POSITIVE) //virus is harmful - if((D.stage > 1) || (D.spread_flags & DISEASE_SPREAD_AIRBORNE)) - virus = 1 - - if(!reagent_id && (virus)) - if(!C.reagents.has_reagent(treatment_virus) && !C.reagents.has_reagent(treatment_virus_avoid)) - reagent_id = treatment_virus - - if(!reagent_id && (C.getBruteLoss() >= heal_threshold)) - if(!C.reagents.has_reagent(treatment_brute) && !C.reagents.has_reagent(treatment_brute_avoid)) - reagent_id = treatment_brute - - if(!reagent_id && (C.getOxyLoss() >= (15 + heal_threshold))) - if(!C.reagents.has_reagent(treatment_oxy) && !C.reagents.has_reagent(treatment_oxy_avoid)) - reagent_id = treatment_oxy - - if(!reagent_id && (C.getFireLoss() >= heal_threshold)) - if(!C.reagents.has_reagent(treatment_fire) && !C.reagents.has_reagent(treatment_fire_avoid)) - reagent_id = treatment_fire - - if(!reagent_id && (C.getToxLoss() >= heal_threshold)) - var/toxin_heal_avoid = get_avoidchem_toxin(C) - var/toxin_healchem = get_healchem_toxin(C) - if(!C.reagents.has_reagent(toxin_healchem) && (isnull(toxin_heal_avoid) || !C.reagents.has_reagent(toxin_heal_avoid))) - reagent_id = toxin_healchem - - //If the patient is injured but doesn't have our special reagent in them then we should give it to them first - if(reagent_id && use_beaker && reagent_glass && reagent_glass.reagents.total_volume) - for(var/A in reagent_glass.reagents.reagent_list) - var/datum/reagent/R = A - if(!C.reagents.has_reagent(R.type)) - reagent_id = "internal_beaker" - break - - if(!reagent_id) //If they don't need any of that they're probably cured! - if(C.maxHealth - C.health < heal_threshold) - to_chat(src, "[C] is healthy! Your programming prevents you from injecting anyone without at least [heal_threshold] damage of any one type ([heal_threshold + 15] for oxygen damage.)") - var/list/messagevoice = list("All patched up!" = 'sound/voice/medbot/patchedup.ogg',"An apple a day keeps me away." = 'sound/voice/medbot/apple.ogg',"Feel better soon!" = 'sound/voice/medbot/feelbetter.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(loc, messagevoice[message], 50, 0) - bot_reset() - return - else - if(!emagged && check_overdose(patient,reagent_id,injection_amount)) - soft_reset() - return - C.visible_message("[src] is trying to inject [patient]!", \ - "[src] is trying to inject you!") - - var/failed = FALSE - if(do_mob(src, patient, 30)) //Is C == patient? This is so confusing - if((get_dist(src, patient) <= 1) && (on) && assess_patient(patient)) - if(reagent_id == "internal_beaker") - if(use_beaker && reagent_glass && reagent_glass.reagents.total_volume) - var/fraction = min(injection_amount/reagent_glass.reagents.total_volume, 1) - reagent_glass.reagents.reaction(patient, INJECT, fraction) - reagent_glass.reagents.trans_to(patient,injection_amount) //Inject from beaker instead. - else - patient.reagents.add_reagent(reagent_id,injection_amount) - C.visible_message("[src] injects [patient] with its syringe!", \ - "[src] injects you with its syringe!") - else - failed = TRUE - else - failed = TRUE - - if(failed) - visible_message("[src] retracts its syringe.") - update_icon() - soft_reset() - return - - reagent_id = null - return - -/mob/living/simple_animal/bot/medbot/proc/check_overdose(mob/living/carbon/patient,reagent_id,injection_amount) - var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_id] - if(!R.overdose_threshold) //Some chems do not have an OD threshold - return FALSE - var/current_volume = patient.reagents.get_reagent_amount(reagent_id) - if(current_volume + injection_amount > R.overdose_threshold) - return TRUE - return FALSE - -/mob/living/simple_animal/bot/medbot/explode() - on = FALSE - visible_message("[src] blows apart!") - var/atom/Tsec = drop_location() - - drop_part(firstaid, Tsec) - new /obj/item/assembly/prox_sensor(Tsec) - drop_part(healthanalyzer, Tsec) - - if(reagent_glass) - drop_part(reagent_glass, Tsec) - - if(prob(50)) - drop_part(robot_arm, Tsec) - - if(emagged && prob(25)) - playsound(loc, 'sound/voice/medbot/insult.ogg', 50, 0) - - do_sparks(3, TRUE, src) - ..() - -/mob/living/simple_animal/bot/medbot/proc/declare(crit_patient) - if(declare_cooldown > world.time) - return - var/area/location = get_area(src) - speak("Medical emergency! [crit_patient ? "[crit_patient]" : "A patient"] is in critical condition at [location]!",radio_channel) - declare_cooldown = world.time + 200 - -/obj/machinery/bot_core/medbot - req_one_access = list(ACCESS_MEDICAL, ACCESS_ROBOTICS) +//MEDBOT +//MEDBOT PATHFINDING +//MEDBOT ASSEMBLY + + +/mob/living/simple_animal/bot/medbot + name = "\improper Medibot" + desc = "A little medical robot. He looks somewhat underwhelmed." + icon = 'icons/mob/aibots.dmi' + icon_state = "medibot0" + density = FALSE + anchored = FALSE + health = 20 + maxHealth = 20 + pass_flags = PASSMOB + + status_flags = (CANPUSH | CANSTUN) + + radio_key = /obj/item/encryptionkey/headset_med + radio_channel = RADIO_CHANNEL_MEDICAL + + bot_type = MED_BOT + model = "Medibot" + bot_core_type = /obj/machinery/bot_core/medbot + window_id = "automed" + window_name = "Automatic Medical Unit v1.1" + data_hud_type = DATA_HUD_MEDICAL_ADVANCED + path_image_color = "#DDDDFF" + + var/obj/item/reagent_containers/glass/reagent_glass = null //Can be set to draw from this for reagents. + var/healthanalyzer = /obj/item/healthanalyzer + var/firstaid = /obj/item/storage/firstaid + var/skin = null //Set to "tox", "ointment" or "o2" for the other two firstaid kits. + var/mob/living/carbon/patient = null + var/mob/living/carbon/oldpatient = null + var/oldloc = null + var/last_found = 0 + var/last_newpatient_speak = 0 //Don't spam the "HEY I'M COMING" messages + var/injection_amount = 15 //How much reagent do we inject at a time? + var/heal_threshold = 10 //Start healing when they have this much damage in a category + var/use_beaker = 0 //Use reagents in beaker instead of default treatment agents. + var/declare_crit = 1 //If active, the bot will transmit a critical patient alert to MedHUD users. + var/declare_cooldown = 0 //Prevents spam of critical patient alerts. + var/stationary_mode = 0 //If enabled, the Medibot will not move automatically. + //Setting which reagents to use to treat what by default. By id. + var/treatment_brute_avoid = /datum/reagent/medicine/tricordrazine + var/treatment_brute = /datum/reagent/medicine/bicaridine + var/treatment_oxy_avoid = null + var/treatment_oxy = /datum/reagent/medicine/dexalin + var/treatment_fire_avoid = /datum/reagent/medicine/tricordrazine + var/treatment_fire = /datum/reagent/medicine/kelotane + var/treatment_tox_avoid = /datum/reagent/medicine/tricordrazine + var/treatment_tox = /datum/reagent/medicine/charcoal + var/treatment_tox_toxlover = /datum/reagent/toxin + var/treatment_virus_avoid = null + var/treatment_virus = /datum/reagent/medicine/spaceacillin + var/treat_virus = 1 //If on, the bot will attempt to treat viral infections, curing them if possible. + var/shut_up = 0 //self explanatory :) + +/mob/living/simple_animal/bot/medbot/mysterious + name = "\improper Mysterious Medibot" + desc = "International Medibot of mystery." + skin = "bezerk" + treatment_brute = /datum/reagent/medicine/tricordrazine + treatment_fire = /datum/reagent/medicine/tricordrazine + treatment_tox = /datum/reagent/medicine/tricordrazine + +/mob/living/simple_animal/bot/medbot/derelict + name = "\improper Old Medibot" + desc = "Looks like it hasn't been modified since the late 2080s." + skin = "bezerk" + heal_threshold = 0 + declare_crit = 0 + treatment_oxy = /datum/reagent/toxin/pancuronium + treatment_brute_avoid = null + treatment_brute = /datum/reagent/toxin/pancuronium + treatment_fire_avoid = null + treatment_fire = /datum/reagent/toxin/sodium_thiopental + treatment_tox_avoid = null + treatment_tox = /datum/reagent/toxin/sodium_thiopental + +/mob/living/simple_animal/bot/medbot/update_icon() + cut_overlays() + if(skin) + add_overlay("medskin_[skin]") + if(!on) + icon_state = "medibot0" + return + if(IsStun()) + icon_state = "medibota" + return + if(mode == BOT_HEALING) + icon_state = "medibots[stationary_mode]" + return + else if(stationary_mode) //Bot has yellow light to indicate stationary mode. + icon_state = "medibot2" + else + icon_state = "medibot1" + +/mob/living/simple_animal/bot/medbot/Initialize(mapload, new_skin) + . = ..() + var/datum/job/doctor/J = new /datum/job/doctor + access_card.access += J.get_access() + prev_access = access_card.access + qdel(J) + skin = new_skin + update_icon() + +/mob/living/simple_animal/bot/medbot/update_canmove() + . = ..() + update_icon() + +/mob/living/simple_animal/bot/medbot/bot_reset() + ..() + patient = null + oldpatient = null + oldloc = null + last_found = world.time + declare_cooldown = 0 + update_icon() + +/mob/living/simple_animal/bot/medbot/proc/soft_reset() //Allows the medibot to still actively perform its medical duties without being completely halted as a hard reset does. + path = list() + patient = null + mode = BOT_IDLE + last_found = world.time + update_icon() + +/mob/living/simple_animal/bot/medbot/set_custom_texts() + + text_hack = "You corrupt [name]'s reagent processor circuits." + text_dehack = "You reset [name]'s reagent processor circuits." + text_dehack_fail = "[name] seems damaged and does not respond to reprogramming!" + +/mob/living/simple_animal/bot/medbot/attack_paw(mob/user) + return attack_hand(user) + +/mob/living/simple_animal/bot/medbot/get_controls(mob/user) + var/dat + dat += hack(user) + dat += showpai(user) + dat += "Medical Unit Controls v1.1

                " + dat += "Status: [on ? "On" : "Off"]
                " + dat += "Maintenance panel panel is [open ? "opened" : "closed"]
                " + dat += "Beaker: " + if(reagent_glass) + dat += "Loaded \[[reagent_glass.reagents.total_volume]/[reagent_glass.reagents.maximum_volume]\]" + else + dat += "None Loaded" + dat += "
                Behaviour controls are [locked ? "locked" : "unlocked"]
                " + if(!locked || issilicon(user) || IsAdminGhost(user)) + dat += "Healing Threshold: " + dat += "-- " + dat += "- " + dat += "[heal_threshold] " + dat += "+ " + dat += "++" + dat += "
                " + + dat += "Injection Level: " + dat += "- " + dat += "[injection_amount] " + dat += "+ " + dat += "
                " + + dat += "Reagent Source: " + dat += "[use_beaker ? "Loaded Beaker (When available)" : "Internal Synthesizer"]
                " + + dat += "Treat Viral Infections: [treat_virus ? "Yes" : "No"]
                " + dat += "The speaker switch is [shut_up ? "off" : "on"]. Toggle
                " + dat += "Critical Patient Alerts: [declare_crit ? "Yes" : "No"]
                " + dat += "Patrol Station: [auto_patrol ? "Yes" : "No"]
                " + dat += "Stationary Mode: [stationary_mode ? "Yes" : "No"]
                " + + return dat + +/mob/living/simple_animal/bot/medbot/Topic(href, href_list) + if(..()) + return 1 + + if(href_list["adj_threshold"]) + var/adjust_num = text2num(href_list["adj_threshold"]) + heal_threshold += adjust_num + if(heal_threshold < 5) + heal_threshold = 5 + if(heal_threshold > 75) + heal_threshold = 75 + + else if(href_list["adj_inject"]) + var/adjust_num = text2num(href_list["adj_inject"]) + injection_amount += adjust_num + if(injection_amount < 5) + injection_amount = 5 + if(injection_amount > 15) + injection_amount = 15 + + else if(href_list["use_beaker"]) + use_beaker = !use_beaker + + else if(href_list["eject"] && (!isnull(reagent_glass))) + reagent_glass.forceMove(drop_location()) + reagent_glass = null + + else if(href_list["togglevoice"]) + shut_up = !shut_up + + else if(href_list["critalerts"]) + declare_crit = !declare_crit + + else if(href_list["stationary"]) + stationary_mode = !stationary_mode + path = list() + update_icon() + + else if(href_list["virus"]) + treat_virus = !treat_virus + + update_controls() + return + +/mob/living/simple_animal/bot/medbot/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, /obj/item/reagent_containers/glass)) + . = 1 //no afterattack + if(locked) + to_chat(user, "You cannot insert a beaker because the panel is locked!") + return + if(!isnull(reagent_glass)) + to_chat(user, "There is already a beaker loaded!") + return + if(!user.transferItemToLoc(W, src)) + return + + reagent_glass = W + to_chat(user, "You insert [W].") + show_controls(user) + + else + var/current_health = health + ..() + if(health < current_health) //if medbot took some damage + step_to(src, (get_step_away(src,user))) + +/mob/living/simple_animal/bot/medbot/emag_act(mob/user) + . = ..() + if(emagged == 2) + declare_crit = 0 + if(user) + to_chat(user, "You short out [src]'s reagent synthesis circuits.") + audible_message("[src] buzzes oddly!") + flick("medibot_spark", src) + playsound(src, "sparks", 75, 1) + if(user) + oldpatient = user + +/mob/living/simple_animal/bot/medbot/process_scan(mob/living/carbon/human/H) + if(H.stat == DEAD) + return + + if((H == oldpatient) && (world.time < last_found + 200)) + return + + if(assess_patient(H)) + last_found = world.time + if((last_newpatient_speak + 300) < world.time) //Don't spam these messages! + var/list/messagevoice = list("Hey, [H.name]! Hold on, I'm coming." = 'sound/voice/medbot/coming.ogg',"Wait [H.name]! I want to help!" = 'sound/voice/medbot/help.ogg',"[H.name], you appear to be injured!" = 'sound/voice/medbot/injured.ogg') + var/message = pick(messagevoice) + speak(message) + playsound(loc, messagevoice[message], 50, 0) + last_newpatient_speak = world.time + return H + else + return + +/mob/living/simple_animal/bot/medbot/handle_automated_action() + if(!..()) + return + + if(mode == BOT_HEALING) + return + + if(IsStun()) + oldpatient = patient + patient = null + mode = BOT_IDLE + return + + if(frustration > 8) + oldpatient = patient + soft_reset() + + if(QDELETED(patient)) + if(!shut_up && prob(1)) + var/list/messagevoice = list("Radar, put a mask on!" = 'sound/voice/medbot/radar.ogg',"There's always a catch, and I'm the best there is." = 'sound/voice/medbot/catch.ogg',"I knew it, I should've been a plastic surgeon." = 'sound/voice/medbot/surgeon.ogg',"What kind of medbay is this? Everyone's dropping like flies." = 'sound/voice/medbot/flies.ogg',"Delicious!" = 'sound/voice/medbot/delicious.ogg') + var/message = pick(messagevoice) + speak(message) + playsound(loc, messagevoice[message], 50, 0) + var/scan_range = (stationary_mode ? 1 : DEFAULT_SCAN_RANGE) //If in stationary mode, scan range is limited to adjacent patients. + patient = scan(/mob/living/carbon/human, oldpatient, scan_range) + oldpatient = patient + + if(patient && (get_dist(src,patient) <= 1)) //Patient is next to us, begin treatment! + if(mode != BOT_HEALING) + mode = BOT_HEALING + update_icon() + frustration = 0 + medicate_patient(patient) + return + + //Patient has moved away from us! + else if(patient && path.len && (get_dist(patient,path[path.len]) > 2)) + path = list() + mode = BOT_IDLE + last_found = world.time + + else if(stationary_mode && patient) //Since we cannot move in this mode, ignore the patient and wait for another. + soft_reset() + return + + if(patient && path.len == 0 && (get_dist(src,patient) > 1)) + path = get_path_to(src, get_turf(patient), /turf/proc/Distance_cardinal, 0, 30,id=access_card) + mode = BOT_MOVING + if(!path.len) //try to get closer if you can't reach the patient directly + path = get_path_to(src, get_turf(patient), /turf/proc/Distance_cardinal, 0, 30,1,id=access_card) + if(!path.len) //Do not chase a patient we cannot reach. + soft_reset() + + if(path.len > 0 && patient) + if(!bot_move(path[path.len])) + oldpatient = patient + soft_reset() + return + + if(path.len > 8 && patient) + frustration++ + + if(auto_patrol && !stationary_mode && !patient) + if(mode == BOT_IDLE || mode == BOT_START_PATROL) + start_patrol() + + if(mode == BOT_PATROL) + bot_patrol() + + return + +/mob/living/simple_animal/bot/medbot/proc/assess_patient(mob/living/carbon/C) + //Time to see if they need medical help! + if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) + return FALSE //welp too late for them! + + if(!(loc == C.loc) && !(isturf(C.loc) && isturf(loc))) + return FALSE + + if(C.suiciding) + return FALSE //Kevorkian school of robotic medical assistants. + + if(emagged == 2) //Everyone needs our medicine. (Our medicine is toxins) + return TRUE + + if(ishuman(C)) + var/mob/living/carbon/human/H = C + if (H.wear_suit && H.head && istype(H.wear_suit, /obj/item/clothing) && istype(H.head, /obj/item/clothing)) + var/obj/item/clothing/CS = H.wear_suit + var/obj/item/clothing/CH = H.head + if (CS.clothing_flags & CH.clothing_flags & THICKMATERIAL) + return FALSE // Skip over them if they have no exposed flesh. + + if(declare_crit && C.health <= 0) //Critical condition! Call for help! + declare(C) + + //If they're injured, we're using a beaker, and don't have one of our WONDERCHEMS. + if((reagent_glass) && (use_beaker) && ((C.getBruteLoss() >= heal_threshold) || (C.getToxLoss() >= heal_threshold) || (C.getToxLoss() >= heal_threshold) || (C.getOxyLoss() >= (heal_threshold + 15)))) + for(var/A in reagent_glass.reagents.reagent_list) + var/datum/reagent/R = A + if(!C.reagents.has_reagent(R.type)) + return TRUE + + //They're injured enough for it! + if((!C.reagents.has_reagent(treatment_brute_avoid)) && (C.getBruteLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_brute))) + return TRUE //If they're already medicated don't bother! + + if((!C.reagents.has_reagent(treatment_oxy_avoid)) && (C.getOxyLoss() >= (15 + heal_threshold)) && (!C.reagents.has_reagent(treatment_oxy))) + return TRUE + + if((!C.reagents.has_reagent(treatment_fire_avoid)) && (C.getFireLoss() >= heal_threshold) && (!C.reagents.has_reagent(treatment_fire))) + return TRUE + var/treatment_toxavoid = get_avoidchem_toxin(C) + if(((isnull(treatment_toxavoid) || !C.reagents.has_reagent(treatment_toxavoid))) && (C.getToxLoss() >= heal_threshold) && (!C.reagents.has_reagent(get_healchem_toxin(C)))) + return TRUE + + if(treat_virus && !C.reagents.has_reagent(treatment_virus_avoid) && !C.reagents.has_reagent(treatment_virus)) + for(var/thing in C.diseases) + var/datum/disease/D = thing + //the medibot can't detect viruses that are undetectable to Health Analyzers or Pandemic machines. + if(!(D.visibility_flags & HIDDEN_SCANNER || D.visibility_flags & HIDDEN_PANDEMIC) \ + && D.severity != DISEASE_SEVERITY_POSITIVE \ + && (D.stage > 1 || (D.spread_flags & DISEASE_SPREAD_AIRBORNE))) // medibot can't detect a virus in its initial stage unless it spreads airborne. + return TRUE //STOP DISEASE FOREVER + + return FALSE + +/mob/living/simple_animal/bot/medbot/proc/get_avoidchem_toxin(mob/M) + return HAS_TRAIT(M, TRAIT_TOXINLOVER)? null : treatment_tox_avoid + +/mob/living/simple_animal/bot/medbot/proc/get_healchem_toxin(mob/M) + return HAS_TRAIT(M, TRAIT_TOXINLOVER)? treatment_tox_toxlover : treatment_tox + +/mob/living/simple_animal/bot/medbot/UnarmedAttack(atom/A) + if(iscarbon(A)) + var/mob/living/carbon/C = A + patient = C + mode = BOT_HEALING + update_icon() + medicate_patient(C) + update_icon() + else + ..() + +/mob/living/simple_animal/bot/medbot/examinate(atom/A as mob|obj|turf in view()) + ..() + if(!is_blind(src)) + chemscan(src, A) + +/mob/living/simple_animal/bot/medbot/proc/medicate_patient(mob/living/carbon/C) + if(!on) + return + + if(!istype(C)) + oldpatient = patient + soft_reset() + return + + if(C.stat == DEAD || (HAS_TRAIT(C, TRAIT_FAKEDEATH))) + var/list/messagevoice = list("No! Stay with me!" = 'sound/voice/medbot/no.ogg',"Live, damnit! LIVE!" = 'sound/voice/medbot/live.ogg',"I...I've never lost a patient before. Not today, I mean." = 'sound/voice/medbot/lost.ogg') + var/message = pick(messagevoice) + speak(message) + playsound(loc, messagevoice[message], 50, 0) + oldpatient = patient + soft_reset() + return + + var/reagent_id = null + + if(emagged == 2) //Emagged! Time to poison everybody. + reagent_id = HAS_TRAIT(C, TRAIT_TOXINLOVER)? "charcoal" : "toxin" + + else + if(treat_virus) + var/virus = 0 + for(var/thing in C.diseases) + var/datum/disease/D = thing + //detectable virus + if((!(D.visibility_flags & HIDDEN_SCANNER)) || (!(D.visibility_flags & HIDDEN_PANDEMIC))) + if(D.severity != DISEASE_SEVERITY_POSITIVE) //virus is harmful + if((D.stage > 1) || (D.spread_flags & DISEASE_SPREAD_AIRBORNE)) + virus = 1 + + if(!reagent_id && (virus)) + if(!C.reagents.has_reagent(treatment_virus) && !C.reagents.has_reagent(treatment_virus_avoid)) + reagent_id = treatment_virus + + if(!reagent_id && (C.getBruteLoss() >= heal_threshold)) + if(!C.reagents.has_reagent(treatment_brute) && !C.reagents.has_reagent(treatment_brute_avoid)) + reagent_id = treatment_brute + + if(!reagent_id && (C.getOxyLoss() >= (15 + heal_threshold))) + if(!C.reagents.has_reagent(treatment_oxy) && !C.reagents.has_reagent(treatment_oxy_avoid)) + reagent_id = treatment_oxy + + if(!reagent_id && (C.getFireLoss() >= heal_threshold)) + if(!C.reagents.has_reagent(treatment_fire) && !C.reagents.has_reagent(treatment_fire_avoid)) + reagent_id = treatment_fire + + if(!reagent_id && (C.getToxLoss() >= heal_threshold)) + var/toxin_heal_avoid = get_avoidchem_toxin(C) + var/toxin_healchem = get_healchem_toxin(C) + if(!C.reagents.has_reagent(toxin_healchem) && (isnull(toxin_heal_avoid) || !C.reagents.has_reagent(toxin_heal_avoid))) + reagent_id = toxin_healchem + + //If the patient is injured but doesn't have our special reagent in them then we should give it to them first + if(reagent_id && use_beaker && reagent_glass && reagent_glass.reagents.total_volume) + for(var/A in reagent_glass.reagents.reagent_list) + var/datum/reagent/R = A + if(!C.reagents.has_reagent(R.type)) + reagent_id = "internal_beaker" + break + + if(!reagent_id) //If they don't need any of that they're probably cured! + if(C.maxHealth - C.health < heal_threshold) + to_chat(src, "[C] is healthy! Your programming prevents you from injecting anyone without at least [heal_threshold] damage of any one type ([heal_threshold + 15] for oxygen damage.)") + var/list/messagevoice = list("All patched up!" = 'sound/voice/medbot/patchedup.ogg',"An apple a day keeps me away." = 'sound/voice/medbot/apple.ogg',"Feel better soon!" = 'sound/voice/medbot/feelbetter.ogg') + var/message = pick(messagevoice) + speak(message) + playsound(loc, messagevoice[message], 50, 0) + bot_reset() + return + else + if(!emagged && check_overdose(patient,reagent_id,injection_amount)) + soft_reset() + return + C.visible_message("[src] is trying to inject [patient]!", \ + "[src] is trying to inject you!") + + var/failed = FALSE + if(do_mob(src, patient, 30)) //Is C == patient? This is so confusing + if((get_dist(src, patient) <= 1) && (on) && assess_patient(patient)) + if(reagent_id == "internal_beaker") + if(use_beaker && reagent_glass && reagent_glass.reagents.total_volume) + var/fraction = min(injection_amount/reagent_glass.reagents.total_volume, 1) + reagent_glass.reagents.reaction(patient, INJECT, fraction) + reagent_glass.reagents.trans_to(patient,injection_amount) //Inject from beaker instead. + else + patient.reagents.add_reagent(reagent_id,injection_amount) + C.visible_message("[src] injects [patient] with its syringe!", \ + "[src] injects you with its syringe!") + else + failed = TRUE + else + failed = TRUE + + if(failed) + visible_message("[src] retracts its syringe.") + update_icon() + soft_reset() + return + + reagent_id = null + return + +/mob/living/simple_animal/bot/medbot/proc/check_overdose(mob/living/carbon/patient,reagent_id,injection_amount) + var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_id] + if(!R.overdose_threshold) //Some chems do not have an OD threshold + return FALSE + var/current_volume = patient.reagents.get_reagent_amount(reagent_id) + if(current_volume + injection_amount > R.overdose_threshold) + return TRUE + return FALSE + +/mob/living/simple_animal/bot/medbot/explode() + on = FALSE + visible_message("[src] blows apart!") + var/atom/Tsec = drop_location() + + drop_part(firstaid, Tsec) + new /obj/item/assembly/prox_sensor(Tsec) + drop_part(healthanalyzer, Tsec) + + if(reagent_glass) + drop_part(reagent_glass, Tsec) + + if(prob(50)) + drop_part(robot_arm, Tsec) + + if(emagged && prob(25)) + playsound(loc, 'sound/voice/medbot/insult.ogg', 50, 0) + + do_sparks(3, TRUE, src) + ..() + +/mob/living/simple_animal/bot/medbot/proc/declare(crit_patient) + if(declare_cooldown > world.time) + return + var/area/location = get_area(src) + speak("Medical emergency! [crit_patient ? "[crit_patient]" : "A patient"] is in critical condition at [location]!",radio_channel) + declare_cooldown = world.time + 200 + +/obj/machinery/bot_core/medbot + req_one_access = list(ACCESS_MEDICAL, ACCESS_ROBOTICS) diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index bfe7b2cde0..d4fc98ee9e 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -1,444 +1,444 @@ -/mob/living/simple_animal/bot/secbot - name = "\improper Securitron" - desc = "A little security robot. He looks less than thrilled." - icon = 'icons/mob/aibots.dmi' - icon_state = "secbot" - density = FALSE - anchored = FALSE - health = 25 - maxHealth = 25 - damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) - pass_flags = PASSMOB - - radio_key = /obj/item/encryptionkey/secbot //AI Priv + Security - radio_channel = RADIO_CHANNEL_SECURITY //Security channel - bot_type = SEC_BOT - model = "Securitron" - bot_core_type = /obj/machinery/bot_core/secbot - window_id = "autosec" - window_name = "Automatic Security Unit v1.6" - allow_pai = 0 - data_hud_type = DATA_HUD_SECURITY_ADVANCED - path_image_color = "#FF0000" - - var/baton_type = /obj/item/melee/baton - var/mob/living/carbon/target - var/oldtarget_name - var/threatlevel = FALSE - var/target_lastloc //Loc of target when arrested. - var/last_found //There's a delay - var/declare_arrests = TRUE //When making an arrest, should it notify everyone on the security channel? - var/idcheck = FALSE //If true, arrest people with no IDs - var/weaponscheck = FALSE //If true, arrest people for weapons if they lack access - var/check_records = TRUE //Does it check security records? - var/arrest_type = FALSE //If true, don't handcuff - -/mob/living/simple_animal/bot/secbot/beepsky - name = "Officer Beep O'sky" - desc = "It's Officer Beep O'sky! Powered by a potato and a shot of whiskey." - idcheck = FALSE - weaponscheck = FALSE - auto_patrol = TRUE - -/mob/living/simple_animal/bot/secbot/beepsky/jr - name = "Officer Pipsqueak" - desc = "It's Officer Beep O'sky's smaller, just-as aggressive cousin, Pipsqueak." - -/mob/living/simple_animal/bot/secbot/beepsky/jr/Initialize() - . = ..() - resize = 0.8 - update_transform() - - -/mob/living/simple_animal/bot/secbot/beepsky/explode() - var/atom/Tsec = drop_location() - new /obj/item/stock_parts/cell/potato(Tsec) - var/obj/item/reagent_containers/food/drinks/drinkingglass/shotglass/S = new(Tsec) - S.reagents.add_reagent(/datum/reagent/consumable/ethanol/whiskey, 15) - S.on_reagent_change(ADD_REAGENT) - ..() - -/mob/living/simple_animal/bot/secbot/pingsky - name = "Officer Pingsky" - desc = "It's Officer Pingsky! Delegated to satellite guard duty for harbouring anti-human sentiment." - radio_channel = RADIO_CHANNEL_AI_PRIVATE - -/mob/living/simple_animal/bot/secbot/Initialize() - . = ..() - update_icon() - var/datum/job/detective/J = new/datum/job/detective - access_card.access += J.get_access() - prev_access = access_card.access - - //SECHUD - var/datum/atom_hud/secsensor = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - secsensor.add_hud_to(src) - -/mob/living/simple_animal/bot/secbot/update_icon() - if(mode == BOT_HUNT) - icon_state = "[initial(icon_state)]-c" - return - ..() - -/mob/living/simple_animal/bot/secbot/turn_off() - ..() - mode = BOT_IDLE - -/mob/living/simple_animal/bot/secbot/bot_reset() - ..() - target = null - oldtarget_name = null - anchored = FALSE - walk_to(src,0) - last_found = world.time - -/mob/living/simple_animal/bot/secbot/set_custom_texts() - - text_hack = "You overload [name]'s target identification system." - text_dehack = "You reboot [name] and restore the target identification." - text_dehack_fail = "[name] refuses to accept your authority!" - -/mob/living/simple_animal/bot/secbot/get_controls(mob/user) - var/dat - dat += hack(user) - dat += showpai(user) - dat += text({" -Securitron v1.6 controls

                -Status: []
                -Behaviour controls are [locked ? "locked" : "unlocked"]
                -Maintenance panel panel is [open ? "opened" : "closed"]"}, - -"[on ? "On" : "Off"]" ) - - if(!locked || issilicon(user) || IsAdminGhost(user)) - dat += text({"
                -Arrest Unidentifiable Persons: []
                -Arrest for Unauthorized Weapons: []
                -Arrest for Warrant: []
                -Operating Mode: []
                -Report Arrests[]
                -Auto Patrol: []"}, - -"[idcheck ? "Yes" : "No"]", -"[weaponscheck ? "Yes" : "No"]", -"[check_records ? "Yes" : "No"]", -"[arrest_type ? "Detain" : "Arrest"]", -"[declare_arrests ? "Yes" : "No"]", -"[auto_patrol ? "On" : "Off"]" ) - - return dat - -/mob/living/simple_animal/bot/secbot/Topic(href, href_list) - if(..()) - return 1 - if(!issilicon(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) - return TRUE - switch(href_list["operation"]) - if("idcheck") - idcheck = !idcheck - update_controls() - if("weaponscheck") - weaponscheck = !weaponscheck - update_controls() - if("ignorerec") - check_records = !check_records - update_controls() - if("switchmode") - arrest_type = !arrest_type - update_controls() - if("declarearrests") - declare_arrests = !declare_arrests - update_controls() - -/mob/living/simple_animal/bot/secbot/proc/retaliate(mob/living/carbon/human/H) - var/judgement_criteria = judgement_criteria() - threatlevel = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) - threatlevel += 6 - if(threatlevel >= 4) - target = H - mode = BOT_HUNT - -/mob/living/simple_animal/bot/secbot/proc/judgement_criteria() - var/final = FALSE - if(idcheck) - final = final|JUDGE_IDCHECK - if(check_records) - final = final|JUDGE_RECORDCHECK - if(weaponscheck) - final = final|JUDGE_WEAPONCHECK - if(emagged == 2) - final = final|JUDGE_EMAGGED - return final - -/mob/living/simple_animal/bot/secbot/proc/special_retaliate_after_attack(mob/user) //allows special actions to take place after being attacked. - return - -/mob/living/simple_animal/bot/secbot/attack_hand(mob/living/carbon/human/H) - if((H.a_intent == INTENT_HARM) || (H.a_intent == INTENT_DISARM)) - retaliate(H) - if(special_retaliate_after_attack(H)) - return - - return ..() - -/mob/living/simple_animal/bot/secbot/attackby(obj/item/W, mob/user, params) - ..() - if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. - return - if(!istype(W, /obj/item/screwdriver) && (W.force) && (!target) && (W.damtype != STAMINA) ) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. - retaliate(user) - if(special_retaliate_after_attack(user)) - return - -/mob/living/simple_animal/bot/secbot/emag_act(mob/user) - . = ..() - if(emagged == 2) - if(user) - to_chat(user, "You short out [src]'s target assessment circuits.") - oldtarget_name = user.name - audible_message("[src] buzzes oddly!") - declare_arrests = FALSE - update_icon() - -/mob/living/simple_animal/bot/secbot/bullet_act(obj/item/projectile/Proj) - if(istype(Proj , /obj/item/projectile/beam)||istype(Proj, /obj/item/projectile/bullet)) - if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)) - if(!Proj.nodamage && Proj.damage < src.health && ishuman(Proj.firer)) - retaliate(Proj.firer) - ..() - - -/mob/living/simple_animal/bot/secbot/UnarmedAttack(atom/A) - if(!on) - return - if(iscarbon(A)) - var/mob/living/carbon/C = A - if(C.canmove || arrest_type) // CIT CHANGE - makes sentient secbots check for canmove rather than !isstun. - stun_attack(A) - else if(C.canBeHandcuffed() && !C.handcuffed) - cuff(A) - else - ..() - - -/mob/living/simple_animal/bot/secbot/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE) - if(istype(AM, /obj/item)) - var/obj/item/I = AM - if(I.throwforce < src.health && I.thrownby && ishuman(I.thrownby)) - var/mob/living/carbon/human/H = I.thrownby - retaliate(H) - ..() - - -/mob/living/simple_animal/bot/secbot/proc/cuff(mob/living/carbon/C) - mode = BOT_ARREST - playsound(src, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2) - C.visible_message("[src] is trying to put zipties on [C]!",\ - "[src] is trying to put zipties on you!") - addtimer(CALLBACK(src, .proc/attempt_handcuff, C), 60) - -/mob/living/simple_animal/bot/secbot/proc/attempt_handcuff(mob/living/carbon/C) - if( !on || !Adjacent(C) || !isturf(C.loc) ) //if he's in a closet or not adjacent, we cancel cuffing. - return - if(!C.handcuffed) - C.handcuffed = new /obj/item/restraints/handcuffs/cable/zipties/used(C) - C.update_handcuffed() - playsound(src, "law", 50, 0) - back_to_idle() - -/mob/living/simple_animal/bot/secbot/proc/stun_attack(mob/living/carbon/C) - var/judgement_criteria = judgement_criteria() - playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1) - icon_state = "secbot-c" - addtimer(CALLBACK(src, /atom/.proc/update_icon), 2) - var/threat = 5 - if(ishuman(C)) - C.stuttering = 5 - C.Knockdown(100) - var/mob/living/carbon/human/H = C - threat = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) - else - C.Knockdown(100) - C.stuttering = 5 - threat = C.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) - - log_combat(src,C,"stunned") - if(declare_arrests) - var/area/location = get_area(src) - speak("[arrest_type ? "Detaining" : "Arresting"] level [threat] scumbag [C] in [location].", radio_channel) - C.visible_message("[src] has stunned [C]!",\ - "[src] has stunned you!") - -/mob/living/simple_animal/bot/secbot/handle_automated_action() - if(!..()) - return - - switch(mode) - - if(BOT_IDLE) // idle - - walk_to(src,0) - look_for_perp() // see if any criminals are in range - if(!mode && auto_patrol) // still idle, and set to patrol - mode = BOT_START_PATROL // switch to patrol mode - - if(BOT_HUNT) // hunting for perp - - // if can't reach perp for long enough, go idle - if(frustration >= 8) - walk_to(src,0) - back_to_idle() - return - - if(target) // make sure target exists - if(Adjacent(target) && isturf(target.loc)) // if right next to perp - stun_attack(target) - - mode = BOT_PREP_ARREST - anchored = TRUE - target_lastloc = target.loc - return - - else // not next to perp - var/turf/olddist = get_dist(src, target) - walk_to(src, target,1,4) - if((get_dist(src, target)) >= (olddist)) - frustration++ - else - frustration = 0 - else - back_to_idle() - - if(BOT_PREP_ARREST) // preparing to arrest target - - // see if he got away. If he's no no longer adjacent or inside a closet or about to get up, we hunt again. - if( !Adjacent(target) || !isturf(target.loc) || target.getStaminaLoss() <= 120 || !target.recoveringstam) //CIT CHANGE - replaces amountknockdown with checks for stamina so secbots dont run into an infinite loop - back_to_hunt() - return - - if(iscarbon(target) && target.canBeHandcuffed()) - if(!arrest_type) - if(!target.handcuffed) //he's not cuffed? Try to cuff him! - cuff(target) - else - back_to_idle() - return - else - back_to_idle() - return - - if(BOT_ARREST) - if(!target) - anchored = FALSE - mode = BOT_IDLE - last_found = world.time - frustration = 0 - return - - if(target.handcuffed) //no target or target cuffed? back to idle. - back_to_idle() - return - - if(!Adjacent(target) || !isturf(target.loc) || (target.loc != target_lastloc && !target.recoveringstam && target.getStaminaLoss() <= 120)) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. CIT CHANGE - replaces amountknockdown with recoveringstam and staminaloss check - back_to_hunt() - return - else //Try arresting again if the target escapes. - mode = BOT_PREP_ARREST - anchored = FALSE - - if(BOT_START_PATROL) - look_for_perp() - start_patrol() - - if(BOT_PATROL) - look_for_perp() - bot_patrol() - - - return - -/mob/living/simple_animal/bot/secbot/proc/back_to_idle() - anchored = FALSE - mode = BOT_IDLE - target = null - last_found = world.time - frustration = 0 - INVOKE_ASYNC(src, .proc/handle_automated_action) - -/mob/living/simple_animal/bot/secbot/proc/back_to_hunt() - anchored = FALSE - frustration = 0 - mode = BOT_HUNT - INVOKE_ASYNC(src, .proc/handle_automated_action) -// look for a criminal in view of the bot - -/mob/living/simple_animal/bot/secbot/proc/look_for_perp() - anchored = FALSE - var/judgement_criteria = judgement_criteria() - for (var/mob/living/carbon/C in view(7,src)) //Let's find us a criminal - if((C.stat) || (C.handcuffed)) - continue - - if((C.name == oldtarget_name) && (world.time < last_found + 100)) - continue - - threatlevel = C.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) - - if(!threatlevel) - continue - - else if(threatlevel >= 4) - target = C - oldtarget_name = C.name - speak("Level [threatlevel] infraction alert!") - playsound(loc, pick('sound/voice/beepsky/criminal.ogg', 'sound/voice/beepsky/justice.ogg', 'sound/voice/beepsky/freeze.ogg'), 50, FALSE) - visible_message("[src] points at [C.name]!") - mode = BOT_HUNT - INVOKE_ASYNC(src, .proc/handle_automated_action) - break - else - continue - -/mob/living/simple_animal/bot/secbot/proc/check_for_weapons(var/obj/item/slot_item) - if(slot_item && (slot_item.item_flags & NEEDS_PERMIT)) - return TRUE - return FALSE - -/mob/living/simple_animal/bot/secbot/explode() - - walk_to(src,0) - visible_message("[src] blows apart!") - var/atom/Tsec = drop_location() - - var/obj/item/bot_assembly/secbot/Sa = new (Tsec) - Sa.build_step = ASSEMBLY_SECOND_STEP - Sa.add_overlay("hs_hole") - Sa.created_name = name - new /obj/item/assembly/prox_sensor(Tsec) - drop_part(baton_type, Tsec) - - if(prob(50)) - drop_part(robot_arm, Tsec) - - do_sparks(3, TRUE, src) - - new /obj/effect/decal/cleanable/oil(loc) - ..() - -/mob/living/simple_animal/bot/secbot/attack_alien(var/mob/living/carbon/alien/user as mob) - ..() - if(!isalien(target)) - target = user - mode = BOT_HUNT - -/mob/living/simple_animal/bot/secbot/Crossed(atom/movable/AM) - if(has_gravity() && ismob(AM) && target) - var/mob/living/carbon/C = AM - if(!istype(C) || !C || in_range(src, target)) - return - knockOver(C) - return - ..() - -/obj/machinery/bot_core/secbot - req_access = list(ACCESS_SECURITY) +/mob/living/simple_animal/bot/secbot + name = "\improper Securitron" + desc = "A little security robot. He looks less than thrilled." + icon = 'icons/mob/aibots.dmi' + icon_state = "secbot" + density = FALSE + anchored = FALSE + health = 25 + maxHealth = 25 + damage_coeff = list(BRUTE = 0.5, BURN = 0.7, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + pass_flags = PASSMOB + + radio_key = /obj/item/encryptionkey/secbot //AI Priv + Security + radio_channel = RADIO_CHANNEL_SECURITY //Security channel + bot_type = SEC_BOT + model = "Securitron" + bot_core_type = /obj/machinery/bot_core/secbot + window_id = "autosec" + window_name = "Automatic Security Unit v1.6" + allow_pai = 0 + data_hud_type = DATA_HUD_SECURITY_ADVANCED + path_image_color = "#FF0000" + + var/baton_type = /obj/item/melee/baton + var/mob/living/carbon/target + var/oldtarget_name + var/threatlevel = FALSE + var/target_lastloc //Loc of target when arrested. + var/last_found //There's a delay + var/declare_arrests = TRUE //When making an arrest, should it notify everyone on the security channel? + var/idcheck = FALSE //If true, arrest people with no IDs + var/weaponscheck = FALSE //If true, arrest people for weapons if they lack access + var/check_records = TRUE //Does it check security records? + var/arrest_type = FALSE //If true, don't handcuff + +/mob/living/simple_animal/bot/secbot/beepsky + name = "Officer Beep O'sky" + desc = "It's Officer Beep O'sky! Powered by a potato and a shot of whiskey." + idcheck = FALSE + weaponscheck = FALSE + auto_patrol = TRUE + +/mob/living/simple_animal/bot/secbot/beepsky/jr + name = "Officer Pipsqueak" + desc = "It's Officer Beep O'sky's smaller, just-as aggressive cousin, Pipsqueak." + +/mob/living/simple_animal/bot/secbot/beepsky/jr/Initialize() + . = ..() + resize = 0.8 + update_transform() + + +/mob/living/simple_animal/bot/secbot/beepsky/explode() + var/atom/Tsec = drop_location() + new /obj/item/stock_parts/cell/potato(Tsec) + var/obj/item/reagent_containers/food/drinks/drinkingglass/shotglass/S = new(Tsec) + S.reagents.add_reagent(/datum/reagent/consumable/ethanol/whiskey, 15) + S.on_reagent_change(ADD_REAGENT) + ..() + +/mob/living/simple_animal/bot/secbot/pingsky + name = "Officer Pingsky" + desc = "It's Officer Pingsky! Delegated to satellite guard duty for harbouring anti-human sentiment." + radio_channel = RADIO_CHANNEL_AI_PRIVATE + +/mob/living/simple_animal/bot/secbot/Initialize() + . = ..() + update_icon() + var/datum/job/detective/J = new/datum/job/detective + access_card.access += J.get_access() + prev_access = access_card.access + + //SECHUD + var/datum/atom_hud/secsensor = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] + secsensor.add_hud_to(src) + +/mob/living/simple_animal/bot/secbot/update_icon() + if(mode == BOT_HUNT) + icon_state = "[initial(icon_state)]-c" + return + ..() + +/mob/living/simple_animal/bot/secbot/turn_off() + ..() + mode = BOT_IDLE + +/mob/living/simple_animal/bot/secbot/bot_reset() + ..() + target = null + oldtarget_name = null + anchored = FALSE + walk_to(src,0) + last_found = world.time + +/mob/living/simple_animal/bot/secbot/set_custom_texts() + + text_hack = "You overload [name]'s target identification system." + text_dehack = "You reboot [name] and restore the target identification." + text_dehack_fail = "[name] refuses to accept your authority!" + +/mob/living/simple_animal/bot/secbot/get_controls(mob/user) + var/dat + dat += hack(user) + dat += showpai(user) + dat += text({" +Securitron v1.6 controls

                +Status: []
                +Behaviour controls are [locked ? "locked" : "unlocked"]
                +Maintenance panel panel is [open ? "opened" : "closed"]"}, + +"[on ? "On" : "Off"]" ) + + if(!locked || issilicon(user) || IsAdminGhost(user)) + dat += text({"
                +Arrest Unidentifiable Persons: []
                +Arrest for Unauthorized Weapons: []
                +Arrest for Warrant: []
                +Operating Mode: []
                +Report Arrests[]
                +Auto Patrol: []"}, + +"[idcheck ? "Yes" : "No"]", +"[weaponscheck ? "Yes" : "No"]", +"[check_records ? "Yes" : "No"]", +"[arrest_type ? "Detain" : "Arrest"]", +"[declare_arrests ? "Yes" : "No"]", +"[auto_patrol ? "On" : "Off"]" ) + + return dat + +/mob/living/simple_animal/bot/secbot/Topic(href, href_list) + if(..()) + return 1 + if(!issilicon(usr) && !IsAdminGhost(usr) && !(bot_core.allowed(usr) || !locked)) + return TRUE + switch(href_list["operation"]) + if("idcheck") + idcheck = !idcheck + update_controls() + if("weaponscheck") + weaponscheck = !weaponscheck + update_controls() + if("ignorerec") + check_records = !check_records + update_controls() + if("switchmode") + arrest_type = !arrest_type + update_controls() + if("declarearrests") + declare_arrests = !declare_arrests + update_controls() + +/mob/living/simple_animal/bot/secbot/proc/retaliate(mob/living/carbon/human/H) + var/judgement_criteria = judgement_criteria() + threatlevel = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) + threatlevel += 6 + if(threatlevel >= 4) + target = H + mode = BOT_HUNT + +/mob/living/simple_animal/bot/secbot/proc/judgement_criteria() + var/final = FALSE + if(idcheck) + final = final|JUDGE_IDCHECK + if(check_records) + final = final|JUDGE_RECORDCHECK + if(weaponscheck) + final = final|JUDGE_WEAPONCHECK + if(emagged == 2) + final = final|JUDGE_EMAGGED + return final + +/mob/living/simple_animal/bot/secbot/proc/special_retaliate_after_attack(mob/user) //allows special actions to take place after being attacked. + return + +/mob/living/simple_animal/bot/secbot/attack_hand(mob/living/carbon/human/H) + if((H.a_intent == INTENT_HARM) || (H.a_intent == INTENT_DISARM)) + retaliate(H) + if(special_retaliate_after_attack(H)) + return + + return ..() + +/mob/living/simple_animal/bot/secbot/attackby(obj/item/W, mob/user, params) + ..() + if(istype(W, /obj/item/weldingtool) && user.a_intent != INTENT_HARM) // Any intent but harm will heal, so we shouldn't get angry. + return + if(!istype(W, /obj/item/screwdriver) && (W.force) && (!target) && (W.damtype != STAMINA) ) // Added check for welding tool to fix #2432. Welding tool behavior is handled in superclass. + retaliate(user) + if(special_retaliate_after_attack(user)) + return + +/mob/living/simple_animal/bot/secbot/emag_act(mob/user) + . = ..() + if(emagged == 2) + if(user) + to_chat(user, "You short out [src]'s target assessment circuits.") + oldtarget_name = user.name + audible_message("[src] buzzes oddly!") + declare_arrests = FALSE + update_icon() + +/mob/living/simple_animal/bot/secbot/bullet_act(obj/item/projectile/Proj) + if(istype(Proj , /obj/item/projectile/beam)||istype(Proj, /obj/item/projectile/bullet)) + if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)) + if(!Proj.nodamage && Proj.damage < src.health && ishuman(Proj.firer)) + retaliate(Proj.firer) + ..() + + +/mob/living/simple_animal/bot/secbot/UnarmedAttack(atom/A) + if(!on) + return + if(iscarbon(A)) + var/mob/living/carbon/C = A + if(C.canmove || arrest_type) // CIT CHANGE - makes sentient secbots check for canmove rather than !isstun. + stun_attack(A) + else if(C.canBeHandcuffed() && !C.handcuffed) + cuff(A) + else + ..() + + +/mob/living/simple_animal/bot/secbot/hitby(atom/movable/AM, skipcatch = FALSE, hitpush = TRUE, blocked = FALSE) + if(istype(AM, /obj/item)) + var/obj/item/I = AM + if(I.throwforce < src.health && I.thrownby && ishuman(I.thrownby)) + var/mob/living/carbon/human/H = I.thrownby + retaliate(H) + ..() + + +/mob/living/simple_animal/bot/secbot/proc/cuff(mob/living/carbon/C) + mode = BOT_ARREST + playsound(src, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2) + C.visible_message("[src] is trying to put zipties on [C]!",\ + "[src] is trying to put zipties on you!") + addtimer(CALLBACK(src, .proc/attempt_handcuff, C), 60) + +/mob/living/simple_animal/bot/secbot/proc/attempt_handcuff(mob/living/carbon/C) + if( !on || !Adjacent(C) || !isturf(C.loc) ) //if he's in a closet or not adjacent, we cancel cuffing. + return + if(!C.handcuffed) + C.handcuffed = new /obj/item/restraints/handcuffs/cable/zipties/used(C) + C.update_handcuffed() + playsound(src, "law", 50, 0) + back_to_idle() + +/mob/living/simple_animal/bot/secbot/proc/stun_attack(mob/living/carbon/C) + var/judgement_criteria = judgement_criteria() + playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1) + icon_state = "secbot-c" + addtimer(CALLBACK(src, /atom/.proc/update_icon), 2) + var/threat = 5 + if(ishuman(C)) + C.stuttering = 5 + C.Knockdown(100) + var/mob/living/carbon/human/H = C + threat = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) + else + C.Knockdown(100) + C.stuttering = 5 + threat = C.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) + + log_combat(src,C,"stunned") + if(declare_arrests) + var/area/location = get_area(src) + speak("[arrest_type ? "Detaining" : "Arresting"] level [threat] scumbag [C] in [location].", radio_channel) + C.visible_message("[src] has stunned [C]!",\ + "[src] has stunned you!") + +/mob/living/simple_animal/bot/secbot/handle_automated_action() + if(!..()) + return + + switch(mode) + + if(BOT_IDLE) // idle + + walk_to(src,0) + look_for_perp() // see if any criminals are in range + if(!mode && auto_patrol) // still idle, and set to patrol + mode = BOT_START_PATROL // switch to patrol mode + + if(BOT_HUNT) // hunting for perp + + // if can't reach perp for long enough, go idle + if(frustration >= 8) + walk_to(src,0) + back_to_idle() + return + + if(target) // make sure target exists + if(Adjacent(target) && isturf(target.loc)) // if right next to perp + stun_attack(target) + + mode = BOT_PREP_ARREST + anchored = TRUE + target_lastloc = target.loc + return + + else // not next to perp + var/turf/olddist = get_dist(src, target) + walk_to(src, target,1,4) + if((get_dist(src, target)) >= (olddist)) + frustration++ + else + frustration = 0 + else + back_to_idle() + + if(BOT_PREP_ARREST) // preparing to arrest target + + // see if he got away. If he's no no longer adjacent or inside a closet or about to get up, we hunt again. + if( !Adjacent(target) || !isturf(target.loc) || target.getStaminaLoss() <= 120 || !target.recoveringstam) //CIT CHANGE - replaces amountknockdown with checks for stamina so secbots dont run into an infinite loop + back_to_hunt() + return + + if(iscarbon(target) && target.canBeHandcuffed()) + if(!arrest_type) + if(!target.handcuffed) //he's not cuffed? Try to cuff him! + cuff(target) + else + back_to_idle() + return + else + back_to_idle() + return + + if(BOT_ARREST) + if(!target) + anchored = FALSE + mode = BOT_IDLE + last_found = world.time + frustration = 0 + return + + if(target.handcuffed) //no target or target cuffed? back to idle. + back_to_idle() + return + + if(!Adjacent(target) || !isturf(target.loc) || (target.loc != target_lastloc && !target.recoveringstam && target.getStaminaLoss() <= 120)) //if he's changed loc and about to get up or not adjacent or got into a closet, we prep arrest again. CIT CHANGE - replaces amountknockdown with recoveringstam and staminaloss check + back_to_hunt() + return + else //Try arresting again if the target escapes. + mode = BOT_PREP_ARREST + anchored = FALSE + + if(BOT_START_PATROL) + look_for_perp() + start_patrol() + + if(BOT_PATROL) + look_for_perp() + bot_patrol() + + + return + +/mob/living/simple_animal/bot/secbot/proc/back_to_idle() + anchored = FALSE + mode = BOT_IDLE + target = null + last_found = world.time + frustration = 0 + INVOKE_ASYNC(src, .proc/handle_automated_action) + +/mob/living/simple_animal/bot/secbot/proc/back_to_hunt() + anchored = FALSE + frustration = 0 + mode = BOT_HUNT + INVOKE_ASYNC(src, .proc/handle_automated_action) +// look for a criminal in view of the bot + +/mob/living/simple_animal/bot/secbot/proc/look_for_perp() + anchored = FALSE + var/judgement_criteria = judgement_criteria() + for (var/mob/living/carbon/C in view(7,src)) //Let's find us a criminal + if((C.stat) || (C.handcuffed)) + continue + + if((C.name == oldtarget_name) && (world.time < last_found + 100)) + continue + + threatlevel = C.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) + + if(!threatlevel) + continue + + else if(threatlevel >= 4) + target = C + oldtarget_name = C.name + speak("Level [threatlevel] infraction alert!") + playsound(loc, pick('sound/voice/beepsky/criminal.ogg', 'sound/voice/beepsky/justice.ogg', 'sound/voice/beepsky/freeze.ogg'), 50, FALSE) + visible_message("[src] points at [C.name]!") + mode = BOT_HUNT + INVOKE_ASYNC(src, .proc/handle_automated_action) + break + else + continue + +/mob/living/simple_animal/bot/secbot/proc/check_for_weapons(var/obj/item/slot_item) + if(slot_item && (slot_item.item_flags & NEEDS_PERMIT)) + return TRUE + return FALSE + +/mob/living/simple_animal/bot/secbot/explode() + + walk_to(src,0) + visible_message("[src] blows apart!") + var/atom/Tsec = drop_location() + + var/obj/item/bot_assembly/secbot/Sa = new (Tsec) + Sa.build_step = ASSEMBLY_SECOND_STEP + Sa.add_overlay("hs_hole") + Sa.created_name = name + new /obj/item/assembly/prox_sensor(Tsec) + drop_part(baton_type, Tsec) + + if(prob(50)) + drop_part(robot_arm, Tsec) + + do_sparks(3, TRUE, src) + + new /obj/effect/decal/cleanable/oil(loc) + ..() + +/mob/living/simple_animal/bot/secbot/attack_alien(var/mob/living/carbon/alien/user as mob) + ..() + if(!isalien(target)) + target = user + mode = BOT_HUNT + +/mob/living/simple_animal/bot/secbot/Crossed(atom/movable/AM) + if(has_gravity() && ismob(AM) && target) + var/mob/living/carbon/C = AM + if(!istype(C) || !C || in_range(src, target)) + return + knockOver(C) + return + ..() + +/obj/machinery/bot_core/secbot + req_access = list(ACCESS_SECURITY) diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index be900aa4ef..736524496f 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -1,462 +1,462 @@ -/mob/living/simple_animal/hostile/construct - name = "Construct" - real_name = "Construct" - desc = "" - gender = NEUTER - mob_biotypes = list(MOB_INORGANIC) - speak_emote = list("hisses") - response_help = "thinks better of touching" - response_disarm = "flails at" - response_harm = "punches" - speak_chance = 1 - icon = 'icons/mob/mob.dmi' - speed = 0 - spacewalk = TRUE - a_intent = INTENT_HARM - stop_automated_movement = 1 - status_flags = CANPUSH - attack_sound = 'sound/weapons/punch1.ogg' - see_in_dark = 7 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) - 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 = INFINITY - healable = 0 - faction = list("cult") - movement_type = FLYING - pressure_resistance = 100 - unique_name = 1 - AIStatus = AI_OFF //normal constructs don't have AI - loot = list(/obj/item/ectoplasm) - del_on_death = TRUE - initial_language_holder = /datum/language_holder/construct - deathmessage = "collapses in a shattered heap." - hud_type = /datum/hud/constructs - blood_volume = 0 - var/list/construct_spells = list() - var/playstyle_string = "You are a generic construct! Your job is to not exist, and you should probably adminhelp this." - var/master = null - var/seeking = FALSE - var/can_repair_constructs = FALSE - var/can_repair_self = FALSE - var/runetype - -/mob/living/simple_animal/hostile/construct/Initialize() - . = ..() - update_health_hud() - var/spellnum = 1 - for(var/spell in construct_spells) - var/the_spell = new spell(null) - AddSpell(the_spell) - var/obj/effect/proc_holder/spell/S = mob_spell_list[spellnum] - var/pos = 2+spellnum*31 - if(construct_spells.len >= 4) - pos -= 31*(construct_spells.len - 4) - S.action.button.screen_loc = "6:[pos],4:-2" - S.action.button.moved = "6:[pos],4:-2" - spellnum++ - if(runetype) - var/datum/action/innate/cult/create_rune/CR = new runetype(src) - CR.Grant(src) - var/pos = 2+spellnum*31 - CR.button.screen_loc = "6:[pos],4:-2" - CR.button.moved = "6:[pos],4:-2" - -/mob/living/simple_animal/hostile/construct/Login() - ..() - to_chat(src, playstyle_string) - -/mob/living/simple_animal/hostile/construct/examine(mob/user) - var/t_He = p_they(TRUE) - var/t_s = p_s() - . = list("*---------*\nThis is [icon2html(src, user)] \a [src]!\n[desc]") - if(health < maxHealth) - if(health >= maxHealth/2) - . += "[t_He] look[t_s] slightly dented." - else - . += "[t_He] look[t_s] severely dented!" - . += "*---------*" - -/mob/living/simple_animal/hostile/construct/attack_animal(mob/living/simple_animal/M) - if(isconstruct(M)) //is it a construct? - var/mob/living/simple_animal/hostile/construct/C = M - if(!C.can_repair_constructs || (C == src && !C.can_repair_self)) - return - if(health < maxHealth) - adjustHealth(-5) - if(src != M) - Beam(M,icon_state="sendbeam",time=4) - M.visible_message("[M] repairs some of \the [src]'s dents.", \ - "You repair some of [src]'s dents, leaving [src] at [health]/[maxHealth] health.") - else - M.visible_message("[M] repairs some of [p_their()] own dents.", \ - "You repair some of your own dents, leaving you at [M.health]/[M.maxHealth] health.") - else - if(src != M) - to_chat(M, "You cannot repair [src]'s dents, as [p_they()] [p_have()] none!") - else - to_chat(M, "You cannot repair your own dents, as you have none!") - else if(src != M) - return ..() - -/mob/living/simple_animal/hostile/construct/narsie_act() - return - -/mob/living/simple_animal/hostile/construct/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = 0, tesla_shock = 0, illusion = 0, stun = TRUE) - return 0 - -/mob/living/simple_animal/hostile/construct/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(updating_health) - update_health_hud() - -/////////////////Juggernaut/////////////// -/mob/living/simple_animal/hostile/construct/armored - name = "Juggernaut" - real_name = "Juggernaut" - desc = "A massive, armored construct built to spearhead attacks and soak up enemy fire." - icon_state = "behemoth" - icon_living = "behemoth" - maxHealth = 150 - health = 150 - response_harm = "harmlessly punches" - harm_intent_damage = 0 - obj_damage = 90 - melee_damage_lower = 25 - melee_damage_upper = 25 - attacktext = "smashes their armored gauntlet into" - speed = 2.5 - environment_smash = ENVIRONMENT_SMASH_WALLS - attack_sound = 'sound/weapons/punch3.ogg' - status_flags = 0 - mob_size = MOB_SIZE_LARGE - force_threshold = 10 - construct_spells = list(/obj/effect/proc_holder/spell/targeted/forcewall/cult, - /obj/effect/proc_holder/spell/dumbfire/juggernaut) - runetype = /datum/action/innate/cult/create_rune/wall - playstyle_string = "You are a Juggernaut. Though slow, your shell can withstand heavy punishment, \ - create shield walls, rip apart enemies and walls alike, and even deflect energy weapons." - -/mob/living/simple_animal/hostile/construct/armored/hostile //actually hostile, will move around, hit things - AIStatus = AI_ON - environment_smash = ENVIRONMENT_SMASH_STRUCTURES //only token destruction, don't smash the cult wall NO STOP - -/mob/living/simple_animal/hostile/construct/armored/bullet_act(obj/item/projectile/P) - if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) - var/reflectchance = 40 - round(P.damage/3) - if(prob(reflectchance)) - apply_damage(P.damage * 0.5, P.damage_type) - visible_message("The [P.name] is reflected by [src]'s armored shell!", \ - "The [P.name] is reflected by your armored shell!") - - // Find a turf near or on the original location to bounce to - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) - var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) - var/turf/curloc = get_turf(src) - - // redirect the projectile - P.original = locate(new_x, new_y, P.z) - P.starting = curloc - P.firer = src - P.yo = new_y - curloc.y - P.xo = new_x - curloc.x - var/new_angle_s = P.Angle + rand(120,240) - while(new_angle_s > 180) // Translate to regular projectile degrees - new_angle_s -= 360 - P.setAngle(new_angle_s) - - return -1 // complete projectile permutation - - return (..(P)) - - - -////////////////////////Wraith///////////////////////////////////////////// -/mob/living/simple_animal/hostile/construct/wraith - name = "Wraith" - real_name = "Wraith" - desc = "A wicked, clawed shell constructed to assassinate enemies and sow chaos behind enemy lines." - icon_state = "floating" - icon_living = "floating" - maxHealth = 65 - health = 65 - melee_damage_lower = 20 - melee_damage_upper = 20 - retreat_distance = 2 //AI wraiths will move in and out of combat - attacktext = "slashes" - attack_sound = 'sound/weapons/bladeslice.ogg' - construct_spells = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) - runetype = /datum/action/innate/cult/create_rune/tele - playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, can phase through walls, and your attacks will lower the cooldown on phasing." - - var/attack_refund = 10 //1 second per attack - var/crit_refund = 50 //5 seconds when putting a target into critical - var/kill_refund = 250 //full refund on kills - -/mob/living/simple_animal/hostile/construct/wraith/AttackingTarget() //refund jaunt cooldown when attacking living targets - var/prev_stat - if(isliving(target) && !iscultist(target)) - var/mob/living/L = target - prev_stat = L.stat - - . = ..() - - if(. && isnum(prev_stat)) - var/mob/living/L = target - var/refund = 0 - if(QDELETED(L) || (L.stat == DEAD && prev_stat != DEAD)) //they're dead, you killed them - refund += kill_refund - else if(L.InCritical() && prev_stat == CONSCIOUS) //you knocked them into critical - refund += crit_refund - if(L.stat != DEAD && prev_stat != DEAD) - refund += attack_refund - for(var/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/S in mob_spell_list) - S.charge_counter = min(S.charge_counter + refund, S.charge_max) - -/mob/living/simple_animal/hostile/construct/wraith/hostile //actually hostile, will move around, hit things - AIStatus = AI_ON - - - -/////////////////////////////Artificer///////////////////////// -/mob/living/simple_animal/hostile/construct/builder - name = "Artificer" - real_name = "Artificer" - desc = "A bulbous construct dedicated to building and maintaining the Cult of Nar'Sie's armies." - icon_state = "artificer" - icon_living = "artificer" - maxHealth = 50 - health = 50 - response_harm = "viciously beats" - harm_intent_damage = 5 - obj_damage = 60 - melee_damage_lower = 5 - melee_damage_upper = 5 - retreat_distance = 10 - minimum_distance = 10 //AI artificers will flee like fuck - attacktext = "rams" - environment_smash = ENVIRONMENT_SMASH_WALLS - attack_sound = 'sound/weapons/punch2.ogg' - construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/wall, - /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, - /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) - runetype = /datum/action/innate/cult/create_rune/revive - playstyle_string = "You are an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, \ - - use magic missile, repair allied constructs, shades, and yourself (by clicking on them), \ - and, most important of all, create new constructs by producing soulstones to capture souls, \ - and shells to place those soulstones into." - can_repair_constructs = TRUE - can_repair_self = TRUE - -/mob/living/simple_animal/hostile/construct/builder/Found(atom/A) //what have we found here? - if(isconstruct(A)) //is it a construct? - var/mob/living/simple_animal/hostile/construct/C = A - if(C.health < C.maxHealth) //is it hurt? let's go heal it if it is - return 1 - else - return 0 - else - return 0 - -/mob/living/simple_animal/hostile/construct/builder/CanAttack(atom/the_target) - if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it - return 0 - if(Found(the_target) || ..()) //If we Found it or Can_Attack it normally, we Can_Attack it as long as it wasn't invisible - return 1 //as a note this shouldn't be added to base hostile mobs because it'll mess up retaliate hostile mobs - -/mob/living/simple_animal/hostile/construct/builder/MoveToTarget(var/list/possible_targets) - ..() - if(isliving(target)) - var/mob/living/L = target - if(isconstruct(L) && L.health >= L.maxHealth) //is this target an unhurt construct? stop trying to heal it - LoseTarget() - return 0 - if(L.health <= melee_damage_lower+melee_damage_upper) //ey bucko you're hurt as fuck let's go hit you - retreat_distance = null - minimum_distance = 1 - -/mob/living/simple_animal/hostile/construct/builder/Aggro() - ..() - if(isconstruct(target)) //oh the target is a construct no need to flee - retreat_distance = null - minimum_distance = 1 - -/mob/living/simple_animal/hostile/construct/builder/LoseAggro() - ..() - retreat_distance = initial(retreat_distance) - minimum_distance = initial(minimum_distance) - -/mob/living/simple_animal/hostile/construct/builder/hostile //actually hostile, will move around, hit things, heal other constructs - AIStatus = AI_ON - environment_smash = ENVIRONMENT_SMASH_STRUCTURES //only token destruction, don't smash the cult wall NO STOP - -/////////////////////////////Non-cult Artificer///////////////////////// -/mob/living/simple_animal/hostile/construct/builder/noncult - construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/wall, - /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, - /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) - - -/////////////////////////////Harvester///////////////////////// -/mob/living/simple_animal/hostile/construct/harvester - name = "Harvester" - real_name = "Harvester" - desc = "A long, thin construct built to herald Nar'Sie's rise. It'll be all over soon." - icon_state = "chosen" - icon_living = "chosen" - maxHealth = 40 - health = 40 - sight = SEE_MOBS - melee_damage_lower = 15 - melee_damage_upper = 20 - attacktext = "butchers" - attack_sound = 'sound/weapons/bladeslice.ogg' - construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/area_conversion, - /obj/effect/proc_holder/spell/targeted/forcewall/cult) - playstyle_string = "You are a Harvester. You are incapable of directly killing humans, but your attacks will remove their limbs: \ - Bring those who still cling to this world of illusion back to the Geometer so they may know Truth. Your form and any you are pulling can pass through runed walls effortlessly." - can_repair_constructs = TRUE - - -/mob/living/simple_animal/hostile/construct/harvester/Bump(atom/AM) - . = ..() - if(istype(AM, /turf/closed/wall/mineral/cult) && AM != loc) //we can go through cult walls - var/atom/movable/stored_pulling = pulling - if(stored_pulling) - stored_pulling.setDir(get_dir(stored_pulling.loc, loc)) - stored_pulling.forceMove(loc) - forceMove(AM) - if(stored_pulling) - start_pulling(stored_pulling, TRUE) //drag anything we're pulling through the wall with us by magic - -/mob/living/simple_animal/hostile/construct/harvester/AttackingTarget() - if(iscarbon(target)) - var/mob/living/carbon/C = target - if(HAS_TRAIT(C, TRAIT_NODISMEMBER)) - return ..() //ATTACK! - var/list/parts = list() - var/undismembermerable_limbs = 0 - for(var/X in C.bodyparts) - var/obj/item/bodypart/BP = X - if(BP.body_part != HEAD && BP.body_part != CHEST) - if(BP.dismemberable) - parts += BP - else - undismembermerable_limbs++ - if(!LAZYLEN(parts)) - if(undismembermerable_limbs) //they have limbs we can't remove, and no parts we can, attack! - return ..() - C.Knockdown(60) - visible_message("[src] knocks [C] down!") - to_chat(src, "\"Bring [C.p_them()] to me.\"") - return FALSE - do_attack_animation(C) - var/obj/item/bodypart/BP = pick(parts) - BP.dismember() - return FALSE - . = ..() - -/mob/living/simple_animal/hostile/construct/harvester/Initialize() - . = ..() - var/datum/action/innate/seek_prey/seek = new() - seek.Grant(src) - seek.Activate() - -///////////////////////Master-Tracker/////////////////////// - -/datum/action/innate/seek_master - name = "Seek your Master" - desc = "You and your master share a soul-link that informs you of their location" - background_icon_state = "bg_demon" - buttontooltipstyle = "cult" - button_icon_state = "cult_mark" - var/tracking = FALSE - var/mob/living/simple_animal/hostile/construct/the_construct - - -/datum/action/innate/seek_master/Grant(var/mob/living/C) - the_construct = C - ..() - -/datum/action/innate/seek_master/Activate() - var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult) - if(!C) - return - var/datum/objective/eldergod/summon_objective = locate() in C.cult_team.objectives - - if(summon_objective.check_completion()) - the_construct.master = C.cult_team.blood_target - - if(!the_construct.master) - to_chat(the_construct, "You have no master to seek!") - the_construct.seeking = FALSE - return - if(tracking) - tracking = FALSE - the_construct.seeking = FALSE - to_chat(the_construct, "You are no longer tracking your master.") - return - else - tracking = TRUE - the_construct.seeking = TRUE - to_chat(the_construct, "You are now tracking your master.") - - -/datum/action/innate/seek_prey - name = "Seek the Harvest" - desc = "None can hide from Nar'Sie, activate to track a survivor attempting to flee the red harvest!" - icon_icon = 'icons/mob/actions/actions_cult.dmi' - background_icon_state = "bg_demon" - buttontooltipstyle = "cult" - button_icon_state = "cult_mark" - var/mob/living/simple_animal/hostile/construct/harvester/the_construct - -/datum/action/innate/seek_prey/Grant(var/mob/living/C) - the_construct = C - ..() - -/datum/action/innate/seek_prey/Activate() - if(GLOB.cult_narsie == null) - return - if(the_construct.seeking) - desc = "None can hide from Nar'Sie, activate to track a survivor attempting to flee the red harvest!" - button_icon_state = "cult_mark" - the_construct.seeking = FALSE - to_chat(the_construct, "You are now tracking Nar'Sie, return to reap the harvest!") - return - else - if(LAZYLEN(GLOB.cult_narsie.souls_needed)) - the_construct.master = pick(GLOB.cult_narsie.souls_needed) - var/mob/living/real_target = the_construct.master //We can typecast this way because Narsie only allows /mob/living into the souls list - to_chat(the_construct, "You are now tracking your prey, [real_target.real_name] - harvest [real_target.p_them()]!") - else - to_chat(the_construct, "Nar'Sie has completed her harvest!") - return - desc = "Activate to track Nar'Sie!" - button_icon_state = "sintouch" - the_construct.seeking = TRUE - - -/////////////////////////////ui stuff///////////////////////////// - -/mob/living/simple_animal/hostile/construct/update_health_hud() - if(hud_used) - if(health >= maxHealth) - hud_used.healths.icon_state = "[icon_state]_health0" - else if(health > maxHealth*0.8) - hud_used.healths.icon_state = "[icon_state]_health2" - else if(health > maxHealth*0.6) - hud_used.healths.icon_state = "[icon_state]_health3" - else if(health > maxHealth*0.4) - hud_used.healths.icon_state = "[icon_state]_health4" - else if(health > maxHealth*0.2) - hud_used.healths.icon_state = "[icon_state]_health5" - else - hud_used.healths.icon_state = "[icon_state]_health6" +/mob/living/simple_animal/hostile/construct + name = "Construct" + real_name = "Construct" + desc = "" + gender = NEUTER + mob_biotypes = list(MOB_INORGANIC) + speak_emote = list("hisses") + response_help = "thinks better of touching" + response_disarm = "flails at" + response_harm = "punches" + speak_chance = 1 + icon = 'icons/mob/mob.dmi' + speed = 0 + spacewalk = TRUE + a_intent = INTENT_HARM + stop_automated_movement = 1 + status_flags = CANPUSH + attack_sound = 'sound/weapons/punch1.ogg' + see_in_dark = 7 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) + 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 = INFINITY + healable = 0 + faction = list("cult") + movement_type = FLYING + pressure_resistance = 100 + unique_name = 1 + AIStatus = AI_OFF //normal constructs don't have AI + loot = list(/obj/item/ectoplasm) + del_on_death = TRUE + initial_language_holder = /datum/language_holder/construct + deathmessage = "collapses in a shattered heap." + hud_type = /datum/hud/constructs + blood_volume = 0 + var/list/construct_spells = list() + var/playstyle_string = "You are a generic construct! Your job is to not exist, and you should probably adminhelp this." + var/master = null + var/seeking = FALSE + var/can_repair_constructs = FALSE + var/can_repair_self = FALSE + var/runetype + +/mob/living/simple_animal/hostile/construct/Initialize() + . = ..() + update_health_hud() + var/spellnum = 1 + for(var/spell in construct_spells) + var/the_spell = new spell(null) + AddSpell(the_spell) + var/obj/effect/proc_holder/spell/S = mob_spell_list[spellnum] + var/pos = 2+spellnum*31 + if(construct_spells.len >= 4) + pos -= 31*(construct_spells.len - 4) + S.action.button.screen_loc = "6:[pos],4:-2" + S.action.button.moved = "6:[pos],4:-2" + spellnum++ + if(runetype) + var/datum/action/innate/cult/create_rune/CR = new runetype(src) + CR.Grant(src) + var/pos = 2+spellnum*31 + CR.button.screen_loc = "6:[pos],4:-2" + CR.button.moved = "6:[pos],4:-2" + +/mob/living/simple_animal/hostile/construct/Login() + ..() + to_chat(src, playstyle_string) + +/mob/living/simple_animal/hostile/construct/examine(mob/user) + var/t_He = p_they(TRUE) + var/t_s = p_s() + . = list("*---------*\nThis is [icon2html(src, user)] \a [src]!\n[desc]") + if(health < maxHealth) + if(health >= maxHealth/2) + . += "[t_He] look[t_s] slightly dented." + else + . += "[t_He] look[t_s] severely dented!" + . += "*---------*" + +/mob/living/simple_animal/hostile/construct/attack_animal(mob/living/simple_animal/M) + if(isconstruct(M)) //is it a construct? + var/mob/living/simple_animal/hostile/construct/C = M + if(!C.can_repair_constructs || (C == src && !C.can_repair_self)) + return + if(health < maxHealth) + adjustHealth(-5) + if(src != M) + Beam(M,icon_state="sendbeam",time=4) + M.visible_message("[M] repairs some of \the [src]'s dents.", \ + "You repair some of [src]'s dents, leaving [src] at [health]/[maxHealth] health.") + else + M.visible_message("[M] repairs some of [p_their()] own dents.", \ + "You repair some of your own dents, leaving you at [M.health]/[M.maxHealth] health.") + else + if(src != M) + to_chat(M, "You cannot repair [src]'s dents, as [p_they()] [p_have()] none!") + else + to_chat(M, "You cannot repair your own dents, as you have none!") + else if(src != M) + return ..() + +/mob/living/simple_animal/hostile/construct/narsie_act() + return + +/mob/living/simple_animal/hostile/construct/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = 0, tesla_shock = 0, illusion = 0, stun = TRUE) + return 0 + +/mob/living/simple_animal/hostile/construct/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + . = ..() + if(updating_health) + update_health_hud() + +/////////////////Juggernaut/////////////// +/mob/living/simple_animal/hostile/construct/armored + name = "Juggernaut" + real_name = "Juggernaut" + desc = "A massive, armored construct built to spearhead attacks and soak up enemy fire." + icon_state = "behemoth" + icon_living = "behemoth" + maxHealth = 150 + health = 150 + response_harm = "harmlessly punches" + harm_intent_damage = 0 + obj_damage = 90 + melee_damage_lower = 25 + melee_damage_upper = 25 + attacktext = "smashes their armored gauntlet into" + speed = 2.5 + environment_smash = ENVIRONMENT_SMASH_WALLS + attack_sound = 'sound/weapons/punch3.ogg' + status_flags = 0 + mob_size = MOB_SIZE_LARGE + force_threshold = 10 + construct_spells = list(/obj/effect/proc_holder/spell/targeted/forcewall/cult, + /obj/effect/proc_holder/spell/dumbfire/juggernaut) + runetype = /datum/action/innate/cult/create_rune/wall + playstyle_string = "You are a Juggernaut. Though slow, your shell can withstand heavy punishment, \ + create shield walls, rip apart enemies and walls alike, and even deflect energy weapons." + +/mob/living/simple_animal/hostile/construct/armored/hostile //actually hostile, will move around, hit things + AIStatus = AI_ON + environment_smash = ENVIRONMENT_SMASH_STRUCTURES //only token destruction, don't smash the cult wall NO STOP + +/mob/living/simple_animal/hostile/construct/armored/bullet_act(obj/item/projectile/P) + if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) + var/reflectchance = 40 - round(P.damage/3) + if(prob(reflectchance)) + apply_damage(P.damage * 0.5, P.damage_type) + visible_message("The [P.name] is reflected by [src]'s armored shell!", \ + "The [P.name] is reflected by your armored shell!") + + // Find a turf near or on the original location to bounce to + if(P.starting) + var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/turf/curloc = get_turf(src) + + // redirect the projectile + P.original = locate(new_x, new_y, P.z) + P.starting = curloc + P.firer = src + P.yo = new_y - curloc.y + P.xo = new_x - curloc.x + var/new_angle_s = P.Angle + rand(120,240) + while(new_angle_s > 180) // Translate to regular projectile degrees + new_angle_s -= 360 + P.setAngle(new_angle_s) + + return -1 // complete projectile permutation + + return (..(P)) + + + +////////////////////////Wraith///////////////////////////////////////////// +/mob/living/simple_animal/hostile/construct/wraith + name = "Wraith" + real_name = "Wraith" + desc = "A wicked, clawed shell constructed to assassinate enemies and sow chaos behind enemy lines." + icon_state = "floating" + icon_living = "floating" + maxHealth = 65 + health = 65 + melee_damage_lower = 20 + melee_damage_upper = 20 + retreat_distance = 2 //AI wraiths will move in and out of combat + attacktext = "slashes" + attack_sound = 'sound/weapons/bladeslice.ogg' + construct_spells = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) + runetype = /datum/action/innate/cult/create_rune/tele + playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, can phase through walls, and your attacks will lower the cooldown on phasing." + + var/attack_refund = 10 //1 second per attack + var/crit_refund = 50 //5 seconds when putting a target into critical + var/kill_refund = 250 //full refund on kills + +/mob/living/simple_animal/hostile/construct/wraith/AttackingTarget() //refund jaunt cooldown when attacking living targets + var/prev_stat + if(isliving(target) && !iscultist(target)) + var/mob/living/L = target + prev_stat = L.stat + + . = ..() + + if(. && isnum(prev_stat)) + var/mob/living/L = target + var/refund = 0 + if(QDELETED(L) || (L.stat == DEAD && prev_stat != DEAD)) //they're dead, you killed them + refund += kill_refund + else if(L.InCritical() && prev_stat == CONSCIOUS) //you knocked them into critical + refund += crit_refund + if(L.stat != DEAD && prev_stat != DEAD) + refund += attack_refund + for(var/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/S in mob_spell_list) + S.charge_counter = min(S.charge_counter + refund, S.charge_max) + +/mob/living/simple_animal/hostile/construct/wraith/hostile //actually hostile, will move around, hit things + AIStatus = AI_ON + + + +/////////////////////////////Artificer///////////////////////// +/mob/living/simple_animal/hostile/construct/builder + name = "Artificer" + real_name = "Artificer" + desc = "A bulbous construct dedicated to building and maintaining the Cult of Nar'Sie's armies." + icon_state = "artificer" + icon_living = "artificer" + maxHealth = 50 + health = 50 + response_harm = "viciously beats" + harm_intent_damage = 5 + obj_damage = 60 + melee_damage_lower = 5 + melee_damage_upper = 5 + retreat_distance = 10 + minimum_distance = 10 //AI artificers will flee like fuck + attacktext = "rams" + environment_smash = ENVIRONMENT_SMASH_WALLS + attack_sound = 'sound/weapons/punch2.ogg' + construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/wall, + /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, + /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone, + /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, + /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) + runetype = /datum/action/innate/cult/create_rune/revive + playstyle_string = "You are an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, \ + + use magic missile, repair allied constructs, shades, and yourself (by clicking on them), \ + and, most important of all, create new constructs by producing soulstones to capture souls, \ + and shells to place those soulstones into." + can_repair_constructs = TRUE + can_repair_self = TRUE + +/mob/living/simple_animal/hostile/construct/builder/Found(atom/A) //what have we found here? + if(isconstruct(A)) //is it a construct? + var/mob/living/simple_animal/hostile/construct/C = A + if(C.health < C.maxHealth) //is it hurt? let's go heal it if it is + return 1 + else + return 0 + else + return 0 + +/mob/living/simple_animal/hostile/construct/builder/CanAttack(atom/the_target) + if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it + return 0 + if(Found(the_target) || ..()) //If we Found it or Can_Attack it normally, we Can_Attack it as long as it wasn't invisible + return 1 //as a note this shouldn't be added to base hostile mobs because it'll mess up retaliate hostile mobs + +/mob/living/simple_animal/hostile/construct/builder/MoveToTarget(var/list/possible_targets) + ..() + if(isliving(target)) + var/mob/living/L = target + if(isconstruct(L) && L.health >= L.maxHealth) //is this target an unhurt construct? stop trying to heal it + LoseTarget() + return 0 + if(L.health <= melee_damage_lower+melee_damage_upper) //ey bucko you're hurt as fuck let's go hit you + retreat_distance = null + minimum_distance = 1 + +/mob/living/simple_animal/hostile/construct/builder/Aggro() + ..() + if(isconstruct(target)) //oh the target is a construct no need to flee + retreat_distance = null + minimum_distance = 1 + +/mob/living/simple_animal/hostile/construct/builder/LoseAggro() + ..() + retreat_distance = initial(retreat_distance) + minimum_distance = initial(minimum_distance) + +/mob/living/simple_animal/hostile/construct/builder/hostile //actually hostile, will move around, hit things, heal other constructs + AIStatus = AI_ON + environment_smash = ENVIRONMENT_SMASH_STRUCTURES //only token destruction, don't smash the cult wall NO STOP + +/////////////////////////////Non-cult Artificer///////////////////////// +/mob/living/simple_animal/hostile/construct/builder/noncult + construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/wall, + /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, + /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult, + /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, + /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) + + +/////////////////////////////Harvester///////////////////////// +/mob/living/simple_animal/hostile/construct/harvester + name = "Harvester" + real_name = "Harvester" + desc = "A long, thin construct built to herald Nar'Sie's rise. It'll be all over soon." + icon_state = "chosen" + icon_living = "chosen" + maxHealth = 40 + health = 40 + sight = SEE_MOBS + melee_damage_lower = 15 + melee_damage_upper = 20 + attacktext = "butchers" + attack_sound = 'sound/weapons/bladeslice.ogg' + construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/area_conversion, + /obj/effect/proc_holder/spell/targeted/forcewall/cult) + playstyle_string = "You are a Harvester. You are incapable of directly killing humans, but your attacks will remove their limbs: \ + Bring those who still cling to this world of illusion back to the Geometer so they may know Truth. Your form and any you are pulling can pass through runed walls effortlessly." + can_repair_constructs = TRUE + + +/mob/living/simple_animal/hostile/construct/harvester/Bump(atom/AM) + . = ..() + if(istype(AM, /turf/closed/wall/mineral/cult) && AM != loc) //we can go through cult walls + var/atom/movable/stored_pulling = pulling + if(stored_pulling) + stored_pulling.setDir(get_dir(stored_pulling.loc, loc)) + stored_pulling.forceMove(loc) + forceMove(AM) + if(stored_pulling) + start_pulling(stored_pulling, TRUE) //drag anything we're pulling through the wall with us by magic + +/mob/living/simple_animal/hostile/construct/harvester/AttackingTarget() + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(HAS_TRAIT(C, TRAIT_NODISMEMBER)) + return ..() //ATTACK! + var/list/parts = list() + var/undismembermerable_limbs = 0 + for(var/X in C.bodyparts) + var/obj/item/bodypart/BP = X + if(BP.body_part != HEAD && BP.body_part != CHEST) + if(BP.dismemberable) + parts += BP + else + undismembermerable_limbs++ + if(!LAZYLEN(parts)) + if(undismembermerable_limbs) //they have limbs we can't remove, and no parts we can, attack! + return ..() + C.Knockdown(60) + visible_message("[src] knocks [C] down!") + to_chat(src, "\"Bring [C.p_them()] to me.\"") + return FALSE + do_attack_animation(C) + var/obj/item/bodypart/BP = pick(parts) + BP.dismember() + return FALSE + . = ..() + +/mob/living/simple_animal/hostile/construct/harvester/Initialize() + . = ..() + var/datum/action/innate/seek_prey/seek = new() + seek.Grant(src) + seek.Activate() + +///////////////////////Master-Tracker/////////////////////// + +/datum/action/innate/seek_master + name = "Seek your Master" + desc = "You and your master share a soul-link that informs you of their location" + background_icon_state = "bg_demon" + buttontooltipstyle = "cult" + button_icon_state = "cult_mark" + var/tracking = FALSE + var/mob/living/simple_animal/hostile/construct/the_construct + + +/datum/action/innate/seek_master/Grant(var/mob/living/C) + the_construct = C + ..() + +/datum/action/innate/seek_master/Activate() + var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult) + if(!C) + return + var/datum/objective/eldergod/summon_objective = locate() in C.cult_team.objectives + + if(summon_objective.check_completion()) + the_construct.master = C.cult_team.blood_target + + if(!the_construct.master) + to_chat(the_construct, "You have no master to seek!") + the_construct.seeking = FALSE + return + if(tracking) + tracking = FALSE + the_construct.seeking = FALSE + to_chat(the_construct, "You are no longer tracking your master.") + return + else + tracking = TRUE + the_construct.seeking = TRUE + to_chat(the_construct, "You are now tracking your master.") + + +/datum/action/innate/seek_prey + name = "Seek the Harvest" + desc = "None can hide from Nar'Sie, activate to track a survivor attempting to flee the red harvest!" + icon_icon = 'icons/mob/actions/actions_cult.dmi' + background_icon_state = "bg_demon" + buttontooltipstyle = "cult" + button_icon_state = "cult_mark" + var/mob/living/simple_animal/hostile/construct/harvester/the_construct + +/datum/action/innate/seek_prey/Grant(var/mob/living/C) + the_construct = C + ..() + +/datum/action/innate/seek_prey/Activate() + if(GLOB.cult_narsie == null) + return + if(the_construct.seeking) + desc = "None can hide from Nar'Sie, activate to track a survivor attempting to flee the red harvest!" + button_icon_state = "cult_mark" + the_construct.seeking = FALSE + to_chat(the_construct, "You are now tracking Nar'Sie, return to reap the harvest!") + return + else + if(LAZYLEN(GLOB.cult_narsie.souls_needed)) + the_construct.master = pick(GLOB.cult_narsie.souls_needed) + var/mob/living/real_target = the_construct.master //We can typecast this way because Narsie only allows /mob/living into the souls list + to_chat(the_construct, "You are now tracking your prey, [real_target.real_name] - harvest [real_target.p_them()]!") + else + to_chat(the_construct, "Nar'Sie has completed her harvest!") + return + desc = "Activate to track Nar'Sie!" + button_icon_state = "sintouch" + the_construct.seeking = TRUE + + +/////////////////////////////ui stuff///////////////////////////// + +/mob/living/simple_animal/hostile/construct/update_health_hud() + if(hud_used) + if(health >= maxHealth) + hud_used.healths.icon_state = "[icon_state]_health0" + else if(health > maxHealth*0.8) + hud_used.healths.icon_state = "[icon_state]_health2" + else if(health > maxHealth*0.6) + hud_used.healths.icon_state = "[icon_state]_health3" + else if(health > maxHealth*0.4) + hud_used.healths.icon_state = "[icon_state]_health4" + else if(health > maxHealth*0.2) + hud_used.healths.icon_state = "[icon_state]_health5" + else + hud_used.healths.icon_state = "[icon_state]_health6" diff --git a/code/modules/mob/living/simple_animal/corpse.dm b/code/modules/mob/living/simple_animal/corpse.dm index ec20fbadcb..17d7530c29 100644 --- a/code/modules/mob/living/simple_animal/corpse.dm +++ b/code/modules/mob/living/simple_animal/corpse.dm @@ -1,221 +1,221 @@ -//Meant for simple animals to drop lootable human bodies. - -//If someone can do this in a neater way, be my guest-Kor - -//This has to be separate from the Away Mission corpses, because New() doesn't work for those, and initialize() doesn't work for these. - -//To do: Allow corpses to appear mangled, bloody, etc. Allow customizing the bodies appearance (they're all bald and white right now). - -//List of different corpse types - -/obj/effect/mob_spawn/human/corpse/syndicatesoldier - name = "Syndicate Operative" - id_job = "Operative" - hair_style = "Bald" - facial_hair_style = "Shaved" - outfit = /datum/outfit/syndicatesoldiercorpse - -/datum/outfit/syndicatesoldiercorpse - name = "Syndicate Operative Corpse" - uniform = /obj/item/clothing/under/syndicate - suit = /obj/item/clothing/suit/armor/vest - shoes = /obj/item/clothing/shoes/combat - gloves = /obj/item/clothing/gloves/combat - ears = /obj/item/radio/headset - mask = /obj/item/clothing/mask/gas - head = /obj/item/clothing/head/helmet/swat - back = /obj/item/storage/backpack - id = /obj/item/card/id/syndicate - -/obj/effect/mob_spawn/human/corpse/syndicatecommando - name = "Syndicate Commando" - id_job = "Operative" - hair_style = "Bald" - facial_hair_style = "Shaved" - outfit = /datum/outfit/syndicatecommandocorpse - -/datum/outfit/syndicatecommandocorpse - name = "Syndicate Commando Corpse" - uniform = /obj/item/clothing/under/syndicate - suit = /obj/item/clothing/suit/space/hardsuit/syndi - shoes = /obj/item/clothing/shoes/combat - gloves = /obj/item/clothing/gloves/combat - ears = /obj/item/radio/headset - mask = /obj/item/clothing/mask/gas/syndicate - back = /obj/item/tank/jetpack/oxygen - r_pocket = /obj/item/tank/internals/emergency_oxygen - id = /obj/item/card/id/syndicate - - -/obj/effect/mob_spawn/human/corpse/syndicatestormtrooper - name = "Syndicate Stormtrooper" - id_job = "Operative" - hair_style = "Bald" - facial_hair_style = "Shaved" - outfit = /datum/outfit/syndicatestormtroopercorpse - -/datum/outfit/syndicatestormtroopercorpse - name = "Syndicate Stormtrooper Corpse" - uniform = /obj/item/clothing/under/syndicate - suit = /obj/item/clothing/suit/space/hardsuit/syndi/elite - shoes = /obj/item/clothing/shoes/combat - gloves = /obj/item/clothing/gloves/combat - ears = /obj/item/radio/headset - mask = /obj/item/clothing/mask/gas/syndicate - back = /obj/item/tank/jetpack/oxygen/harness - id = /obj/item/card/id/syndicate - - -/obj/effect/mob_spawn/human/clown/corpse - roundstart = FALSE - instant = TRUE - skin_tone = "caucasian1" - hair_style = "Bald" - facial_hair_style = "Shaved" - -/obj/effect/mob_spawn/human/corpse/pirate - name = "Pirate" - skin_tone = "caucasian1" //all pirates are white because it's easier that way - outfit = /datum/outfit/piratecorpse - hair_style = "Bald" - facial_hair_style = "Shaved" - -/datum/outfit/piratecorpse - name = "Pirate Corpse" - uniform = /obj/item/clothing/under/pirate - shoes = /obj/item/clothing/shoes/jackboots - glasses = /obj/item/clothing/glasses/eyepatch - head = /obj/item/clothing/head/bandana - - -/obj/effect/mob_spawn/human/corpse/pirate/ranged - name = "Pirate Gunner" - outfit = /datum/outfit/piratecorpse/ranged - -/datum/outfit/piratecorpse/ranged - name = "Pirate Gunner Corpse" - suit = /obj/item/clothing/suit/pirate - head = /obj/item/clothing/head/pirate - - -/obj/effect/mob_spawn/human/corpse/russian - name = "Russian" - outfit = /datum/outfit/russiancorpse - hair_style = "Bald" - facial_hair_style = "Shaved" - -/datum/outfit/russiancorpse - name = "Russian Corpse" - uniform = /obj/item/clothing/under/soviet - shoes = /obj/item/clothing/shoes/jackboots - head = /obj/item/clothing/head/bearpelt - gloves = /obj/item/clothing/gloves/color/black - mask = /obj/item/clothing/mask/gas - - - -/obj/effect/mob_spawn/human/corpse/russian/ranged - outfit = /datum/outfit/russiancorpse/ranged - -/datum/outfit/russiancorpse/ranged - name = "Ranged Russian Corpse" - head = /obj/item/clothing/head/ushanka - - -/obj/effect/mob_spawn/human/corpse/russian/ranged/trooper - outfit = /datum/outfit/russiancorpse/ranged/trooper - -/datum/outfit/russiancorpse/ranged/trooper - name = "Ranged Russian Trooper Corpse" - uniform = /obj/item/clothing/under/syndicate/camo - suit = /obj/item/clothing/suit/armor/bulletproof - shoes = /obj/item/clothing/shoes/combat - gloves = /obj/item/clothing/gloves/combat - ears = /obj/item/radio/headset - head = /obj/item/clothing/head/helmet/alt - mask = /obj/item/clothing/mask/balaclava - - -/obj/effect/mob_spawn/human/corpse/russian/ranged/officer - name = "Russian Officer" - outfit = /datum/outfit/russiancorpse/officer - -/datum/outfit/russiancorpse/officer - name = "Russian Officer Corpse" - uniform = /obj/item/clothing/under/rank/security/navyblue/russian - suit = /obj/item/clothing/suit/security/officer/russian - shoes = /obj/item/clothing/shoes/combat - ears = /obj/item/radio/headset - head = /obj/item/clothing/head/ushanka - - -/obj/effect/mob_spawn/human/corpse/wizard - name = "Space Wizard Corpse" - outfit = /datum/outfit/wizardcorpse - hair_style = "Bald" - facial_hair_style = "Long Beard" - skin_tone = "caucasian1" - -/datum/outfit/wizardcorpse - name = "Space Wizard Corpse" - uniform = /obj/item/clothing/under/color/lightpurple - suit = /obj/item/clothing/suit/wizrobe - shoes = /obj/item/clothing/shoes/sandal/magic - head = /obj/item/clothing/head/wizard - - -/obj/effect/mob_spawn/human/corpse/nanotrasensoldier - name = "Nanotrasen Private Security Officer" - id_job = "Private Security Force" - id_access = "Security Officer" - outfit = /datum/outfit/nanotrasensoldiercorpse2 - hair_style = "Bald" - facial_hair_style = "Shaved" - -/datum/outfit/nanotrasensoldiercorpse2 - name = "NT Private Security Officer Corpse" - uniform = /obj/item/clothing/under/rank/security - suit = /obj/item/clothing/suit/armor/vest - shoes = /obj/item/clothing/shoes/combat - gloves = /obj/item/clothing/gloves/combat - ears = /obj/item/radio/headset - mask = /obj/item/clothing/mask/gas/sechailer/swat - head = /obj/item/clothing/head/helmet/swat/nanotrasen - back = /obj/item/storage/backpack/security - id = /obj/item/card/id - -/obj/effect/mob_spawn/human/corpse/cat_butcher - name = "The Cat Surgeon" - id_job = "Cat Surgeon" - id_access_list = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT) - hair_style = "Cut Hair" - facial_hair_style = "Watson Mustache" - skin_tone = "caucasian1" - outfit = /datum/outfit/cat_butcher - -/datum/outfit/cat_butcher - name = "Cat Butcher Uniform" - uniform = /obj/item/clothing/under/rank/medical/green - suit = /obj/item/clothing/suit/apron/surgical - shoes = /obj/item/clothing/shoes/sneakers/white - gloves = /obj/item/clothing/gloves/color/latex/nitrile - ears = /obj/item/radio/headset - back = /obj/item/storage/backpack/satchel/med - id = /obj/item/card/id - glasses = /obj/item/clothing/glasses/hud/health - -/obj/effect/mob_spawn/human/corpse/bee_terrorist - name = "BLF Operative" - outfit = /datum/outfit/bee_terrorist - -/datum/outfit/bee_terrorist - name = "BLF Operative" - uniform = /obj/item/clothing/under/color/yellow - suit = /obj/item/clothing/suit/hooded/bee_costume - shoes = /obj/item/clothing/shoes/sneakers/yellow - gloves = /obj/item/clothing/gloves/color/yellow - ears = /obj/item/radio/headset - belt = /obj/item/storage/belt/fannypack/yellow/bee_terrorist - id = /obj/item/card/id - l_pocket = /obj/item/paper/fluff/bee_objectives - mask = /obj/item/clothing/mask/rat/bee +//Meant for simple animals to drop lootable human bodies. + +//If someone can do this in a neater way, be my guest-Kor + +//This has to be separate from the Away Mission corpses, because New() doesn't work for those, and initialize() doesn't work for these. + +//To do: Allow corpses to appear mangled, bloody, etc. Allow customizing the bodies appearance (they're all bald and white right now). + +//List of different corpse types + +/obj/effect/mob_spawn/human/corpse/syndicatesoldier + name = "Syndicate Operative" + id_job = "Operative" + hair_style = "Bald" + facial_hair_style = "Shaved" + outfit = /datum/outfit/syndicatesoldiercorpse + +/datum/outfit/syndicatesoldiercorpse + name = "Syndicate Operative Corpse" + uniform = /obj/item/clothing/under/syndicate + suit = /obj/item/clothing/suit/armor/vest + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + ears = /obj/item/radio/headset + mask = /obj/item/clothing/mask/gas + head = /obj/item/clothing/head/helmet/swat + back = /obj/item/storage/backpack + id = /obj/item/card/id/syndicate + +/obj/effect/mob_spawn/human/corpse/syndicatecommando + name = "Syndicate Commando" + id_job = "Operative" + hair_style = "Bald" + facial_hair_style = "Shaved" + outfit = /datum/outfit/syndicatecommandocorpse + +/datum/outfit/syndicatecommandocorpse + name = "Syndicate Commando Corpse" + uniform = /obj/item/clothing/under/syndicate + suit = /obj/item/clothing/suit/space/hardsuit/syndi + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + ears = /obj/item/radio/headset + mask = /obj/item/clothing/mask/gas/syndicate + back = /obj/item/tank/jetpack/oxygen + r_pocket = /obj/item/tank/internals/emergency_oxygen + id = /obj/item/card/id/syndicate + + +/obj/effect/mob_spawn/human/corpse/syndicatestormtrooper + name = "Syndicate Stormtrooper" + id_job = "Operative" + hair_style = "Bald" + facial_hair_style = "Shaved" + outfit = /datum/outfit/syndicatestormtroopercorpse + +/datum/outfit/syndicatestormtroopercorpse + name = "Syndicate Stormtrooper Corpse" + uniform = /obj/item/clothing/under/syndicate + suit = /obj/item/clothing/suit/space/hardsuit/syndi/elite + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + ears = /obj/item/radio/headset + mask = /obj/item/clothing/mask/gas/syndicate + back = /obj/item/tank/jetpack/oxygen/harness + id = /obj/item/card/id/syndicate + + +/obj/effect/mob_spawn/human/clown/corpse + roundstart = FALSE + instant = TRUE + skin_tone = "caucasian1" + hair_style = "Bald" + facial_hair_style = "Shaved" + +/obj/effect/mob_spawn/human/corpse/pirate + name = "Pirate" + skin_tone = "caucasian1" //all pirates are white because it's easier that way + outfit = /datum/outfit/piratecorpse + hair_style = "Bald" + facial_hair_style = "Shaved" + +/datum/outfit/piratecorpse + name = "Pirate Corpse" + uniform = /obj/item/clothing/under/pirate + shoes = /obj/item/clothing/shoes/jackboots + glasses = /obj/item/clothing/glasses/eyepatch + head = /obj/item/clothing/head/bandana + + +/obj/effect/mob_spawn/human/corpse/pirate/ranged + name = "Pirate Gunner" + outfit = /datum/outfit/piratecorpse/ranged + +/datum/outfit/piratecorpse/ranged + name = "Pirate Gunner Corpse" + suit = /obj/item/clothing/suit/pirate + head = /obj/item/clothing/head/pirate + + +/obj/effect/mob_spawn/human/corpse/russian + name = "Russian" + outfit = /datum/outfit/russiancorpse + hair_style = "Bald" + facial_hair_style = "Shaved" + +/datum/outfit/russiancorpse + name = "Russian Corpse" + uniform = /obj/item/clothing/under/soviet + shoes = /obj/item/clothing/shoes/jackboots + head = /obj/item/clothing/head/bearpelt + gloves = /obj/item/clothing/gloves/color/black + mask = /obj/item/clothing/mask/gas + + + +/obj/effect/mob_spawn/human/corpse/russian/ranged + outfit = /datum/outfit/russiancorpse/ranged + +/datum/outfit/russiancorpse/ranged + name = "Ranged Russian Corpse" + head = /obj/item/clothing/head/ushanka + + +/obj/effect/mob_spawn/human/corpse/russian/ranged/trooper + outfit = /datum/outfit/russiancorpse/ranged/trooper + +/datum/outfit/russiancorpse/ranged/trooper + name = "Ranged Russian Trooper Corpse" + uniform = /obj/item/clothing/under/syndicate/camo + suit = /obj/item/clothing/suit/armor/bulletproof + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + ears = /obj/item/radio/headset + head = /obj/item/clothing/head/helmet/alt + mask = /obj/item/clothing/mask/balaclava + + +/obj/effect/mob_spawn/human/corpse/russian/ranged/officer + name = "Russian Officer" + outfit = /datum/outfit/russiancorpse/officer + +/datum/outfit/russiancorpse/officer + name = "Russian Officer Corpse" + uniform = /obj/item/clothing/under/rank/security/navyblue/russian + suit = /obj/item/clothing/suit/security/officer/russian + shoes = /obj/item/clothing/shoes/combat + ears = /obj/item/radio/headset + head = /obj/item/clothing/head/ushanka + + +/obj/effect/mob_spawn/human/corpse/wizard + name = "Space Wizard Corpse" + outfit = /datum/outfit/wizardcorpse + hair_style = "Bald" + facial_hair_style = "Long Beard" + skin_tone = "caucasian1" + +/datum/outfit/wizardcorpse + name = "Space Wizard Corpse" + uniform = /obj/item/clothing/under/color/lightpurple + suit = /obj/item/clothing/suit/wizrobe + shoes = /obj/item/clothing/shoes/sandal/magic + head = /obj/item/clothing/head/wizard + + +/obj/effect/mob_spawn/human/corpse/nanotrasensoldier + name = "Nanotrasen Private Security Officer" + id_job = "Private Security Force" + id_access = "Security Officer" + outfit = /datum/outfit/nanotrasensoldiercorpse2 + hair_style = "Bald" + facial_hair_style = "Shaved" + +/datum/outfit/nanotrasensoldiercorpse2 + name = "NT Private Security Officer Corpse" + uniform = /obj/item/clothing/under/rank/security + suit = /obj/item/clothing/suit/armor/vest + shoes = /obj/item/clothing/shoes/combat + gloves = /obj/item/clothing/gloves/combat + ears = /obj/item/radio/headset + mask = /obj/item/clothing/mask/gas/sechailer/swat + head = /obj/item/clothing/head/helmet/swat/nanotrasen + back = /obj/item/storage/backpack/security + id = /obj/item/card/id + +/obj/effect/mob_spawn/human/corpse/cat_butcher + name = "The Cat Surgeon" + id_job = "Cat Surgeon" + id_access_list = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT) + hair_style = "Cut Hair" + facial_hair_style = "Watson Mustache" + skin_tone = "caucasian1" + outfit = /datum/outfit/cat_butcher + +/datum/outfit/cat_butcher + name = "Cat Butcher Uniform" + uniform = /obj/item/clothing/under/rank/medical/green + suit = /obj/item/clothing/suit/apron/surgical + shoes = /obj/item/clothing/shoes/sneakers/white + gloves = /obj/item/clothing/gloves/color/latex/nitrile + ears = /obj/item/radio/headset + back = /obj/item/storage/backpack/satchel/med + id = /obj/item/card/id + glasses = /obj/item/clothing/glasses/hud/health + +/obj/effect/mob_spawn/human/corpse/bee_terrorist + name = "BLF Operative" + outfit = /datum/outfit/bee_terrorist + +/datum/outfit/bee_terrorist + name = "BLF Operative" + uniform = /obj/item/clothing/under/color/yellow + suit = /obj/item/clothing/suit/hooded/bee_costume + shoes = /obj/item/clothing/shoes/sneakers/yellow + gloves = /obj/item/clothing/gloves/color/yellow + ears = /obj/item/radio/headset + belt = /obj/item/storage/belt/fannypack/yellow/bee_terrorist + id = /obj/item/card/id + l_pocket = /obj/item/paper/fluff/bee_objectives + mask = /obj/item/clothing/mask/rat/bee diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm index dacf8c4833..016bee2d44 100644 --- a/code/modules/mob/living/simple_animal/friendly/cat.dm +++ b/code/modules/mob/living/simple_animal/friendly/cat.dm @@ -1,313 +1,313 @@ -//Cat -/mob/living/simple_animal/pet/cat - name = "cat" - desc = "Kitty!!" - icon = 'icons/mob/pets.dmi' - icon_state = "cat2" - icon_living = "cat2" - icon_dead = "cat2_dead" - gender = MALE - speak = list("Meow!", "Esp!", "Purr!", "HSSSSS") - speak_emote = list("purrs", "meows") - emote_hear = list("meows.", "mews.") - emote_see = list("shakes its head.", "shivers.") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - ventcrawler = VENTCRAWLER_ALWAYS - pass_flags = PASSTABLE - mob_size = MOB_SIZE_SMALL - mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) - minbodytemp = 200 - maxbodytemp = 400 - unsuitable_atmos_damage = 1 - animal_species = /mob/living/simple_animal/pet/cat - childtype = list(/mob/living/simple_animal/pet/cat/kitten) - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 2, /obj/item/organ/ears/cat = 1, /obj/item/organ/tail/cat = 1) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - var/turns_since_scan = 0 - var/mob/living/simple_animal/mouse/movement_target - gold_core_spawnable = FRIENDLY_SPAWN - collar_type = "cat" - can_be_held = "cat2" - do_footstep = TRUE - -/mob/living/simple_animal/pet/cat/Initialize() - . = ..() - verbs += /mob/living/proc/lay_down - -/mob/living/simple_animal/pet/cat/ComponentInitialize() - . = ..() - AddElement(/datum/element/wuv, "purrs!", EMOTE_AUDIBLE, /datum/mood_event/pet_animal, "hisses!", EMOTE_AUDIBLE) - -/mob/living/simple_animal/pet/cat/update_canmove() - ..() - if(client && stat != DEAD) - if (resting) - icon_state = "[icon_living]_rest" - collar_type = "[initial(collar_type)]_rest" - else - icon_state = "[icon_living]" - collar_type = "[initial(collar_type)]" - regenerate_icons() - - -/mob/living/simple_animal/pet/cat/space - name = "space cat" - desc = "It's a cat... in space!" - icon_state = "spacecat" - icon_living = "spacecat" - icon_dead = "spacecat_dead" - unsuitable_atmos_damage = 0 - minbodytemp = TCMB - maxbodytemp = T0C + 40 - -/mob/living/simple_animal/pet/cat/original - name = "Batsy" - desc = "The product of alien DNA and bored geneticists." - gender = FEMALE - icon_state = "original" - icon_living = "original" - icon_dead = "original_dead" - collar_type = null - unique_pet = TRUE - -/mob/living/simple_animal/pet/cat/kitten - name = "kitten" - desc = "D'aaawwww." - icon_state = "kitten" - icon_living = "kitten" - icon_dead = "kitten_dead" - density = FALSE - pass_flags = PASSMOB - mob_size = MOB_SIZE_SMALL - collar_type = "kitten" - can_be_held = "cat" - -//RUNTIME IS ALIVE! SQUEEEEEEEE~ -/mob/living/simple_animal/pet/cat/Runtime - name = "Runtime" - desc = "GCAT" - icon_state = "cat" - icon_living = "cat" - icon_dead = "cat_dead" - gender = FEMALE - gold_core_spawnable = NO_SPAWN - unique_pet = TRUE - var/list/family = list()//var restored from savefile, has count of each child type - var/list/children = list()//Actual mob instances of children - var/cats_deployed = 0 - var/memory_saved = FALSE - -/mob/living/simple_animal/pet/cat/Runtime/Initialize() - if(prob(5)) - icon_state = "original" - icon_living = "original" - icon_dead = "original_dead" - Read_Memory() - . = ..() - -/mob/living/simple_animal/pet/cat/Runtime/Life() - if(!cats_deployed && SSticker.current_state >= GAME_STATE_SETTING_UP) - Deploy_The_Cats() - if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved) - Write_Memory() - memory_saved = TRUE - ..() - -/mob/living/simple_animal/pet/cat/Runtime/make_babies() - var/mob/baby = ..() - if(baby) - children += baby - return baby - -/mob/living/simple_animal/pet/cat/Runtime/death() - if(!memory_saved) - Write_Memory(TRUE) - ..() - -/mob/living/simple_animal/pet/cat/Runtime/proc/Read_Memory() - if(fexists("data/npc_saves/Runtime.sav")) //legacy compatability to convert old format to new - var/savefile/S = new /savefile("data/npc_saves/Runtime.sav") - S["family"] >> family - fdel("data/npc_saves/Runtime.sav") - else - var/json_file = file("data/npc_saves/Runtime.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - family = json["family"] - if(isnull(family)) - family = list() - -/mob/living/simple_animal/pet/cat/Runtime/proc/Write_Memory(dead) - var/json_file = file("data/npc_saves/Runtime.json") - var/list/file_data = list() - family = list() - if(!dead) - for(var/mob/living/simple_animal/pet/cat/kitten/C in children) - if(istype(C,type) || C.stat || !C.z || !C.butcher_results) //That last one is a work around for hologram cats - continue - if(C.type in family) - family[C.type] += 1 - else - family[C.type] = 1 - file_data["family"] = family - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -/mob/living/simple_animal/pet/cat/Runtime/proc/Deploy_The_Cats() - cats_deployed = 1 - for(var/cat_type in family) - if(family[cat_type] > 0) - for(var/i in 1 to min(family[cat_type],100)) //Limits to about 500 cats, you wouldn't think this would be needed (BUT IT IS) - new cat_type(loc) - -/mob/living/simple_animal/pet/cat/Proc - name = "Proc" - gender = MALE - gold_core_spawnable = NO_SPAWN - unique_pet = TRUE - -/mob/living/simple_animal/pet/cat/Life() - if(!stat && !buckled && !client) - if(prob(1)) - emote("me", EMOTE_VISIBLE, pick("stretches out for a belly rub.", "wags its tail.", "lies down.")) - icon_state = "[icon_living]_rest" - collar_type = "[initial(collar_type)]_rest" - resting = 1 - update_canmove() - else if (prob(1)) - emote("me", EMOTE_VISIBLE, pick("sits down.", "crouches on its hind legs.", "looks alert.")) - icon_state = "[icon_living]_sit" - collar_type = "[initial(collar_type)]_sit" - resting = 1 - update_canmove() - else if (prob(1)) - if (resting) - emote("me", EMOTE_VISIBLE, pick("gets up and meows.", "walks around.", "stops resting.")) - icon_state = "[icon_living]" - collar_type = "[initial(collar_type)]" - resting = 0 - update_canmove() - else - emote("me", EMOTE_VISIBLE, pick("grooms its fur.", "twitches its whiskers.", "shakes out its coat.")) - - //MICE! - if((src.loc) && isturf(src.loc)) - if(!stat && !resting && !buckled) - for(var/mob/living/simple_animal/mouse/M in view(1,src)) - if(!M.stat && Adjacent(M)) - emote("me", EMOTE_VISIBLE, "splats \the [M]!") - M.splat() - movement_target = null - stop_automated_movement = 0 - break - for(var/obj/item/toy/cattoy/T in view(1,src)) - if (T.cooldown < (world.time - 400)) - emote("me", EMOTE_VISIBLE, "bats \the [T] around with its paw!") - T.cooldown = world.time - - ..() - - make_babies() - - if(!stat && !resting && !buckled) - turns_since_scan++ - if(turns_since_scan > 5) - walk_to(src,0) - turns_since_scan = 0 - if((movement_target) && !(isturf(movement_target.loc) || ishuman(movement_target.loc) )) - movement_target = null - stop_automated_movement = 0 - if( !movement_target || !(movement_target.loc in oview(src, 3)) ) - movement_target = null - stop_automated_movement = 0 - for(var/mob/living/simple_animal/mouse/snack in oview(src,3)) - if(isturf(snack.loc) && !snack.stat) - movement_target = snack - break - if(movement_target) - stop_automated_movement = 1 - walk_to(src,movement_target,0,3) - -/mob/living/simple_animal/pet/cat/cak //I told you I'd do it, Remie - name = "Keeki" - desc = "It's a cat made out of cake." - icon_state = "cak" - icon_living = "cak" - icon_dead = "cak_dead" - health = 50 - maxHealth = 50 - gender = FEMALE - harm_intent_damage = 10 - butcher_results = list(/obj/item/organ/brain = 1, /obj/item/organ/heart = 1, /obj/item/reagent_containers/food/snacks/cakeslice/birthday = 3, \ - /obj/item/reagent_containers/food/snacks/meat/slab = 2) - response_harm = "takes a bite out of" - attacked_sound = 'sound/items/eatfood.ogg' - deathmessage = "loses its false life and collapses!" - death_sound = "bodyfall" - can_be_held = "cak" - -/mob/living/simple_animal/pet/cat/cak/CheckParts(list/parts) - ..() - var/obj/item/organ/brain/B = locate(/obj/item/organ/brain) in contents - if(!B || !B.brainmob || !B.brainmob.mind) - return - B.brainmob.mind.transfer_to(src) - to_chat(src, "You are a cak! You're a harmless cat/cake hybrid that everyone loves. People can take bites out of you if they're hungry, but you regenerate health \ - so quickly that it generally doesn't matter. You're remarkably resilient to any damage besides this and it's hard for you to really die at all. You should go around and bring happiness and \ - free cake to the station!") - var/new_name = stripped_input(src, "Enter your name, or press \"Cancel\" to stick with Keeki.", "Name Change") - if(new_name) - to_chat(src, "Your name is now \"new_name\"!") - name = new_name - -/mob/living/simple_animal/pet/cat/cak/Life() - ..() - if(stat) - return - if(health < maxHealth) - adjustBruteLoss(-8) //Fast life regen - for(var/obj/item/reagent_containers/food/snacks/donut/D in range(1, src)) //Frosts nearby donuts! - if(!D.is_decorated) - D.decorate_donut() - -/mob/living/simple_animal/pet/cat/cak/attack_hand(mob/living/L) - . = ..() - if(.) //the attack was blocked - return - if(L.a_intent == INTENT_HARM && L.reagents && !stat) - L.reagents.add_reagent(/datum/reagent/consumable/nutriment, 0.4) - L.reagents.add_reagent(/datum/reagent/consumable/nutriment/vitamin, 0.4) - -//Cat made -/mob/living/simple_animal/pet/cat/custom_cat - name = "White cat" //Incase it somehow gets spawned without an ID - desc = "A cute white catto!" - icon_state = "custom_cat" - icon_living = "custom_cat" - icon_dead = "custom_cat_dead" - gender = FEMALE - gold_core_spawnable = NO_SPAWN - health = 50 //So people can't instakill it s - maxHealth = 50 - speak = list("Meowrowr!", "Mew!", "Miauen!") - speak_emote = list("wigglepurrs", "mewls") - emote_hear = list("meows.", "mews.") - emote_see = list("looks at you eagerly for pets!", "wiggles enthusiastically.") - gold_core_spawnable = NO_SPAWN - var/pseudo_death = FALSE - var/mob/living/carbon/human/origin - -/mob/living/simple_animal/pet/cat/custom_cat/death() - if (pseudo_death == TRUE) //secret cat chem - icon_state = "custom_cat_dead" - Stun(1000) - canmove = 0 - friendly = "deads at" - return - else - ..() +//Cat +/mob/living/simple_animal/pet/cat + name = "cat" + desc = "Kitty!!" + icon = 'icons/mob/pets.dmi' + icon_state = "cat2" + icon_living = "cat2" + icon_dead = "cat2_dead" + gender = MALE + speak = list("Meow!", "Esp!", "Purr!", "HSSSSS") + speak_emote = list("purrs", "meows") + emote_hear = list("meows.", "mews.") + emote_see = list("shakes its head.", "shivers.") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + ventcrawler = VENTCRAWLER_ALWAYS + pass_flags = PASSTABLE + mob_size = MOB_SIZE_SMALL + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) + minbodytemp = 200 + maxbodytemp = 400 + unsuitable_atmos_damage = 1 + animal_species = /mob/living/simple_animal/pet/cat + childtype = list(/mob/living/simple_animal/pet/cat/kitten) + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 2, /obj/item/organ/ears/cat = 1, /obj/item/organ/tail/cat = 1) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + var/turns_since_scan = 0 + var/mob/living/simple_animal/mouse/movement_target + gold_core_spawnable = FRIENDLY_SPAWN + collar_type = "cat" + can_be_held = "cat2" + do_footstep = TRUE + +/mob/living/simple_animal/pet/cat/Initialize() + . = ..() + verbs += /mob/living/proc/lay_down + +/mob/living/simple_animal/pet/cat/ComponentInitialize() + . = ..() + AddElement(/datum/element/wuv, "purrs!", EMOTE_AUDIBLE, /datum/mood_event/pet_animal, "hisses!", EMOTE_AUDIBLE) + +/mob/living/simple_animal/pet/cat/update_canmove() + ..() + if(client && stat != DEAD) + if (resting) + icon_state = "[icon_living]_rest" + collar_type = "[initial(collar_type)]_rest" + else + icon_state = "[icon_living]" + collar_type = "[initial(collar_type)]" + regenerate_icons() + + +/mob/living/simple_animal/pet/cat/space + name = "space cat" + desc = "It's a cat... in space!" + icon_state = "spacecat" + icon_living = "spacecat" + icon_dead = "spacecat_dead" + unsuitable_atmos_damage = 0 + minbodytemp = TCMB + maxbodytemp = T0C + 40 + +/mob/living/simple_animal/pet/cat/original + name = "Batsy" + desc = "The product of alien DNA and bored geneticists." + gender = FEMALE + icon_state = "original" + icon_living = "original" + icon_dead = "original_dead" + collar_type = null + unique_pet = TRUE + +/mob/living/simple_animal/pet/cat/kitten + name = "kitten" + desc = "D'aaawwww." + icon_state = "kitten" + icon_living = "kitten" + icon_dead = "kitten_dead" + density = FALSE + pass_flags = PASSMOB + mob_size = MOB_SIZE_SMALL + collar_type = "kitten" + can_be_held = "cat" + +//RUNTIME IS ALIVE! SQUEEEEEEEE~ +/mob/living/simple_animal/pet/cat/Runtime + name = "Runtime" + desc = "GCAT" + icon_state = "cat" + icon_living = "cat" + icon_dead = "cat_dead" + gender = FEMALE + gold_core_spawnable = NO_SPAWN + unique_pet = TRUE + var/list/family = list()//var restored from savefile, has count of each child type + var/list/children = list()//Actual mob instances of children + var/cats_deployed = 0 + var/memory_saved = FALSE + +/mob/living/simple_animal/pet/cat/Runtime/Initialize() + if(prob(5)) + icon_state = "original" + icon_living = "original" + icon_dead = "original_dead" + Read_Memory() + . = ..() + +/mob/living/simple_animal/pet/cat/Runtime/Life() + if(!cats_deployed && SSticker.current_state >= GAME_STATE_SETTING_UP) + Deploy_The_Cats() + if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved) + Write_Memory() + memory_saved = TRUE + ..() + +/mob/living/simple_animal/pet/cat/Runtime/make_babies() + var/mob/baby = ..() + if(baby) + children += baby + return baby + +/mob/living/simple_animal/pet/cat/Runtime/death() + if(!memory_saved) + Write_Memory(TRUE) + ..() + +/mob/living/simple_animal/pet/cat/Runtime/proc/Read_Memory() + if(fexists("data/npc_saves/Runtime.sav")) //legacy compatability to convert old format to new + var/savefile/S = new /savefile("data/npc_saves/Runtime.sav") + S["family"] >> family + fdel("data/npc_saves/Runtime.sav") + else + var/json_file = file("data/npc_saves/Runtime.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + family = json["family"] + if(isnull(family)) + family = list() + +/mob/living/simple_animal/pet/cat/Runtime/proc/Write_Memory(dead) + var/json_file = file("data/npc_saves/Runtime.json") + var/list/file_data = list() + family = list() + if(!dead) + for(var/mob/living/simple_animal/pet/cat/kitten/C in children) + if(istype(C,type) || C.stat || !C.z || !C.butcher_results) //That last one is a work around for hologram cats + continue + if(C.type in family) + family[C.type] += 1 + else + family[C.type] = 1 + file_data["family"] = family + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +/mob/living/simple_animal/pet/cat/Runtime/proc/Deploy_The_Cats() + cats_deployed = 1 + for(var/cat_type in family) + if(family[cat_type] > 0) + for(var/i in 1 to min(family[cat_type],100)) //Limits to about 500 cats, you wouldn't think this would be needed (BUT IT IS) + new cat_type(loc) + +/mob/living/simple_animal/pet/cat/Proc + name = "Proc" + gender = MALE + gold_core_spawnable = NO_SPAWN + unique_pet = TRUE + +/mob/living/simple_animal/pet/cat/Life() + if(!stat && !buckled && !client) + if(prob(1)) + emote("me", EMOTE_VISIBLE, pick("stretches out for a belly rub.", "wags its tail.", "lies down.")) + icon_state = "[icon_living]_rest" + collar_type = "[initial(collar_type)]_rest" + resting = 1 + update_canmove() + else if (prob(1)) + emote("me", EMOTE_VISIBLE, pick("sits down.", "crouches on its hind legs.", "looks alert.")) + icon_state = "[icon_living]_sit" + collar_type = "[initial(collar_type)]_sit" + resting = 1 + update_canmove() + else if (prob(1)) + if (resting) + emote("me", EMOTE_VISIBLE, pick("gets up and meows.", "walks around.", "stops resting.")) + icon_state = "[icon_living]" + collar_type = "[initial(collar_type)]" + resting = 0 + update_canmove() + else + emote("me", EMOTE_VISIBLE, pick("grooms its fur.", "twitches its whiskers.", "shakes out its coat.")) + + //MICE! + if((src.loc) && isturf(src.loc)) + if(!stat && !resting && !buckled) + for(var/mob/living/simple_animal/mouse/M in view(1,src)) + if(!M.stat && Adjacent(M)) + emote("me", EMOTE_VISIBLE, "splats \the [M]!") + M.splat() + movement_target = null + stop_automated_movement = 0 + break + for(var/obj/item/toy/cattoy/T in view(1,src)) + if (T.cooldown < (world.time - 400)) + emote("me", EMOTE_VISIBLE, "bats \the [T] around with its paw!") + T.cooldown = world.time + + ..() + + make_babies() + + if(!stat && !resting && !buckled) + turns_since_scan++ + if(turns_since_scan > 5) + walk_to(src,0) + turns_since_scan = 0 + if((movement_target) && !(isturf(movement_target.loc) || ishuman(movement_target.loc) )) + movement_target = null + stop_automated_movement = 0 + if( !movement_target || !(movement_target.loc in oview(src, 3)) ) + movement_target = null + stop_automated_movement = 0 + for(var/mob/living/simple_animal/mouse/snack in oview(src,3)) + if(isturf(snack.loc) && !snack.stat) + movement_target = snack + break + if(movement_target) + stop_automated_movement = 1 + walk_to(src,movement_target,0,3) + +/mob/living/simple_animal/pet/cat/cak //I told you I'd do it, Remie + name = "Keeki" + desc = "It's a cat made out of cake." + icon_state = "cak" + icon_living = "cak" + icon_dead = "cak_dead" + health = 50 + maxHealth = 50 + gender = FEMALE + harm_intent_damage = 10 + butcher_results = list(/obj/item/organ/brain = 1, /obj/item/organ/heart = 1, /obj/item/reagent_containers/food/snacks/cakeslice/birthday = 3, \ + /obj/item/reagent_containers/food/snacks/meat/slab = 2) + response_harm = "takes a bite out of" + attacked_sound = 'sound/items/eatfood.ogg' + deathmessage = "loses its false life and collapses!" + death_sound = "bodyfall" + can_be_held = "cak" + +/mob/living/simple_animal/pet/cat/cak/CheckParts(list/parts) + ..() + var/obj/item/organ/brain/B = locate(/obj/item/organ/brain) in contents + if(!B || !B.brainmob || !B.brainmob.mind) + return + B.brainmob.mind.transfer_to(src) + to_chat(src, "You are a cak! You're a harmless cat/cake hybrid that everyone loves. People can take bites out of you if they're hungry, but you regenerate health \ + so quickly that it generally doesn't matter. You're remarkably resilient to any damage besides this and it's hard for you to really die at all. You should go around and bring happiness and \ + free cake to the station!") + var/new_name = stripped_input(src, "Enter your name, or press \"Cancel\" to stick with Keeki.", "Name Change") + if(new_name) + to_chat(src, "Your name is now \"new_name\"!") + name = new_name + +/mob/living/simple_animal/pet/cat/cak/Life() + ..() + if(stat) + return + if(health < maxHealth) + adjustBruteLoss(-8) //Fast life regen + for(var/obj/item/reagent_containers/food/snacks/donut/D in range(1, src)) //Frosts nearby donuts! + if(!D.is_decorated) + D.decorate_donut() + +/mob/living/simple_animal/pet/cat/cak/attack_hand(mob/living/L) + . = ..() + if(.) //the attack was blocked + return + if(L.a_intent == INTENT_HARM && L.reagents && !stat) + L.reagents.add_reagent(/datum/reagent/consumable/nutriment, 0.4) + L.reagents.add_reagent(/datum/reagent/consumable/nutriment/vitamin, 0.4) + +//Cat made +/mob/living/simple_animal/pet/cat/custom_cat + name = "White cat" //Incase it somehow gets spawned without an ID + desc = "A cute white catto!" + icon_state = "custom_cat" + icon_living = "custom_cat" + icon_dead = "custom_cat_dead" + gender = FEMALE + gold_core_spawnable = NO_SPAWN + health = 50 //So people can't instakill it s + maxHealth = 50 + speak = list("Meowrowr!", "Mew!", "Miauen!") + speak_emote = list("wigglepurrs", "mewls") + emote_hear = list("meows.", "mews.") + emote_see = list("looks at you eagerly for pets!", "wiggles enthusiastically.") + gold_core_spawnable = NO_SPAWN + var/pseudo_death = FALSE + var/mob/living/carbon/human/origin + +/mob/living/simple_animal/pet/cat/custom_cat/death() + if (pseudo_death == TRUE) //secret cat chem + icon_state = "custom_cat_dead" + Stun(1000) + canmove = 0 + friendly = "deads at" + return + else + ..() diff --git a/code/modules/mob/living/simple_animal/friendly/crab.dm b/code/modules/mob/living/simple_animal/friendly/crab.dm index 9c3e5b5def..5dbf755601 100644 --- a/code/modules/mob/living/simple_animal/friendly/crab.dm +++ b/code/modules/mob/living/simple_animal/friendly/crab.dm @@ -1,79 +1,79 @@ -//Look Sir, free crabs! -/mob/living/simple_animal/crab - name = "crab" - desc = "Free crabs!" - icon_state = "crab" - icon_living = "crab" - icon_dead = "crab_dead" - speak_emote = list("clicks") - emote_hear = list("clicks.") - emote_see = list("clacks.") - blood_volume = 350 - speak_chance = 1 - turns_per_move = 5 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "stomps" - stop_automated_movement = 1 - friendly = "pinches" - ventcrawler = VENTCRAWLER_ALWAYS - var/obj/item/inventory_head - var/obj/item/inventory_mask - gold_core_spawnable = FRIENDLY_SPAWN - -/mob/living/simple_animal/crab/Life() - ..() - //CRAB movement - if(!ckey && !stat) - if(isturf(src.loc) && !resting && !buckled) //This is so it only moves if it's not inside a closet, gentics machine, etc. - turns_since_move++ - if(turns_since_move >= turns_per_move) - var/east_vs_west = pick(4,8) - if(Process_Spacemove(east_vs_west)) - Move(get_step(src,east_vs_west), east_vs_west) - turns_since_move = 0 - regenerate_icons() - -//COFFEE! SQUEEEEEEEEE! -/mob/living/simple_animal/crab/Coffee - name = "Coffee" - real_name = "Coffee" - desc = "It's Coffee, the other pet!" - gender = FEMALE - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "stomps" - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/crab/evil - name = "Evil Crab" - real_name = "Evil Crab" - desc = "Unnerving, isn't it? It has to be planning something nefarious..." - icon_state = "evilcrab" - icon_living = "evilcrab" - icon_dead = "evilcrab_dead" - response_help = "pokes" - response_disarm = "shoves" - response_harm = "stomps" - gold_core_spawnable = HOSTILE_SPAWN - -/mob/living/simple_animal/crab/kreb - name = "Kreb" - desc = "This is a real crab. The other crabs are simply gubbucks in disguise!" - real_name = "Kreb" - icon_state = "kreb" - icon_living = "kreb" - icon_dead = "kreb_dead" - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "stomps" - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/crab/evil/kreb - name = "Evil Kreb" - real_name = "Evil Kreb" - icon_state = "evilkreb" - icon_living = "evilkreb" - icon_dead = "evilkreb_dead" - gold_core_spawnable = NO_SPAWN +//Look Sir, free crabs! +/mob/living/simple_animal/crab + name = "crab" + desc = "Free crabs!" + icon_state = "crab" + icon_living = "crab" + icon_dead = "crab_dead" + speak_emote = list("clicks") + emote_hear = list("clicks.") + emote_see = list("clacks.") + blood_volume = 350 + speak_chance = 1 + turns_per_move = 5 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "stomps" + stop_automated_movement = 1 + friendly = "pinches" + ventcrawler = VENTCRAWLER_ALWAYS + var/obj/item/inventory_head + var/obj/item/inventory_mask + gold_core_spawnable = FRIENDLY_SPAWN + +/mob/living/simple_animal/crab/Life() + ..() + //CRAB movement + if(!ckey && !stat) + if(isturf(src.loc) && !resting && !buckled) //This is so it only moves if it's not inside a closet, gentics machine, etc. + turns_since_move++ + if(turns_since_move >= turns_per_move) + var/east_vs_west = pick(4,8) + if(Process_Spacemove(east_vs_west)) + Move(get_step(src,east_vs_west), east_vs_west) + turns_since_move = 0 + regenerate_icons() + +//COFFEE! SQUEEEEEEEEE! +/mob/living/simple_animal/crab/Coffee + name = "Coffee" + real_name = "Coffee" + desc = "It's Coffee, the other pet!" + gender = FEMALE + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "stomps" + gold_core_spawnable = NO_SPAWN + +/mob/living/simple_animal/crab/evil + name = "Evil Crab" + real_name = "Evil Crab" + desc = "Unnerving, isn't it? It has to be planning something nefarious..." + icon_state = "evilcrab" + icon_living = "evilcrab" + icon_dead = "evilcrab_dead" + response_help = "pokes" + response_disarm = "shoves" + response_harm = "stomps" + gold_core_spawnable = HOSTILE_SPAWN + +/mob/living/simple_animal/crab/kreb + name = "Kreb" + desc = "This is a real crab. The other crabs are simply gubbucks in disguise!" + real_name = "Kreb" + icon_state = "kreb" + icon_living = "kreb" + icon_dead = "kreb_dead" + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "stomps" + gold_core_spawnable = NO_SPAWN + +/mob/living/simple_animal/crab/evil/kreb + name = "Evil Kreb" + real_name = "Evil Kreb" + icon_state = "evilkreb" + icon_living = "evilkreb" + icon_dead = "evilkreb_dead" + gold_core_spawnable = NO_SPAWN diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index 6754f94b01..38bcfbde79 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -1,474 +1,474 @@ -//goat -/mob/living/simple_animal/hostile/retaliate/goat - name = "goat" - desc = "Not known for their pleasant disposition." - icon_state = "goat" - icon_living = "goat" - icon_dead = "goat_dead" - speak = list("EHEHEHEHEH","eh?") - speak_emote = list("brays") - emote_hear = list("brays.") - emote_see = list("shakes its head.", "stamps a foot.", "glares around.") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 4) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - faction = list("neutral") - mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) - attack_same = 1 - attacktext = "kicks" - attack_sound = 'sound/weapons/punch1.ogg' - health = 40 - maxHealth = 40 - melee_damage_lower = 1 - melee_damage_upper = 2 - environment_smash = ENVIRONMENT_SMASH_NONE - stop_automated_movement_when_pulled = 1 - blood_volume = BLOOD_VOLUME_NORMAL - var/obj/item/udder/udder = null - - do_footstep = TRUE - -/mob/living/simple_animal/hostile/retaliate/goat/Initialize() - udder = new() - . = ..() - -/mob/living/simple_animal/hostile/retaliate/goat/Destroy() - qdel(udder) - udder = null - return ..() - -/mob/living/simple_animal/hostile/retaliate/goat/Life() - . = ..() - if(.) - //chance to go crazy and start wacking stuff - if(!enemies.len && prob(1)) - Retaliate() - - if(enemies.len && prob(10)) - enemies = list() - LoseTarget() - src.visible_message("[src] calms down.") - if(stat == CONSCIOUS) - udder.generateMilk() - eat_plants() - if(!pulledby) - for(var/direction in shuffle(list(1,2,4,8,5,6,9,10))) - var/step = get_step(src, direction) - if(step) - if(locate(/obj/structure/spacevine) in step || locate(/obj/structure/glowshroom) in step) - Move(step, get_dir(src, step)) - -/mob/living/simple_animal/hostile/retaliate/goat/Retaliate() - ..() - src.visible_message("[src] gets an evil-looking gleam in [p_their()] eye.") - -/mob/living/simple_animal/hostile/retaliate/goat/Move() - . = ..() - if(!stat) - eat_plants() - -/mob/living/simple_animal/hostile/retaliate/goat/proc/eat_plants() - var/eaten = FALSE - var/obj/structure/spacevine/SV = locate(/obj/structure/spacevine) in loc - if(SV) - SV.eat(src) - eaten = TRUE - - var/obj/structure/glowshroom/GS = locate(/obj/structure/glowshroom) in loc - if(GS) - qdel(GS) - eaten = TRUE - - if(eaten && prob(10)) - say("Nom") - -/mob/living/simple_animal/hostile/retaliate/goat/attackby(obj/item/O, mob/user, params) - if(stat == CONSCIOUS && istype(O, /obj/item/reagent_containers/glass)) - udder.milkAnimal(O, user) - return 1 - else - return ..() - - -/mob/living/simple_animal/hostile/retaliate/goat/AttackingTarget() - . = ..() - if(. && ishuman(target)) - var/mob/living/carbon/human/H = target - if(istype(H.dna.species, /datum/species/pod)) - var/obj/item/bodypart/NB = pick(H.bodyparts) - H.visible_message("[src] takes a big chomp out of [H]!", \ - "[src] takes a big chomp out of your [NB]!") - NB.dismember() -//cow -/mob/living/simple_animal/cow - name = "cow" - desc = "Known for their milk, just don't tip them over." - icon_state = "cow" - icon_living = "cow" - icon_dead = "cow_dead" - icon_gib = "cow_gib" - gender = FEMALE - mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) - speak = list("moo?","moo","MOOOOOO") - speak_emote = list("moos","moos hauntingly") - emote_hear = list("brays.") - emote_see = list("shakes its head.") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 6 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 6) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - attacktext = "kicks" - attack_sound = 'sound/weapons/punch1.ogg' - health = 50 - maxHealth = 50 - var/obj/item/udder/udder = null - gold_core_spawnable = FRIENDLY_SPAWN - blood_volume = BLOOD_VOLUME_NORMAL - - do_footstep = TRUE - -/mob/living/simple_animal/cow/Initialize() - udder = new() - . = ..() - -/mob/living/simple_animal/cow/Destroy() - qdel(udder) - udder = null - return ..() - -/mob/living/simple_animal/cow/attackby(obj/item/O, mob/user, params) - if(stat == CONSCIOUS && istype(O, /obj/item/reagent_containers/glass)) - udder.milkAnimal(O, user) - return 1 - else - return ..() - -/mob/living/simple_animal/cow/Life() - . = ..() - if(stat == CONSCIOUS) - udder.generateMilk() - -/mob/living/simple_animal/cow/attack_hand(mob/living/carbon/M) - if(!stat && M.a_intent == INTENT_DISARM && icon_state != icon_dead) - M.visible_message("[M] tips over [src].", - "You tip over [src].") - to_chat(src, "You are tipped over by [M]!") - Knockdown(60,ignore_canknockdown = TRUE) - icon_state = icon_dead - spawn(rand(20,50)) - if(!stat && M) - icon_state = icon_living - var/external - var/internal - switch(pick(1,2,3,4)) - if(1,2,3) - var/text = pick("imploringly.", "pleadingly.", - "with a resigned expression.") - external = "[src] looks at [M] [text]" - internal = "You look at [M] [text]" - if(4) - external = "[src] seems resigned to its fate." - internal = "You resign yourself to your fate." - visible_message("[external]", - "[internal]") - else - ..() - -/mob/living/simple_animal/chick - name = "\improper chick" - desc = "Adorable! They make such a racket though." - icon_state = "chick" - icon_living = "chick" - icon_dead = "chick_dead" - icon_gib = "chick_gib" - gender = FEMALE - mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) - speak = list("Cherp.","Cherp?","Chirrup.","Cheep!") - speak_emote = list("cheeps") - emote_hear = list("cheeps.") - emote_see = list("pecks at the ground.","flaps its tiny wings.") - density = FALSE - speak_chance = 2 - turns_per_move = 2 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/chicken = 1) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - attacktext = "kicks" - health = 3 - maxHealth = 3 - ventcrawler = VENTCRAWLER_ALWAYS - var/amount_grown = 0 - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - mob_size = MOB_SIZE_TINY - gold_core_spawnable = FRIENDLY_SPAWN - - do_footstep = TRUE - -/mob/living/simple_animal/chick/Initialize() - . = ..() - pixel_x = rand(-6, 6) - pixel_y = rand(0, 10) - -/mob/living/simple_animal/chick/Life() - . =..() - if(!.) - return - if(!stat && !ckey) - amount_grown += rand(1,2) - if(amount_grown >= 100) - new /mob/living/simple_animal/chicken(src.loc) - qdel(src) - -/mob/living/simple_animal/chick/holo/Life() - ..() - amount_grown = 0 - -/mob/living/simple_animal/chicken - name = "\improper chicken" - desc = "Hopefully the eggs are good this season." - gender = FEMALE - mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) - icon_state = "chicken_brown" - icon_living = "chicken_brown" - icon_dead = "chicken_brown_dead" - speak = list("Cluck!","BWAAAAARK BWAK BWAK BWAK!","Bwaak bwak.") - speak_emote = list("clucks","croons") - emote_hear = list("clucks.") - emote_see = list("pecks at the ground.","flaps its wings viciously.") - density = FALSE - speak_chance = 2 - turns_per_move = 3 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/chicken = 2) - var/egg_type = /obj/item/reagent_containers/food/snacks/egg - var/food_type = /obj/item/reagent_containers/food/snacks/grown/wheat - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - attacktext = "kicks" - health = 15 - maxHealth = 15 - ventcrawler = VENTCRAWLER_ALWAYS - var/eggsleft = 0 - var/eggsFertile = TRUE - var/body_color - var/icon_prefix = "chicken" - pass_flags = PASSTABLE | PASSMOB - mob_size = MOB_SIZE_SMALL - var/list/feedMessages = list("It clucks happily.","It clucks happily.") - var/list/layMessage = EGG_LAYING_MESSAGES - var/list/validColors = list("brown","black","white") - gold_core_spawnable = FRIENDLY_SPAWN - var/static/chicken_count = 0 - - do_footstep = TRUE - -/mob/living/simple_animal/chicken/Initialize() - . = ..() - if(!body_color) - body_color = pick(validColors) - icon_state = "[icon_prefix]_[body_color]" - icon_living = "[icon_prefix]_[body_color]" - icon_dead = "[icon_prefix]_[body_color]_dead" - pixel_x = rand(-6, 6) - pixel_y = rand(0, 10) - ++chicken_count - -/mob/living/simple_animal/chicken/Destroy() - --chicken_count - return ..() - -/mob/living/simple_animal/chicken/attackby(obj/item/O, mob/user, params) - if(istype(O, food_type)) //feedin' dem chickens - if(!stat && eggsleft < 8) - var/feedmsg = "[user] feeds [O] to [name]! [pick(feedMessages)]" - user.visible_message(feedmsg) - qdel(O) - eggsleft += rand(1, 4) - else - to_chat(user, "[name] doesn't seem hungry!") - else - ..() - -/mob/living/simple_animal/chicken/Life() - . =..() - if(!.) - return - if((!stat && prob(3) && eggsleft > 0) && egg_type) - visible_message("[src] [pick(layMessage)]") - eggsleft-- - var/obj/item/E = new egg_type(get_turf(src)) - E.pixel_x = rand(-6,6) - E.pixel_y = rand(-6,6) - if(eggsFertile) - if(chicken_count < MAX_CHICKENS && prob(25)) - START_PROCESSING(SSobj, E) - -/obj/item/reagent_containers/food/snacks/egg/var/amount_grown = 0 -/obj/item/reagent_containers/food/snacks/egg/process() - if(isturf(loc)) - amount_grown += rand(1,2) - if(amount_grown >= 100) - visible_message("[src] hatches with a quiet cracking sound.") - new /mob/living/simple_animal/chick(get_turf(src)) - STOP_PROCESSING(SSobj, src) - qdel(src) - else - STOP_PROCESSING(SSobj, src) - -// Space kiwis, ergo quite a copypasta of chickens. - -/mob/living/simple_animal/kiwi - name = "space kiwi" - desc = "Exposure to low gravity made them grow larger." - gender = FEMALE - icon_state = "kiwi" - icon_living = "kiwi" - icon_dead = "kiwi_dead" - speak = list("Chirp!","Cheep cheep chirp!!","Cheep.") - speak_emote = list("chirps","trills") - emote_hear = list("chirps.") - emote_see = list("pecks at the ground.","jumps in place.") - density = FALSE - speak_chance = 2 - turns_per_move = 3 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 3) - var/egg_type = /obj/item/reagent_containers/food/snacks/egg/kiwiEgg - var/food_type = /obj/item/reagent_containers/food/snacks/grown/wheat - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - attacktext = "kicks" - health = 25 - maxHealth = 25 - ventcrawler = VENTCRAWLER_ALWAYS - var/eggsleft = 0 - var/eggsFertile = TRUE - pass_flags = PASSTABLE | PASSMOB - mob_size = MOB_SIZE_SMALL - var/list/feedMessages = list("It chirps happily.","It chirps happily.") - var/list/layMessage = list("lays an egg.","squats down and croons.","begins making a huge racket.","begins chirping raucously.") - gold_core_spawnable = FRIENDLY_SPAWN - var/static/kiwi_count = 0 - -/mob/living/simple_animal/kiwi/Destroy() - --kiwi_count - return ..() - -/mob/living/simple_animal/kiwi/Initialize() - . = ..() - ++kiwi_count - -/mob/living/simple_animal/kiwi/Life() - . =..() - if(!.) - return - if((!stat && prob(3) && eggsleft > 0) && egg_type) - visible_message("[src] [pick(layMessage)]") - eggsleft-- - var/obj/item/E = new egg_type(get_turf(src)) - E.pixel_x = rand(-6,6) - E.pixel_y = rand(-6,6) - if(eggsFertile) - if(kiwi_count < MAX_CHICKENS && prob(25)) - START_PROCESSING(SSobj, E) - -/obj/item/reagent_containers/food/snacks/egg/kiwiEgg/process() - if(isturf(loc)) - amount_grown += rand(1,2) - if(amount_grown >= 100) - visible_message("[src] hatches with a quiet cracking sound.") - new /mob/living/simple_animal/babyKiwi(get_turf(src)) - STOP_PROCESSING(SSobj, src) - qdel(src) - else - STOP_PROCESSING(SSobj, src) - -/mob/living/simple_animal/kiwi/attackby(obj/item/O, mob/user, params) - if(istype(O, food_type)) //feedin' dem kiwis - if(!stat && eggsleft < 8) - var/feedmsg = "[user] feeds [O] to [name]! [pick(feedMessages)]" - user.visible_message(feedmsg) - qdel(O) - eggsleft += rand(1, 4) - else - to_chat(user, "[name] doesn't seem hungry!") - else - ..() - -/mob/living/simple_animal/babyKiwi - name = "baby space kiwi" - desc = "So huggable." - icon_state = "babykiwi" - icon_living = "babykiwi" - icon_dead = "babykiwi_dead" - gender = FEMALE - speak = list("Cherp.","Cherp?","Chirrup.","Cheep!") - speak_emote = list("chirps") - emote_hear = list("chirps.") - emote_see = list("pecks at the ground.","Happily bounces in place.") - density = FALSE - speak_chance = 2 - turns_per_move = 2 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 2) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - attacktext = "kicks" - health = 10 - maxHealth = 10 - ventcrawler = VENTCRAWLER_ALWAYS - var/amount_grown = 0 - pass_flags = PASSTABLE | PASSGRILLE | PASSMOB - mob_size = MOB_SIZE_TINY - gold_core_spawnable = FRIENDLY_SPAWN - -/mob/living/simple_animal/babyKiwi/Initialize() - . = ..() - pixel_x = rand(-6, 6) - pixel_y = rand(0, 10) - -/mob/living/simple_animal/babyKiwi/Life() - . =..() - if(!.) - return - if(!stat && !ckey) - amount_grown += rand(1,2) - if(amount_grown >= 100) - new /mob/living/simple_animal/kiwi(src.loc) - qdel(src) - -/obj/item/reagent_containers/food/snacks/egg/kiwiEgg - name = "kiwi egg" - desc = "A slightly bigger egg!" - icon_state = "kiwiegg" - -/obj/item/udder - name = "udder" - -/obj/item/udder/Initialize() - create_reagents(50) - reagents.add_reagent(/datum/reagent/consumable/milk, 20) - . = ..() - -/obj/item/udder/proc/generateMilk() - if(prob(5)) - reagents.add_reagent(/datum/reagent/consumable/milk, rand(5, 10)) - -/obj/item/udder/proc/milkAnimal(obj/O, mob/user) - var/obj/item/reagent_containers/glass/G = O - if(G.reagents.total_volume >= G.volume) - to_chat(user, "[O] is full.") - return - var/transfered = reagents.trans_to(O, rand(5,10)) - if(transfered) - user.visible_message("[user] milks [src] using \the [O].", "You milk [src] using \the [O].") - else - to_chat(user, "The udder is dry. Wait a bit longer...") +//goat +/mob/living/simple_animal/hostile/retaliate/goat + name = "goat" + desc = "Not known for their pleasant disposition." + icon_state = "goat" + icon_living = "goat" + icon_dead = "goat_dead" + speak = list("EHEHEHEHEH","eh?") + speak_emote = list("brays") + emote_hear = list("brays.") + emote_see = list("shakes its head.", "stamps a foot.", "glares around.") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 4) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + faction = list("neutral") + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) + attack_same = 1 + attacktext = "kicks" + attack_sound = 'sound/weapons/punch1.ogg' + health = 40 + maxHealth = 40 + melee_damage_lower = 1 + melee_damage_upper = 2 + environment_smash = ENVIRONMENT_SMASH_NONE + stop_automated_movement_when_pulled = 1 + blood_volume = BLOOD_VOLUME_NORMAL + var/obj/item/udder/udder = null + + do_footstep = TRUE + +/mob/living/simple_animal/hostile/retaliate/goat/Initialize() + udder = new() + . = ..() + +/mob/living/simple_animal/hostile/retaliate/goat/Destroy() + qdel(udder) + udder = null + return ..() + +/mob/living/simple_animal/hostile/retaliate/goat/Life() + . = ..() + if(.) + //chance to go crazy and start wacking stuff + if(!enemies.len && prob(1)) + Retaliate() + + if(enemies.len && prob(10)) + enemies = list() + LoseTarget() + src.visible_message("[src] calms down.") + if(stat == CONSCIOUS) + udder.generateMilk() + eat_plants() + if(!pulledby) + for(var/direction in shuffle(list(1,2,4,8,5,6,9,10))) + var/step = get_step(src, direction) + if(step) + if(locate(/obj/structure/spacevine) in step || locate(/obj/structure/glowshroom) in step) + Move(step, get_dir(src, step)) + +/mob/living/simple_animal/hostile/retaliate/goat/Retaliate() + ..() + src.visible_message("[src] gets an evil-looking gleam in [p_their()] eye.") + +/mob/living/simple_animal/hostile/retaliate/goat/Move() + . = ..() + if(!stat) + eat_plants() + +/mob/living/simple_animal/hostile/retaliate/goat/proc/eat_plants() + var/eaten = FALSE + var/obj/structure/spacevine/SV = locate(/obj/structure/spacevine) in loc + if(SV) + SV.eat(src) + eaten = TRUE + + var/obj/structure/glowshroom/GS = locate(/obj/structure/glowshroom) in loc + if(GS) + qdel(GS) + eaten = TRUE + + if(eaten && prob(10)) + say("Nom") + +/mob/living/simple_animal/hostile/retaliate/goat/attackby(obj/item/O, mob/user, params) + if(stat == CONSCIOUS && istype(O, /obj/item/reagent_containers/glass)) + udder.milkAnimal(O, user) + return 1 + else + return ..() + + +/mob/living/simple_animal/hostile/retaliate/goat/AttackingTarget() + . = ..() + if(. && ishuman(target)) + var/mob/living/carbon/human/H = target + if(istype(H.dna.species, /datum/species/pod)) + var/obj/item/bodypart/NB = pick(H.bodyparts) + H.visible_message("[src] takes a big chomp out of [H]!", \ + "[src] takes a big chomp out of your [NB]!") + NB.dismember() +//cow +/mob/living/simple_animal/cow + name = "cow" + desc = "Known for their milk, just don't tip them over." + icon_state = "cow" + icon_living = "cow" + icon_dead = "cow_dead" + icon_gib = "cow_gib" + gender = FEMALE + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) + speak = list("moo?","moo","MOOOOOO") + speak_emote = list("moos","moos hauntingly") + emote_hear = list("brays.") + emote_see = list("shakes its head.") + speak_chance = 1 + turns_per_move = 5 + see_in_dark = 6 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 6) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + attacktext = "kicks" + attack_sound = 'sound/weapons/punch1.ogg' + health = 50 + maxHealth = 50 + var/obj/item/udder/udder = null + gold_core_spawnable = FRIENDLY_SPAWN + blood_volume = BLOOD_VOLUME_NORMAL + + do_footstep = TRUE + +/mob/living/simple_animal/cow/Initialize() + udder = new() + . = ..() + +/mob/living/simple_animal/cow/Destroy() + qdel(udder) + udder = null + return ..() + +/mob/living/simple_animal/cow/attackby(obj/item/O, mob/user, params) + if(stat == CONSCIOUS && istype(O, /obj/item/reagent_containers/glass)) + udder.milkAnimal(O, user) + return 1 + else + return ..() + +/mob/living/simple_animal/cow/Life() + . = ..() + if(stat == CONSCIOUS) + udder.generateMilk() + +/mob/living/simple_animal/cow/attack_hand(mob/living/carbon/M) + if(!stat && M.a_intent == INTENT_DISARM && icon_state != icon_dead) + M.visible_message("[M] tips over [src].", + "You tip over [src].") + to_chat(src, "You are tipped over by [M]!") + Knockdown(60,ignore_canknockdown = TRUE) + icon_state = icon_dead + spawn(rand(20,50)) + if(!stat && M) + icon_state = icon_living + var/external + var/internal + switch(pick(1,2,3,4)) + if(1,2,3) + var/text = pick("imploringly.", "pleadingly.", + "with a resigned expression.") + external = "[src] looks at [M] [text]" + internal = "You look at [M] [text]" + if(4) + external = "[src] seems resigned to its fate." + internal = "You resign yourself to your fate." + visible_message("[external]", + "[internal]") + else + ..() + +/mob/living/simple_animal/chick + name = "\improper chick" + desc = "Adorable! They make such a racket though." + icon_state = "chick" + icon_living = "chick" + icon_dead = "chick_dead" + icon_gib = "chick_gib" + gender = FEMALE + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) + speak = list("Cherp.","Cherp?","Chirrup.","Cheep!") + speak_emote = list("cheeps") + emote_hear = list("cheeps.") + emote_see = list("pecks at the ground.","flaps its tiny wings.") + density = FALSE + speak_chance = 2 + turns_per_move = 2 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/chicken = 1) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + attacktext = "kicks" + health = 3 + maxHealth = 3 + ventcrawler = VENTCRAWLER_ALWAYS + var/amount_grown = 0 + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB + mob_size = MOB_SIZE_TINY + gold_core_spawnable = FRIENDLY_SPAWN + + do_footstep = TRUE + +/mob/living/simple_animal/chick/Initialize() + . = ..() + pixel_x = rand(-6, 6) + pixel_y = rand(0, 10) + +/mob/living/simple_animal/chick/Life() + . =..() + if(!.) + return + if(!stat && !ckey) + amount_grown += rand(1,2) + if(amount_grown >= 100) + new /mob/living/simple_animal/chicken(src.loc) + qdel(src) + +/mob/living/simple_animal/chick/holo/Life() + ..() + amount_grown = 0 + +/mob/living/simple_animal/chicken + name = "\improper chicken" + desc = "Hopefully the eggs are good this season." + gender = FEMALE + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) + icon_state = "chicken_brown" + icon_living = "chicken_brown" + icon_dead = "chicken_brown_dead" + speak = list("Cluck!","BWAAAAARK BWAK BWAK BWAK!","Bwaak bwak.") + speak_emote = list("clucks","croons") + emote_hear = list("clucks.") + emote_see = list("pecks at the ground.","flaps its wings viciously.") + density = FALSE + speak_chance = 2 + turns_per_move = 3 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/chicken = 2) + var/egg_type = /obj/item/reagent_containers/food/snacks/egg + var/food_type = /obj/item/reagent_containers/food/snacks/grown/wheat + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + attacktext = "kicks" + health = 15 + maxHealth = 15 + ventcrawler = VENTCRAWLER_ALWAYS + var/eggsleft = 0 + var/eggsFertile = TRUE + var/body_color + var/icon_prefix = "chicken" + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_SMALL + var/list/feedMessages = list("It clucks happily.","It clucks happily.") + var/list/layMessage = EGG_LAYING_MESSAGES + var/list/validColors = list("brown","black","white") + gold_core_spawnable = FRIENDLY_SPAWN + var/static/chicken_count = 0 + + do_footstep = TRUE + +/mob/living/simple_animal/chicken/Initialize() + . = ..() + if(!body_color) + body_color = pick(validColors) + icon_state = "[icon_prefix]_[body_color]" + icon_living = "[icon_prefix]_[body_color]" + icon_dead = "[icon_prefix]_[body_color]_dead" + pixel_x = rand(-6, 6) + pixel_y = rand(0, 10) + ++chicken_count + +/mob/living/simple_animal/chicken/Destroy() + --chicken_count + return ..() + +/mob/living/simple_animal/chicken/attackby(obj/item/O, mob/user, params) + if(istype(O, food_type)) //feedin' dem chickens + if(!stat && eggsleft < 8) + var/feedmsg = "[user] feeds [O] to [name]! [pick(feedMessages)]" + user.visible_message(feedmsg) + qdel(O) + eggsleft += rand(1, 4) + else + to_chat(user, "[name] doesn't seem hungry!") + else + ..() + +/mob/living/simple_animal/chicken/Life() + . =..() + if(!.) + return + if((!stat && prob(3) && eggsleft > 0) && egg_type) + visible_message("[src] [pick(layMessage)]") + eggsleft-- + var/obj/item/E = new egg_type(get_turf(src)) + E.pixel_x = rand(-6,6) + E.pixel_y = rand(-6,6) + if(eggsFertile) + if(chicken_count < MAX_CHICKENS && prob(25)) + START_PROCESSING(SSobj, E) + +/obj/item/reagent_containers/food/snacks/egg/var/amount_grown = 0 +/obj/item/reagent_containers/food/snacks/egg/process() + if(isturf(loc)) + amount_grown += rand(1,2) + if(amount_grown >= 100) + visible_message("[src] hatches with a quiet cracking sound.") + new /mob/living/simple_animal/chick(get_turf(src)) + STOP_PROCESSING(SSobj, src) + qdel(src) + else + STOP_PROCESSING(SSobj, src) + +// Space kiwis, ergo quite a copypasta of chickens. + +/mob/living/simple_animal/kiwi + name = "space kiwi" + desc = "Exposure to low gravity made them grow larger." + gender = FEMALE + icon_state = "kiwi" + icon_living = "kiwi" + icon_dead = "kiwi_dead" + speak = list("Chirp!","Cheep cheep chirp!!","Cheep.") + speak_emote = list("chirps","trills") + emote_hear = list("chirps.") + emote_see = list("pecks at the ground.","jumps in place.") + density = FALSE + speak_chance = 2 + turns_per_move = 3 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 3) + var/egg_type = /obj/item/reagent_containers/food/snacks/egg/kiwiEgg + var/food_type = /obj/item/reagent_containers/food/snacks/grown/wheat + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + attacktext = "kicks" + health = 25 + maxHealth = 25 + ventcrawler = VENTCRAWLER_ALWAYS + var/eggsleft = 0 + var/eggsFertile = TRUE + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_SMALL + var/list/feedMessages = list("It chirps happily.","It chirps happily.") + var/list/layMessage = list("lays an egg.","squats down and croons.","begins making a huge racket.","begins chirping raucously.") + gold_core_spawnable = FRIENDLY_SPAWN + var/static/kiwi_count = 0 + +/mob/living/simple_animal/kiwi/Destroy() + --kiwi_count + return ..() + +/mob/living/simple_animal/kiwi/Initialize() + . = ..() + ++kiwi_count + +/mob/living/simple_animal/kiwi/Life() + . =..() + if(!.) + return + if((!stat && prob(3) && eggsleft > 0) && egg_type) + visible_message("[src] [pick(layMessage)]") + eggsleft-- + var/obj/item/E = new egg_type(get_turf(src)) + E.pixel_x = rand(-6,6) + E.pixel_y = rand(-6,6) + if(eggsFertile) + if(kiwi_count < MAX_CHICKENS && prob(25)) + START_PROCESSING(SSobj, E) + +/obj/item/reagent_containers/food/snacks/egg/kiwiEgg/process() + if(isturf(loc)) + amount_grown += rand(1,2) + if(amount_grown >= 100) + visible_message("[src] hatches with a quiet cracking sound.") + new /mob/living/simple_animal/babyKiwi(get_turf(src)) + STOP_PROCESSING(SSobj, src) + qdel(src) + else + STOP_PROCESSING(SSobj, src) + +/mob/living/simple_animal/kiwi/attackby(obj/item/O, mob/user, params) + if(istype(O, food_type)) //feedin' dem kiwis + if(!stat && eggsleft < 8) + var/feedmsg = "[user] feeds [O] to [name]! [pick(feedMessages)]" + user.visible_message(feedmsg) + qdel(O) + eggsleft += rand(1, 4) + else + to_chat(user, "[name] doesn't seem hungry!") + else + ..() + +/mob/living/simple_animal/babyKiwi + name = "baby space kiwi" + desc = "So huggable." + icon_state = "babykiwi" + icon_living = "babykiwi" + icon_dead = "babykiwi_dead" + gender = FEMALE + speak = list("Cherp.","Cherp?","Chirrup.","Cheep!") + speak_emote = list("chirps") + emote_hear = list("chirps.") + emote_see = list("pecks at the ground.","Happily bounces in place.") + density = FALSE + speak_chance = 2 + turns_per_move = 2 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 2) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + attacktext = "kicks" + health = 10 + maxHealth = 10 + ventcrawler = VENTCRAWLER_ALWAYS + var/amount_grown = 0 + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB + mob_size = MOB_SIZE_TINY + gold_core_spawnable = FRIENDLY_SPAWN + +/mob/living/simple_animal/babyKiwi/Initialize() + . = ..() + pixel_x = rand(-6, 6) + pixel_y = rand(0, 10) + +/mob/living/simple_animal/babyKiwi/Life() + . =..() + if(!.) + return + if(!stat && !ckey) + amount_grown += rand(1,2) + if(amount_grown >= 100) + new /mob/living/simple_animal/kiwi(src.loc) + qdel(src) + +/obj/item/reagent_containers/food/snacks/egg/kiwiEgg + name = "kiwi egg" + desc = "A slightly bigger egg!" + icon_state = "kiwiegg" + +/obj/item/udder + name = "udder" + +/obj/item/udder/Initialize() + create_reagents(50) + reagents.add_reagent(/datum/reagent/consumable/milk, 20) + . = ..() + +/obj/item/udder/proc/generateMilk() + if(prob(5)) + reagents.add_reagent(/datum/reagent/consumable/milk, rand(5, 10)) + +/obj/item/udder/proc/milkAnimal(obj/O, mob/user) + var/obj/item/reagent_containers/glass/G = O + if(G.reagents.total_volume >= G.volume) + to_chat(user, "[O] is full.") + return + var/transfered = reagents.trans_to(O, rand(5,10)) + if(transfered) + user.visible_message("[user] milks [src] using \the [O].", "You milk [src] using \the [O].") + else + to_chat(user, "The udder is dry. Wait a bit longer...") diff --git a/code/modules/mob/living/simple_animal/friendly/lizard.dm b/code/modules/mob/living/simple_animal/friendly/lizard.dm index 1a9a4ff4d7..cafa0d9009 100644 --- a/code/modules/mob/living/simple_animal/friendly/lizard.dm +++ b/code/modules/mob/living/simple_animal/friendly/lizard.dm @@ -1,46 +1,46 @@ -/mob/living/simple_animal/hostile/lizard - name = "Lizard" - desc = "A cute tiny lizard." - icon_state = "lizard" - icon_living = "lizard" - icon_dead = "lizard_dead" - speak_emote = list("hisses") - health = 5 - maxHealth = 5 - faction = list("Lizard") - attacktext = "bites" - melee_damage_lower = 1 - melee_damage_upper = 2 - response_help = "pets" - response_disarm = "shoos" - response_harm = "stomps on" - ventcrawler = VENTCRAWLER_ALWAYS - density = FALSE - pass_flags = PASSTABLE | PASSMOB - mob_size = MOB_SIZE_SMALL - mob_biotypes = list(MOB_ORGANIC, MOB_BEAST, MOB_REPTILE) - gold_core_spawnable = FRIENDLY_SPAWN - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - var/static/list/edibles = typecacheof(list(/mob/living/simple_animal/butterfly, /mob/living/simple_animal/cockroach)) //list of atoms, however turfs won't affect AI, but will affect consumption. - can_be_held = "lizard" //you can hold lizards now. - -/mob/living/simple_animal/hostile/lizard/CanAttack(atom/the_target)//Can we actually attack a possible target? - if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it - return FALSE - if(is_type_in_typecache(the_target,edibles)) - return TRUE - return FALSE - -/mob/living/simple_animal/hostile/lizard/AttackingTarget() - if(is_type_in_typecache(target,edibles)) //Makes sure player lizards only consume edibles. - visible_message("[name] consumes [target] in a single gulp", "You consume [target] in a single gulp") - QDEL_NULL(target) //Nom - adjustBruteLoss(-2) - return TRUE - else - return ..() - -/mob/living/simple_animal/hostile/lizard/generate_mob_holder() - var/obj/item/clothing/head/mob_holder/holder = new(get_turf(src), src, "lizard", 'icons/mob/animals_held.dmi', 'icons/mob/animals_held_lh.dmi', 'icons/mob/animals_held_rh.dmi', TRUE) - return holder +/mob/living/simple_animal/hostile/lizard + name = "Lizard" + desc = "A cute tiny lizard." + icon_state = "lizard" + icon_living = "lizard" + icon_dead = "lizard_dead" + speak_emote = list("hisses") + health = 5 + maxHealth = 5 + faction = list("Lizard") + attacktext = "bites" + melee_damage_lower = 1 + melee_damage_upper = 2 + response_help = "pets" + response_disarm = "shoos" + response_harm = "stomps on" + ventcrawler = VENTCRAWLER_ALWAYS + density = FALSE + pass_flags = PASSTABLE | PASSMOB + mob_size = MOB_SIZE_SMALL + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST, MOB_REPTILE) + gold_core_spawnable = FRIENDLY_SPAWN + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + var/static/list/edibles = typecacheof(list(/mob/living/simple_animal/butterfly, /mob/living/simple_animal/cockroach)) //list of atoms, however turfs won't affect AI, but will affect consumption. + can_be_held = "lizard" //you can hold lizards now. + +/mob/living/simple_animal/hostile/lizard/CanAttack(atom/the_target)//Can we actually attack a possible target? + if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it + return FALSE + if(is_type_in_typecache(the_target,edibles)) + return TRUE + return FALSE + +/mob/living/simple_animal/hostile/lizard/AttackingTarget() + if(is_type_in_typecache(target,edibles)) //Makes sure player lizards only consume edibles. + visible_message("[name] consumes [target] in a single gulp", "You consume [target] in a single gulp") + QDEL_NULL(target) //Nom + adjustBruteLoss(-2) + return TRUE + else + return ..() + +/mob/living/simple_animal/hostile/lizard/generate_mob_holder() + var/obj/item/clothing/head/mob_holder/holder = new(get_turf(src), src, "lizard", 'icons/mob/animals_held.dmi', 'icons/mob/animals_held_lh.dmi', 'icons/mob/animals_held_rh.dmi', TRUE) + return holder diff --git a/code/modules/mob/living/simple_animal/hostile/carp.dm b/code/modules/mob/living/simple_animal/hostile/carp.dm index be9b435a18..4122cab6d9 100644 --- a/code/modules/mob/living/simple_animal/hostile/carp.dm +++ b/code/modules/mob/living/simple_animal/hostile/carp.dm @@ -1,107 +1,107 @@ -#define REGENERATION_DELAY 60 // After taking damage, how long it takes for automatic regeneration to begin for megacarps (ty robustin!) - -/mob/living/simple_animal/hostile/carp - name = "space carp" - desc = "A ferocious, fang-bearing creature that resembles a fish." - icon_state = "carp" - icon_living = "carp" - icon_dead = "carp_dead" - icon_gib = "carp_gib" - mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) - speak_chance = 0 - turns_per_move = 5 - butcher_results = list(/obj/item/reagent_containers/food/snacks/carpmeat = 2) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "hits" - emote_taunt = list("gnashes") - taunt_chance = 30 - speed = 0 - maxHealth = 25 - health = 25 - spacewalk = TRUE - - harm_intent_damage = 8 - obj_damage = 50 - melee_damage_lower = 15 - melee_damage_upper = 15 - attacktext = "bites" - attack_sound = 'sound/weapons/bite.ogg' - speak_emote = list("gnashes") - - //Space carp 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 - faction = list("carp") - movement_type = FLYING - pressure_resistance = 200 - gold_core_spawnable = HOSTILE_SPAWN - -/mob/living/simple_animal/hostile/carp/AttackingTarget() - . = ..() - if(. && ishuman(target)) - var/mob/living/carbon/human/H = target - H.adjustStaminaLoss(8) - -/mob/living/simple_animal/hostile/carp/holocarp - icon_state = "holocarp" - icon_living = "holocarp" - maxbodytemp = INFINITY - gold_core_spawnable = NO_SPAWN - del_on_death = 1 - -/mob/living/simple_animal/hostile/carp/megacarp - icon = 'icons/mob/broadMobs.dmi' - name = "Mega Space Carp" - desc = "A ferocious, fang bearing creature that resembles a shark. This one seems especially ticked off." - icon_state = "megacarp" - icon_living = "megacarp" - icon_dead = "megacarp_dead" - icon_gib = "megacarp_gib" - maxHealth = 20 - health = 20 - pixel_x = -16 - mob_size = MOB_SIZE_LARGE - - obj_damage = 80 - melee_damage_lower = 20 - melee_damage_upper = 20 - - var/regen_cooldown = 0 - -/mob/living/simple_animal/hostile/carp/megacarp/Initialize() - . = ..() - name = "[pick(GLOB.megacarp_first_names)] [pick(GLOB.megacarp_last_names)]" - melee_damage_lower += rand(2, 10) - melee_damage_upper += rand(10,20) - maxHealth += rand(30,60) - move_to_delay = rand(3,7) - -/mob/living/simple_animal/hostile/carp/megacarp/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(.) - regen_cooldown = world.time + REGENERATION_DELAY - -/mob/living/simple_animal/hostile/carp/megacarp/Life() - . = ..() - if(regen_cooldown < world.time) - heal_overall_damage(4) - -/mob/living/simple_animal/hostile/carp/cayenne - name = "Cayenne" - desc = "A failed Syndicate experiment in weaponized space carp technology, it now serves as a lovable mascot." - gender = FEMALE - speak_emote = list("squeaks") - maxHealth = 90 - health = 90 - gold_core_spawnable = NO_SPAWN - faction = list(ROLE_SYNDICATE) - AIStatus = AI_OFF - - harm_intent_damage = 12 - obj_damage = 70 - melee_damage_lower = 15 - melee_damage_upper = 18 - -#undef REGENERATION_DELAY +#define REGENERATION_DELAY 60 // After taking damage, how long it takes for automatic regeneration to begin for megacarps (ty robustin!) + +/mob/living/simple_animal/hostile/carp + name = "space carp" + desc = "A ferocious, fang-bearing creature that resembles a fish." + icon_state = "carp" + icon_living = "carp" + icon_dead = "carp_dead" + icon_gib = "carp_gib" + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) + speak_chance = 0 + turns_per_move = 5 + butcher_results = list(/obj/item/reagent_containers/food/snacks/carpmeat = 2) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "hits" + emote_taunt = list("gnashes") + taunt_chance = 30 + speed = 0 + maxHealth = 25 + health = 25 + spacewalk = TRUE + + harm_intent_damage = 8 + obj_damage = 50 + melee_damage_lower = 15 + melee_damage_upper = 15 + attacktext = "bites" + attack_sound = 'sound/weapons/bite.ogg' + speak_emote = list("gnashes") + + //Space carp 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 + faction = list("carp") + movement_type = FLYING + pressure_resistance = 200 + gold_core_spawnable = HOSTILE_SPAWN + +/mob/living/simple_animal/hostile/carp/AttackingTarget() + . = ..() + if(. && ishuman(target)) + var/mob/living/carbon/human/H = target + H.adjustStaminaLoss(8) + +/mob/living/simple_animal/hostile/carp/holocarp + icon_state = "holocarp" + icon_living = "holocarp" + maxbodytemp = INFINITY + gold_core_spawnable = NO_SPAWN + del_on_death = 1 + +/mob/living/simple_animal/hostile/carp/megacarp + icon = 'icons/mob/broadMobs.dmi' + name = "Mega Space Carp" + desc = "A ferocious, fang bearing creature that resembles a shark. This one seems especially ticked off." + icon_state = "megacarp" + icon_living = "megacarp" + icon_dead = "megacarp_dead" + icon_gib = "megacarp_gib" + maxHealth = 20 + health = 20 + pixel_x = -16 + mob_size = MOB_SIZE_LARGE + + obj_damage = 80 + melee_damage_lower = 20 + melee_damage_upper = 20 + + var/regen_cooldown = 0 + +/mob/living/simple_animal/hostile/carp/megacarp/Initialize() + . = ..() + name = "[pick(GLOB.megacarp_first_names)] [pick(GLOB.megacarp_last_names)]" + melee_damage_lower += rand(2, 10) + melee_damage_upper += rand(10,20) + maxHealth += rand(30,60) + move_to_delay = rand(3,7) + +/mob/living/simple_animal/hostile/carp/megacarp/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + . = ..() + if(.) + regen_cooldown = world.time + REGENERATION_DELAY + +/mob/living/simple_animal/hostile/carp/megacarp/Life() + . = ..() + if(regen_cooldown < world.time) + heal_overall_damage(4) + +/mob/living/simple_animal/hostile/carp/cayenne + name = "Cayenne" + desc = "A failed Syndicate experiment in weaponized space carp technology, it now serves as a lovable mascot." + gender = FEMALE + speak_emote = list("squeaks") + maxHealth = 90 + health = 90 + gold_core_spawnable = NO_SPAWN + faction = list(ROLE_SYNDICATE) + AIStatus = AI_OFF + + harm_intent_damage = 12 + obj_damage = 70 + melee_damage_lower = 15 + melee_damage_upper = 18 + +#undef REGENERATION_DELAY diff --git a/code/modules/mob/living/simple_animal/hostile/faithless.dm b/code/modules/mob/living/simple_animal/hostile/faithless.dm index bc766f7409..3d411738c1 100644 --- a/code/modules/mob/living/simple_animal/hostile/faithless.dm +++ b/code/modules/mob/living/simple_animal/hostile/faithless.dm @@ -1,46 +1,46 @@ -/mob/living/simple_animal/hostile/faithless - name = "The Faithless" - desc = "The Wish Granter's faith in humanity, incarnate." - icon_state = "faithless" - icon_living = "faithless" - icon_dead = "faithless_dead" - mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) - gender = MALE - speak_chance = 0 - turns_per_move = 5 - response_help = "passes through" - response_disarm = "shoves" - response_harm = "hits" - emote_taunt = list("wails") - taunt_chance = 25 - speed = 0 - maxHealth = 80 - health = 80 - spacewalk = TRUE - stat_attack = UNCONSCIOUS - robust_searching = 1 - blood_volume = 0 - - harm_intent_damage = 10 - obj_damage = 50 - melee_damage_lower = 15 - melee_damage_upper = 15 - attacktext = "grips" - attack_sound = 'sound/hallucinations/growl1.ogg' - speak_emote = list("growls") - - 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 - - faction = list("faithless") - gold_core_spawnable = HOSTILE_SPAWN - - do_footstep = TRUE - -/mob/living/simple_animal/hostile/faithless/AttackingTarget() - . = ..() - if(. && prob(12) && iscarbon(target)) - var/mob/living/carbon/C = target - C.Knockdown(60) - C.visible_message("\The [src] knocks down \the [C]!", \ - "\The [src] knocks you down!") +/mob/living/simple_animal/hostile/faithless + name = "The Faithless" + desc = "The Wish Granter's faith in humanity, incarnate." + icon_state = "faithless" + icon_living = "faithless" + icon_dead = "faithless_dead" + mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) + gender = MALE + speak_chance = 0 + turns_per_move = 5 + response_help = "passes through" + response_disarm = "shoves" + response_harm = "hits" + emote_taunt = list("wails") + taunt_chance = 25 + speed = 0 + maxHealth = 80 + health = 80 + spacewalk = TRUE + stat_attack = UNCONSCIOUS + robust_searching = 1 + blood_volume = 0 + + harm_intent_damage = 10 + obj_damage = 50 + melee_damage_lower = 15 + melee_damage_upper = 15 + attacktext = "grips" + attack_sound = 'sound/hallucinations/growl1.ogg' + speak_emote = list("growls") + + 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 + + faction = list("faithless") + gold_core_spawnable = HOSTILE_SPAWN + + do_footstep = TRUE + +/mob/living/simple_animal/hostile/faithless/AttackingTarget() + . = ..() + if(. && prob(12) && iscarbon(target)) + var/mob/living/carbon/C = target + C.Knockdown(60) + C.visible_message("\The [src] knocks down \the [C]!", \ + "\The [src] knocks you down!") diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index 2e7c0ff455..20ce5ac751 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -1,544 +1,544 @@ -#define SPIDER_IDLE 0 -#define SPINNING_WEB 1 -#define LAYING_EGGS 2 -#define MOVING_TO_TARGET 3 -#define SPINNING_COCOON 4 - -/mob/living/simple_animal/hostile/poison - var/poison_per_bite = 5 - var/poison_type = /datum/reagent/toxin - -/mob/living/simple_animal/hostile/poison/AttackingTarget() - . = ..() - if(. && isliving(target)) - var/mob/living/L = target - if(L.reagents) - L.reagents.add_reagent(poison_type, poison_per_bite) - -//basic spider mob, these generally guard nests -/mob/living/simple_animal/hostile/poison/giant_spider - name = "giant spider" - desc = "Furry and black, it makes you shudder to look at it. This one has deep red eyes." - icon_state = "guard" - icon_living = "guard" - icon_dead = "guard_dead" - mob_biotypes = list(MOB_ORGANIC, MOB_BUG) - speak_emote = list("chitters") - emote_hear = list("chitters") - speak_chance = 5 - turns_per_move = 5 - see_in_dark = 10 - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/spider = 2, /obj/item/reagent_containers/food/snacks/spiderleg = 8) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "hits" - maxHealth = 200 - health = 200 - obj_damage = 60 - melee_damage_lower = 15 - melee_damage_upper = 20 - faction = list("spiders") - var/busy = SPIDER_IDLE - pass_flags = PASSTABLE - move_to_delay = 6 - ventcrawler = VENTCRAWLER_ALWAYS - attacktext = "bites" - attack_sound = 'sound/weapons/bite.ogg' - unique_name = 1 - gold_core_spawnable = HOSTILE_SPAWN - see_in_dark = 4 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - var/playable_spider = FALSE - var/datum/action/innate/spider/lay_web/lay_web - var/directive = "" //Message passed down to children, to relay the creator's orders - - do_footstep = TRUE - -/mob/living/simple_animal/hostile/poison/giant_spider/Initialize() - . = ..() - lay_web = new - lay_web.Grant(src) - -/mob/living/simple_animal/hostile/poison/giant_spider/Destroy() - QDEL_NULL(lay_web) - return ..() - -/mob/living/simple_animal/hostile/poison/giant_spider/Topic(href, href_list) - if(href_list["activate"]) - var/mob/dead/observer/ghost = usr - if(istype(ghost) && playable_spider) - humanize_spider(ghost) - -/mob/living/simple_animal/hostile/poison/giant_spider/Login() - ..() - if(directive) - to_chat(src, "Your mother left you a directive! Follow it at all costs.") - to_chat(src, "[directive]") - -/mob/living/simple_animal/hostile/poison/giant_spider/attack_ghost(mob/user) - . = ..() - if(.) - return - humanize_spider(user) - -/mob/living/simple_animal/hostile/poison/giant_spider/proc/humanize_spider(mob/user) - if(key || !playable_spider || stat)//Someone is in it, it's dead, or the fun police are shutting it down - return FALSE - if(isobserver(user)) - var/mob/dead/observer/O = user - if(!O.can_reenter_round()) - return FALSE - var/spider_ask = alert("Become a spider?", "Are you australian?", "Yes", "No") - if(spider_ask == "No" || !src || QDELETED(src)) - return TRUE - if(key) - to_chat(user, "Someone else already took this spider.") - return TRUE - user.transfer_ckey(src, FALSE) - return TRUE - -//nursemaids - these create webs and eggs -/mob/living/simple_animal/hostile/poison/giant_spider/nurse - desc = "Furry and black, it makes you shudder to look at it. This one has brilliant green eyes." - icon_state = "nurse" - icon_living = "nurse" - icon_dead = "nurse_dead" - gender = FEMALE - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/spider = 2, /obj/item/reagent_containers/food/snacks/spiderleg = 8, /obj/item/reagent_containers/food/snacks/spidereggs = 4) - maxHealth = 40 - health = 40 - melee_damage_lower = 5 - melee_damage_upper = 10 - poison_per_bite = 3 - var/atom/movable/cocoon_target - var/fed = 0 - var/obj/effect/proc_holder/wrap/wrap - var/datum/action/innate/spider/lay_eggs/lay_eggs - var/datum/action/innate/spider/set_directive/set_directive - var/static/list/consumed_mobs = list() //the tags of mobs that have been consumed by nurse spiders to lay eggs - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/Initialize() - . = ..() - wrap = new - AddAbility(wrap) - lay_eggs = new - lay_eggs.Grant(src) - set_directive = new - set_directive.Grant(src) - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/Destroy() - RemoveAbility(wrap) - QDEL_NULL(lay_eggs) - QDEL_NULL(set_directive) - return ..() - -//hunters have the most poison and move the fastest, so they can find prey -/mob/living/simple_animal/hostile/poison/giant_spider/hunter - desc = "Furry and black, it makes you shudder to look at it. This one has sparkling purple eyes." - icon_state = "hunter" - icon_living = "hunter" - icon_dead = "hunter_dead" - maxHealth = 120 - health = 120 - melee_damage_lower = 10 - melee_damage_upper = 20 - poison_per_bite = 5 - move_to_delay = 5 - -//vipers are the rare variant of the hunter, no IMMEDIATE damage but so much poison medical care will be needed fast. -/mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper - name = "viper" - desc = "Furry and black, it makes you shudder to look at it. This one has effervescent purple eyes." - icon_state = "viper" - icon_living = "viper" - icon_dead = "viper_dead" - maxHealth = 40 - health = 40 - melee_damage_lower = 1 - melee_damage_upper = 1 - poison_per_bite = 12 - move_to_delay = 4 - poison_type = /datum/reagent/toxin/venom //all in venom, glass cannon. you bite 5 times and they are DEFINITELY dead, but 40 health and you are extremely obvious. Ambush, maybe? - speed = 1 - gold_core_spawnable = NO_SPAWN - -//tarantulas are really tanky, regenerating (maybe), hulky monster but are also extremely slow, so. -/mob/living/simple_animal/hostile/poison/giant_spider/tarantula - name = "tarantula" - desc = "Furry and black, it makes you shudder to look at it. This one has abyssal red eyes." - icon_state = "tarantula" - icon_living = "tarantula" - icon_dead = "tarantula_dead" - maxHealth = 300 // woah nelly - health = 300 - melee_damage_lower = 35 - melee_damage_upper = 40 - poison_per_bite = 0 - move_to_delay = 8 - speed = 7 - status_flags = NONE - mob_size = MOB_SIZE_LARGE - gold_core_spawnable = NO_SPAWN - var/slowed_by_webs = FALSE - -/mob/living/simple_animal/hostile/poison/giant_spider/tarantula/Moved(atom/oldloc, dir) - . = ..() - if(slowed_by_webs) - if(!(locate(/obj/structure/spider/stickyweb) in loc)) - remove_movespeed_modifier(MOVESPEED_ID_TARANTULA_WEB) - slowed_by_webs = FALSE - else if(locate(/obj/structure/spider/stickyweb) in loc) - add_movespeed_modifier(MOVESPEED_ID_TARANTULA_WEB, priority=100, multiplicative_slowdown=3) - slowed_by_webs = TRUE - -//midwives are the queen of the spiders, can send messages to all them and web faster. That rare round where you get a queen spider and turn your 'for honor' players into 'r6siege' players will be a fun one. -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife - name = "midwife" - desc = "Furry and black, it makes you shudder to look at it. This one has scintillating green eyes." - icon_state = "midwife" - icon_living = "midwife" - icon_dead = "midwife_dead" - maxHealth = 40 - health = 40 - var/datum/action/innate/spider/comm/letmetalkpls - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife/Initialize() - . = ..() - letmetalkpls = new - letmetalkpls.Grant(src) - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife/Destroy() - QDEL_NULL(letmetalkpls) - return ..() - -/mob/living/simple_animal/hostile/poison/giant_spider/ice //spiders dont usually like tempatures of 140 kelvin who knew - name = "giant ice spider" - 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 - poison_type = /datum/reagent/consumable/frostoil - color = rgb(114,228,250) - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/ice - name = "giant ice spider" - 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 - poison_type = /datum/reagent/consumable/frostoil - color = rgb(114,228,250) - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/hostile/poison/giant_spider/hunter/ice - name = "giant ice spider" - 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 - poison_type = /datum/reagent/consumable/frostoil - color = rgb(114,228,250) - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/hostile/poison/giant_spider/handle_automated_action() - if(!..()) //AIStatus is off - return 0 - if(AIStatus == AI_IDLE) - //1% chance to skitter madly away - if(!busy && prob(1)) - stop_automated_movement = 1 - Goto(pick(urange(20, src, 1)), move_to_delay) - spawn(50) - stop_automated_movement = 0 - walk(src,0) - return 1 - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/proc/GiveUp(C) - spawn(100) - if(busy == MOVING_TO_TARGET) - if(cocoon_target == C && get_dist(src,cocoon_target) > 1) - cocoon_target = null - busy = FALSE - stop_automated_movement = 0 - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/handle_automated_action() - if(..()) - var/list/can_see = view(src, 10) - if(!busy && prob(30)) //30% chance to stop wandering and do something - //first, check for potential food nearby to cocoon - for(var/mob/living/C in can_see) - if(C.stat && !istype(C, /mob/living/simple_animal/hostile/poison/giant_spider) && !C.anchored) - cocoon_target = C - busy = MOVING_TO_TARGET - Goto(C, move_to_delay) - //give up if we can't reach them after 10 seconds - GiveUp(C) - return - - //second, spin a sticky spiderweb on this tile - var/obj/structure/spider/stickyweb/W = locate() in get_turf(src) - if(!W) - lay_web.Activate() - else - //third, lay an egg cluster there - if(fed) - lay_eggs.Activate() - else - //fourthly, cocoon any nearby items so those pesky pinkskins can't use them - for(var/obj/O in can_see) - - if(O.anchored) - continue - - if(isitem(O) || isstructure(O) || ismachinery(O)) - cocoon_target = O - busy = MOVING_TO_TARGET - stop_automated_movement = 1 - Goto(O, move_to_delay) - //give up if we can't reach them after 10 seconds - GiveUp(O) - - else if(busy == MOVING_TO_TARGET && cocoon_target) - if(get_dist(src, cocoon_target) <= 1) - cocoon() - - else - busy = SPIDER_IDLE - stop_automated_movement = FALSE - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/proc/cocoon() - if(stat != DEAD && cocoon_target && !cocoon_target.anchored) - if(cocoon_target == src) - to_chat(src, "You can't wrap yourself!") - return - if(istype(cocoon_target, /mob/living/simple_animal/hostile/poison/giant_spider)) - to_chat(src, "You can't wrap other spiders!") - return - if(!Adjacent(cocoon_target)) - to_chat(src, "You can't reach [cocoon_target]!") - return - if(busy == SPINNING_COCOON) - to_chat(src, "You're already spinning a cocoon!") - return //we're already doing this, don't cancel out or anything - busy = SPINNING_COCOON - visible_message("[src] begins to secrete a sticky substance around [cocoon_target].","You begin wrapping [cocoon_target] into a cocoon.") - stop_automated_movement = TRUE - walk(src,0) - if(do_after(src, 50, target = cocoon_target)) - if(busy == SPINNING_COCOON) - var/obj/structure/spider/cocoon/C = new(cocoon_target.loc) - if(isliving(cocoon_target)) - var/mob/living/L = cocoon_target - if(L.blood_volume && (L.stat != DEAD || !consumed_mobs[L.tag])) //if they're not dead, you can consume them anyway - consumed_mobs[L.tag] = TRUE - fed++ - lay_eggs.UpdateButtonIcon(TRUE) - visible_message("[src] sticks a proboscis into [L] and sucks a viscous substance out.","You suck the nutriment out of [L], feeding you enough to lay a cluster of eggs.") - L.death() //you just ate them, they're dead. - else - to_chat(src, "[L] cannot sate your hunger!") - cocoon_target.forceMove(C) - - if(cocoon_target.density || ismob(cocoon_target)) - C.icon_state = pick("cocoon_large1","cocoon_large2","cocoon_large3") - cocoon_target = null - busy = SPIDER_IDLE - stop_automated_movement = FALSE - -/datum/action/innate/spider - icon_icon = 'icons/mob/actions/actions_animal.dmi' - background_icon_state = "bg_alien" - -/datum/action/innate/spider/lay_web - name = "Spin Web" - desc = "Spin a web to slow down potential prey." - check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "lay_web" - -/datum/action/innate/spider/lay_web/Activate() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider)) - return - var/mob/living/simple_animal/hostile/poison/giant_spider/S = owner - - if(!isturf(S.loc)) - return - var/turf/T = get_turf(S) - - var/obj/structure/spider/stickyweb/W = locate() in T - if(W) - to_chat(S, "There's already a web here!") - return - - if(S.busy != SPINNING_WEB) - S.busy = SPINNING_WEB - S.visible_message("[S] begins to secrete a sticky substance.","You begin to lay a web.") - S.stop_automated_movement = TRUE - if(do_after(S, 40, target = T)) - if(S.busy == SPINNING_WEB && S.loc == T) - new /obj/structure/spider/stickyweb(T) - S.busy = SPIDER_IDLE - S.stop_automated_movement = FALSE - else - to_chat(S, "You're already spinning a web!") - -/obj/effect/proc_holder/wrap - name = "Wrap" - panel = "Spider" - active = FALSE - datum/action/spell_action/action = null - desc = "Wrap something or someone in a cocoon. If it's a living being, you'll also consume them, allowing you to lay eggs." - ranged_mousepointer = 'icons/effects/wrap_target.dmi' - action_icon = 'icons/mob/actions/actions_animal.dmi' - action_icon_state = "wrap_0" - action_background_icon_state = "bg_alien" - -/obj/effect/proc_holder/wrap/Initialize() - . = ..() - action = new(src) - -/obj/effect/proc_holder/wrap/update_icon() - action.button_icon_state = "wrap_[active]" - action.UpdateButtonIcon() - -/obj/effect/proc_holder/wrap/Click() - if(!istype(usr, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return TRUE - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/user = usr - activate(user) - return TRUE - -/obj/effect/proc_holder/wrap/proc/activate(mob/living/user) - var/message - if(active) - message = "You no longer prepare to wrap something in a cocoon." - remove_ranged_ability(message) - else - message = "You prepare to wrap something in a cocoon. Left-click your target to start wrapping!" - add_ranged_ability(user, message, TRUE) - return 1 - -/obj/effect/proc_holder/wrap/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return - if(ranged_ability_user.incapacitated() || !istype(ranged_ability_user, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - remove_ranged_ability() - return - - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/user = ranged_ability_user - - if(user.Adjacent(target) && (ismob(target) || isobj(target))) - var/atom/movable/target_atom = target - if(target_atom.anchored) - return - user.cocoon_target = target_atom - INVOKE_ASYNC(user, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/.proc/cocoon) - remove_ranged_ability() - return TRUE - -/obj/effect/proc_holder/wrap/on_lose(mob/living/carbon/user) - remove_ranged_ability() - -/datum/action/innate/spider/lay_eggs - name = "Lay Eggs" - desc = "Lay a cluster of eggs, which will soon grow into more spiders. You must wrap a living being to do this." - check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "lay_eggs" - -/datum/action/innate/spider/lay_eggs/IsAvailable() - if(..()) - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return 0 - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner - if(S.fed) - return 1 - return 0 - -/datum/action/innate/spider/lay_eggs/Activate() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner - - var/obj/structure/spider/eggcluster/E = locate() in get_turf(S) - if(E) - to_chat(S, "There is already a cluster of eggs here!") - else if(!S.fed) - to_chat(S, "You are too hungry to do this!") - else if(S.busy != LAYING_EGGS) - S.busy = LAYING_EGGS - S.visible_message("[S] begins to lay a cluster of eggs.","You begin to lay a cluster of eggs.") - S.stop_automated_movement = TRUE - if(do_after(S, 50, target = get_turf(S))) - if(S.busy == LAYING_EGGS) - E = locate() in get_turf(S) - if(!E || !isturf(S.loc)) - var/obj/structure/spider/eggcluster/C = new /obj/structure/spider/eggcluster(get_turf(S)) - if(S.ckey) - C.player_spiders = TRUE - C.directive = S.directive - C.poison_type = S.poison_type - C.poison_per_bite = S.poison_per_bite - C.faction = S.faction.Copy() - S.fed-- - UpdateButtonIcon(TRUE) - S.busy = SPIDER_IDLE - S.stop_automated_movement = FALSE - -/datum/action/innate/spider/set_directive - name = "Set Directive" - desc = "Set a directive for your children to follow." - check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "directive" - -/datum/action/innate/spider/set_directive/Activate() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner - S.directive = stripped_input(S, "Enter the new directive", "Create directive", "[S.directive]") - -/mob/living/simple_animal/hostile/poison/giant_spider/Login() - . = ..() - GLOB.spidermobs[src] = TRUE - -/mob/living/simple_animal/hostile/poison/giant_spider/Destroy() - GLOB.spidermobs -= src - return ..() - -/datum/action/innate/spider/comm - name = "Command" - desc = "Send a command to all living spiders." - button_icon_state = "command" - -/datum/action/innate/spider/comm/IsAvailable() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife)) - return FALSE - return TRUE - -/datum/action/innate/spider/comm/Trigger() - var/input = stripped_input(owner, "Input a command for your legions to follow.", "Command", "") - if(QDELETED(src) || !input || !IsAvailable()) - return FALSE - spider_command(owner, input) - return TRUE - -/datum/action/innate/spider/comm/proc/spider_command(mob/living/user, message) - if(!message) - return - var/my_message - my_message = "Command from [user]: [message]" - for(var/mob/living/simple_animal/hostile/poison/giant_spider/M in GLOB.spidermobs) - to_chat(M, my_message) - for(var/M in GLOB.dead_mob_list) - var/link = FOLLOW_LINK(M, user) - to_chat(M, "[link] [my_message]") - usr.log_talk(message, LOG_SAY, tag="spider command") - -/mob/living/simple_animal/hostile/poison/giant_spider/handle_temperature_damage() - if(bodytemperature < minbodytemp) - adjustBruteLoss(20) - else if(bodytemperature > maxbodytemp) - adjustBruteLoss(20) - -#undef SPIDER_IDLE -#undef SPINNING_WEB -#undef LAYING_EGGS -#undef MOVING_TO_TARGET -#undef SPINNING_COCOON +#define SPIDER_IDLE 0 +#define SPINNING_WEB 1 +#define LAYING_EGGS 2 +#define MOVING_TO_TARGET 3 +#define SPINNING_COCOON 4 + +/mob/living/simple_animal/hostile/poison + var/poison_per_bite = 5 + var/poison_type = /datum/reagent/toxin + +/mob/living/simple_animal/hostile/poison/AttackingTarget() + . = ..() + if(. && isliving(target)) + var/mob/living/L = target + if(L.reagents) + L.reagents.add_reagent(poison_type, poison_per_bite) + +//basic spider mob, these generally guard nests +/mob/living/simple_animal/hostile/poison/giant_spider + name = "giant spider" + desc = "Furry and black, it makes you shudder to look at it. This one has deep red eyes." + icon_state = "guard" + icon_living = "guard" + icon_dead = "guard_dead" + mob_biotypes = list(MOB_ORGANIC, MOB_BUG) + speak_emote = list("chitters") + emote_hear = list("chitters") + speak_chance = 5 + turns_per_move = 5 + see_in_dark = 10 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/spider = 2, /obj/item/reagent_containers/food/snacks/spiderleg = 8) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "hits" + maxHealth = 200 + health = 200 + obj_damage = 60 + melee_damage_lower = 15 + melee_damage_upper = 20 + faction = list("spiders") + var/busy = SPIDER_IDLE + pass_flags = PASSTABLE + move_to_delay = 6 + ventcrawler = VENTCRAWLER_ALWAYS + attacktext = "bites" + attack_sound = 'sound/weapons/bite.ogg' + unique_name = 1 + gold_core_spawnable = HOSTILE_SPAWN + see_in_dark = 4 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + var/playable_spider = FALSE + var/datum/action/innate/spider/lay_web/lay_web + var/directive = "" //Message passed down to children, to relay the creator's orders + + do_footstep = TRUE + +/mob/living/simple_animal/hostile/poison/giant_spider/Initialize() + . = ..() + lay_web = new + lay_web.Grant(src) + +/mob/living/simple_animal/hostile/poison/giant_spider/Destroy() + QDEL_NULL(lay_web) + return ..() + +/mob/living/simple_animal/hostile/poison/giant_spider/Topic(href, href_list) + if(href_list["activate"]) + var/mob/dead/observer/ghost = usr + if(istype(ghost) && playable_spider) + humanize_spider(ghost) + +/mob/living/simple_animal/hostile/poison/giant_spider/Login() + ..() + if(directive) + to_chat(src, "Your mother left you a directive! Follow it at all costs.") + to_chat(src, "[directive]") + +/mob/living/simple_animal/hostile/poison/giant_spider/attack_ghost(mob/user) + . = ..() + if(.) + return + humanize_spider(user) + +/mob/living/simple_animal/hostile/poison/giant_spider/proc/humanize_spider(mob/user) + if(key || !playable_spider || stat)//Someone is in it, it's dead, or the fun police are shutting it down + return FALSE + if(isobserver(user)) + var/mob/dead/observer/O = user + if(!O.can_reenter_round()) + return FALSE + var/spider_ask = alert("Become a spider?", "Are you australian?", "Yes", "No") + if(spider_ask == "No" || !src || QDELETED(src)) + return TRUE + if(key) + to_chat(user, "Someone else already took this spider.") + return TRUE + user.transfer_ckey(src, FALSE) + return TRUE + +//nursemaids - these create webs and eggs +/mob/living/simple_animal/hostile/poison/giant_spider/nurse + desc = "Furry and black, it makes you shudder to look at it. This one has brilliant green eyes." + icon_state = "nurse" + icon_living = "nurse" + icon_dead = "nurse_dead" + gender = FEMALE + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/spider = 2, /obj/item/reagent_containers/food/snacks/spiderleg = 8, /obj/item/reagent_containers/food/snacks/spidereggs = 4) + maxHealth = 40 + health = 40 + melee_damage_lower = 5 + melee_damage_upper = 10 + poison_per_bite = 3 + var/atom/movable/cocoon_target + var/fed = 0 + var/obj/effect/proc_holder/wrap/wrap + var/datum/action/innate/spider/lay_eggs/lay_eggs + var/datum/action/innate/spider/set_directive/set_directive + var/static/list/consumed_mobs = list() //the tags of mobs that have been consumed by nurse spiders to lay eggs + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/Initialize() + . = ..() + wrap = new + AddAbility(wrap) + lay_eggs = new + lay_eggs.Grant(src) + set_directive = new + set_directive.Grant(src) + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/Destroy() + RemoveAbility(wrap) + QDEL_NULL(lay_eggs) + QDEL_NULL(set_directive) + return ..() + +//hunters have the most poison and move the fastest, so they can find prey +/mob/living/simple_animal/hostile/poison/giant_spider/hunter + desc = "Furry and black, it makes you shudder to look at it. This one has sparkling purple eyes." + icon_state = "hunter" + icon_living = "hunter" + icon_dead = "hunter_dead" + maxHealth = 120 + health = 120 + melee_damage_lower = 10 + melee_damage_upper = 20 + poison_per_bite = 5 + move_to_delay = 5 + +//vipers are the rare variant of the hunter, no IMMEDIATE damage but so much poison medical care will be needed fast. +/mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper + name = "viper" + desc = "Furry and black, it makes you shudder to look at it. This one has effervescent purple eyes." + icon_state = "viper" + icon_living = "viper" + icon_dead = "viper_dead" + maxHealth = 40 + health = 40 + melee_damage_lower = 1 + melee_damage_upper = 1 + poison_per_bite = 12 + move_to_delay = 4 + poison_type = /datum/reagent/toxin/venom //all in venom, glass cannon. you bite 5 times and they are DEFINITELY dead, but 40 health and you are extremely obvious. Ambush, maybe? + speed = 1 + gold_core_spawnable = NO_SPAWN + +//tarantulas are really tanky, regenerating (maybe), hulky monster but are also extremely slow, so. +/mob/living/simple_animal/hostile/poison/giant_spider/tarantula + name = "tarantula" + desc = "Furry and black, it makes you shudder to look at it. This one has abyssal red eyes." + icon_state = "tarantula" + icon_living = "tarantula" + icon_dead = "tarantula_dead" + maxHealth = 300 // woah nelly + health = 300 + melee_damage_lower = 35 + melee_damage_upper = 40 + poison_per_bite = 0 + move_to_delay = 8 + speed = 7 + status_flags = NONE + mob_size = MOB_SIZE_LARGE + gold_core_spawnable = NO_SPAWN + var/slowed_by_webs = FALSE + +/mob/living/simple_animal/hostile/poison/giant_spider/tarantula/Moved(atom/oldloc, dir) + . = ..() + if(slowed_by_webs) + if(!(locate(/obj/structure/spider/stickyweb) in loc)) + remove_movespeed_modifier(MOVESPEED_ID_TARANTULA_WEB) + slowed_by_webs = FALSE + else if(locate(/obj/structure/spider/stickyweb) in loc) + add_movespeed_modifier(MOVESPEED_ID_TARANTULA_WEB, priority=100, multiplicative_slowdown=3) + slowed_by_webs = TRUE + +//midwives are the queen of the spiders, can send messages to all them and web faster. That rare round where you get a queen spider and turn your 'for honor' players into 'r6siege' players will be a fun one. +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife + name = "midwife" + desc = "Furry and black, it makes you shudder to look at it. This one has scintillating green eyes." + icon_state = "midwife" + icon_living = "midwife" + icon_dead = "midwife_dead" + maxHealth = 40 + health = 40 + var/datum/action/innate/spider/comm/letmetalkpls + gold_core_spawnable = NO_SPAWN + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife/Initialize() + . = ..() + letmetalkpls = new + letmetalkpls.Grant(src) + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife/Destroy() + QDEL_NULL(letmetalkpls) + return ..() + +/mob/living/simple_animal/hostile/poison/giant_spider/ice //spiders dont usually like tempatures of 140 kelvin who knew + name = "giant ice spider" + 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 + poison_type = /datum/reagent/consumable/frostoil + color = rgb(114,228,250) + gold_core_spawnable = NO_SPAWN + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/ice + name = "giant ice spider" + 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 + poison_type = /datum/reagent/consumable/frostoil + color = rgb(114,228,250) + gold_core_spawnable = NO_SPAWN + +/mob/living/simple_animal/hostile/poison/giant_spider/hunter/ice + name = "giant ice spider" + 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 + poison_type = /datum/reagent/consumable/frostoil + color = rgb(114,228,250) + gold_core_spawnable = NO_SPAWN + +/mob/living/simple_animal/hostile/poison/giant_spider/handle_automated_action() + if(!..()) //AIStatus is off + return 0 + if(AIStatus == AI_IDLE) + //1% chance to skitter madly away + if(!busy && prob(1)) + stop_automated_movement = 1 + Goto(pick(urange(20, src, 1)), move_to_delay) + spawn(50) + stop_automated_movement = 0 + walk(src,0) + return 1 + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/proc/GiveUp(C) + spawn(100) + if(busy == MOVING_TO_TARGET) + if(cocoon_target == C && get_dist(src,cocoon_target) > 1) + cocoon_target = null + busy = FALSE + stop_automated_movement = 0 + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/handle_automated_action() + if(..()) + var/list/can_see = view(src, 10) + if(!busy && prob(30)) //30% chance to stop wandering and do something + //first, check for potential food nearby to cocoon + for(var/mob/living/C in can_see) + if(C.stat && !istype(C, /mob/living/simple_animal/hostile/poison/giant_spider) && !C.anchored) + cocoon_target = C + busy = MOVING_TO_TARGET + Goto(C, move_to_delay) + //give up if we can't reach them after 10 seconds + GiveUp(C) + return + + //second, spin a sticky spiderweb on this tile + var/obj/structure/spider/stickyweb/W = locate() in get_turf(src) + if(!W) + lay_web.Activate() + else + //third, lay an egg cluster there + if(fed) + lay_eggs.Activate() + else + //fourthly, cocoon any nearby items so those pesky pinkskins can't use them + for(var/obj/O in can_see) + + if(O.anchored) + continue + + if(isitem(O) || isstructure(O) || ismachinery(O)) + cocoon_target = O + busy = MOVING_TO_TARGET + stop_automated_movement = 1 + Goto(O, move_to_delay) + //give up if we can't reach them after 10 seconds + GiveUp(O) + + else if(busy == MOVING_TO_TARGET && cocoon_target) + if(get_dist(src, cocoon_target) <= 1) + cocoon() + + else + busy = SPIDER_IDLE + stop_automated_movement = FALSE + +/mob/living/simple_animal/hostile/poison/giant_spider/nurse/proc/cocoon() + if(stat != DEAD && cocoon_target && !cocoon_target.anchored) + if(cocoon_target == src) + to_chat(src, "You can't wrap yourself!") + return + if(istype(cocoon_target, /mob/living/simple_animal/hostile/poison/giant_spider)) + to_chat(src, "You can't wrap other spiders!") + return + if(!Adjacent(cocoon_target)) + to_chat(src, "You can't reach [cocoon_target]!") + return + if(busy == SPINNING_COCOON) + to_chat(src, "You're already spinning a cocoon!") + return //we're already doing this, don't cancel out or anything + busy = SPINNING_COCOON + visible_message("[src] begins to secrete a sticky substance around [cocoon_target].","You begin wrapping [cocoon_target] into a cocoon.") + stop_automated_movement = TRUE + walk(src,0) + if(do_after(src, 50, target = cocoon_target)) + if(busy == SPINNING_COCOON) + var/obj/structure/spider/cocoon/C = new(cocoon_target.loc) + if(isliving(cocoon_target)) + var/mob/living/L = cocoon_target + if(L.blood_volume && (L.stat != DEAD || !consumed_mobs[L.tag])) //if they're not dead, you can consume them anyway + consumed_mobs[L.tag] = TRUE + fed++ + lay_eggs.UpdateButtonIcon(TRUE) + visible_message("[src] sticks a proboscis into [L] and sucks a viscous substance out.","You suck the nutriment out of [L], feeding you enough to lay a cluster of eggs.") + L.death() //you just ate them, they're dead. + else + to_chat(src, "[L] cannot sate your hunger!") + cocoon_target.forceMove(C) + + if(cocoon_target.density || ismob(cocoon_target)) + C.icon_state = pick("cocoon_large1","cocoon_large2","cocoon_large3") + cocoon_target = null + busy = SPIDER_IDLE + stop_automated_movement = FALSE + +/datum/action/innate/spider + icon_icon = 'icons/mob/actions/actions_animal.dmi' + background_icon_state = "bg_alien" + +/datum/action/innate/spider/lay_web + name = "Spin Web" + desc = "Spin a web to slow down potential prey." + check_flags = AB_CHECK_CONSCIOUS + button_icon_state = "lay_web" + +/datum/action/innate/spider/lay_web/Activate() + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider)) + return + var/mob/living/simple_animal/hostile/poison/giant_spider/S = owner + + if(!isturf(S.loc)) + return + var/turf/T = get_turf(S) + + var/obj/structure/spider/stickyweb/W = locate() in T + if(W) + to_chat(S, "There's already a web here!") + return + + if(S.busy != SPINNING_WEB) + S.busy = SPINNING_WEB + S.visible_message("[S] begins to secrete a sticky substance.","You begin to lay a web.") + S.stop_automated_movement = TRUE + if(do_after(S, 40, target = T)) + if(S.busy == SPINNING_WEB && S.loc == T) + new /obj/structure/spider/stickyweb(T) + S.busy = SPIDER_IDLE + S.stop_automated_movement = FALSE + else + to_chat(S, "You're already spinning a web!") + +/obj/effect/proc_holder/wrap + name = "Wrap" + panel = "Spider" + active = FALSE + datum/action/spell_action/action = null + desc = "Wrap something or someone in a cocoon. If it's a living being, you'll also consume them, allowing you to lay eggs." + ranged_mousepointer = 'icons/effects/wrap_target.dmi' + action_icon = 'icons/mob/actions/actions_animal.dmi' + action_icon_state = "wrap_0" + action_background_icon_state = "bg_alien" + +/obj/effect/proc_holder/wrap/Initialize() + . = ..() + action = new(src) + +/obj/effect/proc_holder/wrap/update_icon() + action.button_icon_state = "wrap_[active]" + action.UpdateButtonIcon() + +/obj/effect/proc_holder/wrap/Click() + if(!istype(usr, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) + return TRUE + var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/user = usr + activate(user) + return TRUE + +/obj/effect/proc_holder/wrap/proc/activate(mob/living/user) + var/message + if(active) + message = "You no longer prepare to wrap something in a cocoon." + remove_ranged_ability(message) + else + message = "You prepare to wrap something in a cocoon. Left-click your target to start wrapping!" + add_ranged_ability(user, message, TRUE) + return 1 + +/obj/effect/proc_holder/wrap/InterceptClickOn(mob/living/caller, params, atom/target) + if(..()) + return + if(ranged_ability_user.incapacitated() || !istype(ranged_ability_user, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) + remove_ranged_ability() + return + + var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/user = ranged_ability_user + + if(user.Adjacent(target) && (ismob(target) || isobj(target))) + var/atom/movable/target_atom = target + if(target_atom.anchored) + return + user.cocoon_target = target_atom + INVOKE_ASYNC(user, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/.proc/cocoon) + remove_ranged_ability() + return TRUE + +/obj/effect/proc_holder/wrap/on_lose(mob/living/carbon/user) + remove_ranged_ability() + +/datum/action/innate/spider/lay_eggs + name = "Lay Eggs" + desc = "Lay a cluster of eggs, which will soon grow into more spiders. You must wrap a living being to do this." + check_flags = AB_CHECK_CONSCIOUS + button_icon_state = "lay_eggs" + +/datum/action/innate/spider/lay_eggs/IsAvailable() + if(..()) + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) + return 0 + var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner + if(S.fed) + return 1 + return 0 + +/datum/action/innate/spider/lay_eggs/Activate() + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) + return + var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner + + var/obj/structure/spider/eggcluster/E = locate() in get_turf(S) + if(E) + to_chat(S, "There is already a cluster of eggs here!") + else if(!S.fed) + to_chat(S, "You are too hungry to do this!") + else if(S.busy != LAYING_EGGS) + S.busy = LAYING_EGGS + S.visible_message("[S] begins to lay a cluster of eggs.","You begin to lay a cluster of eggs.") + S.stop_automated_movement = TRUE + if(do_after(S, 50, target = get_turf(S))) + if(S.busy == LAYING_EGGS) + E = locate() in get_turf(S) + if(!E || !isturf(S.loc)) + var/obj/structure/spider/eggcluster/C = new /obj/structure/spider/eggcluster(get_turf(S)) + if(S.ckey) + C.player_spiders = TRUE + C.directive = S.directive + C.poison_type = S.poison_type + C.poison_per_bite = S.poison_per_bite + C.faction = S.faction.Copy() + S.fed-- + UpdateButtonIcon(TRUE) + S.busy = SPIDER_IDLE + S.stop_automated_movement = FALSE + +/datum/action/innate/spider/set_directive + name = "Set Directive" + desc = "Set a directive for your children to follow." + check_flags = AB_CHECK_CONSCIOUS + button_icon_state = "directive" + +/datum/action/innate/spider/set_directive/Activate() + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) + return + var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner + S.directive = stripped_input(S, "Enter the new directive", "Create directive", "[S.directive]") + +/mob/living/simple_animal/hostile/poison/giant_spider/Login() + . = ..() + GLOB.spidermobs[src] = TRUE + +/mob/living/simple_animal/hostile/poison/giant_spider/Destroy() + GLOB.spidermobs -= src + return ..() + +/datum/action/innate/spider/comm + name = "Command" + desc = "Send a command to all living spiders." + button_icon_state = "command" + +/datum/action/innate/spider/comm/IsAvailable() + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife)) + return FALSE + return TRUE + +/datum/action/innate/spider/comm/Trigger() + var/input = stripped_input(owner, "Input a command for your legions to follow.", "Command", "") + if(QDELETED(src) || !input || !IsAvailable()) + return FALSE + spider_command(owner, input) + return TRUE + +/datum/action/innate/spider/comm/proc/spider_command(mob/living/user, message) + if(!message) + return + var/my_message + my_message = "Command from [user]: [message]" + for(var/mob/living/simple_animal/hostile/poison/giant_spider/M in GLOB.spidermobs) + to_chat(M, my_message) + for(var/M in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(M, user) + to_chat(M, "[link] [my_message]") + usr.log_talk(message, LOG_SAY, tag="spider command") + +/mob/living/simple_animal/hostile/poison/giant_spider/handle_temperature_damage() + if(bodytemperature < minbodytemp) + adjustBruteLoss(20) + else if(bodytemperature > maxbodytemp) + adjustBruteLoss(20) + +#undef SPIDER_IDLE +#undef SPINNING_WEB +#undef LAYING_EGGS +#undef MOVING_TO_TARGET +#undef SPINNING_COCOON diff --git a/code/modules/mob/living/simple_animal/hostile/hivebot.dm b/code/modules/mob/living/simple_animal/hostile/hivebot.dm index a9576a467b..ee9d1dddbd 100644 --- a/code/modules/mob/living/simple_animal/hostile/hivebot.dm +++ b/code/modules/mob/living/simple_animal/hostile/hivebot.dm @@ -1,80 +1,80 @@ -/obj/item/projectile/hivebotbullet - damage = 15 - damage_type = BRUTE - -/mob/living/simple_animal/hostile/hivebot - name = "hivebot" - desc = "A strange robot that does not seem pleased to meet you." - icon = 'icons/mob/hivebot.dmi' - icon_state = "basic" - icon_living = "basic" - icon_dead = "basic" - gender = NEUTER - mob_biotypes = list(MOB_ROBOTIC) - health = 50 - maxHealth = 50 - healable = 0 - melee_damage_lower = 10 - melee_damage_upper = 10 - attacktext = "saw" - attack_sound = 'sound/weapons/bladeslice.ogg' - projectilesound = 'sound/weapons/gunshot.ogg' - projectiletype = /obj/item/projectile/hivebotbullet - faction = list("hivebot") - check_friendly_fire = 1 - 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 - speak_emote = list("states") - gold_core_spawnable = HOSTILE_SPAWN - del_on_death = 1 - loot = list(/obj/effect/decal/cleanable/robot_debris) - blood_volume = 0 - - do_footstep = TRUE - -/mob/living/simple_animal/hostile/hivebot/Initialize() - . = ..() - deathmessage = "[src] blows apart!" - -/mob/living/simple_animal/hostile/hivebot/range - name = "combat hivebot" - desc = "An armed robot that does not seem pleased to meet you." - icon_state = "ranged" - icon_living = "ranged" - icon_dead = "ranged" - ranged = 1 - retreat_distance = 5 - minimum_distance = 5 - -/mob/living/simple_animal/hostile/hivebot/rapid - name = "gunner hivebot" - icon_state = "ranged" - icon_living = "ranged" - icon_dead = "ranged" - ranged = 1 - rapid = 3 - retreat_distance = 5 - minimum_distance = 5 - -/mob/living/simple_animal/hostile/hivebot/engineering - name = "engineering hivebot" - icon_state = "EngBot" - icon_living = "EngBot" - icon_dead = "EngBot" - desc = "A strange engineering robot that does not seem pleased to meet you." - health = 75 - maxHealth = 75 - -/mob/living/simple_animal/hostile/hivebot/strong - name = "elite hivebot" - icon_state = "strong" - icon_living = "strong" - icon_dead = "strong" - desc = "A heavily armed and armored robot that does not seem pleased to meet you." - health = 100 - maxHealth = 100 - ranged = 1 - -/mob/living/simple_animal/hostile/hivebot/death(gibbed) - do_sparks(3, TRUE, src) - ..(1) +/obj/item/projectile/hivebotbullet + damage = 15 + damage_type = BRUTE + +/mob/living/simple_animal/hostile/hivebot + name = "hivebot" + desc = "A strange robot that does not seem pleased to meet you." + icon = 'icons/mob/hivebot.dmi' + icon_state = "basic" + icon_living = "basic" + icon_dead = "basic" + gender = NEUTER + mob_biotypes = list(MOB_ROBOTIC) + health = 50 + maxHealth = 50 + healable = 0 + melee_damage_lower = 10 + melee_damage_upper = 10 + attacktext = "saw" + attack_sound = 'sound/weapons/bladeslice.ogg' + projectilesound = 'sound/weapons/gunshot.ogg' + projectiletype = /obj/item/projectile/hivebotbullet + faction = list("hivebot") + check_friendly_fire = 1 + 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 + speak_emote = list("states") + gold_core_spawnable = HOSTILE_SPAWN + del_on_death = 1 + loot = list(/obj/effect/decal/cleanable/robot_debris) + blood_volume = 0 + + do_footstep = TRUE + +/mob/living/simple_animal/hostile/hivebot/Initialize() + . = ..() + deathmessage = "[src] blows apart!" + +/mob/living/simple_animal/hostile/hivebot/range + name = "combat hivebot" + desc = "An armed robot that does not seem pleased to meet you." + icon_state = "ranged" + icon_living = "ranged" + icon_dead = "ranged" + ranged = 1 + retreat_distance = 5 + minimum_distance = 5 + +/mob/living/simple_animal/hostile/hivebot/rapid + name = "gunner hivebot" + icon_state = "ranged" + icon_living = "ranged" + icon_dead = "ranged" + ranged = 1 + rapid = 3 + retreat_distance = 5 + minimum_distance = 5 + +/mob/living/simple_animal/hostile/hivebot/engineering + name = "engineering hivebot" + icon_state = "EngBot" + icon_living = "EngBot" + icon_dead = "EngBot" + desc = "A strange engineering robot that does not seem pleased to meet you." + health = 75 + maxHealth = 75 + +/mob/living/simple_animal/hostile/hivebot/strong + name = "elite hivebot" + icon_state = "strong" + icon_living = "strong" + icon_dead = "strong" + desc = "A heavily armed and armored robot that does not seem pleased to meet you." + health = 100 + maxHealth = 100 + ranged = 1 + +/mob/living/simple_animal/hostile/hivebot/death(gibbed) + do_sparks(3, TRUE, src) + ..(1) diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm index ca5323a978..bbdd7ab4b0 100644 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm @@ -1,274 +1,274 @@ -/mob/living/simple_animal/hostile/mimic - name = "crate" - desc = "A rectangular steel crate." - icon = 'icons/obj/crates.dmi' - icon_state = "crate" - icon_living = "crate" - - response_help = "touches" - response_disarm = "pushes" - response_harm = "hits" - speed = 0 - maxHealth = 250 - health = 250 - gender = NEUTER - mob_biotypes = list(MOB_INORGANIC) - - harm_intent_damage = 5 - melee_damage_lower = 8 - melee_damage_upper = 12 - attacktext = "attacks" - attack_sound = 'sound/weapons/punch1.ogg' - emote_taunt = list("growls") - speak_emote = list("creaks") - taunt_chance = 30 - - 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 - - faction = list("mimic") - move_to_delay = 9 - gold_core_spawnable = HOSTILE_SPAWN - del_on_death = 1 - -// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies. -/mob/living/simple_animal/hostile/mimic/crate - attacktext = "bites" - speak_emote = list("clatters") - stop_automated_movement = 1 - wander = 0 - var/attempt_open = FALSE - -// Pickup loot -/mob/living/simple_animal/hostile/mimic/crate/Initialize(mapload) - . = ..() - if(mapload) //eat shit - for(var/obj/item/I in loc) - I.forceMove(src) - -/mob/living/simple_animal/hostile/mimic/crate/DestroyPathToTarget() - ..() - if(prob(90)) - icon_state = "[initial(icon_state)]open" - else - icon_state = initial(icon_state) - -/mob/living/simple_animal/hostile/mimic/crate/ListTargets() - if(attempt_open) - return ..() - return ..(1) - -/mob/living/simple_animal/hostile/mimic/crate/FindTarget() - . = ..() - if(.) - trigger() - -/mob/living/simple_animal/hostile/mimic/crate/AttackingTarget() - . = ..() - if(.) - icon_state = initial(icon_state) - if(prob(15) && iscarbon(target)) - var/mob/living/carbon/C = target - C.Knockdown(40) - C.visible_message("\The [src] knocks down \the [C]!", \ - "\The [src] knocks you down!") - -/mob/living/simple_animal/hostile/mimic/crate/proc/trigger() - if(!attempt_open) - visible_message("[src] starts to move!") - attempt_open = TRUE - -/mob/living/simple_animal/hostile/mimic/crate/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - trigger() - . = ..() - -/mob/living/simple_animal/hostile/mimic/crate/LoseTarget() - ..() - icon_state = initial(icon_state) - -/mob/living/simple_animal/hostile/mimic/crate/death() - var/obj/structure/closet/crate/C = new(get_turf(src)) - // Put loot in crate - for(var/obj/O in src) - O.forceMove(C) - ..() - -GLOBAL_LIST_INIT(protected_objects, list(/obj/structure/table, /obj/structure/cable, /obj/structure/window)) - -/mob/living/simple_animal/hostile/mimic/copy - health = 100 - maxHealth = 100 - var/mob/living/creator = null // the creator - var/destroy_objects = 0 - var/knockdown_people = 0 - var/static/mutable_appearance/googly_eyes = mutable_appearance('icons/mob/mob.dmi', "googly_eyes") - var/overlay_googly_eyes = TRUE - var/idledamage = TRUE - gold_core_spawnable = NO_SPAWN - -/mob/living/simple_animal/hostile/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = 0, no_googlies = FALSE) - . = ..() - if (no_googlies) - overlay_googly_eyes = FALSE - CopyObject(copy, creator, destroy_original) - -/mob/living/simple_animal/hostile/mimic/copy/Life() - ..() - if(idledamage && !target && !ckey) //Objects eventually revert to normal if no one is around to terrorize - adjustBruteLoss(1) - for(var/mob/living/M in contents) //a fix for animated statues from the flesh to stone spell - death() - -/mob/living/simple_animal/hostile/mimic/copy/death() - for(var/atom/movable/M in src) - M.forceMove(get_turf(src)) - ..() - -/mob/living/simple_animal/hostile/mimic/copy/ListTargets() - . = ..() - return . - creator - -/mob/living/simple_animal/hostile/mimic/copy/proc/ChangeOwner(mob/owner) - if(owner != creator) - LoseTarget() - creator = owner - faction |= "[REF(owner)]" - -/mob/living/simple_animal/hostile/mimic/copy/proc/CheckObject(obj/O) - if((isitem(O) || isstructure(O)) && !is_type_in_list(O, GLOB.protected_objects)) - return 1 - return 0 - -/mob/living/simple_animal/hostile/mimic/copy/proc/CopyObject(obj/O, mob/living/user, destroy_original = 0) - if(destroy_original || CheckObject(O)) - O.forceMove(src) - name = O.name - desc = O.desc - icon = O.icon - icon_state = O.icon_state - icon_living = icon_state - copy_overlays(O) - if (overlay_googly_eyes) - add_overlay(googly_eyes) - if(isstructure(O) || ismachinery(O)) - health = (anchored * 50) + 50 - destroy_objects = 1 - if(O.density && O.anchored) - knockdown_people = 1 - melee_damage_lower *= 2 - melee_damage_upper *= 2 - else if(isitem(O)) - var/obj/item/I = O - health = 15 * I.w_class - melee_damage_lower = 2 + I.force - melee_damage_upper = 2 + I.force - move_to_delay = 2 * I.w_class + 1 - maxHealth = health - if(user) - creator = user - faction += "[REF(creator)]" // very unique - if(destroy_original) - qdel(O) - return 1 - -/mob/living/simple_animal/hostile/mimic/copy/DestroySurroundings() - if(destroy_objects) - ..() - -/mob/living/simple_animal/hostile/mimic/copy/AttackingTarget() - . = ..() - if(knockdown_people && . && prob(15) && iscarbon(target)) - var/mob/living/carbon/C = target - C.Knockdown(40) - C.visible_message("\The [src] knocks down \the [C]!", \ - "\The [src] knocks you down!") - -/mob/living/simple_animal/hostile/mimic/copy/machine - speak = list("HUMANS ARE IMPERFECT!", "YOU SHALL BE ASSIMILATED!", "YOU ARE HARMING YOURSELF", "You have been deemed hazardous. Will you comply?", \ - "My logic is undeniable.", "One of us.", "FLESH IS WEAK", "THIS ISN'T WAR, THIS IS EXTERMINATION!") - speak_chance = 7 - -/mob/living/simple_animal/hostile/mimic/copy/machine/CanAttack(atom/the_target) - if(the_target == creator) // Don't attack our creator AI. - return 0 - if(iscyborg(the_target)) - var/mob/living/silicon/robot/R = the_target - if(R.connected_ai == creator) // Only attack robots that aren't synced to our creator AI. - return 0 - return ..() - - - -/mob/living/simple_animal/hostile/mimic/copy/ranged - var/obj/item/gun/TrueGun = null - var/obj/item/gun/magic/Zapstick - var/obj/item/gun/ballistic/Pewgun - var/obj/item/gun/energy/Zapgun - -/mob/living/simple_animal/hostile/mimic/copy/ranged/CopyObject(obj/O, mob/living/creator, destroy_original = 0) - if(..()) - emote_see = list("aims menacingly") - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE //needed? seems weird for them to do so - ranged = 1 - retreat_distance = 1 //just enough to shoot - minimum_distance = 6 - var/obj/item/gun/G = O - melee_damage_upper = G.force - melee_damage_lower = G.force - max(0, (G.force / 2)) - move_to_delay = 2 * G.w_class + 1 - projectilesound = G.fire_sound - TrueGun = G - if(istype(G, /obj/item/gun/magic)) - Zapstick = G - var/obj/item/ammo_casing/magic/M = Zapstick.ammo_type - projectiletype = initial(M.projectile_type) - if(istype(G, /obj/item/gun/ballistic)) - Pewgun = G - var/obj/item/ammo_box/magazine/M = Pewgun.mag_type - casingtype = initial(M.ammo_type) - if(istype(G, /obj/item/gun/energy)) - Zapgun = G - var/selectfiresetting = Zapgun.select - var/obj/item/ammo_casing/energy/E = Zapgun.ammo_type[selectfiresetting] - projectiletype = initial(E.projectile_type) - -/mob/living/simple_animal/hostile/mimic/copy/ranged/OpenFire(the_target) - if(Zapgun) - if(Zapgun.cell) - var/obj/item/ammo_casing/energy/shot = Zapgun.ammo_type[Zapgun.select] - if(Zapgun.cell.charge >= shot.e_cost) - Zapgun.cell.use(shot.e_cost) - Zapgun.update_icon() - ..() - else if(Zapstick) - if(Zapstick.charges) - Zapstick.charges-- - Zapstick.update_icon() - ..() - else if(Pewgun) - if(Pewgun.chambered) - if(Pewgun.chambered.BB) - qdel(Pewgun.chambered.BB) - Pewgun.chambered.BB = null //because qdel takes too long, ensures icon update - Pewgun.chambered.update_icon() - ..() - else - visible_message("The [src] clears a jam!") - Pewgun.chambered.forceMove(loc) //rip revolver immersions, blame shotgun snowflake procs - Pewgun.chambered = null - if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) - Pewgun.chambered = Pewgun.magazine.get_round(0) - Pewgun.chambered.forceMove(Pewgun) - Pewgun.update_icon() - else if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) //only true for pumpguns i think - Pewgun.chambered = Pewgun.magazine.get_round(0) - Pewgun.chambered.forceMove(Pewgun) - visible_message("The [src] cocks itself!") - else - ranged = 0 //BANZAIIII - retreat_distance = 0 - minimum_distance = 1 - return - icon_state = TrueGun.icon_state - icon_living = TrueGun.icon_state +/mob/living/simple_animal/hostile/mimic + name = "crate" + desc = "A rectangular steel crate." + icon = 'icons/obj/crates.dmi' + icon_state = "crate" + icon_living = "crate" + + response_help = "touches" + response_disarm = "pushes" + response_harm = "hits" + speed = 0 + maxHealth = 250 + health = 250 + gender = NEUTER + mob_biotypes = list(MOB_INORGANIC) + + harm_intent_damage = 5 + melee_damage_lower = 8 + melee_damage_upper = 12 + attacktext = "attacks" + attack_sound = 'sound/weapons/punch1.ogg' + emote_taunt = list("growls") + speak_emote = list("creaks") + taunt_chance = 30 + + 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 + + faction = list("mimic") + move_to_delay = 9 + gold_core_spawnable = HOSTILE_SPAWN + del_on_death = 1 + +// Aggro when you try to open them. Will also pickup loot when spawns and drop it when dies. +/mob/living/simple_animal/hostile/mimic/crate + attacktext = "bites" + speak_emote = list("clatters") + stop_automated_movement = 1 + wander = 0 + var/attempt_open = FALSE + +// Pickup loot +/mob/living/simple_animal/hostile/mimic/crate/Initialize(mapload) + . = ..() + if(mapload) //eat shit + for(var/obj/item/I in loc) + I.forceMove(src) + +/mob/living/simple_animal/hostile/mimic/crate/DestroyPathToTarget() + ..() + if(prob(90)) + icon_state = "[initial(icon_state)]open" + else + icon_state = initial(icon_state) + +/mob/living/simple_animal/hostile/mimic/crate/ListTargets() + if(attempt_open) + return ..() + return ..(1) + +/mob/living/simple_animal/hostile/mimic/crate/FindTarget() + . = ..() + if(.) + trigger() + +/mob/living/simple_animal/hostile/mimic/crate/AttackingTarget() + . = ..() + if(.) + icon_state = initial(icon_state) + if(prob(15) && iscarbon(target)) + var/mob/living/carbon/C = target + C.Knockdown(40) + C.visible_message("\The [src] knocks down \the [C]!", \ + "\The [src] knocks you down!") + +/mob/living/simple_animal/hostile/mimic/crate/proc/trigger() + if(!attempt_open) + visible_message("[src] starts to move!") + attempt_open = TRUE + +/mob/living/simple_animal/hostile/mimic/crate/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + trigger() + . = ..() + +/mob/living/simple_animal/hostile/mimic/crate/LoseTarget() + ..() + icon_state = initial(icon_state) + +/mob/living/simple_animal/hostile/mimic/crate/death() + var/obj/structure/closet/crate/C = new(get_turf(src)) + // Put loot in crate + for(var/obj/O in src) + O.forceMove(C) + ..() + +GLOBAL_LIST_INIT(protected_objects, list(/obj/structure/table, /obj/structure/cable, /obj/structure/window)) + +/mob/living/simple_animal/hostile/mimic/copy + health = 100 + maxHealth = 100 + var/mob/living/creator = null // the creator + var/destroy_objects = 0 + var/knockdown_people = 0 + var/static/mutable_appearance/googly_eyes = mutable_appearance('icons/mob/mob.dmi', "googly_eyes") + var/overlay_googly_eyes = TRUE + var/idledamage = TRUE + gold_core_spawnable = NO_SPAWN + +/mob/living/simple_animal/hostile/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = 0, no_googlies = FALSE) + . = ..() + if (no_googlies) + overlay_googly_eyes = FALSE + CopyObject(copy, creator, destroy_original) + +/mob/living/simple_animal/hostile/mimic/copy/Life() + ..() + if(idledamage && !target && !ckey) //Objects eventually revert to normal if no one is around to terrorize + adjustBruteLoss(1) + for(var/mob/living/M in contents) //a fix for animated statues from the flesh to stone spell + death() + +/mob/living/simple_animal/hostile/mimic/copy/death() + for(var/atom/movable/M in src) + M.forceMove(get_turf(src)) + ..() + +/mob/living/simple_animal/hostile/mimic/copy/ListTargets() + . = ..() + return . - creator + +/mob/living/simple_animal/hostile/mimic/copy/proc/ChangeOwner(mob/owner) + if(owner != creator) + LoseTarget() + creator = owner + faction |= "[REF(owner)]" + +/mob/living/simple_animal/hostile/mimic/copy/proc/CheckObject(obj/O) + if((isitem(O) || isstructure(O)) && !is_type_in_list(O, GLOB.protected_objects)) + return 1 + return 0 + +/mob/living/simple_animal/hostile/mimic/copy/proc/CopyObject(obj/O, mob/living/user, destroy_original = 0) + if(destroy_original || CheckObject(O)) + O.forceMove(src) + name = O.name + desc = O.desc + icon = O.icon + icon_state = O.icon_state + icon_living = icon_state + copy_overlays(O) + if (overlay_googly_eyes) + add_overlay(googly_eyes) + if(isstructure(O) || ismachinery(O)) + health = (anchored * 50) + 50 + destroy_objects = 1 + if(O.density && O.anchored) + knockdown_people = 1 + melee_damage_lower *= 2 + melee_damage_upper *= 2 + else if(isitem(O)) + var/obj/item/I = O + health = 15 * I.w_class + melee_damage_lower = 2 + I.force + melee_damage_upper = 2 + I.force + move_to_delay = 2 * I.w_class + 1 + maxHealth = health + if(user) + creator = user + faction += "[REF(creator)]" // very unique + if(destroy_original) + qdel(O) + return 1 + +/mob/living/simple_animal/hostile/mimic/copy/DestroySurroundings() + if(destroy_objects) + ..() + +/mob/living/simple_animal/hostile/mimic/copy/AttackingTarget() + . = ..() + if(knockdown_people && . && prob(15) && iscarbon(target)) + var/mob/living/carbon/C = target + C.Knockdown(40) + C.visible_message("\The [src] knocks down \the [C]!", \ + "\The [src] knocks you down!") + +/mob/living/simple_animal/hostile/mimic/copy/machine + speak = list("HUMANS ARE IMPERFECT!", "YOU SHALL BE ASSIMILATED!", "YOU ARE HARMING YOURSELF", "You have been deemed hazardous. Will you comply?", \ + "My logic is undeniable.", "One of us.", "FLESH IS WEAK", "THIS ISN'T WAR, THIS IS EXTERMINATION!") + speak_chance = 7 + +/mob/living/simple_animal/hostile/mimic/copy/machine/CanAttack(atom/the_target) + if(the_target == creator) // Don't attack our creator AI. + return 0 + if(iscyborg(the_target)) + var/mob/living/silicon/robot/R = the_target + if(R.connected_ai == creator) // Only attack robots that aren't synced to our creator AI. + return 0 + return ..() + + + +/mob/living/simple_animal/hostile/mimic/copy/ranged + var/obj/item/gun/TrueGun = null + var/obj/item/gun/magic/Zapstick + var/obj/item/gun/ballistic/Pewgun + var/obj/item/gun/energy/Zapgun + +/mob/living/simple_animal/hostile/mimic/copy/ranged/CopyObject(obj/O, mob/living/creator, destroy_original = 0) + if(..()) + emote_see = list("aims menacingly") + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE //needed? seems weird for them to do so + ranged = 1 + retreat_distance = 1 //just enough to shoot + minimum_distance = 6 + var/obj/item/gun/G = O + melee_damage_upper = G.force + melee_damage_lower = G.force - max(0, (G.force / 2)) + move_to_delay = 2 * G.w_class + 1 + projectilesound = G.fire_sound + TrueGun = G + if(istype(G, /obj/item/gun/magic)) + Zapstick = G + var/obj/item/ammo_casing/magic/M = Zapstick.ammo_type + projectiletype = initial(M.projectile_type) + if(istype(G, /obj/item/gun/ballistic)) + Pewgun = G + var/obj/item/ammo_box/magazine/M = Pewgun.mag_type + casingtype = initial(M.ammo_type) + if(istype(G, /obj/item/gun/energy)) + Zapgun = G + var/selectfiresetting = Zapgun.select + var/obj/item/ammo_casing/energy/E = Zapgun.ammo_type[selectfiresetting] + projectiletype = initial(E.projectile_type) + +/mob/living/simple_animal/hostile/mimic/copy/ranged/OpenFire(the_target) + if(Zapgun) + if(Zapgun.cell) + var/obj/item/ammo_casing/energy/shot = Zapgun.ammo_type[Zapgun.select] + if(Zapgun.cell.charge >= shot.e_cost) + Zapgun.cell.use(shot.e_cost) + Zapgun.update_icon() + ..() + else if(Zapstick) + if(Zapstick.charges) + Zapstick.charges-- + Zapstick.update_icon() + ..() + else if(Pewgun) + if(Pewgun.chambered) + if(Pewgun.chambered.BB) + qdel(Pewgun.chambered.BB) + Pewgun.chambered.BB = null //because qdel takes too long, ensures icon update + Pewgun.chambered.update_icon() + ..() + else + visible_message("The [src] clears a jam!") + Pewgun.chambered.forceMove(loc) //rip revolver immersions, blame shotgun snowflake procs + Pewgun.chambered = null + if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) + Pewgun.chambered = Pewgun.magazine.get_round(0) + Pewgun.chambered.forceMove(Pewgun) + Pewgun.update_icon() + else if(Pewgun.magazine && Pewgun.magazine.stored_ammo.len) //only true for pumpguns i think + Pewgun.chambered = Pewgun.magazine.get_round(0) + Pewgun.chambered.forceMove(Pewgun) + visible_message("The [src] cocks itself!") + else + ranged = 0 //BANZAIIII + retreat_distance = 0 + minimum_distance = 1 + return + icon_state = TrueGun.icon_state + icon_living = TrueGun.icon_state diff --git a/code/modules/mob/living/simple_animal/hostile/mushroom.dm b/code/modules/mob/living/simple_animal/hostile/mushroom.dm index 8301489e8c..8727652103 100644 --- a/code/modules/mob/living/simple_animal/hostile/mushroom.dm +++ b/code/modules/mob/living/simple_animal/hostile/mushroom.dm @@ -1,192 +1,192 @@ -/mob/living/simple_animal/hostile/mushroom - name = "walking mushroom" - desc = "It's a massive mushroom... with legs?" - icon_state = "mushroom_color" - icon_living = "mushroom_color" - icon_dead = "mushroom_dead" - speak_chance = 0 - turns_per_move = 1 - maxHealth = 10 - health = 10 - butcher_results = list(/obj/item/reagent_containers/food/snacks/hugemushroomslice = 1) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "whacks" - harm_intent_damage = 5 - obj_damage = 0 - melee_damage_lower = 1 - melee_damage_upper = 1 - attack_same = 2 - attacktext = "chomps" - attack_sound = 'sound/weapons/bite.ogg' - faction = list("mushroom") - environment_smash = ENVIRONMENT_SMASH_NONE - stat_attack = DEAD - mouse_opacity = MOUSE_OPACITY_ICON - speed = 1 - ventcrawler = VENTCRAWLER_ALWAYS - robust_searching = 1 - unique_name = 1 - speak_emote = list("squeaks") - deathmessage = "fainted." - var/cap_color = "#ffffff" - var/powerlevel = 0 //Tracks our general strength level gained from eating other shrooms - var/bruised = 0 //If someone tries to cheat the system by attacking a shroom to lower its health, punish them so that it wont award levels to shrooms that eat it - var/recovery_cooldown = 0 //So you can't repeatedly revive it during a fight - var/faint_ticker = 0 //If we hit three, another mushroom's gonna eat us - var/static/mutable_appearance/cap_living //Where we store our cap icons so we dont generate them constantly to update our icon - var/static/mutable_appearance/cap_dead - -/mob/living/simple_animal/hostile/mushroom/examine(mob/user) - . = ..() - if(health >= maxHealth) - . += "It looks healthy." - else - . += "It looks like it's been roughed up." - -/mob/living/simple_animal/hostile/mushroom/Life() - ..() - if(!stat)//Mushrooms slowly regenerate if conscious, for people who want to save them from being eaten - adjustBruteLoss(-2) - -/mob/living/simple_animal/hostile/mushroom/Initialize()//Makes every shroom a little unique - melee_damage_lower += rand(3, 5) - melee_damage_upper += rand(10,20) - maxHealth += rand(40,60) - move_to_delay = rand(3,11) - cap_living = cap_living || mutable_appearance(icon, "mushroom_cap") - cap_dead = cap_dead || mutable_appearance(icon, "mushroom_cap_dead") - - cap_color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) - UpdateMushroomCap() - health = maxHealth - . = ..() - -/mob/living/simple_animal/hostile/mushroom/CanAttack(atom/the_target) // Mushroom-specific version of CanAttack to handle stupid attack_same = 2 crap so we don't have to do it for literally every single simple_animal/hostile because this shit never gets spawned - if(!the_target || isturf(the_target) || istype(the_target, /atom/movable/lighting_object)) - return FALSE - - if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it - return FALSE - - if(isliving(the_target)) - var/mob/living/L = the_target - - if (!faction_check_mob(L) && attack_same == 2) - return FALSE - if(L.stat > stat_attack) - return FALSE - - return TRUE - - return FALSE - -/mob/living/simple_animal/hostile/mushroom/adjustHealth(amount, updating_health = TRUE, forced = FALSE) //Possibility to flee from a fight just to make it more visually interesting - if(!retreat_distance && prob(33)) - retreat_distance = 5 - addtimer(CALLBACK(src, .proc/stop_retreat), 30) - . = ..() - -/mob/living/simple_animal/hostile/mushroom/proc/stop_retreat() - retreat_distance = null - -/mob/living/simple_animal/hostile/mushroom/attack_animal(mob/living/L) - if(istype(L, /mob/living/simple_animal/hostile/mushroom) && stat == DEAD) - var/mob/living/simple_animal/hostile/mushroom/M = L - if(faint_ticker < 2) - M.visible_message("[M] chews a bit on [src].") - faint_ticker++ - return TRUE - M.visible_message("[M] devours [src]!") - var/level_gain = (powerlevel - M.powerlevel) - if(level_gain >= -1 && !bruised && !M.ckey)//Player shrooms can't level up to become robust gods. - if(level_gain < 1)//So we still gain a level if two mushrooms were the same level - level_gain = 1 - M.LevelUp(level_gain) - M.adjustBruteLoss(-M.maxHealth) - qdel(src) - return TRUE - return ..() - -/mob/living/simple_animal/hostile/mushroom/revive(full_heal = 0, admin_revive = 0) - if(..()) - icon_state = "mushroom_color" - UpdateMushroomCap() - . = 1 - -/mob/living/simple_animal/hostile/mushroom/death(gibbed) - ..(gibbed) - UpdateMushroomCap() - -/mob/living/simple_animal/hostile/mushroom/proc/UpdateMushroomCap() - cut_overlays() - cap_living.color = cap_color - cap_dead.color = cap_color - if(health == 0) - add_overlay(cap_dead) - else - add_overlay(cap_living) - -/mob/living/simple_animal/hostile/mushroom/proc/Recover() - visible_message("[src] slowly begins to recover.") - faint_ticker = 0 - revive(full_heal = 1) - UpdateMushroomCap() - recovery_cooldown = 1 - addtimer(CALLBACK(src, .proc/recovery_recharge), 300) - -/mob/living/simple_animal/hostile/mushroom/proc/recovery_recharge() - recovery_cooldown = 0 - -/mob/living/simple_animal/hostile/mushroom/proc/LevelUp(level_gain) - if(powerlevel <= 9) - powerlevel += level_gain - if(prob(25)) - melee_damage_lower += (level_gain * rand(1,5)) - else - melee_damage_upper += (level_gain * rand(1,5)) - maxHealth += (level_gain * rand(1,5)) - adjustBruteLoss(-maxHealth) //They'll always heal, even if they don't gain a level, in case you want to keep this shroom around instead of harvesting it - -/mob/living/simple_animal/hostile/mushroom/proc/Bruise() - if(!bruised && !stat) - src.visible_message("The [src.name] was bruised!") - bruised = 1 - -/mob/living/simple_animal/hostile/mushroom/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/reagent_containers/food/snacks/grown/mushroom)) - if(stat == DEAD && !recovery_cooldown) - Recover() - qdel(I) - else - to_chat(user, "[src] won't eat it!") - return - if(I.force) - Bruise() - ..() - -/mob/living/simple_animal/hostile/mushroom/attack_hand(mob/living/carbon/human/M) - . = ..() - if(.) // the attack was blocked - return - if(M.a_intent == INTENT_HARM) - Bruise() - -/mob/living/simple_animal/hostile/mushroom/hitby(atom/movable/AM) - ..() - if(istype(AM, /obj/item)) - var/obj/item/T = AM - if(T.throwforce) - Bruise() - -/mob/living/simple_animal/hostile/mushroom/bullet_act() - ..() - Bruise() - -/mob/living/simple_animal/hostile/mushroom/harvest() - var/counter - for(counter=0, counter<=powerlevel, counter++) - var/obj/item/reagent_containers/food/snacks/hugemushroomslice/S = new /obj/item/reagent_containers/food/snacks/hugemushroomslice(src.loc) - S.reagents.add_reagent(/datum/reagent/drug/mushroomhallucinogen, powerlevel) - S.reagents.add_reagent(/datum/reagent/medicine/omnizine, powerlevel) - S.reagents.add_reagent(/datum/reagent/medicine/synaptizine, powerlevel) +/mob/living/simple_animal/hostile/mushroom + name = "walking mushroom" + desc = "It's a massive mushroom... with legs?" + icon_state = "mushroom_color" + icon_living = "mushroom_color" + icon_dead = "mushroom_dead" + speak_chance = 0 + turns_per_move = 1 + maxHealth = 10 + health = 10 + butcher_results = list(/obj/item/reagent_containers/food/snacks/hugemushroomslice = 1) + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "whacks" + harm_intent_damage = 5 + obj_damage = 0 + melee_damage_lower = 1 + melee_damage_upper = 1 + attack_same = 2 + attacktext = "chomps" + attack_sound = 'sound/weapons/bite.ogg' + faction = list("mushroom") + environment_smash = ENVIRONMENT_SMASH_NONE + stat_attack = DEAD + mouse_opacity = MOUSE_OPACITY_ICON + speed = 1 + ventcrawler = VENTCRAWLER_ALWAYS + robust_searching = 1 + unique_name = 1 + speak_emote = list("squeaks") + deathmessage = "fainted." + var/cap_color = "#ffffff" + var/powerlevel = 0 //Tracks our general strength level gained from eating other shrooms + var/bruised = 0 //If someone tries to cheat the system by attacking a shroom to lower its health, punish them so that it wont award levels to shrooms that eat it + var/recovery_cooldown = 0 //So you can't repeatedly revive it during a fight + var/faint_ticker = 0 //If we hit three, another mushroom's gonna eat us + var/static/mutable_appearance/cap_living //Where we store our cap icons so we dont generate them constantly to update our icon + var/static/mutable_appearance/cap_dead + +/mob/living/simple_animal/hostile/mushroom/examine(mob/user) + . = ..() + if(health >= maxHealth) + . += "It looks healthy." + else + . += "It looks like it's been roughed up." + +/mob/living/simple_animal/hostile/mushroom/Life() + ..() + if(!stat)//Mushrooms slowly regenerate if conscious, for people who want to save them from being eaten + adjustBruteLoss(-2) + +/mob/living/simple_animal/hostile/mushroom/Initialize()//Makes every shroom a little unique + melee_damage_lower += rand(3, 5) + melee_damage_upper += rand(10,20) + maxHealth += rand(40,60) + move_to_delay = rand(3,11) + cap_living = cap_living || mutable_appearance(icon, "mushroom_cap") + cap_dead = cap_dead || mutable_appearance(icon, "mushroom_cap_dead") + + cap_color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) + UpdateMushroomCap() + health = maxHealth + . = ..() + +/mob/living/simple_animal/hostile/mushroom/CanAttack(atom/the_target) // Mushroom-specific version of CanAttack to handle stupid attack_same = 2 crap so we don't have to do it for literally every single simple_animal/hostile because this shit never gets spawned + if(!the_target || isturf(the_target) || istype(the_target, /atom/movable/lighting_object)) + return FALSE + + if(see_invisible < the_target.invisibility)//Target's invisible to us, forget it + return FALSE + + if(isliving(the_target)) + var/mob/living/L = the_target + + if (!faction_check_mob(L) && attack_same == 2) + return FALSE + if(L.stat > stat_attack) + return FALSE + + return TRUE + + return FALSE + +/mob/living/simple_animal/hostile/mushroom/adjustHealth(amount, updating_health = TRUE, forced = FALSE) //Possibility to flee from a fight just to make it more visually interesting + if(!retreat_distance && prob(33)) + retreat_distance = 5 + addtimer(CALLBACK(src, .proc/stop_retreat), 30) + . = ..() + +/mob/living/simple_animal/hostile/mushroom/proc/stop_retreat() + retreat_distance = null + +/mob/living/simple_animal/hostile/mushroom/attack_animal(mob/living/L) + if(istype(L, /mob/living/simple_animal/hostile/mushroom) && stat == DEAD) + var/mob/living/simple_animal/hostile/mushroom/M = L + if(faint_ticker < 2) + M.visible_message("[M] chews a bit on [src].") + faint_ticker++ + return TRUE + M.visible_message("[M] devours [src]!") + var/level_gain = (powerlevel - M.powerlevel) + if(level_gain >= -1 && !bruised && !M.ckey)//Player shrooms can't level up to become robust gods. + if(level_gain < 1)//So we still gain a level if two mushrooms were the same level + level_gain = 1 + M.LevelUp(level_gain) + M.adjustBruteLoss(-M.maxHealth) + qdel(src) + return TRUE + return ..() + +/mob/living/simple_animal/hostile/mushroom/revive(full_heal = 0, admin_revive = 0) + if(..()) + icon_state = "mushroom_color" + UpdateMushroomCap() + . = 1 + +/mob/living/simple_animal/hostile/mushroom/death(gibbed) + ..(gibbed) + UpdateMushroomCap() + +/mob/living/simple_animal/hostile/mushroom/proc/UpdateMushroomCap() + cut_overlays() + cap_living.color = cap_color + cap_dead.color = cap_color + if(health == 0) + add_overlay(cap_dead) + else + add_overlay(cap_living) + +/mob/living/simple_animal/hostile/mushroom/proc/Recover() + visible_message("[src] slowly begins to recover.") + faint_ticker = 0 + revive(full_heal = 1) + UpdateMushroomCap() + recovery_cooldown = 1 + addtimer(CALLBACK(src, .proc/recovery_recharge), 300) + +/mob/living/simple_animal/hostile/mushroom/proc/recovery_recharge() + recovery_cooldown = 0 + +/mob/living/simple_animal/hostile/mushroom/proc/LevelUp(level_gain) + if(powerlevel <= 9) + powerlevel += level_gain + if(prob(25)) + melee_damage_lower += (level_gain * rand(1,5)) + else + melee_damage_upper += (level_gain * rand(1,5)) + maxHealth += (level_gain * rand(1,5)) + adjustBruteLoss(-maxHealth) //They'll always heal, even if they don't gain a level, in case you want to keep this shroom around instead of harvesting it + +/mob/living/simple_animal/hostile/mushroom/proc/Bruise() + if(!bruised && !stat) + src.visible_message("The [src.name] was bruised!") + bruised = 1 + +/mob/living/simple_animal/hostile/mushroom/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/reagent_containers/food/snacks/grown/mushroom)) + if(stat == DEAD && !recovery_cooldown) + Recover() + qdel(I) + else + to_chat(user, "[src] won't eat it!") + return + if(I.force) + Bruise() + ..() + +/mob/living/simple_animal/hostile/mushroom/attack_hand(mob/living/carbon/human/M) + . = ..() + if(.) // the attack was blocked + return + if(M.a_intent == INTENT_HARM) + Bruise() + +/mob/living/simple_animal/hostile/mushroom/hitby(atom/movable/AM) + ..() + if(istype(AM, /obj/item)) + var/obj/item/T = AM + if(T.throwforce) + Bruise() + +/mob/living/simple_animal/hostile/mushroom/bullet_act() + ..() + Bruise() + +/mob/living/simple_animal/hostile/mushroom/harvest() + var/counter + for(counter=0, counter<=powerlevel, counter++) + var/obj/item/reagent_containers/food/snacks/hugemushroomslice/S = new /obj/item/reagent_containers/food/snacks/hugemushroomslice(src.loc) + S.reagents.add_reagent(/datum/reagent/drug/mushroomhallucinogen, powerlevel) + S.reagents.add_reagent(/datum/reagent/medicine/omnizine, powerlevel) + S.reagents.add_reagent(/datum/reagent/medicine/synaptizine, powerlevel) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm index 4856ba2176..c518cbe083 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/bat.dm @@ -1,51 +1,51 @@ -/mob/living/simple_animal/hostile/retaliate/bat - name = "Space Bat" - desc = "A rare breed of bat which roosts in spaceships, probably not vampiric." - icon_state = "bat" - icon_living = "bat" - icon_dead = "bat_dead" - icon_gib = "bat_dead" - turns_per_move = 1 - blood_volume = 250 - response_help = "brushes aside" - response_disarm = "flails at" - response_harm = "hits" - mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) - speak_chance = 0 - maxHealth = 15 - health = 15 - spacewalk = TRUE - see_in_dark = 10 - harm_intent_damage = 6 - melee_damage_lower = 6 - melee_damage_upper = 5 - attacktext = "bites" - butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1) - pass_flags = PASSTABLE - faction = list("hostile") - attack_sound = 'sound/weapons/bite.ogg' - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - ventcrawler = VENTCRAWLER_ALWAYS - mob_size = MOB_SIZE_TINY - movement_type = FLYING - speak_emote = list("squeaks") - var/max_co2 = 0 //to be removed once metastation map no longer use those for Sgt Araneus - var/min_oxy = 0 - var/max_tox = 0 - - - //Space bats need no air to fly in. - 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 - -/mob/living/simple_animal/hostile/retaliate/bat/secbat - name = "Security Bat" - icon_state = "secbat" - icon_living = "secbat" - icon_dead = "secbat_dead" - icon_gib = "secbat_dead" - desc = "A fruit bat with a tiny little security hat who is ready to inject cuteness into any security operation." - emote_see = list("is ready to law down the law.", "flaps about with an air of authority.") - response_help = "respects the authority of" - gold_core_spawnable = FRIENDLY_SPAWN +/mob/living/simple_animal/hostile/retaliate/bat + name = "Space Bat" + desc = "A rare breed of bat which roosts in spaceships, probably not vampiric." + icon_state = "bat" + icon_living = "bat" + icon_dead = "bat_dead" + icon_gib = "bat_dead" + turns_per_move = 1 + blood_volume = 250 + response_help = "brushes aside" + response_disarm = "flails at" + response_harm = "hits" + mob_biotypes = list(MOB_ORGANIC, MOB_BEAST) + speak_chance = 0 + maxHealth = 15 + health = 15 + spacewalk = TRUE + see_in_dark = 10 + harm_intent_damage = 6 + melee_damage_lower = 6 + melee_damage_upper = 5 + attacktext = "bites" + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1) + pass_flags = PASSTABLE + faction = list("hostile") + attack_sound = 'sound/weapons/bite.ogg' + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + ventcrawler = VENTCRAWLER_ALWAYS + mob_size = MOB_SIZE_TINY + movement_type = FLYING + speak_emote = list("squeaks") + var/max_co2 = 0 //to be removed once metastation map no longer use those for Sgt Araneus + var/min_oxy = 0 + var/max_tox = 0 + + + //Space bats need no air to fly in. + 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 + +/mob/living/simple_animal/hostile/retaliate/bat/secbat + name = "Security Bat" + icon_state = "secbat" + icon_living = "secbat" + icon_dead = "secbat_dead" + icon_gib = "secbat_dead" + desc = "A fruit bat with a tiny little security hat who is ready to inject cuteness into any security operation." + emote_see = list("is ready to law down the law.", "flaps about with an air of authority.") + response_help = "respects the authority of" + gold_core_spawnable = FRIENDLY_SPAWN diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm index cd978b7066..d2c8f1f270 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/clown.dm @@ -1,46 +1,46 @@ -/mob/living/simple_animal/hostile/retaliate/clown - name = "Clown" - desc = "A denizen of clown planet." - icon = 'icons/mob/simple_human.dmi' - icon_state = "clown" - icon_living = "clown" - icon_dead = "clown_dead" - icon_gib = "clown_gib" - mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) - turns_per_move = 5 - response_help = "pokes" - response_disarm = "gently pushes aside" - response_harm = "robusts" - speak = list("HONK", "Honk!", "Welcome to clown planet!") - emote_see = list("honks", "squeaks") - speak_chance = 1 - a_intent = INTENT_HARM - maxHealth = 75 - health = 75 - speed = 1 - harm_intent_damage = 8 - melee_damage_lower = 10 - melee_damage_upper = 10 - attacktext = "attacks" - attack_sound = 'sound/items/bikehorn.ogg' - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - del_on_death = 1 - loot = list(/obj/effect/mob_spawn/human/clown/corpse) - - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - minbodytemp = 270 - maxbodytemp = 370 - unsuitable_atmos_damage = 10 - - do_footstep = TRUE - -/mob/living/simple_animal/hostile/retaliate/clown/handle_temperature_damage() - if(bodytemperature < minbodytemp) - adjustBruteLoss(10) - else if(bodytemperature > maxbodytemp) - adjustBruteLoss(15) - -/mob/living/simple_animal/hostile/retaliate/clown/attack_hand(mob/living/carbon/human/M) - ..() - playsound(src.loc, 'sound/items/bikehorn.ogg', 50, 1) +/mob/living/simple_animal/hostile/retaliate/clown + name = "Clown" + desc = "A denizen of clown planet." + icon = 'icons/mob/simple_human.dmi' + icon_state = "clown" + icon_living = "clown" + icon_dead = "clown_dead" + icon_gib = "clown_gib" + mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) + turns_per_move = 5 + response_help = "pokes" + response_disarm = "gently pushes aside" + response_harm = "robusts" + speak = list("HONK", "Honk!", "Welcome to clown planet!") + emote_see = list("honks", "squeaks") + speak_chance = 1 + a_intent = INTENT_HARM + maxHealth = 75 + health = 75 + speed = 1 + harm_intent_damage = 8 + melee_damage_lower = 10 + melee_damage_upper = 10 + attacktext = "attacks" + attack_sound = 'sound/items/bikehorn.ogg' + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + del_on_death = 1 + loot = list(/obj/effect/mob_spawn/human/clown/corpse) + + atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) + minbodytemp = 270 + maxbodytemp = 370 + unsuitable_atmos_damage = 10 + + do_footstep = TRUE + +/mob/living/simple_animal/hostile/retaliate/clown/handle_temperature_damage() + if(bodytemperature < minbodytemp) + adjustBruteLoss(10) + else if(bodytemperature > maxbodytemp) + adjustBruteLoss(15) + +/mob/living/simple_animal/hostile/retaliate/clown/attack_hand(mob/living/carbon/human/M) + ..() + playsound(src.loc, 'sound/items/bikehorn.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm index 57df489d4e..63a796a809 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm @@ -1,47 +1,47 @@ -/mob/living/simple_animal/hostile/retaliate - var/list/enemies = list() - -/mob/living/simple_animal/hostile/retaliate/Found(atom/A) - if(isliving(A)) - var/mob/living/L = A - if(!L.stat) - return L - else - enemies -= L - else if(ismecha(A)) - var/obj/mecha/M = A - if(M.occupant) - return A - -/mob/living/simple_animal/hostile/retaliate/ListTargets() - if(!enemies.len) - return list() - var/list/see = ..() - see &= enemies // Remove all entries that aren't in enemies - return see - -/mob/living/simple_animal/hostile/retaliate/proc/Retaliate() - var/list/around = view(src, vision_range) - - for(var/atom/movable/A in around) - if(A == src) - continue - if(isliving(A)) - var/mob/living/M = A - if(faction_check_mob(M) && attack_same || !faction_check_mob(M)) - enemies |= M - else if(ismecha(A)) - var/obj/mecha/M = A - if(M.occupant) - enemies |= M - enemies |= M.occupant - - for(var/mob/living/simple_animal/hostile/retaliate/H in around) - if(faction_check_mob(H) && !attack_same && !H.attack_same) - H.enemies |= enemies - return 0 - -/mob/living/simple_animal/hostile/retaliate/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(. > 0 && stat == CONSCIOUS) - Retaliate() +/mob/living/simple_animal/hostile/retaliate + var/list/enemies = list() + +/mob/living/simple_animal/hostile/retaliate/Found(atom/A) + if(isliving(A)) + var/mob/living/L = A + if(!L.stat) + return L + else + enemies -= L + else if(ismecha(A)) + var/obj/mecha/M = A + if(M.occupant) + return A + +/mob/living/simple_animal/hostile/retaliate/ListTargets() + if(!enemies.len) + return list() + var/list/see = ..() + see &= enemies // Remove all entries that aren't in enemies + return see + +/mob/living/simple_animal/hostile/retaliate/proc/Retaliate() + var/list/around = view(src, vision_range) + + for(var/atom/movable/A in around) + if(A == src) + continue + if(isliving(A)) + var/mob/living/M = A + if(faction_check_mob(M) && attack_same || !faction_check_mob(M)) + enemies |= M + else if(ismecha(A)) + var/obj/mecha/M = A + if(M.occupant) + enemies |= M + enemies |= M.occupant + + for(var/mob/living/simple_animal/hostile/retaliate/H in around) + if(faction_check_mob(H) && !attack_same && !H.attack_same) + H.enemies |= enemies + return 0 + +/mob/living/simple_animal/hostile/retaliate/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + . = ..() + if(. > 0 && stat == CONSCIOUS) + Retaliate() diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm index dd08a009d4..0faee34f85 100644 --- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm +++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm @@ -1,313 +1,313 @@ -/* - CONTENTS - LINE 10 - BASE MOB - LINE 52 - SWORD AND SHIELD - LINE 164 - GUNS - LINE 267 - MISC -*/ - - -///////////////Base mob//////////// -/obj/effect/light_emitter/red_energy_sword //used so there's a combination of both their head light and light coming off the energy sword - set_luminosity = 2 - set_cap = 2.5 - light_color = LIGHT_COLOR_RED - - -/mob/living/simple_animal/hostile/syndicate - name = "Syndicate Operative" - desc = "Death to Nanotrasen." - icon = 'icons/mob/simple_human.dmi' - icon_state = "syndicate" - icon_living = "syndicate" - icon_dead = "syndicate_dead" - icon_gib = "syndicate_gib" - mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) - speak_chance = 0 - turns_per_move = 5 - response_help = "pokes" - response_disarm = "shoves" - response_harm = "hits" - speed = 0 - stat_attack = UNCONSCIOUS - robust_searching = 1 - maxHealth = 100 - health = 100 - harm_intent_damage = 5 - melee_damage_lower = 10 - melee_damage_upper = 10 - attacktext = "punches" - attack_sound = 'sound/weapons/punch1.ogg' - a_intent = INTENT_HARM - loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier) - atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 15 - faction = list(ROLE_SYNDICATE) - check_friendly_fire = 1 - status_flags = CANPUSH - del_on_death = 1 - dodging = TRUE - rapid_melee = 2 - - do_footstep = TRUE - -///////////////Melee//////////// - -/mob/living/simple_animal/hostile/syndicate/space - icon_state = "syndicate_space" - icon_living = "syndicate_space" - name = "Syndicate Commando" - maxHealth = 170 - health = 170 - 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 - speed = 1 - spacewalk = TRUE - -/mob/living/simple_animal/hostile/syndicate/space/Initialize() - . = ..() - set_light(4) - -/mob/living/simple_animal/hostile/syndicate/space/stormtrooper - icon_state = "syndicate_stormtrooper" - icon_living = "syndicate_stormtrooper" - name = "Syndicate Stormtrooper" - maxHealth = 250 - health = 250 - -/mob/living/simple_animal/hostile/syndicate/melee - melee_damage_lower = 15 - melee_damage_upper = 15 - icon_state = "syndicate_knife" - icon_living = "syndicate_knife" - loot = list(/obj/effect/gibspawner/human) - attacktext = "slashes" - attack_sound = 'sound/weapons/bladeslice.ogg' - status_flags = 0 - -/mob/living/simple_animal/hostile/syndicate/melee/space - icon_state = "syndicate_space_knife" - icon_living = "syndicate_space_knife" - name = "Syndicate Commando" - maxHealth = 170 - health = 170 - 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 - speed = 1 - spacewalk = TRUE - -/mob/living/simple_animal/hostile/syndicate/melee/space/Initialize() - . = ..() - set_light(4) - -/mob/living/simple_animal/hostile/syndicate/melee/space/stormtrooper - icon_state = "syndicate_stormtrooper_knife" - icon_living = "syndicate_stormtrooper_knife" - name = "Syndicate Stormtrooper" - maxHealth = 250 - health = 250 - -/mob/living/simple_animal/hostile/syndicate/melee/sword - melee_damage_lower = 30 - melee_damage_upper = 30 - icon_state = "syndicate_sword" - icon_living = "syndicate_sword" - attacktext = "slashes" - attack_sound = 'sound/weapons/blade1.ogg' - armour_penetration = 35 - light_color = LIGHT_COLOR_RED - status_flags = 0 - var/obj/effect/light_emitter/red_energy_sword/sord - -/mob/living/simple_animal/hostile/syndicate/melee/sword/Initialize() - . = ..() - set_light(2) - -/mob/living/simple_animal/hostile/syndicate/melee/sword/Destroy() - QDEL_NULL(sord) - return ..() - -/mob/living/simple_animal/hostile/syndicate/melee/bullet_act(obj/item/projectile/Proj) - if(!Proj) - return - if(prob(25)) - return ..() - else - visible_message("[src] blocks [Proj] with its shield!") - return 0 - -/mob/living/simple_animal/hostile/syndicate/melee/sword/space - icon_state = "syndicate_space_sword" - icon_living = "syndicate_space_sword" - name = "Syndicate Commando" - maxHealth = 170 - health = 170 - 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 - speed = 1 - spacewalk = TRUE - -/mob/living/simple_animal/hostile/syndicate/melee/sword/space/Initialize() - . = ..() - sord = new(src) - set_light(4) - -/mob/living/simple_animal/hostile/syndicate/melee/sword/space/Destroy() - QDEL_NULL(sord) - return ..() - -/mob/living/simple_animal/hostile/syndicate/melee/sword/space/stormtrooper - icon_state = "syndicate_stormtrooper_sword" - icon_living = "syndicate_stormtrooper_sword" - name = "Syndicate Stormtrooper" - maxHealth = 250 - health = 250 - -///////////////Guns//////////// - -/mob/living/simple_animal/hostile/syndicate/ranged - ranged = 1 - retreat_distance = 5 - minimum_distance = 5 - icon_state = "syndicate_pistol" - icon_living = "syndicate_pistol" - casingtype = /obj/item/ammo_casing/c10mm - projectilesound = 'sound/weapons/gunshot.ogg' - loot = list(/obj/effect/gibspawner/human) - dodging = FALSE - rapid_melee = 1 - -/mob/living/simple_animal/hostile/syndicate/ranged/infiltrator //shuttle loan event - projectilesound = 'sound/weapons/gunshot_silenced.ogg' - loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier) - -/mob/living/simple_animal/hostile/syndicate/ranged/space - icon_state = "syndicate_space_pistol" - icon_living = "syndicate_space_pistol" - name = "Syndicate Commando" - maxHealth = 170 - health = 170 - 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 - speed = 1 - spacewalk = TRUE - -/mob/living/simple_animal/hostile/syndicate/ranged/space/Initialize() - . = ..() - set_light(4) - -/mob/living/simple_animal/hostile/syndicate/ranged/space/stormtrooper - icon_state = "syndicate_stormtrooper_pistol" - icon_living = "syndicate_stormtrooper_pistol" - name = "Syndicate Stormtrooper" - maxHealth = 250 - health = 250 - -/mob/living/simple_animal/hostile/syndicate/ranged/smg - rapid = 2 - icon_state = "syndicate_smg" - icon_living = "syndicate_smg" - casingtype = /obj/item/ammo_casing/c45/nostamina - projectilesound = 'sound/weapons/gunshot_smg.ogg' - -/mob/living/simple_animal/hostile/syndicate/ranged/smg/pilot //caravan ambush ruin - name = "Syndicate Salvage Pilot" - loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier) - -/mob/living/simple_animal/hostile/syndicate/ranged/smg/space - icon_state = "syndicate_space_smg" - icon_living = "syndicate_space_smg" - name = "Syndicate Commando" - maxHealth = 170 - health = 170 - 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 - speed = 1 - spacewalk = TRUE - -/mob/living/simple_animal/hostile/syndicate/ranged/smg/space/Initialize() - . = ..() - set_light(4) - -/mob/living/simple_animal/hostile/syndicate/ranged/smg/space/stormtrooper - icon_state = "syndicate_stormtrooper_smg" - icon_living = "syndicate_stormtrooper_smg" - name = "Syndicate Stormtrooper" - maxHealth = 250 - health = 250 - -/mob/living/simple_animal/hostile/syndicate/ranged/shotgun - rapid = 2 - rapid_fire_delay = 6 - minimum_distance = 3 - icon_state = "syndicate_shotgun" - icon_living = "syndicate_shotgun" - casingtype = /obj/item/ammo_casing/shotgun/buckshot //buckshot (up to 72.5 brute) fired in a two-round burst - -/mob/living/simple_animal/hostile/syndicate/ranged/shotgun/space - icon_state = "syndicate_space_shotgun" - icon_living = "syndicate_space_shotgun" - name = "Syndicate Commando" - maxHealth = 170 - health = 170 - 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 - speed = 1 - spacewalk = TRUE - -/mob/living/simple_animal/hostile/syndicate/ranged/shotgun/space/Initialize() - . = ..() - set_light(4) - -/mob/living/simple_animal/hostile/syndicate/ranged/shotgun/space/stormtrooper - icon_state = "syndicate_stormtrooper_shotgun" - icon_living = "syndicate_stormtrooper_shotgun" - name = "Syndicate Stormtrooper" - maxHealth = 250 - health = 250 - -///////////////Misc//////////// - -/mob/living/simple_animal/hostile/syndicate/civilian - minimum_distance = 10 - retreat_distance = 10 - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - -/mob/living/simple_animal/hostile/syndicate/civilian/Aggro() - ..() - summon_backup(15) - say("GUARDS!!") - - -/mob/living/simple_animal/hostile/viscerator - name = "viscerator" - desc = "A small, twin-bladed machine capable of inflicting very deadly lacerations." - icon_state = "viscerator_attack" - icon_living = "viscerator_attack" - pass_flags = PASSTABLE | PASSMOB - a_intent = INTENT_HARM - mob_biotypes = list(MOB_ROBOTIC) - health = 25 - maxHealth = 25 - melee_damage_lower = 15 - melee_damage_upper = 15 - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - attacktext = "cuts" - attack_sound = 'sound/weapons/bladeslice.ogg' - faction = list(ROLE_SYNDICATE) - 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 - mob_size = MOB_SIZE_TINY - movement_type = FLYING - limb_destroyer = 1 - speak_emote = list("states") - bubble_icon = "syndibot" - gold_core_spawnable = HOSTILE_SPAWN - del_on_death = 1 - deathmessage = "is smashed into pieces!" - -/mob/living/simple_animal/hostile/viscerator/Initialize() - . = ..() - AddComponent(/datum/component/swarming) +/* + CONTENTS + LINE 10 - BASE MOB + LINE 52 - SWORD AND SHIELD + LINE 164 - GUNS + LINE 267 - MISC +*/ + + +///////////////Base mob//////////// +/obj/effect/light_emitter/red_energy_sword //used so there's a combination of both their head light and light coming off the energy sword + set_luminosity = 2 + set_cap = 2.5 + light_color = LIGHT_COLOR_RED + + +/mob/living/simple_animal/hostile/syndicate + name = "Syndicate Operative" + desc = "Death to Nanotrasen." + icon = 'icons/mob/simple_human.dmi' + icon_state = "syndicate" + icon_living = "syndicate" + icon_dead = "syndicate_dead" + icon_gib = "syndicate_gib" + mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID) + speak_chance = 0 + turns_per_move = 5 + response_help = "pokes" + response_disarm = "shoves" + response_harm = "hits" + speed = 0 + stat_attack = UNCONSCIOUS + robust_searching = 1 + maxHealth = 100 + health = 100 + harm_intent_damage = 5 + melee_damage_lower = 10 + melee_damage_upper = 10 + attacktext = "punches" + attack_sound = 'sound/weapons/punch1.ogg' + a_intent = INTENT_HARM + loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier) + atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) + unsuitable_atmos_damage = 15 + faction = list(ROLE_SYNDICATE) + check_friendly_fire = 1 + status_flags = CANPUSH + del_on_death = 1 + dodging = TRUE + rapid_melee = 2 + + do_footstep = TRUE + +///////////////Melee//////////// + +/mob/living/simple_animal/hostile/syndicate/space + icon_state = "syndicate_space" + icon_living = "syndicate_space" + name = "Syndicate Commando" + maxHealth = 170 + health = 170 + 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 + speed = 1 + spacewalk = TRUE + +/mob/living/simple_animal/hostile/syndicate/space/Initialize() + . = ..() + set_light(4) + +/mob/living/simple_animal/hostile/syndicate/space/stormtrooper + icon_state = "syndicate_stormtrooper" + icon_living = "syndicate_stormtrooper" + name = "Syndicate Stormtrooper" + maxHealth = 250 + health = 250 + +/mob/living/simple_animal/hostile/syndicate/melee + melee_damage_lower = 15 + melee_damage_upper = 15 + icon_state = "syndicate_knife" + icon_living = "syndicate_knife" + loot = list(/obj/effect/gibspawner/human) + attacktext = "slashes" + attack_sound = 'sound/weapons/bladeslice.ogg' + status_flags = 0 + +/mob/living/simple_animal/hostile/syndicate/melee/space + icon_state = "syndicate_space_knife" + icon_living = "syndicate_space_knife" + name = "Syndicate Commando" + maxHealth = 170 + health = 170 + 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 + speed = 1 + spacewalk = TRUE + +/mob/living/simple_animal/hostile/syndicate/melee/space/Initialize() + . = ..() + set_light(4) + +/mob/living/simple_animal/hostile/syndicate/melee/space/stormtrooper + icon_state = "syndicate_stormtrooper_knife" + icon_living = "syndicate_stormtrooper_knife" + name = "Syndicate Stormtrooper" + maxHealth = 250 + health = 250 + +/mob/living/simple_animal/hostile/syndicate/melee/sword + melee_damage_lower = 30 + melee_damage_upper = 30 + icon_state = "syndicate_sword" + icon_living = "syndicate_sword" + attacktext = "slashes" + attack_sound = 'sound/weapons/blade1.ogg' + armour_penetration = 35 + light_color = LIGHT_COLOR_RED + status_flags = 0 + var/obj/effect/light_emitter/red_energy_sword/sord + +/mob/living/simple_animal/hostile/syndicate/melee/sword/Initialize() + . = ..() + set_light(2) + +/mob/living/simple_animal/hostile/syndicate/melee/sword/Destroy() + QDEL_NULL(sord) + return ..() + +/mob/living/simple_animal/hostile/syndicate/melee/bullet_act(obj/item/projectile/Proj) + if(!Proj) + return + if(prob(25)) + return ..() + else + visible_message("[src] blocks [Proj] with its shield!") + return 0 + +/mob/living/simple_animal/hostile/syndicate/melee/sword/space + icon_state = "syndicate_space_sword" + icon_living = "syndicate_space_sword" + name = "Syndicate Commando" + maxHealth = 170 + health = 170 + 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 + speed = 1 + spacewalk = TRUE + +/mob/living/simple_animal/hostile/syndicate/melee/sword/space/Initialize() + . = ..() + sord = new(src) + set_light(4) + +/mob/living/simple_animal/hostile/syndicate/melee/sword/space/Destroy() + QDEL_NULL(sord) + return ..() + +/mob/living/simple_animal/hostile/syndicate/melee/sword/space/stormtrooper + icon_state = "syndicate_stormtrooper_sword" + icon_living = "syndicate_stormtrooper_sword" + name = "Syndicate Stormtrooper" + maxHealth = 250 + health = 250 + +///////////////Guns//////////// + +/mob/living/simple_animal/hostile/syndicate/ranged + ranged = 1 + retreat_distance = 5 + minimum_distance = 5 + icon_state = "syndicate_pistol" + icon_living = "syndicate_pistol" + casingtype = /obj/item/ammo_casing/c10mm + projectilesound = 'sound/weapons/gunshot.ogg' + loot = list(/obj/effect/gibspawner/human) + dodging = FALSE + rapid_melee = 1 + +/mob/living/simple_animal/hostile/syndicate/ranged/infiltrator //shuttle loan event + projectilesound = 'sound/weapons/gunshot_silenced.ogg' + loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier) + +/mob/living/simple_animal/hostile/syndicate/ranged/space + icon_state = "syndicate_space_pistol" + icon_living = "syndicate_space_pistol" + name = "Syndicate Commando" + maxHealth = 170 + health = 170 + 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 + speed = 1 + spacewalk = TRUE + +/mob/living/simple_animal/hostile/syndicate/ranged/space/Initialize() + . = ..() + set_light(4) + +/mob/living/simple_animal/hostile/syndicate/ranged/space/stormtrooper + icon_state = "syndicate_stormtrooper_pistol" + icon_living = "syndicate_stormtrooper_pistol" + name = "Syndicate Stormtrooper" + maxHealth = 250 + health = 250 + +/mob/living/simple_animal/hostile/syndicate/ranged/smg + rapid = 2 + icon_state = "syndicate_smg" + icon_living = "syndicate_smg" + casingtype = /obj/item/ammo_casing/c45/nostamina + projectilesound = 'sound/weapons/gunshot_smg.ogg' + +/mob/living/simple_animal/hostile/syndicate/ranged/smg/pilot //caravan ambush ruin + name = "Syndicate Salvage Pilot" + loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier) + +/mob/living/simple_animal/hostile/syndicate/ranged/smg/space + icon_state = "syndicate_space_smg" + icon_living = "syndicate_space_smg" + name = "Syndicate Commando" + maxHealth = 170 + health = 170 + 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 + speed = 1 + spacewalk = TRUE + +/mob/living/simple_animal/hostile/syndicate/ranged/smg/space/Initialize() + . = ..() + set_light(4) + +/mob/living/simple_animal/hostile/syndicate/ranged/smg/space/stormtrooper + icon_state = "syndicate_stormtrooper_smg" + icon_living = "syndicate_stormtrooper_smg" + name = "Syndicate Stormtrooper" + maxHealth = 250 + health = 250 + +/mob/living/simple_animal/hostile/syndicate/ranged/shotgun + rapid = 2 + rapid_fire_delay = 6 + minimum_distance = 3 + icon_state = "syndicate_shotgun" + icon_living = "syndicate_shotgun" + casingtype = /obj/item/ammo_casing/shotgun/buckshot //buckshot (up to 72.5 brute) fired in a two-round burst + +/mob/living/simple_animal/hostile/syndicate/ranged/shotgun/space + icon_state = "syndicate_space_shotgun" + icon_living = "syndicate_space_shotgun" + name = "Syndicate Commando" + maxHealth = 170 + health = 170 + 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 + speed = 1 + spacewalk = TRUE + +/mob/living/simple_animal/hostile/syndicate/ranged/shotgun/space/Initialize() + . = ..() + set_light(4) + +/mob/living/simple_animal/hostile/syndicate/ranged/shotgun/space/stormtrooper + icon_state = "syndicate_stormtrooper_shotgun" + icon_living = "syndicate_stormtrooper_shotgun" + name = "Syndicate Stormtrooper" + maxHealth = 250 + health = 250 + +///////////////Misc//////////// + +/mob/living/simple_animal/hostile/syndicate/civilian + minimum_distance = 10 + retreat_distance = 10 + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + +/mob/living/simple_animal/hostile/syndicate/civilian/Aggro() + ..() + summon_backup(15) + say("GUARDS!!") + + +/mob/living/simple_animal/hostile/viscerator + name = "viscerator" + desc = "A small, twin-bladed machine capable of inflicting very deadly lacerations." + icon_state = "viscerator_attack" + icon_living = "viscerator_attack" + pass_flags = PASSTABLE | PASSMOB + a_intent = INTENT_HARM + mob_biotypes = list(MOB_ROBOTIC) + health = 25 + maxHealth = 25 + melee_damage_lower = 15 + melee_damage_upper = 15 + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + attacktext = "cuts" + attack_sound = 'sound/weapons/bladeslice.ogg' + faction = list(ROLE_SYNDICATE) + 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 + mob_size = MOB_SIZE_TINY + movement_type = FLYING + limb_destroyer = 1 + speak_emote = list("states") + bubble_icon = "syndibot" + gold_core_spawnable = HOSTILE_SPAWN + del_on_death = 1 + deathmessage = "is smashed into pieces!" + +/mob/living/simple_animal/hostile/viscerator/Initialize() + . = ..() + AddComponent(/datum/component/swarming) diff --git a/code/modules/mob/living/simple_animal/hostile/tree.dm b/code/modules/mob/living/simple_animal/hostile/tree.dm index fc51b9afe4..e7b2da1844 100644 --- a/code/modules/mob/living/simple_animal/hostile/tree.dm +++ b/code/modules/mob/living/simple_animal/hostile/tree.dm @@ -1,71 +1,71 @@ -/mob/living/simple_animal/hostile/tree - name = "pine tree" - desc = "A pissed off tree-like alien. It seems annoyed with the festivities..." - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "pine_1" - icon_living = "pine_1" - icon_dead = "pine_1" - icon_gib = "pine_1" - gender = NEUTER - speak_chance = 0 - turns_per_move = 5 - response_help = "brushes" - response_disarm = "pushes" - response_harm = "hits" - speed = 1 - maxHealth = 250 - health = 250 - mob_size = MOB_SIZE_LARGE - - pixel_x = -16 - - harm_intent_damage = 5 - melee_damage_lower = 8 - melee_damage_upper = 12 - attacktext = "bites" - attack_sound = 'sound/weapons/bite.ogg' - speak_emote = list("pines") - emote_taunt = list("growls") - taunt_chance = 20 - - atmos_requirements = list("min_oxy" = 2, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 5 - minbodytemp = 0 - maxbodytemp = 1200 - - faction = list("hostile") - deathmessage = "is hacked into pieces!" - loot = list(/obj/item/stack/sheet/mineral/wood) - gold_core_spawnable = HOSTILE_SPAWN - del_on_death = 1 - -/mob/living/simple_animal/hostile/tree/Life() - ..() - if(isopenturf(loc)) - var/turf/open/T = src.loc - if(T.air && T.air.gases[/datum/gas/carbon_dioxide]) - var/co2 = T.air.gases[/datum/gas/carbon_dioxide] - if(co2 > 0) - if(prob(25)) - var/amt = min(co2, 9) - T.air.gases[/datum/gas/carbon_dioxide] -= amt - T.atmos_spawn_air("o2=[amt]") - -/mob/living/simple_animal/hostile/tree/AttackingTarget() - . = ..() - if(iscarbon(target)) - var/mob/living/carbon/C = target - if(prob(15)) - C.Knockdown(60) - C.visible_message("\The [src] knocks down \the [C]!", \ - "\The [src] knocks you down!") - -/mob/living/simple_animal/hostile/tree/festivus - name = "festivus pole" - desc = "Serenity now... SERENITY NOW!" - icon_state = "festivus_pole" - icon_living = "festivus_pole" - icon_dead = "festivus_pole" - icon_gib = "festivus_pole" - loot = list(/obj/item/stack/rods) - speak_emote = list("polls") +/mob/living/simple_animal/hostile/tree + name = "pine tree" + desc = "A pissed off tree-like alien. It seems annoyed with the festivities..." + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "pine_1" + icon_living = "pine_1" + icon_dead = "pine_1" + icon_gib = "pine_1" + gender = NEUTER + speak_chance = 0 + turns_per_move = 5 + response_help = "brushes" + response_disarm = "pushes" + response_harm = "hits" + speed = 1 + maxHealth = 250 + health = 250 + mob_size = MOB_SIZE_LARGE + + pixel_x = -16 + + harm_intent_damage = 5 + melee_damage_lower = 8 + melee_damage_upper = 12 + attacktext = "bites" + attack_sound = 'sound/weapons/bite.ogg' + speak_emote = list("pines") + emote_taunt = list("growls") + taunt_chance = 20 + + atmos_requirements = list("min_oxy" = 2, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + unsuitable_atmos_damage = 5 + minbodytemp = 0 + maxbodytemp = 1200 + + faction = list("hostile") + deathmessage = "is hacked into pieces!" + loot = list(/obj/item/stack/sheet/mineral/wood) + gold_core_spawnable = HOSTILE_SPAWN + del_on_death = 1 + +/mob/living/simple_animal/hostile/tree/Life() + ..() + if(isopenturf(loc)) + var/turf/open/T = src.loc + if(T.air && T.air.gases[/datum/gas/carbon_dioxide]) + var/co2 = T.air.gases[/datum/gas/carbon_dioxide] + if(co2 > 0) + if(prob(25)) + var/amt = min(co2, 9) + T.air.gases[/datum/gas/carbon_dioxide] -= amt + T.atmos_spawn_air("o2=[amt]") + +/mob/living/simple_animal/hostile/tree/AttackingTarget() + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(prob(15)) + C.Knockdown(60) + C.visible_message("\The [src] knocks down \the [C]!", \ + "\The [src] knocks you down!") + +/mob/living/simple_animal/hostile/tree/festivus + name = "festivus pole" + desc = "Serenity now... SERENITY NOW!" + icon_state = "festivus_pole" + icon_living = "festivus_pole" + icon_dead = "festivus_pole" + icon_gib = "festivus_pole" + loot = list(/obj/item/stack/rods) + speak_emote = list("polls") diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 3d23baae48..68d08aabd2 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -1,1024 +1,1024 @@ -/* Parrots! - * Contains - * Defines - * Inventory (headset stuff) - * Attack responces - * AI - * Procs / Verbs (usable by players) - * Sub-types - * Hear & say (the things we do for gimmicks) - */ - -/* - * Defines - */ - -//Only a maximum of one action and one intent should be active at any given time. -//Actions -#define PARROT_PERCH (1<<0) //Sitting/sleeping, not moving -#define PARROT_SWOOP (1<<1) //Moving towards or away from a target -#define PARROT_WANDER (1<<2) //Moving without a specific target in mind - -//Intents -#define PARROT_STEAL (1<<3) //Flying towards a target to steal it/from it -#define PARROT_ATTACK (1<<4) //Flying towards a target to attack it -#define PARROT_RETURN (1<<5) //Flying towards its perch -#define PARROT_FLEE (1<<6) //Flying away from its attacker - - -/mob/living/simple_animal/parrot - name = "parrot" - desc = "The parrot squaks, \"It's a Parrot! BAWWK!\"" //' - icon = 'icons/mob/animal.dmi' - icon_state = "parrot_fly" - icon_living = "parrot_fly" - icon_dead = "parrot_dead" - var/icon_sit = "parrot_sit" - density = FALSE - health = 80 - maxHealth = 80 - pass_flags = PASSTABLE | PASSMOB - - speak = list("Hi!","Hello!","Cracker?","BAWWWWK george mellons griffing me!") - speak_emote = list("squawks","says","yells") - emote_hear = list("squawks.","bawks!") - emote_see = list("flutters its wings.") - - speak_chance = 1 //1% (1 in 100) chance every tick; So about once per 150 seconds, assuming an average tick is 1.5s - turns_per_move = 5 - butcher_results = list(/obj/item/reagent_containers/food/snacks/cracker/ = 1) - melee_damage_upper = 10 - melee_damage_lower = 5 - - response_help = "pets" - response_disarm = "gently moves aside" - response_harm = "swats" - stop_automated_movement = 1 - a_intent = INTENT_HARM //parrots now start "aggressive" since only player parrots will nuzzle. - attacktext = "chomps" - friendly = "grooms" - mob_size = MOB_SIZE_SMALL - movement_type = FLYING - gold_core_spawnable = FRIENDLY_SPAWN - - var/parrot_damage_upper = 10 - var/parrot_state = PARROT_WANDER //Hunt for a perch when created - var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in life() being run every single tick. - var/parrot_sleep_dur = 25 //Same as above, this is the var that physically counts down - var/parrot_dam_zone = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_R_LEG) //For humans, select a bodypart to attack - - var/parrot_speed = 5 //"Delay in world ticks between movement." according to byond. Yeah, that's BS but it does directly affect movement. Higher number = slower. - var/parrot_lastmove = null //Updates/Stores position of the parrot while it's moving - var/parrot_stuck = 0 //If parrot_lastmove hasnt changed, this will increment until it reaches parrot_stuck_threshold - var/parrot_stuck_threshold = 10 //if this == parrot_stuck, it'll force the parrot back to wandering - - var/list/speech_buffer = list() - var/speech_shuffle_rate = 20 - var/list/available_channels = list() - - //Headset for Poly to yell at engineers :) - var/obj/item/radio/headset/ears = null - - //The thing the parrot is currently interested in. This gets used for items the parrot wants to pick up, mobs it wants to steal from, - //mobs it wants to attack or mobs that have attacked it - var/atom/movable/parrot_interest = null - - //Parrots will generally sit on their perch unless something catches their eye. - //These vars store their preffered perch and if they dont have one, what they can use as a perch - var/obj/parrot_perch = null - var/obj/desired_perches = list(/obj/structure/frame/computer, /obj/structure/displaycase, \ - /obj/structure/filingcabinet, /obj/machinery/teleport, \ - /obj/machinery/computer, /obj/machinery/clonepod, \ - /obj/machinery/dna_scannernew, /obj/machinery/telecomms, \ - /obj/machinery/nuclearbomb, /obj/machinery/particle_accelerator, \ - /obj/machinery/recharge_station, /obj/machinery/smartfridge, \ - /obj/machinery/suit_storage_unit) - - //Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding. - var/obj/item/held_item = null - - -/mob/living/simple_animal/parrot/Initialize() - . = ..() - if(!ears) - var/headset = pick(/obj/item/radio/headset/headset_sec, \ - /obj/item/radio/headset/headset_eng, \ - /obj/item/radio/headset/headset_med, \ - /obj/item/radio/headset/headset_sci, \ - /obj/item/radio/headset/headset_cargo) - ears = new headset(src) - - parrot_sleep_dur = parrot_sleep_max //In case someone decides to change the max without changing the duration var - - verbs.Add(/mob/living/simple_animal/parrot/proc/steal_from_ground, \ - /mob/living/simple_animal/parrot/proc/steal_from_mob, \ - /mob/living/simple_animal/parrot/verb/drop_held_item_player, \ - /mob/living/simple_animal/parrot/proc/perch_player, \ - /mob/living/simple_animal/parrot/proc/toggle_mode, - /mob/living/simple_animal/parrot/proc/perch_mob_player) - - -/mob/living/simple_animal/parrot/examine(mob/user) - . = ..() - if(stat) - . += pick("This parrot is no more.", "This is a late parrot.", "This is an ex-parrot.") - -/mob/living/simple_animal/parrot/death(gibbed) - if(held_item) - held_item.forceMove(drop_location()) - held_item = null - walk(src,0) - - if(buckled) - buckled.unbuckle_mob(src,force=1) - buckled = null - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - - ..(gibbed) - -/mob/living/simple_animal/parrot/Stat() - ..() - if(statpanel("Status")) - stat("Held Item", held_item) - stat("Mode",a_intent) - -/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) - . = ..() - if(speaker != src && prob(50)) //Dont imitate ourselves - if(!radio_freq || prob(10)) - if(speech_buffer.len >= 500) - speech_buffer -= pick(speech_buffer) - speech_buffer |= html_decode(raw_message) - if(speaker == src && !client) //If a parrot squawks in the woods and no one is around to hear it, does it make a sound? This code says yes! - return message - -/mob/living/simple_animal/parrot/radio(message, message_mode, list/spans, language) //literally copied from human/radio(), but there's no other way to do this. at least it's better than it used to be. - . = ..() - if(. != 0) - return . - - switch(message_mode) - if(MODE_HEADSET) - if (ears) - ears.talk_into(src, message, , spans, language) - return ITALICS | REDUCE_RANGE - - if(MODE_DEPARTMENT) - if (ears) - ears.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - if(message_mode in GLOB.radiochannels) - if(ears) - ears.talk_into(src, message, message_mode, spans, language) - return ITALICS | REDUCE_RANGE - - return 0 - -/* - * Inventory - */ -/mob/living/simple_animal/parrot/show_inv(mob/user) - user.set_machine(src) - - var/dat = "
                Inventory of [name]

                " - dat += "
                Headset: [ears]" : "add_inv=ears'>Nothing"]" - - user << browse(dat, "window=mob[REF(src)];size=325x500") - onclose(user, "window=mob[REF(src)]") - - -/mob/living/simple_animal/parrot/Topic(href, href_list) - if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - usr << browse(null, "window=mob[REF(src)]") - usr.unset_machine() - return - - //Removing from inventory - if(href_list["remove_inv"]) - var/remove_from = href_list["remove_inv"] - switch(remove_from) - if("ears") - if(!ears) - to_chat(usr, "There is nothing to remove from its [remove_from]!") - return - if(!stat) - say("[available_channels.len ? "[pick(available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") - ears.forceMove(drop_location()) - ears = null - for(var/possible_phrase in speak) - if(copytext(possible_phrase,1,3) in GLOB.department_radio_keys) - possible_phrase = copytext(possible_phrase,3) - - //Adding things to inventory - else if(href_list["add_inv"]) - var/add_to = href_list["add_inv"] - if(!usr.get_active_held_item()) - to_chat(usr, "You have nothing in your hand to put on its [add_to]!") - return - switch(add_to) - if("ears") - if(ears) - to_chat(usr, "It's already wearing something!") - return - else - var/obj/item/item_to_add = usr.get_active_held_item() - if(!item_to_add) - return - - if( !istype(item_to_add, /obj/item/radio/headset) ) - to_chat(usr, "This object won't fit!") - return - - var/obj/item/radio/headset/headset_to_add = item_to_add - - if(!usr.transferItemToLoc(headset_to_add, src)) - return - ears = headset_to_add - to_chat(usr, "You fit the headset onto [src].") - - clearlist(available_channels) - for(var/ch in headset_to_add.channels) - switch(ch) - if(RADIO_CHANNEL_ENGINEERING) - available_channels.Add(RADIO_TOKEN_ENGINEERING) - if(RADIO_CHANNEL_COMMAND) - available_channels.Add(RADIO_TOKEN_COMMAND) - if(RADIO_CHANNEL_SECURITY) - available_channels.Add(RADIO_TOKEN_SECURITY) - if(RADIO_CHANNEL_SCIENCE) - available_channels.Add(RADIO_TOKEN_SCIENCE) - if(RADIO_CHANNEL_MEDICAL) - available_channels.Add(RADIO_TOKEN_MEDICAL) - if(RADIO_CHANNEL_SUPPLY) - available_channels.Add(RADIO_TOKEN_SUPPLY) - if(RADIO_CHANNEL_SERVICE) - available_channels.Add(RADIO_TOKEN_SERVICE) - - if(headset_to_add.translate_binary) - available_channels.Add(MODE_TOKEN_BINARY) - else - return ..() - - -/* - * Attack responces - */ -//Humans, monkeys, aliens -/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M) - ..() - if(client) - return - if(!stat && M.a_intent == INTENT_HARM) - - icon_state = icon_living //It is going to be flying regardless of whether it flees or attacks - - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - parrot_interest = M - parrot_state = PARROT_SWOOP //The parrot just got hit, it WILL move, now to pick a direction.. - - if(health > 30) //Let's get in there and squawk it up! - parrot_state |= PARROT_ATTACK - else - parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell! - drop_held_item(0) - if(stat != DEAD && M.a_intent == INTENT_HELP) - handle_automated_speech(1) //assured speak/emote - return - -/mob/living/simple_animal/parrot/attack_paw(mob/living/carbon/monkey/M) - return attack_hand(M) - -/mob/living/simple_animal/parrot/attack_alien(mob/living/carbon/alien/M) - return attack_hand(M) - -//Simple animals -/mob/living/simple_animal/parrot/attack_animal(mob/living/simple_animal/M) - . = ..() //goodbye immortal parrots - - if(client) - return - - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - if(M.melee_damage_upper > 0 && !stat) - parrot_interest = M - parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless - icon_state = icon_living - -//Mobs with objects -/mob/living/simple_animal/parrot/attackby(obj/item/O, mob/living/user, params) - if(!stat && !client && !istype(O, /obj/item/stack/medical) && !istype(O, /obj/item/reagent_containers/food/snacks/cracker)) - if(O.force) - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - parrot_interest = user - parrot_state = PARROT_SWOOP - if(health > 30) //Let's get in there and squawk it up! - parrot_state |= PARROT_ATTACK - else - parrot_state |= PARROT_FLEE - icon_state = icon_living - drop_held_item(0) - else if(istype(O, /obj/item/reagent_containers/food/snacks/cracker)) //Poly wants a cracker. - qdel(O) - if(health < maxHealth) - adjustBruteLoss(-10) - speak_chance *= 1.27 // 20 crackers to go from 1% to 100% - speech_shuffle_rate += 10 - to_chat(user, "[src] eagerly devours the cracker.") - ..() - return - -//Bullets -/mob/living/simple_animal/parrot/bullet_act(obj/item/projectile/Proj) - ..() - if(!stat && !client) - if(parrot_state == PARROT_PERCH) - parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched - - parrot_interest = null - parrot_state = PARROT_WANDER | PARROT_FLEE //Been shot and survived! RUN LIKE HELL! - //parrot_been_shot += 5 - icon_state = icon_living - drop_held_item(0) - return - - -/* - * AI - Not really intelligent, but I'm calling it AI anyway. - */ -/mob/living/simple_animal/parrot/Life() - ..() - - //Sprite update for when a parrot gets pulled - if(pulledby && !stat && parrot_state != PARROT_WANDER) - if(buckled) - buckled.unbuckle_mob(src, TRUE) - buckled = null - icon_state = icon_living - parrot_state = PARROT_WANDER - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - return - - -//-----SPEECH - /* Parrot speech mimickry! - Phrases that the parrot Hear()s get added to speach_buffer. - Every once in a while, the parrot picks one of the lines from the buffer and replaces an element of the 'speech' list. */ -/mob/living/simple_animal/parrot/handle_automated_speech() - ..() - if(speech_buffer.len && prob(speech_shuffle_rate)) //shuffle out a phrase and add in a new one - if(speak.len) - speak.Remove(pick(speak)) - - speak.Add(pick(speech_buffer)) - - -/mob/living/simple_animal/parrot/handle_automated_movement() - if(!isturf(src.loc) || !canmove || buckled) - return //If it can't move, dont let it move. (The buckled check probably isn't necessary thanks to canmove) - - if(client && stat == CONSCIOUS && parrot_state != icon_living) - icon_state = icon_living - //Because the most appropriate place to set icon_state is movement_delay(), clearly - -//-----SLEEPING - if(parrot_state == PARROT_PERCH) - if(parrot_perch && parrot_perch.loc != src.loc) //Make sure someone hasnt moved our perch on us - if(parrot_perch in view(src)) - parrot_state = PARROT_SWOOP | PARROT_RETURN - icon_state = icon_living - return - else - parrot_state = PARROT_WANDER - icon_state = icon_living - return - - if(--parrot_sleep_dur) //Zzz - return - - else - //This way we only call the stuff below once every [sleep_max] ticks. - parrot_sleep_dur = parrot_sleep_max - - //Cycle through message modes for the headset - if(speak.len) - var/list/newspeak = list() - - if(available_channels.len && src.ears) - for(var/possible_phrase in speak) - - //50/50 chance to not use the radio at all - var/useradio = 0 - if(prob(50)) - useradio = 1 - - if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys)) - possible_phrase = "[useradio?pick(available_channels):""][copytext(possible_phrase,3)]" //crop out the channel prefix - else - possible_phrase = "[useradio?pick(available_channels):""][possible_phrase]" - - newspeak.Add(possible_phrase) - - else //If we have no headset or channels to use, dont try to use any! - for(var/possible_phrase in speak) - if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys)) - possible_phrase = copytext(possible_phrase,3) //crop out the channel prefix - newspeak.Add(possible_phrase) - speak = newspeak - - //Search for item to steal - parrot_interest = search_for_item() - if(parrot_interest) - emote("me", EMOTE_VISIBLE, "looks in [parrot_interest]'s direction and takes flight.") - parrot_state = PARROT_SWOOP | PARROT_STEAL - icon_state = icon_living - return - -//-----WANDERING - This is basically a 'I dont know what to do yet' state - else if(parrot_state == PARROT_WANDER) - //Stop movement, we'll set it later - walk(src, 0) - parrot_interest = null - - //Wander around aimlessly. This will help keep the loops from searches down - //and possibly move the mob into a new are in view of something they can use - if(prob(90)) - step(src, pick(GLOB.cardinals)) - return - - if(!held_item && !parrot_perch) //If we've got nothing to do.. look for something to do. - var/atom/movable/AM = search_for_perch_and_item() //This handles checking through lists so we know it's either a perch or stealable item - if(AM) - if(istype(AM, /obj/item) || isliving(AM)) //If stealable item - parrot_interest = AM - emote("me", EMOTE_VISIBLE, "turns and flies towards [parrot_interest].") - parrot_state = PARROT_SWOOP | PARROT_STEAL - return - else //Else it's a perch - parrot_perch = AM - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - return - - if(parrot_interest && parrot_interest in view(src)) - parrot_state = PARROT_SWOOP | PARROT_STEAL - return - - if(parrot_perch && parrot_perch in view(src)) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - else //Have an item but no perch? Find one! - parrot_perch = search_for_perch() - if(parrot_perch) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return -//-----STEALING - else if(parrot_state == (PARROT_SWOOP | PARROT_STEAL)) - walk(src,0) - if(!parrot_interest || held_item) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - if(!(parrot_interest in view(src))) - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - if(Adjacent(parrot_interest)) - - if(isliving(parrot_interest)) - steal_from_mob() - - else //This should ensure that we only grab the item we want, and make sure it's not already collected on our perch - if(!parrot_perch || parrot_interest.loc != parrot_perch.loc) - held_item = parrot_interest - parrot_interest.forceMove(src) - visible_message("[src] grabs [held_item]!", "You grab [held_item]!", "You hear the sounds of wings flapping furiously.") - - parrot_interest = null - parrot_state = PARROT_SWOOP | PARROT_RETURN - return - - walk_to(src, parrot_interest, 1, parrot_speed) - if(isStuck()) - return - - return - -//-----RETURNING TO PERCH - else if(parrot_state == (PARROT_SWOOP | PARROT_RETURN)) - walk(src, 0) - if(!parrot_perch || !isturf(parrot_perch.loc)) //Make sure the perch exists and somehow isnt inside of something else. - parrot_perch = null - parrot_state = PARROT_WANDER - return - - if(Adjacent(parrot_perch)) - forceMove(parrot_perch.loc) - drop_held_item() - parrot_state = PARROT_PERCH - icon_state = icon_sit - return - - walk_to(src, parrot_perch, 1, parrot_speed) - if(isStuck()) - return - - return - -//-----FLEEING - else if(parrot_state == (PARROT_SWOOP | PARROT_FLEE)) - walk(src,0) - if(!parrot_interest || !isliving(parrot_interest)) //Sanity - parrot_state = PARROT_WANDER - - walk_away(src, parrot_interest, 1, parrot_speed) - if(isStuck()) - return - - return - -//-----ATTACKING - else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK)) - - //If we're attacking a nothing, an object, a turf or a ghost for some stupid reason, switch to wander - if(!parrot_interest || !isliving(parrot_interest)) - parrot_interest = null - parrot_state = PARROT_WANDER - return - - var/mob/living/L = parrot_interest - if(melee_damage_upper == 0) - melee_damage_upper = parrot_damage_upper - a_intent = INTENT_HARM - - //If the mob is close enough to interact with - if(Adjacent(parrot_interest)) - - //If the mob we've been chasing/attacking dies or falls into crit, check for loot! - if(L.stat) - parrot_interest = null - if(!held_item) - held_item = steal_from_ground() - if(!held_item) - held_item = steal_from_mob() //Apparently it's possible for dead mobs to hang onto items in certain circumstances. - if(parrot_perch in view(src)) //If we have a home nearby, go to it, otherwise find a new home - parrot_state = PARROT_SWOOP | PARROT_RETURN - else - parrot_state = PARROT_WANDER - return - - attacktext = pick("claws at", "chomps") - L.attack_animal(src)//Time for the hurt to begin! - //Otherwise, fly towards the mob! - else - walk_to(src, parrot_interest, 1, parrot_speed) - if(isStuck()) - return - - return -//-----STATE MISHAP - else //This should not happen. If it does lets reset everything and try again - walk(src,0) - parrot_interest = null - parrot_perch = null - drop_held_item() - parrot_state = PARROT_WANDER - return - -/* - * Procs - */ - -/mob/living/simple_animal/parrot/proc/isStuck() - //Check to see if the parrot is stuck due to things like windows or doors or windowdoors - if(parrot_lastmove) - if(parrot_lastmove == src.loc) - if(parrot_stuck_threshold >= ++parrot_stuck) //If it has been stuck for a while, go back to wander. - parrot_state = PARROT_WANDER - parrot_stuck = 0 - parrot_lastmove = null - return 1 - else - parrot_lastmove = null - else - parrot_lastmove = src.loc - return 0 - -/mob/living/simple_animal/parrot/proc/search_for_item() - var/item - for(var/atom/movable/AM in view(src)) - //Skip items we already stole or are wearing or are too big - if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src) - continue - if(istype(AM, /obj/item)) - var/obj/item/I = AM - if(I.w_class < WEIGHT_CLASS_SMALL) - item = I - else if(iscarbon(AM)) - var/mob/living/carbon/C = AM - for(var/obj/item/I in C.held_items) - if(I.w_class <= WEIGHT_CLASS_SMALL) - item = I - break - if(item) - if(!AStar(src, get_turf(item), /turf/proc/Distance_cardinal)) - item = null - continue - return item - - return null - -/mob/living/simple_animal/parrot/proc/search_for_perch() - for(var/obj/O in view(src)) - for(var/path in desired_perches) - if(istype(O, path)) - return O - return null - -//This proc was made to save on doing two 'in view' loops seperatly -/mob/living/simple_animal/parrot/proc/search_for_perch_and_item() - for(var/atom/movable/AM in view(src)) - for(var/perch_path in desired_perches) - if(istype(AM, perch_path)) - return AM - - //Skip items we already stole or are wearing or are too big - if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src) - continue - - if(istype(AM, /obj/item)) - var/obj/item/I = AM - if(I.w_class <= WEIGHT_CLASS_SMALL) - return I - - if(iscarbon(AM)) - var/mob/living/carbon/C = AM - for(var/obj/item/I in C.held_items) - if(I.w_class <= WEIGHT_CLASS_SMALL) - return C - return null - - -/* - * Verbs - These are actually procs, but can be used as verbs by player-controlled parrots. - */ -/mob/living/simple_animal/parrot/proc/steal_from_ground() - set name = "Steal from ground" - set category = "Parrot" - set desc = "Grabs a nearby item." - - if(stat) - return -1 - - if(held_item) - to_chat(src, "You are already holding [held_item]!") - return 1 - - for(var/obj/item/I in view(1,src)) - //Make sure we're not already holding it and it's small enough - if(I.loc != src && I.w_class <= WEIGHT_CLASS_SMALL) - - //If we have a perch and the item is sitting on it, continue - if(!client && parrot_perch && I.loc == parrot_perch.loc) - continue - - held_item = I - I.forceMove(src) - visible_message("[src] grabs [held_item]!", "You grab [held_item]!", "You hear the sounds of wings flapping furiously.") - return held_item - - to_chat(src, "There is nothing of interest to take!") - return 0 - -/mob/living/simple_animal/parrot/proc/steal_from_mob() - set name = "Steal from mob" - set category = "Parrot" - set desc = "Steals an item right out of a person's hand!" - - if(stat) - return -1 - - if(held_item) - to_chat(src, "You are already holding [held_item]!") - return 1 - - var/obj/item/stolen_item = null - - for(var/mob/living/carbon/C in view(1,src)) - for(var/obj/item/I in C.held_items) - if(I.w_class <= WEIGHT_CLASS_SMALL) - stolen_item = I - break - - if(stolen_item) - C.transferItemToLoc(stolen_item, src, TRUE) - held_item = stolen_item - visible_message("[src] grabs [held_item] out of [C]'s hand!", "You snag [held_item] out of [C]'s hand!", "You hear the sounds of wings flapping furiously.") - return held_item - - to_chat(src, "There is nothing of interest to take!") - return 0 - -/mob/living/simple_animal/parrot/verb/drop_held_item_player() - set name = "Drop held item" - set category = "Parrot" - set desc = "Drop the item you're holding." - - if(stat) - return - - src.drop_held_item() - - return - -/mob/living/simple_animal/parrot/proc/drop_held_item(drop_gently = 1) - set name = "Drop held item" - set category = "Parrot" - set desc = "Drop the item you're holding." - - if(stat) - return -1 - - if(!held_item) - if(src == usr) //So that other mobs wont make this message appear when they're bludgeoning you. - to_chat(src, "You have nothing to drop!") - return 0 - - -//parrots will eat crackers instead of dropping them - if(istype(held_item, /obj/item/reagent_containers/food/snacks/cracker) && (drop_gently)) - qdel(held_item) - held_item = null - if(health < maxHealth) - adjustBruteLoss(-10) - emote("me", EMOTE_VISIBLE, "[src] eagerly downs the cracker.") - return 1 - - - if(!drop_gently) - if(istype(held_item, /obj/item/grenade)) - var/obj/item/grenade/G = held_item - G.forceMove(drop_location()) - G.prime() - to_chat(src, "You let go of [held_item]!") - held_item = null - return 1 - - to_chat(src, "You drop [held_item].") - - held_item.forceMove(drop_location()) - held_item = null - return 1 - -/mob/living/simple_animal/parrot/proc/perch_player() - set name = "Sit" - set category = "Parrot" - set desc = "Sit on a nice comfy perch." - - if(stat || !client) - return - - if(icon_state == icon_living) - for(var/atom/movable/AM in view(src,1)) - for(var/perch_path in desired_perches) - if(istype(AM, perch_path)) - src.forceMove(AM.loc) - icon_state = icon_sit - parrot_state = PARROT_PERCH - return - to_chat(src, "There is no perch nearby to sit on!") - return - -/mob/living/simple_animal/parrot/Moved(oldLoc, dir) - . = ..() - if(. && !stat && client && parrot_state == PARROT_PERCH) - parrot_state = PARROT_WANDER - icon_state = icon_living - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - -/mob/living/simple_animal/parrot/proc/perch_mob_player() - set name = "Sit on Human's Shoulder" - set category = "Parrot" - set desc = "Sit on a nice comfy human being!" - - if(stat || !client) - return - - if(!buckled) - for(var/mob/living/carbon/human/H in view(src,1)) - if(H.has_buckled_mobs() && H.buckled_mobs.len >= H.max_buckled_mobs) //Already has a parrot, or is being eaten by a slime - continue - perch_on_human(H) - return - to_chat(src, "There is nobody nearby that you can sit on!") - else - icon_state = icon_living - parrot_state = PARROT_WANDER - if(buckled) - to_chat(src, "You are no longer sitting on [buckled]'s shoulder.") - buckled.unbuckle_mob(src, TRUE) - buckled = null - pixel_x = initial(pixel_x) - pixel_y = initial(pixel_y) - - - -/mob/living/simple_animal/parrot/proc/perch_on_human(mob/living/carbon/human/H) - if(!H) - return - forceMove(get_turf(H)) - if(H.buckle_mob(src, TRUE)) - pixel_y = 9 - pixel_x = pick(-8,8) //pick left or right shoulder - icon_state = icon_sit - parrot_state = PARROT_PERCH - to_chat(src, "You sit on [H]'s shoulder.") - - -/mob/living/simple_animal/parrot/proc/toggle_mode() - set name = "Toggle mode" - set category = "Parrot" - set desc = "Time to bear those claws!" - - if(stat || !client) - return - - if(a_intent != INTENT_HELP) - melee_damage_upper = 0 - a_intent = INTENT_HELP - else - melee_damage_upper = parrot_damage_upper - a_intent = INTENT_HARM - to_chat(src, "You will now [a_intent] others.") - return - -/* - * Sub-types - */ -/mob/living/simple_animal/parrot/Poly - name = "Poly" - desc = "Poly the Parrot. An expert on quantum cracker theory." - speak = list("Poly wanna cracker!", ":e Check the crystal, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN HARDSUITS?",":e OH GOD ITS ABOUT TO DELAMINATE CALL THE SHUTTLE") - gold_core_spawnable = NO_SPAWN - speak_chance = 3 - var/memory_saved = FALSE - var/rounds_survived = 0 - var/longest_survival = 0 - var/longest_deathstreak = 0 - -/mob/living/simple_animal/parrot/Poly/Initialize() - ears = new /obj/item/radio/headset/headset_eng(src) - available_channels = list(":e") - Read_Memory() - if(rounds_survived == longest_survival) - speak += pick("...[longest_survival].", "The things I've seen!", "I have lived many lives!", "What are you before me?") - desc += " Old as sin, and just as loud. Claimed to be [rounds_survived]." - speak_chance = 20 //His hubris has made him more annoying/easier to justify killing - add_atom_colour("#EEEE22", FIXED_COLOUR_PRIORITY) - else if(rounds_survived == longest_deathstreak) - speak += pick("What are you waiting for!", "Violence breeds violence!", "Blood! Blood!", "Strike me down if you dare!") - desc += " The squawks of [-rounds_survived] dead parrots ring out in your ears..." - add_atom_colour("#BB7777", FIXED_COLOUR_PRIORITY) - else if(rounds_survived > 0) - speak += pick("...again?", "No, It was over!", "Let me out!", "It never ends!") - desc += " Over [rounds_survived] shifts without a \"terrible\" \"accident\"!" - else - speak += pick("...alive?", "This isn't parrot heaven!", "I live, I die, I live again!", "The void fades!") - - . = ..() - -/mob/living/simple_animal/parrot/Poly/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - . = ..() - if(. && !client && prob(1) && prob(1)) //Only the one true bird may speak across dimensions. - world.TgsTargetedChatBroadcast("A stray squawk is heard... \"[message]\"", FALSE) - -/mob/living/simple_animal/parrot/Poly/Life() - if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved) - Write_Memory(FALSE) - memory_saved = TRUE - ..() - -/mob/living/simple_animal/parrot/Poly/death(gibbed) - if(!memory_saved) - Write_Memory(TRUE) - if(rounds_survived == longest_survival || rounds_survived == longest_deathstreak || prob(0.666)) - var/mob/living/simple_animal/parrot/Poly/ghost/G = new(loc) - if(mind) - mind.transfer_to(G) - else - transfer_ckey(G) - ..(gibbed) - -/mob/living/simple_animal/parrot/Poly/proc/Read_Memory() - if(fexists("data/npc_saves/Poly.sav")) //legacy compatability to convert old format to new - var/savefile/S = new /savefile("data/npc_saves/Poly.sav") - S["phrases"] >> speech_buffer - S["roundssurvived"] >> rounds_survived - S["longestsurvival"] >> longest_survival - S["longestdeathstreak"] >> longest_deathstreak - fdel("data/npc_saves/Poly.sav") - else - var/json_file = file("data/npc_saves/Poly.json") - if(!fexists(json_file)) - return - var/list/json = json_decode(file2text(json_file)) - speech_buffer = json["phrases"] - rounds_survived = json["roundssurvived"] - longest_survival = json["longestsurvival"] - longest_deathstreak = json["longestdeathstreak"] - if(!islist(speech_buffer)) - speech_buffer = list() - -/mob/living/simple_animal/parrot/Poly/proc/Write_Memory(dead) - var/json_file = file("data/npc_saves/Poly.json") - var/list/file_data = list() - if(islist(speech_buffer)) - file_data["phrases"] = speech_buffer - if(dead) - file_data["roundssurvived"] = min(rounds_survived - 1, 0) - file_data["longestsurvival"] = longest_survival - if(rounds_survived - 1 < longest_deathstreak) - file_data["longestdeathstreak"] = rounds_survived - 1 - else - file_data["longestdeathstreak"] = longest_deathstreak - else - file_data["roundssurvived"] = rounds_survived + 1 - if(rounds_survived + 1 > longest_survival) - file_data["longestsurvival"] = rounds_survived + 1 - else - file_data["longestsurvival"] = longest_survival - file_data["longestdeathstreak"] = longest_deathstreak - fdel(json_file) - WRITE_FILE(json_file, json_encode(file_data)) - -/mob/living/simple_animal/parrot/Poly/ratvar_act() - playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 75, TRUE) - var/mob/living/simple_animal/parrot/clock_hawk/H = new(loc) - H.setDir(dir) - qdel(src) - -/mob/living/simple_animal/parrot/Poly/ghost - name = "The Ghost of Poly" - desc = "Doomed to squawk the Earth." - color = "#FFFFFF77" - speak_chance = 20 - status_flags = GODMODE - incorporeal_move = INCORPOREAL_MOVE_BASIC - butcher_results = list(/obj/item/ectoplasm = 1) - -/mob/living/simple_animal/parrot/Poly/ghost/Initialize() - memory_saved = TRUE //At this point nothing is saved - . = ..() - -/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_speech() - if(ismob(loc)) - return - ..() - -/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_movement() - if(isliving(parrot_interest)) - if(!ishuman(parrot_interest)) - parrot_interest = null - else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK) && Adjacent(parrot_interest)) - walk_to(src, parrot_interest, 0, parrot_speed) - Possess(parrot_interest) - ..() - -/mob/living/simple_animal/parrot/Poly/ghost/proc/Possess(mob/living/carbon/human/H) - if(!ishuman(H)) - return - var/datum/disease/parrot_possession/P = new - P.parrot = src - forceMove(H) - H.ForceContractDisease(P) - parrot_interest = null - H.visible_message("[src] dive bombs into [H]'s chest and vanishes!", "[src] dive bombs into your chest, vanishing! This can't be good!") - - -/mob/living/simple_animal/parrot/clock_hawk - name = "clock hawk" - desc = "Cbyl jnaan penpxre! Fdhnnnjx!" - icon_state = "clock_hawk_fly" - icon_living = "clock_hawk_fly" - icon_sit = "clock_hawk_sit" - speak = list("Penpxre!", "Ratvar vf n qhzo anzr naljnl!") - speak_emote = list("squawks rustily", "says crassly", "yells brassly") - emote_hear = list("squawks rustily.", "bawks metallically!") - emote_see = list("flutters its metal wings.") - faction = list("ratvar") - gold_core_spawnable = NO_SPAWN - del_on_death = TRUE - death_sound = 'sound/magic/clockwork/anima_fragment_death.ogg' - -/mob/living/simple_animal/parrot/clock_hawk/ratvar_act() - return +/* Parrots! + * Contains + * Defines + * Inventory (headset stuff) + * Attack responces + * AI + * Procs / Verbs (usable by players) + * Sub-types + * Hear & say (the things we do for gimmicks) + */ + +/* + * Defines + */ + +//Only a maximum of one action and one intent should be active at any given time. +//Actions +#define PARROT_PERCH (1<<0) //Sitting/sleeping, not moving +#define PARROT_SWOOP (1<<1) //Moving towards or away from a target +#define PARROT_WANDER (1<<2) //Moving without a specific target in mind + +//Intents +#define PARROT_STEAL (1<<3) //Flying towards a target to steal it/from it +#define PARROT_ATTACK (1<<4) //Flying towards a target to attack it +#define PARROT_RETURN (1<<5) //Flying towards its perch +#define PARROT_FLEE (1<<6) //Flying away from its attacker + + +/mob/living/simple_animal/parrot + name = "parrot" + desc = "The parrot squaks, \"It's a Parrot! BAWWK!\"" //' + icon = 'icons/mob/animal.dmi' + icon_state = "parrot_fly" + icon_living = "parrot_fly" + icon_dead = "parrot_dead" + var/icon_sit = "parrot_sit" + density = FALSE + health = 80 + maxHealth = 80 + pass_flags = PASSTABLE | PASSMOB + + speak = list("Hi!","Hello!","Cracker?","BAWWWWK george mellons griffing me!") + speak_emote = list("squawks","says","yells") + emote_hear = list("squawks.","bawks!") + emote_see = list("flutters its wings.") + + speak_chance = 1 //1% (1 in 100) chance every tick; So about once per 150 seconds, assuming an average tick is 1.5s + turns_per_move = 5 + butcher_results = list(/obj/item/reagent_containers/food/snacks/cracker/ = 1) + melee_damage_upper = 10 + melee_damage_lower = 5 + + response_help = "pets" + response_disarm = "gently moves aside" + response_harm = "swats" + stop_automated_movement = 1 + a_intent = INTENT_HARM //parrots now start "aggressive" since only player parrots will nuzzle. + attacktext = "chomps" + friendly = "grooms" + mob_size = MOB_SIZE_SMALL + movement_type = FLYING + gold_core_spawnable = FRIENDLY_SPAWN + + var/parrot_damage_upper = 10 + var/parrot_state = PARROT_WANDER //Hunt for a perch when created + var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in life() being run every single tick. + var/parrot_sleep_dur = 25 //Same as above, this is the var that physically counts down + var/parrot_dam_zone = list(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_R_LEG) //For humans, select a bodypart to attack + + var/parrot_speed = 5 //"Delay in world ticks between movement." according to byond. Yeah, that's BS but it does directly affect movement. Higher number = slower. + var/parrot_lastmove = null //Updates/Stores position of the parrot while it's moving + var/parrot_stuck = 0 //If parrot_lastmove hasnt changed, this will increment until it reaches parrot_stuck_threshold + var/parrot_stuck_threshold = 10 //if this == parrot_stuck, it'll force the parrot back to wandering + + var/list/speech_buffer = list() + var/speech_shuffle_rate = 20 + var/list/available_channels = list() + + //Headset for Poly to yell at engineers :) + var/obj/item/radio/headset/ears = null + + //The thing the parrot is currently interested in. This gets used for items the parrot wants to pick up, mobs it wants to steal from, + //mobs it wants to attack or mobs that have attacked it + var/atom/movable/parrot_interest = null + + //Parrots will generally sit on their perch unless something catches their eye. + //These vars store their preffered perch and if they dont have one, what they can use as a perch + var/obj/parrot_perch = null + var/obj/desired_perches = list(/obj/structure/frame/computer, /obj/structure/displaycase, \ + /obj/structure/filingcabinet, /obj/machinery/teleport, \ + /obj/machinery/computer, /obj/machinery/clonepod, \ + /obj/machinery/dna_scannernew, /obj/machinery/telecomms, \ + /obj/machinery/nuclearbomb, /obj/machinery/particle_accelerator, \ + /obj/machinery/recharge_station, /obj/machinery/smartfridge, \ + /obj/machinery/suit_storage_unit) + + //Parrots are kleptomaniacs. This variable ... stores the item a parrot is holding. + var/obj/item/held_item = null + + +/mob/living/simple_animal/parrot/Initialize() + . = ..() + if(!ears) + var/headset = pick(/obj/item/radio/headset/headset_sec, \ + /obj/item/radio/headset/headset_eng, \ + /obj/item/radio/headset/headset_med, \ + /obj/item/radio/headset/headset_sci, \ + /obj/item/radio/headset/headset_cargo) + ears = new headset(src) + + parrot_sleep_dur = parrot_sleep_max //In case someone decides to change the max without changing the duration var + + verbs.Add(/mob/living/simple_animal/parrot/proc/steal_from_ground, \ + /mob/living/simple_animal/parrot/proc/steal_from_mob, \ + /mob/living/simple_animal/parrot/verb/drop_held_item_player, \ + /mob/living/simple_animal/parrot/proc/perch_player, \ + /mob/living/simple_animal/parrot/proc/toggle_mode, + /mob/living/simple_animal/parrot/proc/perch_mob_player) + + +/mob/living/simple_animal/parrot/examine(mob/user) + . = ..() + if(stat) + . += pick("This parrot is no more.", "This is a late parrot.", "This is an ex-parrot.") + +/mob/living/simple_animal/parrot/death(gibbed) + if(held_item) + held_item.forceMove(drop_location()) + held_item = null + walk(src,0) + + if(buckled) + buckled.unbuckle_mob(src,force=1) + buckled = null + pixel_x = initial(pixel_x) + pixel_y = initial(pixel_y) + + ..(gibbed) + +/mob/living/simple_animal/parrot/Stat() + ..() + if(statpanel("Status")) + stat("Held Item", held_item) + stat("Mode",a_intent) + +/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode, atom/movable/source) + . = ..() + if(speaker != src && prob(50)) //Dont imitate ourselves + if(!radio_freq || prob(10)) + if(speech_buffer.len >= 500) + speech_buffer -= pick(speech_buffer) + speech_buffer |= html_decode(raw_message) + if(speaker == src && !client) //If a parrot squawks in the woods and no one is around to hear it, does it make a sound? This code says yes! + return message + +/mob/living/simple_animal/parrot/radio(message, message_mode, list/spans, language) //literally copied from human/radio(), but there's no other way to do this. at least it's better than it used to be. + . = ..() + if(. != 0) + return . + + switch(message_mode) + if(MODE_HEADSET) + if (ears) + ears.talk_into(src, message, , spans, language) + return ITALICS | REDUCE_RANGE + + if(MODE_DEPARTMENT) + if (ears) + ears.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + if(message_mode in GLOB.radiochannels) + if(ears) + ears.talk_into(src, message, message_mode, spans, language) + return ITALICS | REDUCE_RANGE + + return 0 + +/* + * Inventory + */ +/mob/living/simple_animal/parrot/show_inv(mob/user) + user.set_machine(src) + + var/dat = "

                Inventory of [name]

                " + dat += "
                Headset: [ears]" : "add_inv=ears'>Nothing"]" + + user << browse(dat, "window=mob[REF(src)];size=325x500") + onclose(user, "window=mob[REF(src)]") + + +/mob/living/simple_animal/parrot/Topic(href, href_list) + if(!(iscarbon(usr) || iscyborg(usr)) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + usr << browse(null, "window=mob[REF(src)]") + usr.unset_machine() + return + + //Removing from inventory + if(href_list["remove_inv"]) + var/remove_from = href_list["remove_inv"] + switch(remove_from) + if("ears") + if(!ears) + to_chat(usr, "There is nothing to remove from its [remove_from]!") + return + if(!stat) + say("[available_channels.len ? "[pick(available_channels)] " : null]BAWWWWWK LEAVE THE HEADSET BAWKKKKK!") + ears.forceMove(drop_location()) + ears = null + for(var/possible_phrase in speak) + if(copytext(possible_phrase,1,3) in GLOB.department_radio_keys) + possible_phrase = copytext(possible_phrase,3) + + //Adding things to inventory + else if(href_list["add_inv"]) + var/add_to = href_list["add_inv"] + if(!usr.get_active_held_item()) + to_chat(usr, "You have nothing in your hand to put on its [add_to]!") + return + switch(add_to) + if("ears") + if(ears) + to_chat(usr, "It's already wearing something!") + return + else + var/obj/item/item_to_add = usr.get_active_held_item() + if(!item_to_add) + return + + if( !istype(item_to_add, /obj/item/radio/headset) ) + to_chat(usr, "This object won't fit!") + return + + var/obj/item/radio/headset/headset_to_add = item_to_add + + if(!usr.transferItemToLoc(headset_to_add, src)) + return + ears = headset_to_add + to_chat(usr, "You fit the headset onto [src].") + + clearlist(available_channels) + for(var/ch in headset_to_add.channels) + switch(ch) + if(RADIO_CHANNEL_ENGINEERING) + available_channels.Add(RADIO_TOKEN_ENGINEERING) + if(RADIO_CHANNEL_COMMAND) + available_channels.Add(RADIO_TOKEN_COMMAND) + if(RADIO_CHANNEL_SECURITY) + available_channels.Add(RADIO_TOKEN_SECURITY) + if(RADIO_CHANNEL_SCIENCE) + available_channels.Add(RADIO_TOKEN_SCIENCE) + if(RADIO_CHANNEL_MEDICAL) + available_channels.Add(RADIO_TOKEN_MEDICAL) + if(RADIO_CHANNEL_SUPPLY) + available_channels.Add(RADIO_TOKEN_SUPPLY) + if(RADIO_CHANNEL_SERVICE) + available_channels.Add(RADIO_TOKEN_SERVICE) + + if(headset_to_add.translate_binary) + available_channels.Add(MODE_TOKEN_BINARY) + else + return ..() + + +/* + * Attack responces + */ +//Humans, monkeys, aliens +/mob/living/simple_animal/parrot/attack_hand(mob/living/carbon/M) + ..() + if(client) + return + if(!stat && M.a_intent == INTENT_HARM) + + icon_state = icon_living //It is going to be flying regardless of whether it flees or attacks + + if(parrot_state == PARROT_PERCH) + parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched + + parrot_interest = M + parrot_state = PARROT_SWOOP //The parrot just got hit, it WILL move, now to pick a direction.. + + if(health > 30) //Let's get in there and squawk it up! + parrot_state |= PARROT_ATTACK + else + parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell! + drop_held_item(0) + if(stat != DEAD && M.a_intent == INTENT_HELP) + handle_automated_speech(1) //assured speak/emote + return + +/mob/living/simple_animal/parrot/attack_paw(mob/living/carbon/monkey/M) + return attack_hand(M) + +/mob/living/simple_animal/parrot/attack_alien(mob/living/carbon/alien/M) + return attack_hand(M) + +//Simple animals +/mob/living/simple_animal/parrot/attack_animal(mob/living/simple_animal/M) + . = ..() //goodbye immortal parrots + + if(client) + return + + if(parrot_state == PARROT_PERCH) + parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched + + if(M.melee_damage_upper > 0 && !stat) + parrot_interest = M + parrot_state = PARROT_SWOOP | PARROT_ATTACK //Attack other animals regardless + icon_state = icon_living + +//Mobs with objects +/mob/living/simple_animal/parrot/attackby(obj/item/O, mob/living/user, params) + if(!stat && !client && !istype(O, /obj/item/stack/medical) && !istype(O, /obj/item/reagent_containers/food/snacks/cracker)) + if(O.force) + if(parrot_state == PARROT_PERCH) + parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched + + parrot_interest = user + parrot_state = PARROT_SWOOP + if(health > 30) //Let's get in there and squawk it up! + parrot_state |= PARROT_ATTACK + else + parrot_state |= PARROT_FLEE + icon_state = icon_living + drop_held_item(0) + else if(istype(O, /obj/item/reagent_containers/food/snacks/cracker)) //Poly wants a cracker. + qdel(O) + if(health < maxHealth) + adjustBruteLoss(-10) + speak_chance *= 1.27 // 20 crackers to go from 1% to 100% + speech_shuffle_rate += 10 + to_chat(user, "[src] eagerly devours the cracker.") + ..() + return + +//Bullets +/mob/living/simple_animal/parrot/bullet_act(obj/item/projectile/Proj) + ..() + if(!stat && !client) + if(parrot_state == PARROT_PERCH) + parrot_sleep_dur = parrot_sleep_max //Reset it's sleep timer if it was perched + + parrot_interest = null + parrot_state = PARROT_WANDER | PARROT_FLEE //Been shot and survived! RUN LIKE HELL! + //parrot_been_shot += 5 + icon_state = icon_living + drop_held_item(0) + return + + +/* + * AI - Not really intelligent, but I'm calling it AI anyway. + */ +/mob/living/simple_animal/parrot/Life() + ..() + + //Sprite update for when a parrot gets pulled + if(pulledby && !stat && parrot_state != PARROT_WANDER) + if(buckled) + buckled.unbuckle_mob(src, TRUE) + buckled = null + icon_state = icon_living + parrot_state = PARROT_WANDER + pixel_x = initial(pixel_x) + pixel_y = initial(pixel_y) + return + + +//-----SPEECH + /* Parrot speech mimickry! + Phrases that the parrot Hear()s get added to speach_buffer. + Every once in a while, the parrot picks one of the lines from the buffer and replaces an element of the 'speech' list. */ +/mob/living/simple_animal/parrot/handle_automated_speech() + ..() + if(speech_buffer.len && prob(speech_shuffle_rate)) //shuffle out a phrase and add in a new one + if(speak.len) + speak.Remove(pick(speak)) + + speak.Add(pick(speech_buffer)) + + +/mob/living/simple_animal/parrot/handle_automated_movement() + if(!isturf(src.loc) || !canmove || buckled) + return //If it can't move, dont let it move. (The buckled check probably isn't necessary thanks to canmove) + + if(client && stat == CONSCIOUS && parrot_state != icon_living) + icon_state = icon_living + //Because the most appropriate place to set icon_state is movement_delay(), clearly + +//-----SLEEPING + if(parrot_state == PARROT_PERCH) + if(parrot_perch && parrot_perch.loc != src.loc) //Make sure someone hasnt moved our perch on us + if(parrot_perch in view(src)) + parrot_state = PARROT_SWOOP | PARROT_RETURN + icon_state = icon_living + return + else + parrot_state = PARROT_WANDER + icon_state = icon_living + return + + if(--parrot_sleep_dur) //Zzz + return + + else + //This way we only call the stuff below once every [sleep_max] ticks. + parrot_sleep_dur = parrot_sleep_max + + //Cycle through message modes for the headset + if(speak.len) + var/list/newspeak = list() + + if(available_channels.len && src.ears) + for(var/possible_phrase in speak) + + //50/50 chance to not use the radio at all + var/useradio = 0 + if(prob(50)) + useradio = 1 + + if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys)) + possible_phrase = "[useradio?pick(available_channels):""][copytext(possible_phrase,3)]" //crop out the channel prefix + else + possible_phrase = "[useradio?pick(available_channels):""][possible_phrase]" + + newspeak.Add(possible_phrase) + + else //If we have no headset or channels to use, dont try to use any! + for(var/possible_phrase in speak) + if((copytext(possible_phrase,1,2) in GLOB.department_radio_prefixes) && (copytext(possible_phrase,2,3) in GLOB.department_radio_keys)) + possible_phrase = copytext(possible_phrase,3) //crop out the channel prefix + newspeak.Add(possible_phrase) + speak = newspeak + + //Search for item to steal + parrot_interest = search_for_item() + if(parrot_interest) + emote("me", EMOTE_VISIBLE, "looks in [parrot_interest]'s direction and takes flight.") + parrot_state = PARROT_SWOOP | PARROT_STEAL + icon_state = icon_living + return + +//-----WANDERING - This is basically a 'I dont know what to do yet' state + else if(parrot_state == PARROT_WANDER) + //Stop movement, we'll set it later + walk(src, 0) + parrot_interest = null + + //Wander around aimlessly. This will help keep the loops from searches down + //and possibly move the mob into a new are in view of something they can use + if(prob(90)) + step(src, pick(GLOB.cardinals)) + return + + if(!held_item && !parrot_perch) //If we've got nothing to do.. look for something to do. + var/atom/movable/AM = search_for_perch_and_item() //This handles checking through lists so we know it's either a perch or stealable item + if(AM) + if(istype(AM, /obj/item) || isliving(AM)) //If stealable item + parrot_interest = AM + emote("me", EMOTE_VISIBLE, "turns and flies towards [parrot_interest].") + parrot_state = PARROT_SWOOP | PARROT_STEAL + return + else //Else it's a perch + parrot_perch = AM + parrot_state = PARROT_SWOOP | PARROT_RETURN + return + return + + if(parrot_interest && parrot_interest in view(src)) + parrot_state = PARROT_SWOOP | PARROT_STEAL + return + + if(parrot_perch && parrot_perch in view(src)) + parrot_state = PARROT_SWOOP | PARROT_RETURN + return + + else //Have an item but no perch? Find one! + parrot_perch = search_for_perch() + if(parrot_perch) + parrot_state = PARROT_SWOOP | PARROT_RETURN + return +//-----STEALING + else if(parrot_state == (PARROT_SWOOP | PARROT_STEAL)) + walk(src,0) + if(!parrot_interest || held_item) + parrot_state = PARROT_SWOOP | PARROT_RETURN + return + + if(!(parrot_interest in view(src))) + parrot_state = PARROT_SWOOP | PARROT_RETURN + return + + if(Adjacent(parrot_interest)) + + if(isliving(parrot_interest)) + steal_from_mob() + + else //This should ensure that we only grab the item we want, and make sure it's not already collected on our perch + if(!parrot_perch || parrot_interest.loc != parrot_perch.loc) + held_item = parrot_interest + parrot_interest.forceMove(src) + visible_message("[src] grabs [held_item]!", "You grab [held_item]!", "You hear the sounds of wings flapping furiously.") + + parrot_interest = null + parrot_state = PARROT_SWOOP | PARROT_RETURN + return + + walk_to(src, parrot_interest, 1, parrot_speed) + if(isStuck()) + return + + return + +//-----RETURNING TO PERCH + else if(parrot_state == (PARROT_SWOOP | PARROT_RETURN)) + walk(src, 0) + if(!parrot_perch || !isturf(parrot_perch.loc)) //Make sure the perch exists and somehow isnt inside of something else. + parrot_perch = null + parrot_state = PARROT_WANDER + return + + if(Adjacent(parrot_perch)) + forceMove(parrot_perch.loc) + drop_held_item() + parrot_state = PARROT_PERCH + icon_state = icon_sit + return + + walk_to(src, parrot_perch, 1, parrot_speed) + if(isStuck()) + return + + return + +//-----FLEEING + else if(parrot_state == (PARROT_SWOOP | PARROT_FLEE)) + walk(src,0) + if(!parrot_interest || !isliving(parrot_interest)) //Sanity + parrot_state = PARROT_WANDER + + walk_away(src, parrot_interest, 1, parrot_speed) + if(isStuck()) + return + + return + +//-----ATTACKING + else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK)) + + //If we're attacking a nothing, an object, a turf or a ghost for some stupid reason, switch to wander + if(!parrot_interest || !isliving(parrot_interest)) + parrot_interest = null + parrot_state = PARROT_WANDER + return + + var/mob/living/L = parrot_interest + if(melee_damage_upper == 0) + melee_damage_upper = parrot_damage_upper + a_intent = INTENT_HARM + + //If the mob is close enough to interact with + if(Adjacent(parrot_interest)) + + //If the mob we've been chasing/attacking dies or falls into crit, check for loot! + if(L.stat) + parrot_interest = null + if(!held_item) + held_item = steal_from_ground() + if(!held_item) + held_item = steal_from_mob() //Apparently it's possible for dead mobs to hang onto items in certain circumstances. + if(parrot_perch in view(src)) //If we have a home nearby, go to it, otherwise find a new home + parrot_state = PARROT_SWOOP | PARROT_RETURN + else + parrot_state = PARROT_WANDER + return + + attacktext = pick("claws at", "chomps") + L.attack_animal(src)//Time for the hurt to begin! + //Otherwise, fly towards the mob! + else + walk_to(src, parrot_interest, 1, parrot_speed) + if(isStuck()) + return + + return +//-----STATE MISHAP + else //This should not happen. If it does lets reset everything and try again + walk(src,0) + parrot_interest = null + parrot_perch = null + drop_held_item() + parrot_state = PARROT_WANDER + return + +/* + * Procs + */ + +/mob/living/simple_animal/parrot/proc/isStuck() + //Check to see if the parrot is stuck due to things like windows or doors or windowdoors + if(parrot_lastmove) + if(parrot_lastmove == src.loc) + if(parrot_stuck_threshold >= ++parrot_stuck) //If it has been stuck for a while, go back to wander. + parrot_state = PARROT_WANDER + parrot_stuck = 0 + parrot_lastmove = null + return 1 + else + parrot_lastmove = null + else + parrot_lastmove = src.loc + return 0 + +/mob/living/simple_animal/parrot/proc/search_for_item() + var/item + for(var/atom/movable/AM in view(src)) + //Skip items we already stole or are wearing or are too big + if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src) + continue + if(istype(AM, /obj/item)) + var/obj/item/I = AM + if(I.w_class < WEIGHT_CLASS_SMALL) + item = I + else if(iscarbon(AM)) + var/mob/living/carbon/C = AM + for(var/obj/item/I in C.held_items) + if(I.w_class <= WEIGHT_CLASS_SMALL) + item = I + break + if(item) + if(!AStar(src, get_turf(item), /turf/proc/Distance_cardinal)) + item = null + continue + return item + + return null + +/mob/living/simple_animal/parrot/proc/search_for_perch() + for(var/obj/O in view(src)) + for(var/path in desired_perches) + if(istype(O, path)) + return O + return null + +//This proc was made to save on doing two 'in view' loops seperatly +/mob/living/simple_animal/parrot/proc/search_for_perch_and_item() + for(var/atom/movable/AM in view(src)) + for(var/perch_path in desired_perches) + if(istype(AM, perch_path)) + return AM + + //Skip items we already stole or are wearing or are too big + if(parrot_perch && AM.loc == parrot_perch.loc || AM.loc == src) + continue + + if(istype(AM, /obj/item)) + var/obj/item/I = AM + if(I.w_class <= WEIGHT_CLASS_SMALL) + return I + + if(iscarbon(AM)) + var/mob/living/carbon/C = AM + for(var/obj/item/I in C.held_items) + if(I.w_class <= WEIGHT_CLASS_SMALL) + return C + return null + + +/* + * Verbs - These are actually procs, but can be used as verbs by player-controlled parrots. + */ +/mob/living/simple_animal/parrot/proc/steal_from_ground() + set name = "Steal from ground" + set category = "Parrot" + set desc = "Grabs a nearby item." + + if(stat) + return -1 + + if(held_item) + to_chat(src, "You are already holding [held_item]!") + return 1 + + for(var/obj/item/I in view(1,src)) + //Make sure we're not already holding it and it's small enough + if(I.loc != src && I.w_class <= WEIGHT_CLASS_SMALL) + + //If we have a perch and the item is sitting on it, continue + if(!client && parrot_perch && I.loc == parrot_perch.loc) + continue + + held_item = I + I.forceMove(src) + visible_message("[src] grabs [held_item]!", "You grab [held_item]!", "You hear the sounds of wings flapping furiously.") + return held_item + + to_chat(src, "There is nothing of interest to take!") + return 0 + +/mob/living/simple_animal/parrot/proc/steal_from_mob() + set name = "Steal from mob" + set category = "Parrot" + set desc = "Steals an item right out of a person's hand!" + + if(stat) + return -1 + + if(held_item) + to_chat(src, "You are already holding [held_item]!") + return 1 + + var/obj/item/stolen_item = null + + for(var/mob/living/carbon/C in view(1,src)) + for(var/obj/item/I in C.held_items) + if(I.w_class <= WEIGHT_CLASS_SMALL) + stolen_item = I + break + + if(stolen_item) + C.transferItemToLoc(stolen_item, src, TRUE) + held_item = stolen_item + visible_message("[src] grabs [held_item] out of [C]'s hand!", "You snag [held_item] out of [C]'s hand!", "You hear the sounds of wings flapping furiously.") + return held_item + + to_chat(src, "There is nothing of interest to take!") + return 0 + +/mob/living/simple_animal/parrot/verb/drop_held_item_player() + set name = "Drop held item" + set category = "Parrot" + set desc = "Drop the item you're holding." + + if(stat) + return + + src.drop_held_item() + + return + +/mob/living/simple_animal/parrot/proc/drop_held_item(drop_gently = 1) + set name = "Drop held item" + set category = "Parrot" + set desc = "Drop the item you're holding." + + if(stat) + return -1 + + if(!held_item) + if(src == usr) //So that other mobs wont make this message appear when they're bludgeoning you. + to_chat(src, "You have nothing to drop!") + return 0 + + +//parrots will eat crackers instead of dropping them + if(istype(held_item, /obj/item/reagent_containers/food/snacks/cracker) && (drop_gently)) + qdel(held_item) + held_item = null + if(health < maxHealth) + adjustBruteLoss(-10) + emote("me", EMOTE_VISIBLE, "[src] eagerly downs the cracker.") + return 1 + + + if(!drop_gently) + if(istype(held_item, /obj/item/grenade)) + var/obj/item/grenade/G = held_item + G.forceMove(drop_location()) + G.prime() + to_chat(src, "You let go of [held_item]!") + held_item = null + return 1 + + to_chat(src, "You drop [held_item].") + + held_item.forceMove(drop_location()) + held_item = null + return 1 + +/mob/living/simple_animal/parrot/proc/perch_player() + set name = "Sit" + set category = "Parrot" + set desc = "Sit on a nice comfy perch." + + if(stat || !client) + return + + if(icon_state == icon_living) + for(var/atom/movable/AM in view(src,1)) + for(var/perch_path in desired_perches) + if(istype(AM, perch_path)) + src.forceMove(AM.loc) + icon_state = icon_sit + parrot_state = PARROT_PERCH + return + to_chat(src, "There is no perch nearby to sit on!") + return + +/mob/living/simple_animal/parrot/Moved(oldLoc, dir) + . = ..() + if(. && !stat && client && parrot_state == PARROT_PERCH) + parrot_state = PARROT_WANDER + icon_state = icon_living + pixel_x = initial(pixel_x) + pixel_y = initial(pixel_y) + +/mob/living/simple_animal/parrot/proc/perch_mob_player() + set name = "Sit on Human's Shoulder" + set category = "Parrot" + set desc = "Sit on a nice comfy human being!" + + if(stat || !client) + return + + if(!buckled) + for(var/mob/living/carbon/human/H in view(src,1)) + if(H.has_buckled_mobs() && H.buckled_mobs.len >= H.max_buckled_mobs) //Already has a parrot, or is being eaten by a slime + continue + perch_on_human(H) + return + to_chat(src, "There is nobody nearby that you can sit on!") + else + icon_state = icon_living + parrot_state = PARROT_WANDER + if(buckled) + to_chat(src, "You are no longer sitting on [buckled]'s shoulder.") + buckled.unbuckle_mob(src, TRUE) + buckled = null + pixel_x = initial(pixel_x) + pixel_y = initial(pixel_y) + + + +/mob/living/simple_animal/parrot/proc/perch_on_human(mob/living/carbon/human/H) + if(!H) + return + forceMove(get_turf(H)) + if(H.buckle_mob(src, TRUE)) + pixel_y = 9 + pixel_x = pick(-8,8) //pick left or right shoulder + icon_state = icon_sit + parrot_state = PARROT_PERCH + to_chat(src, "You sit on [H]'s shoulder.") + + +/mob/living/simple_animal/parrot/proc/toggle_mode() + set name = "Toggle mode" + set category = "Parrot" + set desc = "Time to bear those claws!" + + if(stat || !client) + return + + if(a_intent != INTENT_HELP) + melee_damage_upper = 0 + a_intent = INTENT_HELP + else + melee_damage_upper = parrot_damage_upper + a_intent = INTENT_HARM + to_chat(src, "You will now [a_intent] others.") + return + +/* + * Sub-types + */ +/mob/living/simple_animal/parrot/Poly + name = "Poly" + desc = "Poly the Parrot. An expert on quantum cracker theory." + speak = list("Poly wanna cracker!", ":e Check the crystal, you chucklefucks!",":e Wire the solars, you lazy bums!",":e WHO TOOK THE DAMN HARDSUITS?",":e OH GOD ITS ABOUT TO DELAMINATE CALL THE SHUTTLE") + gold_core_spawnable = NO_SPAWN + speak_chance = 3 + var/memory_saved = FALSE + var/rounds_survived = 0 + var/longest_survival = 0 + var/longest_deathstreak = 0 + +/mob/living/simple_animal/parrot/Poly/Initialize() + ears = new /obj/item/radio/headset/headset_eng(src) + available_channels = list(":e") + Read_Memory() + if(rounds_survived == longest_survival) + speak += pick("...[longest_survival].", "The things I've seen!", "I have lived many lives!", "What are you before me?") + desc += " Old as sin, and just as loud. Claimed to be [rounds_survived]." + speak_chance = 20 //His hubris has made him more annoying/easier to justify killing + add_atom_colour("#EEEE22", FIXED_COLOUR_PRIORITY) + else if(rounds_survived == longest_deathstreak) + speak += pick("What are you waiting for!", "Violence breeds violence!", "Blood! Blood!", "Strike me down if you dare!") + desc += " The squawks of [-rounds_survived] dead parrots ring out in your ears..." + add_atom_colour("#BB7777", FIXED_COLOUR_PRIORITY) + else if(rounds_survived > 0) + speak += pick("...again?", "No, It was over!", "Let me out!", "It never ends!") + desc += " Over [rounds_survived] shifts without a \"terrible\" \"accident\"!" + else + speak += pick("...alive?", "This isn't parrot heaven!", "I live, I die, I live again!", "The void fades!") + + . = ..() + +/mob/living/simple_animal/parrot/Poly/say(message, bubble_type,var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) + . = ..() + if(. && !client && prob(1) && prob(1)) //Only the one true bird may speak across dimensions. + world.TgsTargetedChatBroadcast("A stray squawk is heard... \"[message]\"", FALSE) + +/mob/living/simple_animal/parrot/Poly/Life() + if(!stat && SSticker.current_state == GAME_STATE_FINISHED && !memory_saved) + Write_Memory(FALSE) + memory_saved = TRUE + ..() + +/mob/living/simple_animal/parrot/Poly/death(gibbed) + if(!memory_saved) + Write_Memory(TRUE) + if(rounds_survived == longest_survival || rounds_survived == longest_deathstreak || prob(0.666)) + var/mob/living/simple_animal/parrot/Poly/ghost/G = new(loc) + if(mind) + mind.transfer_to(G) + else + transfer_ckey(G) + ..(gibbed) + +/mob/living/simple_animal/parrot/Poly/proc/Read_Memory() + if(fexists("data/npc_saves/Poly.sav")) //legacy compatability to convert old format to new + var/savefile/S = new /savefile("data/npc_saves/Poly.sav") + S["phrases"] >> speech_buffer + S["roundssurvived"] >> rounds_survived + S["longestsurvival"] >> longest_survival + S["longestdeathstreak"] >> longest_deathstreak + fdel("data/npc_saves/Poly.sav") + else + var/json_file = file("data/npc_saves/Poly.json") + if(!fexists(json_file)) + return + var/list/json = json_decode(file2text(json_file)) + speech_buffer = json["phrases"] + rounds_survived = json["roundssurvived"] + longest_survival = json["longestsurvival"] + longest_deathstreak = json["longestdeathstreak"] + if(!islist(speech_buffer)) + speech_buffer = list() + +/mob/living/simple_animal/parrot/Poly/proc/Write_Memory(dead) + var/json_file = file("data/npc_saves/Poly.json") + var/list/file_data = list() + if(islist(speech_buffer)) + file_data["phrases"] = speech_buffer + if(dead) + file_data["roundssurvived"] = min(rounds_survived - 1, 0) + file_data["longestsurvival"] = longest_survival + if(rounds_survived - 1 < longest_deathstreak) + file_data["longestdeathstreak"] = rounds_survived - 1 + else + file_data["longestdeathstreak"] = longest_deathstreak + else + file_data["roundssurvived"] = rounds_survived + 1 + if(rounds_survived + 1 > longest_survival) + file_data["longestsurvival"] = rounds_survived + 1 + else + file_data["longestsurvival"] = longest_survival + file_data["longestdeathstreak"] = longest_deathstreak + fdel(json_file) + WRITE_FILE(json_file, json_encode(file_data)) + +/mob/living/simple_animal/parrot/Poly/ratvar_act() + playsound(src, 'sound/magic/clockwork/fellowship_armory.ogg', 75, TRUE) + var/mob/living/simple_animal/parrot/clock_hawk/H = new(loc) + H.setDir(dir) + qdel(src) + +/mob/living/simple_animal/parrot/Poly/ghost + name = "The Ghost of Poly" + desc = "Doomed to squawk the Earth." + color = "#FFFFFF77" + speak_chance = 20 + status_flags = GODMODE + incorporeal_move = INCORPOREAL_MOVE_BASIC + butcher_results = list(/obj/item/ectoplasm = 1) + +/mob/living/simple_animal/parrot/Poly/ghost/Initialize() + memory_saved = TRUE //At this point nothing is saved + . = ..() + +/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_speech() + if(ismob(loc)) + return + ..() + +/mob/living/simple_animal/parrot/Poly/ghost/handle_automated_movement() + if(isliving(parrot_interest)) + if(!ishuman(parrot_interest)) + parrot_interest = null + else if(parrot_state == (PARROT_SWOOP | PARROT_ATTACK) && Adjacent(parrot_interest)) + walk_to(src, parrot_interest, 0, parrot_speed) + Possess(parrot_interest) + ..() + +/mob/living/simple_animal/parrot/Poly/ghost/proc/Possess(mob/living/carbon/human/H) + if(!ishuman(H)) + return + var/datum/disease/parrot_possession/P = new + P.parrot = src + forceMove(H) + H.ForceContractDisease(P) + parrot_interest = null + H.visible_message("[src] dive bombs into [H]'s chest and vanishes!", "[src] dive bombs into your chest, vanishing! This can't be good!") + + +/mob/living/simple_animal/parrot/clock_hawk + name = "clock hawk" + desc = "Cbyl jnaan penpxre! Fdhnnnjx!" + icon_state = "clock_hawk_fly" + icon_living = "clock_hawk_fly" + icon_sit = "clock_hawk_sit" + speak = list("Penpxre!", "Ratvar vf n qhzo anzr naljnl!") + speak_emote = list("squawks rustily", "says crassly", "yells brassly") + emote_hear = list("squawks rustily.", "bawks metallically!") + emote_see = list("flutters its metal wings.") + faction = list("ratvar") + gold_core_spawnable = NO_SPAWN + del_on_death = TRUE + death_sound = 'sound/magic/clockwork/anima_fragment_death.ogg' + +/mob/living/simple_animal/parrot/clock_hawk/ratvar_act() + return diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm index 32ef52a6ae..64ffa147cf 100644 --- a/code/modules/mob/living/simple_animal/shade.dm +++ b/code/modules/mob/living/simple_animal/shade.dm @@ -1,65 +1,65 @@ -/mob/living/simple_animal/shade - name = "Shade" - real_name = "Shade" - desc = "A bound spirit." - gender = PLURAL - icon = 'icons/mob/mob.dmi' - icon_state = "shade" - icon_living = "shade" - mob_biotypes = list(MOB_SPIRIT) - maxHealth = 40 - health = 40 - spacewalk = TRUE - healable = 0 - speak_emote = list("hisses") - emote_hear = list("wails.","screeches.") - response_help = "puts their hand through" - response_disarm = "flails at" - response_harm = "punches" - speak_chance = 1 - melee_damage_lower = 5 - melee_damage_upper = 12 - attacktext = "metaphysically strikes" - minbodytemp = 0 - maxbodytemp = INFINITY - 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) - stop_automated_movement = 1 - status_flags = 0 - faction = list("cult") - status_flags = CANPUSH - movement_type = FLYING - loot = list(/obj/item/ectoplasm) - del_on_death = TRUE - initial_language_holder = /datum/language_holder/construct - blood_volume = 0 - -/mob/living/simple_animal/shade/death() - deathmessage = "lets out a contented sigh as [p_their()] form unwinds." - ..() - -/mob/living/simple_animal/shade/canSuicide() - if(istype(loc, /obj/item/soulstone)) //do not suicide inside the soulstone - return 0 - return ..() - -/mob/living/simple_animal/shade/attack_animal(mob/living/simple_animal/M) - if(isconstruct(M)) - var/mob/living/simple_animal/hostile/construct/C = M - if(!C.can_repair_constructs) - return - if(health < maxHealth) - adjustHealth(-25) - Beam(M,icon_state="sendbeam",time=4) - M.visible_message("[M] heals \the [src].", \ - "You heal [src], leaving [src] at [health]/[maxHealth] health.") - else - to_chat(M, "You cannot heal [src], as [p_theyre()] unharmed!") - else if(src != M) - return ..() - -/mob/living/simple_animal/shade/attackby(obj/item/O, mob/user, params) //Marker -Agouri - if(istype(O, /obj/item/soulstone)) - var/obj/item/soulstone/SS = O - SS.transfer_soul("SHADE", src, user) - else - . = ..() +/mob/living/simple_animal/shade + name = "Shade" + real_name = "Shade" + desc = "A bound spirit." + gender = PLURAL + icon = 'icons/mob/mob.dmi' + icon_state = "shade" + icon_living = "shade" + mob_biotypes = list(MOB_SPIRIT) + maxHealth = 40 + health = 40 + spacewalk = TRUE + healable = 0 + speak_emote = list("hisses") + emote_hear = list("wails.","screeches.") + response_help = "puts their hand through" + response_disarm = "flails at" + response_harm = "punches" + speak_chance = 1 + melee_damage_lower = 5 + melee_damage_upper = 12 + attacktext = "metaphysically strikes" + minbodytemp = 0 + maxbodytemp = INFINITY + 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) + stop_automated_movement = 1 + status_flags = 0 + faction = list("cult") + status_flags = CANPUSH + movement_type = FLYING + loot = list(/obj/item/ectoplasm) + del_on_death = TRUE + initial_language_holder = /datum/language_holder/construct + blood_volume = 0 + +/mob/living/simple_animal/shade/death() + deathmessage = "lets out a contented sigh as [p_their()] form unwinds." + ..() + +/mob/living/simple_animal/shade/canSuicide() + if(istype(loc, /obj/item/soulstone)) //do not suicide inside the soulstone + return 0 + return ..() + +/mob/living/simple_animal/shade/attack_animal(mob/living/simple_animal/M) + if(isconstruct(M)) + var/mob/living/simple_animal/hostile/construct/C = M + if(!C.can_repair_constructs) + return + if(health < maxHealth) + adjustHealth(-25) + Beam(M,icon_state="sendbeam",time=4) + M.visible_message("[M] heals \the [src].", \ + "You heal [src], leaving [src] at [health]/[maxHealth] health.") + else + to_chat(M, "You cannot heal [src], as [p_theyre()] unharmed!") + else if(src != M) + return ..() + +/mob/living/simple_animal/shade/attackby(obj/item/O, mob/user, params) //Marker -Agouri + if(istype(O, /obj/item/soulstone)) + var/obj/item/soulstone/SS = O + SS.transfer_soul("SHADE", src, user) + else + . = ..() diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 990fa008fe..0d146df04c 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -1,589 +1,589 @@ -/mob/living/simple_animal - name = "animal" - icon = 'icons/mob/animal.dmi' - health = 20 - maxHealth = 20 - gender = PLURAL //placeholder - blood_volume = 550 //How much blud it has for bloodsucking - - status_flags = CANPUSH - - var/icon_living = "" - var/icon_dead = "" //icon when the animal is dead. Don't use animated icons for this. - var/icon_gib = null //We only try to show a gibbing animation if this exists. - - var/list/speak = list() - var/list/speak_emote = list()// Emotes while speaking IE: Ian [emote], [text] -- Ian barks, "WOOF!". Spoken text is generated from the speak variable. - var/speak_chance = 0 - var/list/emote_hear = list() //Hearable emotes - var/list/emote_see = list() //Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps - - var/turns_per_move = 1 - var/turns_since_move = 0 - var/stop_automated_movement = 0 //Use this to temporarely stop random movement or to if you write special movement code for animals. - var/wander = 1 // Does the mob wander around when idle? - var/stop_automated_movement_when_pulled = 1 //When set to 1 this stops the animal from moving when someone is pulling it. - - //Interaction - var/response_help = "pokes" - var/response_disarm = "shoves" - var/response_harm = "hits" - var/harm_intent_damage = 3 - var/force_threshold = 0 //Minimum force required to deal any damage - - //Temperature effect - var/minbodytemp = 250 - var/maxbodytemp = 350 - - //Healable by medical stacks? Defaults to yes. - var/healable = 1 - - //Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage - var/list/atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) //Leaving something at 0 means it's off - has no maximum - var/unsuitable_atmos_damage = 2 //This damage is taken when atmos doesn't fit all the requirements above - - //LETTING SIMPLE ANIMALS ATTACK? WHAT COULD GO WRONG. Defaults to zero so Ian can still be cuddly - var/melee_damage_lower = 0 - var/melee_damage_upper = 0 - var/obj_damage = 0 //how much damage this simple animal does to objects, if any - var/armour_penetration = 0 //How much armour they ignore, as a flat reduction from the targets armour value - var/melee_damage_type = BRUTE //Damage type of a simple mob's melee attack, should it do damage. - var/list/damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) // 1 for full damage , 0 for none , -1 for 1:1 heal from that source - var/attacktext = "attacks" - var/attack_sound = null - var/friendly = "nuzzles" //If the mob does no damage with it's attack - var/environment_smash = ENVIRONMENT_SMASH_NONE //Set to 1 to allow breaking of crates,lockers,racks,tables; 2 for walls; 3 for Rwalls - - var/speed = 1 //LETS SEE IF I CAN SET SPEEDS FOR SIMPLE MOBS WITHOUT DESTROYING EVERYTHING. Higher speed is slower, negative speed is faster - - //Hot simple_animal baby making vars - var/list/childtype = null - var/next_scan_time = 0 - var/animal_species //Sorry, no spider+corgi buttbabies. - - //simple_animal access - var/obj/item/card/id/access_card = null //innate access uses an internal ID card - var/buffed = 0 //In the event that you want to have a buffing effect on the mob, but don't want it to stack with other effects, any outside force that applies a buff to a simple mob should at least set this to 1, so we have something to check against - var/gold_core_spawnable = NO_SPAWN //If the mob can be spawned with a gold slime core. HOSTILE_SPAWN are spawned with plasma, FRIENDLY_SPAWN are spawned with blood - - var/mob/living/simple_animal/hostile/spawner/nest - - var/sentience_type = SENTIENCE_ORGANIC // Sentience type, for slime potions - - var/list/loot = list() //list of things spawned at mob's loc when it dies - var/del_on_death = 0 //causes mob to be deleted on death, useful for mobs that spawn lootable corpses - var/deathmessage = "" - var/death_sound = null //The sound played on death - - var/allow_movement_on_non_turfs = FALSE - - var/attacked_sound = "punch" //Played when someone punches the creature - - var/dextrous = FALSE //If the creature has, and can use, hands - var/dextrous_hud_type = /datum/hud/dextrous - var/datum/personal_crafting/handcrafting - - var/AIStatus = AI_ON //The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever), AI_Z_OFF (Temporarily off due to nonpresence of players) - - var/shouldwakeup = FALSE //convenience var for forcibly waking up an idling AI on next check. - - //domestication - var/tame = 0 - - var/my_z // I don't want to confuse this with client registered_z - - var/do_footstep = FALSE - -/mob/living/simple_animal/Initialize() - . = ..() - GLOB.simple_animals[AIStatus] += src - handcrafting = new() - if(gender == PLURAL) - gender = pick(MALE,FEMALE) - if(!real_name) - real_name = name - if(!loc) - stack_trace("Simple animal being instantiated in nullspace") - update_simplemob_varspeed() - -/mob/living/simple_animal/Destroy() - GLOB.simple_animals[AIStatus] -= src - if (SSnpcpool.state == SS_PAUSED && LAZYLEN(SSnpcpool.currentrun)) - SSnpcpool.currentrun -= src - - if(nest) - nest.spawned_mobs -= src - nest = null - - var/turf/T = get_turf(src) - if (T && AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src - - return ..() - -/mob/living/simple_animal/initialize_footstep() - if(do_footstep) - ..() - -/mob/living/simple_animal/updatehealth() - ..() - health = CLAMP(health, 0, maxHealth) - -/mob/living/simple_animal/update_stat() - if(status_flags & GODMODE) - return - if(stat != DEAD) - if(health <= 0) - death() - else - stat = CONSCIOUS - med_hud_set_status() - - -/mob/living/simple_animal/handle_status_effects() - ..() - if(stuttering) - stuttering = 0 - -/mob/living/simple_animal/proc/handle_automated_action() - set waitfor = FALSE - return - -/mob/living/simple_animal/proc/handle_automated_movement() - set waitfor = FALSE - if(!stop_automated_movement && wander) - if((isturf(src.loc) || allow_movement_on_non_turfs) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. - turns_since_move++ - if(turns_since_move >= turns_per_move) - if(!(stop_automated_movement_when_pulled && pulledby)) //Some animals don't move when pulled - var/anydir = pick(GLOB.cardinals) - if(Process_Spacemove(anydir)) - Move(get_step(src, anydir), anydir) - turns_since_move = 0 - return 1 - -/mob/living/simple_animal/proc/handle_automated_speech(var/override) - set waitfor = FALSE - if(speak_chance) - if(prob(speak_chance) || override) - if(speak && speak.len) - if((emote_hear && emote_hear.len) || (emote_see && emote_see.len)) - var/length = speak.len - if(emote_hear && emote_hear.len) - length += emote_hear.len - if(emote_see && emote_see.len) - length += emote_see.len - var/randomValue = rand(1,length) - if(randomValue <= speak.len) - say(pick(speak), forced = "poly") - else - randomValue -= speak.len - if(emote_see && randomValue <= emote_see.len) - emote("me [pick(emote_see)]", 1) - else - emote("me [pick(emote_hear)]", 2) - else - say(pick(speak), forced = "poly") - else - if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - emote("me", EMOTE_VISIBLE, pick(emote_see)) - if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) - emote("me", EMOTE_AUDIBLE, pick(emote_hear)) - if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - var/length = emote_hear.len + emote_see.len - var/pick = rand(1,length) - if(pick <= emote_see.len) - emote("me", EMOTE_VISIBLE, pick(emote_see)) - else - emote("me", EMOTE_AUDIBLE, pick(emote_hear)) - - -/mob/living/simple_animal/proc/environment_is_safe(datum/gas_mixture/environment, check_temp = FALSE) - . = TRUE - - if(pulledby && pulledby.grab_state >= GRAB_KILL && atmos_requirements["min_oxy"]) - . = FALSE //getting choked - - if(isturf(src.loc) && isopenturf(src.loc)) - var/turf/open/ST = src.loc - if(ST.air) - var/ST_gases = ST.air.gases - - var/tox = ST_gases[/datum/gas/plasma] - var/oxy = ST_gases[/datum/gas/oxygen] - var/n2 = ST_gases[/datum/gas/nitrogen] - var/co2 = ST_gases[/datum/gas/carbon_dioxide] - - GAS_GARBAGE_COLLECT(ST.air.gases) - - if(atmos_requirements["min_oxy"] && oxy < atmos_requirements["min_oxy"]) - . = FALSE - else if(atmos_requirements["max_oxy"] && oxy > atmos_requirements["max_oxy"]) - . = FALSE - else if(atmos_requirements["min_tox"] && tox < atmos_requirements["min_tox"]) - . = FALSE - else if(atmos_requirements["max_tox"] && tox > atmos_requirements["max_tox"]) - . = FALSE - else if(atmos_requirements["min_n2"] && n2 < atmos_requirements["min_n2"]) - . = FALSE - else if(atmos_requirements["max_n2"] && n2 > atmos_requirements["max_n2"]) - . = FALSE - else if(atmos_requirements["min_co2"] && co2 < atmos_requirements["min_co2"]) - . = FALSE - else if(atmos_requirements["max_co2"] && co2 > atmos_requirements["max_co2"]) - . = FALSE - else - if(atmos_requirements["min_oxy"] || atmos_requirements["min_tox"] || atmos_requirements["min_n2"] || atmos_requirements["min_co2"]) - . = FALSE - - if(check_temp) - var/areatemp = get_temperature(environment) - if((areatemp < minbodytemp) || (areatemp > maxbodytemp)) - . = FALSE - - -/mob/living/simple_animal/handle_environment(datum/gas_mixture/environment) - var/atom/A = src.loc - if(isturf(A)) - var/areatemp = get_temperature(environment) - if( abs(areatemp - bodytemperature) > 5) - var/diff = areatemp - bodytemperature - diff = diff / 5 - adjust_bodytemperature(diff) - - if(!environment_is_safe(environment)) - adjustHealth(unsuitable_atmos_damage) - - handle_temperature_damage() - -/mob/living/simple_animal/proc/handle_temperature_damage() - if((bodytemperature < minbodytemp) || (bodytemperature > maxbodytemp)) - adjustHealth(unsuitable_atmos_damage) - -/mob/living/simple_animal/gib() - if(butcher_results) - var/atom/Tsec = drop_location() - for(var/path in butcher_results) - for(var/i in 1 to butcher_results[path]) - new path(Tsec) - ..() - -/mob/living/simple_animal/gib_animation() - if(icon_gib) - new /obj/effect/temp_visual/gib_animation/animal(loc, icon_gib) - -/mob/living/simple_animal/say_mod(input, message_mode) - if(speak_emote && speak_emote.len) - verb_say = pick(speak_emote) - . = ..() - -/mob/living/simple_animal/emote(act, m_type=1, message = null, intentional = FALSE) - if(stat) - return - if(act == "scream") - message = "makes a loud and pained whimper." //ugly hack to stop animals screaming when crushed :P - act = "me" - ..(act, m_type, message) - -/mob/living/simple_animal/proc/set_varspeed(var_value) - speed = var_value - update_simplemob_varspeed() - -/mob/living/simple_animal/proc/update_simplemob_varspeed() - if(speed == 0) - remove_movespeed_modifier(MOVESPEED_ID_SIMPLEMOB_VARSPEED, TRUE) - add_movespeed_modifier(MOVESPEED_ID_SIMPLEMOB_VARSPEED, TRUE, 100, multiplicative_slowdown = speed, override = TRUE) - -/mob/living/simple_animal/Stat() - ..() - if(statpanel("Status")) - stat(null, "Health: [round((health / maxHealth) * 100)]%") - return 1 - -/mob/living/simple_animal/proc/drop_loot() - if(loot.len) - for(var/i in loot) - new i(loc) - -/mob/living/simple_animal/death(gibbed) - movement_type &= ~FLYING - if(nest) - nest.spawned_mobs -= src - nest = null - drop_loot() - if(dextrous) - drop_all_held_items() - if(!gibbed) - if(death_sound) - playsound(get_turf(src),death_sound, 200, 1) - if(deathmessage || !del_on_death) - emote("deathgasp") - if(del_on_death) - ..() - //Prevent infinite loops if the mob Destroy() is overridden in such - //a manner as to cause a call to death() again - del_on_death = FALSE - qdel(src) - else - health = 0 - icon_state = icon_dead - density = FALSE - lying = 1 - ..() - -/mob/living/simple_animal/proc/CanAttack(atom/the_target) - if(see_invisible < the_target.invisibility) - return FALSE - if(ismob(the_target)) - var/mob/M = the_target - if(M.status_flags & GODMODE) - return FALSE - if (isliving(the_target)) - var/mob/living/L = the_target - if(L.stat != CONSCIOUS) - return FALSE - if (ismecha(the_target)) - var/obj/mecha/M = the_target - if (M.occupant) - return FALSE - return TRUE - -/mob/living/simple_animal/handle_fire() - return - -/mob/living/simple_animal/IgniteMob() - return FALSE - -/mob/living/simple_animal/ExtinguishMob() - return - -/mob/living/simple_animal/revive(full_heal = 0, admin_revive = 0) - if(..()) //successfully ressuscitated from death - icon = initial(icon) - icon_state = icon_living - density = initial(density) - lying = 0 - . = 1 - setMovetype(initial(movement_type)) - -/mob/living/simple_animal/proc/make_babies() // <3 <3 <3 - if(gender != FEMALE || stat || next_scan_time > world.time || !childtype || !animal_species || !SSticker.IsRoundInProgress()) - return - next_scan_time = world.time + 400 - var/alone = 1 - var/mob/living/simple_animal/partner - var/children = 0 - for(var/mob/M in view(7, src)) - if(M.stat != CONSCIOUS) //Check if it's conscious FIRST. - continue - else if(istype(M, childtype)) //Check for children SECOND. - children++ - else if(istype(M, animal_species)) - if(M.ckey) - continue - else if(!istype(M, childtype) && M.gender == MALE) //Better safe than sorry ;_; - partner = M - - else if(isliving(M) && !faction_check_mob(M)) //shyness check. we're not shy in front of things that share a faction with us. - return //we never mate when not alone, so just abort early - - if(alone && partner && children < 3) - var/childspawn = pickweight(childtype) - var/turf/target = get_turf(loc) - if(target) - return new childspawn(target) - -/mob/living/simple_animal/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) - if(incapacitated()) - to_chat(src, "You can't do that right now!") - return FALSE - if(be_close && !in_range(M, src)) - to_chat(src, "You are too far away!") - return FALSE - if(!(no_dextery || dextrous)) - to_chat(src, "You don't have the dexterity to do this!") - return FALSE - return TRUE - -/mob/living/simple_animal/stripPanelUnequip(obj/item/what, mob/who, where) - if(!canUseTopic(who, BE_CLOSE)) - return - else - ..() - -/mob/living/simple_animal/stripPanelEquip(obj/item/what, mob/who, where) - if(!canUseTopic(who, BE_CLOSE)) - return - else - ..() - -/mob/living/simple_animal/update_canmove(value_otherwise = TRUE) - if(IsUnconscious() || IsStun() || IsKnockdown() || stat || resting) - drop_all_held_items() - canmove = FALSE - else if(buckled) - canmove = FALSE - else - canmove = value_otherwise - update_transform() - update_action_buttons_icon() - return canmove - -/mob/living/simple_animal/update_transform() - var/matrix/ntransform = matrix(transform) //aka transform.Copy() - var/changed = 0 - - if(resize != RESIZE_DEFAULT_SIZE) - changed++ - ntransform.Scale(resize) - resize = RESIZE_DEFAULT_SIZE - - if(changed) - animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) - -/mob/living/simple_animal/proc/sentience_act() //Called when a simple animal gains sentience via gold slime potion - toggle_ai(AI_OFF) // To prevent any weirdness. - -/mob/living/simple_animal/update_sight() - if(!client) - return - if(stat == DEAD) - sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS) - see_in_dark = 8 - see_invisible = SEE_INVISIBLE_OBSERVER - return - - see_invisible = initial(see_invisible) - see_in_dark = initial(see_in_dark) - sight = initial(sight) - - if(client.eye != src) - var/atom/A = client.eye - if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. - return - sync_lighting_plane_alpha() - -/mob/living/simple_animal/get_idcard(hand_first = TRUE) - return ..() || access_card - -/mob/living/simple_animal/OpenCraftingMenu() - if(dextrous) - handcrafting.ui_interact(src) - -/mob/living/simple_animal/can_hold_items() - return dextrous - -/mob/living/simple_animal/IsAdvancedToolUser() - return dextrous - -/mob/living/simple_animal/activate_hand(selhand) - if(!dextrous) - return ..() - if(!selhand) - selhand = (active_hand_index % held_items.len)+1 - if(istext(selhand)) - selhand = lowertext(selhand) - if(selhand == "right" || selhand == "r") - selhand = 2 - if(selhand == "left" || selhand == "l") - selhand = 1 - if(selhand != active_hand_index) - swap_hand(selhand) - else - mode() - -/mob/living/simple_animal/swap_hand(hand_index) - if(!dextrous) - return ..() - if(!hand_index) - hand_index = (active_hand_index % held_items.len)+1 - var/obj/item/held_item = get_active_held_item() - if(held_item) - if(istype(held_item, /obj/item/twohanded)) - var/obj/item/twohanded/T = held_item - if(T.wielded == 1) - to_chat(usr, "Your other hand is too busy holding the [T.name].") - return - var/oindex = active_hand_index - active_hand_index = hand_index - if(hud_used) - var/obj/screen/inventory/hand/H - H = hud_used.hand_slots["[hand_index]"] - if(H) - H.update_icon() - H = hud_used.hand_slots["[oindex]"] - if(H) - H.update_icon() - -/mob/living/simple_animal/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE) - . = ..(I, del_on_fail, merge_stacks) - update_inv_hands() - -/mob/living/simple_animal/update_inv_hands() - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - var/obj/item/l_hand = get_item_for_held_index(1) - var/obj/item/r_hand = get_item_for_held_index(2) - if(r_hand) - r_hand.layer = ABOVE_HUD_LAYER - r_hand.plane = ABOVE_HUD_PLANE - r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand)) - client.screen |= r_hand - if(l_hand) - l_hand.layer = ABOVE_HUD_LAYER - l_hand.plane = ABOVE_HUD_PLANE - l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand)) - client.screen |= l_hand - -//ANIMAL RIDING - -/mob/living/simple_animal/user_buckle_mob(mob/living/M, mob/user) - var/datum/component/riding/riding_datum = GetComponent(/datum/component/riding) - if(riding_datum) - if(user.incapacitated()) - return - for(var/atom/movable/A in get_turf(src)) - if(A != src && A != M && A.density) - return - M.forceMove(get_turf(src)) - return ..() - -/mob/living/simple_animal/relaymove(mob/user, direction) - var/datum/component/riding/riding_datum = GetComponent(/datum/component/riding) - if(tame && riding_datum) - riding_datum.handle_ride(user, direction) - -/mob/living/simple_animal/buckle_mob(mob/living/buckled_mob, force = 0, check_loc = 1) - . = ..() - LoadComponent(/datum/component/riding) - -/mob/living/simple_animal/proc/toggle_ai(togglestatus) - if (AIStatus != togglestatus) - if (togglestatus > 0 && togglestatus < 5) - if (togglestatus == AI_Z_OFF || AIStatus == AI_Z_OFF) - var/turf/T = get_turf(src) - if (AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src - else - SSidlenpcpool.idle_mobs_by_zlevel[T.z] += src - GLOB.simple_animals[AIStatus] -= src - GLOB.simple_animals[togglestatus] += src - AIStatus = togglestatus - else - stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") - -/mob/living/simple_animal/proc/consider_wakeup() - if (pulledby || shouldwakeup) - toggle_ai(AI_ON) - -/mob/living/simple_animal/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(!ckey && !stat)//Not unconscious - if(AIStatus == AI_IDLE) - toggle_ai(AI_ON) - - -/mob/living/simple_animal/onTransitZ(old_z, new_z) - ..() - if (AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[old_z] -= src - toggle_ai(initial(AIStatus)) +/mob/living/simple_animal + name = "animal" + icon = 'icons/mob/animal.dmi' + health = 20 + maxHealth = 20 + gender = PLURAL //placeholder + blood_volume = 550 //How much blud it has for bloodsucking + + status_flags = CANPUSH + + var/icon_living = "" + var/icon_dead = "" //icon when the animal is dead. Don't use animated icons for this. + var/icon_gib = null //We only try to show a gibbing animation if this exists. + + var/list/speak = list() + var/list/speak_emote = list()// Emotes while speaking IE: Ian [emote], [text] -- Ian barks, "WOOF!". Spoken text is generated from the speak variable. + var/speak_chance = 0 + var/list/emote_hear = list() //Hearable emotes + var/list/emote_see = list() //Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps + + var/turns_per_move = 1 + var/turns_since_move = 0 + var/stop_automated_movement = 0 //Use this to temporarely stop random movement or to if you write special movement code for animals. + var/wander = 1 // Does the mob wander around when idle? + var/stop_automated_movement_when_pulled = 1 //When set to 1 this stops the animal from moving when someone is pulling it. + + //Interaction + var/response_help = "pokes" + var/response_disarm = "shoves" + var/response_harm = "hits" + var/harm_intent_damage = 3 + var/force_threshold = 0 //Minimum force required to deal any damage + + //Temperature effect + var/minbodytemp = 250 + var/maxbodytemp = 350 + + //Healable by medical stacks? Defaults to yes. + var/healable = 1 + + //Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage + var/list/atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) //Leaving something at 0 means it's off - has no maximum + var/unsuitable_atmos_damage = 2 //This damage is taken when atmos doesn't fit all the requirements above + + //LETTING SIMPLE ANIMALS ATTACK? WHAT COULD GO WRONG. Defaults to zero so Ian can still be cuddly + var/melee_damage_lower = 0 + var/melee_damage_upper = 0 + var/obj_damage = 0 //how much damage this simple animal does to objects, if any + var/armour_penetration = 0 //How much armour they ignore, as a flat reduction from the targets armour value + var/melee_damage_type = BRUTE //Damage type of a simple mob's melee attack, should it do damage. + var/list/damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) // 1 for full damage , 0 for none , -1 for 1:1 heal from that source + var/attacktext = "attacks" + var/attack_sound = null + var/friendly = "nuzzles" //If the mob does no damage with it's attack + var/environment_smash = ENVIRONMENT_SMASH_NONE //Set to 1 to allow breaking of crates,lockers,racks,tables; 2 for walls; 3 for Rwalls + + var/speed = 1 //LETS SEE IF I CAN SET SPEEDS FOR SIMPLE MOBS WITHOUT DESTROYING EVERYTHING. Higher speed is slower, negative speed is faster + + //Hot simple_animal baby making vars + var/list/childtype = null + var/next_scan_time = 0 + var/animal_species //Sorry, no spider+corgi buttbabies. + + //simple_animal access + var/obj/item/card/id/access_card = null //innate access uses an internal ID card + var/buffed = 0 //In the event that you want to have a buffing effect on the mob, but don't want it to stack with other effects, any outside force that applies a buff to a simple mob should at least set this to 1, so we have something to check against + var/gold_core_spawnable = NO_SPAWN //If the mob can be spawned with a gold slime core. HOSTILE_SPAWN are spawned with plasma, FRIENDLY_SPAWN are spawned with blood + + var/mob/living/simple_animal/hostile/spawner/nest + + var/sentience_type = SENTIENCE_ORGANIC // Sentience type, for slime potions + + var/list/loot = list() //list of things spawned at mob's loc when it dies + var/del_on_death = 0 //causes mob to be deleted on death, useful for mobs that spawn lootable corpses + var/deathmessage = "" + var/death_sound = null //The sound played on death + + var/allow_movement_on_non_turfs = FALSE + + var/attacked_sound = "punch" //Played when someone punches the creature + + var/dextrous = FALSE //If the creature has, and can use, hands + var/dextrous_hud_type = /datum/hud/dextrous + var/datum/personal_crafting/handcrafting + + var/AIStatus = AI_ON //The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever), AI_Z_OFF (Temporarily off due to nonpresence of players) + + var/shouldwakeup = FALSE //convenience var for forcibly waking up an idling AI on next check. + + //domestication + var/tame = 0 + + var/my_z // I don't want to confuse this with client registered_z + + var/do_footstep = FALSE + +/mob/living/simple_animal/Initialize() + . = ..() + GLOB.simple_animals[AIStatus] += src + handcrafting = new() + if(gender == PLURAL) + gender = pick(MALE,FEMALE) + if(!real_name) + real_name = name + if(!loc) + stack_trace("Simple animal being instantiated in nullspace") + update_simplemob_varspeed() + +/mob/living/simple_animal/Destroy() + GLOB.simple_animals[AIStatus] -= src + if (SSnpcpool.state == SS_PAUSED && LAZYLEN(SSnpcpool.currentrun)) + SSnpcpool.currentrun -= src + + if(nest) + nest.spawned_mobs -= src + nest = null + + var/turf/T = get_turf(src) + if (T && AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src + + return ..() + +/mob/living/simple_animal/initialize_footstep() + if(do_footstep) + ..() + +/mob/living/simple_animal/updatehealth() + ..() + health = CLAMP(health, 0, maxHealth) + +/mob/living/simple_animal/update_stat() + if(status_flags & GODMODE) + return + if(stat != DEAD) + if(health <= 0) + death() + else + stat = CONSCIOUS + med_hud_set_status() + + +/mob/living/simple_animal/handle_status_effects() + ..() + if(stuttering) + stuttering = 0 + +/mob/living/simple_animal/proc/handle_automated_action() + set waitfor = FALSE + return + +/mob/living/simple_animal/proc/handle_automated_movement() + set waitfor = FALSE + if(!stop_automated_movement && wander) + if((isturf(src.loc) || allow_movement_on_non_turfs) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. + turns_since_move++ + if(turns_since_move >= turns_per_move) + if(!(stop_automated_movement_when_pulled && pulledby)) //Some animals don't move when pulled + var/anydir = pick(GLOB.cardinals) + if(Process_Spacemove(anydir)) + Move(get_step(src, anydir), anydir) + turns_since_move = 0 + return 1 + +/mob/living/simple_animal/proc/handle_automated_speech(var/override) + set waitfor = FALSE + if(speak_chance) + if(prob(speak_chance) || override) + if(speak && speak.len) + if((emote_hear && emote_hear.len) || (emote_see && emote_see.len)) + var/length = speak.len + if(emote_hear && emote_hear.len) + length += emote_hear.len + if(emote_see && emote_see.len) + length += emote_see.len + var/randomValue = rand(1,length) + if(randomValue <= speak.len) + say(pick(speak), forced = "poly") + else + randomValue -= speak.len + if(emote_see && randomValue <= emote_see.len) + emote("me [pick(emote_see)]", 1) + else + emote("me [pick(emote_hear)]", 2) + else + say(pick(speak), forced = "poly") + else + if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) + emote("me", EMOTE_VISIBLE, pick(emote_see)) + if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) + emote("me", EMOTE_AUDIBLE, pick(emote_hear)) + if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) + var/length = emote_hear.len + emote_see.len + var/pick = rand(1,length) + if(pick <= emote_see.len) + emote("me", EMOTE_VISIBLE, pick(emote_see)) + else + emote("me", EMOTE_AUDIBLE, pick(emote_hear)) + + +/mob/living/simple_animal/proc/environment_is_safe(datum/gas_mixture/environment, check_temp = FALSE) + . = TRUE + + if(pulledby && pulledby.grab_state >= GRAB_KILL && atmos_requirements["min_oxy"]) + . = FALSE //getting choked + + if(isturf(src.loc) && isopenturf(src.loc)) + var/turf/open/ST = src.loc + if(ST.air) + var/ST_gases = ST.air.gases + + var/tox = ST_gases[/datum/gas/plasma] + var/oxy = ST_gases[/datum/gas/oxygen] + var/n2 = ST_gases[/datum/gas/nitrogen] + var/co2 = ST_gases[/datum/gas/carbon_dioxide] + + GAS_GARBAGE_COLLECT(ST.air.gases) + + if(atmos_requirements["min_oxy"] && oxy < atmos_requirements["min_oxy"]) + . = FALSE + else if(atmos_requirements["max_oxy"] && oxy > atmos_requirements["max_oxy"]) + . = FALSE + else if(atmos_requirements["min_tox"] && tox < atmos_requirements["min_tox"]) + . = FALSE + else if(atmos_requirements["max_tox"] && tox > atmos_requirements["max_tox"]) + . = FALSE + else if(atmos_requirements["min_n2"] && n2 < atmos_requirements["min_n2"]) + . = FALSE + else if(atmos_requirements["max_n2"] && n2 > atmos_requirements["max_n2"]) + . = FALSE + else if(atmos_requirements["min_co2"] && co2 < atmos_requirements["min_co2"]) + . = FALSE + else if(atmos_requirements["max_co2"] && co2 > atmos_requirements["max_co2"]) + . = FALSE + else + if(atmos_requirements["min_oxy"] || atmos_requirements["min_tox"] || atmos_requirements["min_n2"] || atmos_requirements["min_co2"]) + . = FALSE + + if(check_temp) + var/areatemp = get_temperature(environment) + if((areatemp < minbodytemp) || (areatemp > maxbodytemp)) + . = FALSE + + +/mob/living/simple_animal/handle_environment(datum/gas_mixture/environment) + var/atom/A = src.loc + if(isturf(A)) + var/areatemp = get_temperature(environment) + if( abs(areatemp - bodytemperature) > 5) + var/diff = areatemp - bodytemperature + diff = diff / 5 + adjust_bodytemperature(diff) + + if(!environment_is_safe(environment)) + adjustHealth(unsuitable_atmos_damage) + + handle_temperature_damage() + +/mob/living/simple_animal/proc/handle_temperature_damage() + if((bodytemperature < minbodytemp) || (bodytemperature > maxbodytemp)) + adjustHealth(unsuitable_atmos_damage) + +/mob/living/simple_animal/gib() + if(butcher_results) + var/atom/Tsec = drop_location() + for(var/path in butcher_results) + for(var/i in 1 to butcher_results[path]) + new path(Tsec) + ..() + +/mob/living/simple_animal/gib_animation() + if(icon_gib) + new /obj/effect/temp_visual/gib_animation/animal(loc, icon_gib) + +/mob/living/simple_animal/say_mod(input, message_mode) + if(speak_emote && speak_emote.len) + verb_say = pick(speak_emote) + . = ..() + +/mob/living/simple_animal/emote(act, m_type=1, message = null, intentional = FALSE) + if(stat) + return + if(act == "scream") + message = "makes a loud and pained whimper." //ugly hack to stop animals screaming when crushed :P + act = "me" + ..(act, m_type, message) + +/mob/living/simple_animal/proc/set_varspeed(var_value) + speed = var_value + update_simplemob_varspeed() + +/mob/living/simple_animal/proc/update_simplemob_varspeed() + if(speed == 0) + remove_movespeed_modifier(MOVESPEED_ID_SIMPLEMOB_VARSPEED, TRUE) + add_movespeed_modifier(MOVESPEED_ID_SIMPLEMOB_VARSPEED, TRUE, 100, multiplicative_slowdown = speed, override = TRUE) + +/mob/living/simple_animal/Stat() + ..() + if(statpanel("Status")) + stat(null, "Health: [round((health / maxHealth) * 100)]%") + return 1 + +/mob/living/simple_animal/proc/drop_loot() + if(loot.len) + for(var/i in loot) + new i(loc) + +/mob/living/simple_animal/death(gibbed) + movement_type &= ~FLYING + if(nest) + nest.spawned_mobs -= src + nest = null + drop_loot() + if(dextrous) + drop_all_held_items() + if(!gibbed) + if(death_sound) + playsound(get_turf(src),death_sound, 200, 1) + if(deathmessage || !del_on_death) + emote("deathgasp") + if(del_on_death) + ..() + //Prevent infinite loops if the mob Destroy() is overridden in such + //a manner as to cause a call to death() again + del_on_death = FALSE + qdel(src) + else + health = 0 + icon_state = icon_dead + density = FALSE + lying = 1 + ..() + +/mob/living/simple_animal/proc/CanAttack(atom/the_target) + if(see_invisible < the_target.invisibility) + return FALSE + if(ismob(the_target)) + var/mob/M = the_target + if(M.status_flags & GODMODE) + return FALSE + if (isliving(the_target)) + var/mob/living/L = the_target + if(L.stat != CONSCIOUS) + return FALSE + if (ismecha(the_target)) + var/obj/mecha/M = the_target + if (M.occupant) + return FALSE + return TRUE + +/mob/living/simple_animal/handle_fire() + return + +/mob/living/simple_animal/IgniteMob() + return FALSE + +/mob/living/simple_animal/ExtinguishMob() + return + +/mob/living/simple_animal/revive(full_heal = 0, admin_revive = 0) + if(..()) //successfully ressuscitated from death + icon = initial(icon) + icon_state = icon_living + density = initial(density) + lying = 0 + . = 1 + setMovetype(initial(movement_type)) + +/mob/living/simple_animal/proc/make_babies() // <3 <3 <3 + if(gender != FEMALE || stat || next_scan_time > world.time || !childtype || !animal_species || !SSticker.IsRoundInProgress()) + return + next_scan_time = world.time + 400 + var/alone = 1 + var/mob/living/simple_animal/partner + var/children = 0 + for(var/mob/M in view(7, src)) + if(M.stat != CONSCIOUS) //Check if it's conscious FIRST. + continue + else if(istype(M, childtype)) //Check for children SECOND. + children++ + else if(istype(M, animal_species)) + if(M.ckey) + continue + else if(!istype(M, childtype) && M.gender == MALE) //Better safe than sorry ;_; + partner = M + + else if(isliving(M) && !faction_check_mob(M)) //shyness check. we're not shy in front of things that share a faction with us. + return //we never mate when not alone, so just abort early + + if(alone && partner && children < 3) + var/childspawn = pickweight(childtype) + var/turf/target = get_turf(loc) + if(target) + return new childspawn(target) + +/mob/living/simple_animal/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE) + if(incapacitated()) + to_chat(src, "You can't do that right now!") + return FALSE + if(be_close && !in_range(M, src)) + to_chat(src, "You are too far away!") + return FALSE + if(!(no_dextery || dextrous)) + to_chat(src, "You don't have the dexterity to do this!") + return FALSE + return TRUE + +/mob/living/simple_animal/stripPanelUnequip(obj/item/what, mob/who, where) + if(!canUseTopic(who, BE_CLOSE)) + return + else + ..() + +/mob/living/simple_animal/stripPanelEquip(obj/item/what, mob/who, where) + if(!canUseTopic(who, BE_CLOSE)) + return + else + ..() + +/mob/living/simple_animal/update_canmove(value_otherwise = TRUE) + if(IsUnconscious() || IsStun() || IsKnockdown() || stat || resting) + drop_all_held_items() + canmove = FALSE + else if(buckled) + canmove = FALSE + else + canmove = value_otherwise + update_transform() + update_action_buttons_icon() + return canmove + +/mob/living/simple_animal/update_transform() + var/matrix/ntransform = matrix(transform) //aka transform.Copy() + var/changed = 0 + + if(resize != RESIZE_DEFAULT_SIZE) + changed++ + ntransform.Scale(resize) + resize = RESIZE_DEFAULT_SIZE + + if(changed) + animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) + +/mob/living/simple_animal/proc/sentience_act() //Called when a simple animal gains sentience via gold slime potion + toggle_ai(AI_OFF) // To prevent any weirdness. + +/mob/living/simple_animal/update_sight() + if(!client) + return + if(stat == DEAD) + sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS) + see_in_dark = 8 + see_invisible = SEE_INVISIBLE_OBSERVER + return + + see_invisible = initial(see_invisible) + see_in_dark = initial(see_in_dark) + sight = initial(sight) + + if(client.eye != src) + var/atom/A = client.eye + if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. + return + sync_lighting_plane_alpha() + +/mob/living/simple_animal/get_idcard(hand_first = TRUE) + return ..() || access_card + +/mob/living/simple_animal/OpenCraftingMenu() + if(dextrous) + handcrafting.ui_interact(src) + +/mob/living/simple_animal/can_hold_items() + return dextrous + +/mob/living/simple_animal/IsAdvancedToolUser() + return dextrous + +/mob/living/simple_animal/activate_hand(selhand) + if(!dextrous) + return ..() + if(!selhand) + selhand = (active_hand_index % held_items.len)+1 + if(istext(selhand)) + selhand = lowertext(selhand) + if(selhand == "right" || selhand == "r") + selhand = 2 + if(selhand == "left" || selhand == "l") + selhand = 1 + if(selhand != active_hand_index) + swap_hand(selhand) + else + mode() + +/mob/living/simple_animal/swap_hand(hand_index) + if(!dextrous) + return ..() + if(!hand_index) + hand_index = (active_hand_index % held_items.len)+1 + var/obj/item/held_item = get_active_held_item() + if(held_item) + if(istype(held_item, /obj/item/twohanded)) + var/obj/item/twohanded/T = held_item + if(T.wielded == 1) + to_chat(usr, "Your other hand is too busy holding the [T.name].") + return + var/oindex = active_hand_index + active_hand_index = hand_index + if(hud_used) + var/obj/screen/inventory/hand/H + H = hud_used.hand_slots["[hand_index]"] + if(H) + H.update_icon() + H = hud_used.hand_slots["[oindex]"] + if(H) + H.update_icon() + +/mob/living/simple_animal/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE) + . = ..(I, del_on_fail, merge_stacks) + update_inv_hands() + +/mob/living/simple_animal/update_inv_hands() + if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) + var/obj/item/l_hand = get_item_for_held_index(1) + var/obj/item/r_hand = get_item_for_held_index(2) + if(r_hand) + r_hand.layer = ABOVE_HUD_LAYER + r_hand.plane = ABOVE_HUD_PLANE + r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand)) + client.screen |= r_hand + if(l_hand) + l_hand.layer = ABOVE_HUD_LAYER + l_hand.plane = ABOVE_HUD_PLANE + l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand)) + client.screen |= l_hand + +//ANIMAL RIDING + +/mob/living/simple_animal/user_buckle_mob(mob/living/M, mob/user) + var/datum/component/riding/riding_datum = GetComponent(/datum/component/riding) + if(riding_datum) + if(user.incapacitated()) + return + for(var/atom/movable/A in get_turf(src)) + if(A != src && A != M && A.density) + return + M.forceMove(get_turf(src)) + return ..() + +/mob/living/simple_animal/relaymove(mob/user, direction) + var/datum/component/riding/riding_datum = GetComponent(/datum/component/riding) + if(tame && riding_datum) + riding_datum.handle_ride(user, direction) + +/mob/living/simple_animal/buckle_mob(mob/living/buckled_mob, force = 0, check_loc = 1) + . = ..() + LoadComponent(/datum/component/riding) + +/mob/living/simple_animal/proc/toggle_ai(togglestatus) + if (AIStatus != togglestatus) + if (togglestatus > 0 && togglestatus < 5) + if (togglestatus == AI_Z_OFF || AIStatus == AI_Z_OFF) + var/turf/T = get_turf(src) + if (AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src + else + SSidlenpcpool.idle_mobs_by_zlevel[T.z] += src + GLOB.simple_animals[AIStatus] -= src + GLOB.simple_animals[togglestatus] += src + AIStatus = togglestatus + else + stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") + +/mob/living/simple_animal/proc/consider_wakeup() + if (pulledby || shouldwakeup) + toggle_ai(AI_ON) + +/mob/living/simple_animal/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + . = ..() + if(!ckey && !stat)//Not unconscious + if(AIStatus == AI_IDLE) + toggle_ai(AI_ON) + + +/mob/living/simple_animal/onTransitZ(old_z, new_z) + ..() + if (AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[old_z] -= src + toggle_ai(initial(AIStatus)) diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 4d5d75c7e6..4c39d51d90 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -1,55 +1,55 @@ -/mob/Login() - GLOB.player_list |= src - lastKnownIP = client.address - computer_id = client.computer_id - log_access("Mob Login: [key_name(src)] was assigned to a [type]") - world.update_status() - client.screen = list() //remove hud items just in case - client.images = list() - - if(!hud_used) - create_mob_hud() - if(hud_used) - hud_used.show_hud(hud_used.hud_version) - hud_used.update_ui_style(ui_style2icon(client.prefs.UI_style)) - - next_move = 1 - - ..() - - reset_perspective(loc) - - if(loc) - loc.on_log(TRUE) - - //readd this mob's HUDs (antag, med, etc) - reload_huds() - - reload_fullscreen() // Reload any fullscreen overlays this mob has. - - add_click_catcher() - - sync_mind() - - //Reload alternate appearances - for(var/v in GLOB.active_alternate_appearances) - if(!v) - continue - var/datum/atom_hud/alternate_appearance/AA = v - AA.onNewMob(src) - - update_client_colour() - update_mouse_pointer() - if(client) - client.change_view(CONFIG_GET(string/default_view)) // Resets the client.view in case it was changed. - - if(client.player_details && istype(client.player_details)) - if(client.player_details.player_actions.len) - for(var/datum/action/A in client.player_details.player_actions) - A.Grant(src) - - for(var/foo in client.player_details.post_login_callbacks) - var/datum/callback/CB = foo - CB.Invoke() - - log_message("Client [key_name(src)] has taken ownership of mob [src]([src.type])", LOG_OWNERSHIP) +/mob/Login() + GLOB.player_list |= src + lastKnownIP = client.address + computer_id = client.computer_id + log_access("Mob Login: [key_name(src)] was assigned to a [type]") + world.update_status() + client.screen = list() //remove hud items just in case + client.images = list() + + if(!hud_used) + create_mob_hud() + if(hud_used) + hud_used.show_hud(hud_used.hud_version) + hud_used.update_ui_style(ui_style2icon(client.prefs.UI_style)) + + next_move = 1 + + ..() + + reset_perspective(loc) + + if(loc) + loc.on_log(TRUE) + + //readd this mob's HUDs (antag, med, etc) + reload_huds() + + reload_fullscreen() // Reload any fullscreen overlays this mob has. + + add_click_catcher() + + sync_mind() + + //Reload alternate appearances + for(var/v in GLOB.active_alternate_appearances) + if(!v) + continue + var/datum/atom_hud/alternate_appearance/AA = v + AA.onNewMob(src) + + update_client_colour() + update_mouse_pointer() + if(client) + client.change_view(CONFIG_GET(string/default_view)) // Resets the client.view in case it was changed. + + if(client.player_details && istype(client.player_details)) + if(client.player_details.player_actions.len) + for(var/datum/action/A in client.player_details.player_actions) + A.Grant(src) + + for(var/foo in client.player_details.post_login_callbacks) + var/datum/callback/CB = foo + CB.Invoke() + + log_message("Client [key_name(src)] has taken ownership of mob [src]([src.type])", LOG_OWNERSHIP) diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index 3f68f3e7a2..e5aaa016bd 100644 --- a/code/modules/mob/logout.dm +++ b/code/modules/mob/logout.dm @@ -1,17 +1,17 @@ -/mob/Logout() - log_message("[key_name(src)] is no longer owning mob [src]([src.type])", LOG_OWNERSHIP) - SStgui.on_logout(src) - unset_machine() - GLOB.player_list -= src - - ..() - - if(loc) - loc.on_log(FALSE) - - if(client) - for(var/foo in client.player_details.post_logout_callbacks) - var/datum/callback/CB = foo - CB.Invoke() - - return TRUE +/mob/Logout() + log_message("[key_name(src)] is no longer owning mob [src]([src.type])", LOG_OWNERSHIP) + SStgui.on_logout(src) + unset_machine() + GLOB.player_list -= src + + ..() + + if(loc) + loc.on_log(FALSE) + + if(client) + for(var/foo in client.player_details.post_logout_callbacks) + var/datum/callback/CB = foo + CB.Invoke() + + return TRUE diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 18fc2bfb42..d78ae0ba8c 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -1,538 +1,538 @@ - -// see _DEFINES/is_helpers.dm for mob type checks - -/mob/proc/lowest_buckled_mob() - . = src - if(buckled && ismob(buckled)) - var/mob/Buckled = buckled - . = Buckled.lowest_buckled_mob() - -/proc/check_zone(zone) - if(!zone) - return BODY_ZONE_CHEST - switch(zone) - if(BODY_ZONE_PRECISE_EYES) - zone = BODY_ZONE_HEAD - if(BODY_ZONE_PRECISE_MOUTH) - zone = BODY_ZONE_HEAD - if(BODY_ZONE_PRECISE_L_HAND) - zone = BODY_ZONE_L_ARM - if(BODY_ZONE_PRECISE_R_HAND) - zone = BODY_ZONE_R_ARM - if(BODY_ZONE_PRECISE_L_FOOT) - zone = BODY_ZONE_L_LEG - if(BODY_ZONE_PRECISE_R_FOOT) - zone = BODY_ZONE_R_LEG - if(BODY_ZONE_PRECISE_GROIN) - zone = BODY_ZONE_CHEST - return zone - - -/proc/ran_zone(zone, probability = 80) - if(prob(probability)) - zone = check_zone(zone) - else - zone = pickweight(list(BODY_ZONE_HEAD = 6, BODY_ZONE_CHEST = 6, BODY_ZONE_L_ARM = 22, BODY_ZONE_R_ARM = 22, BODY_ZONE_L_LEG = 22, BODY_ZONE_R_LEG = 22)) - return zone - -/proc/above_neck(zone) - var/list/zones = list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES) - if(zones.Find(zone)) - return 1 - else - return 0 - -/proc/stars(n, pr) - n = html_encode(n) - if (pr == null) - pr = 25 - if (pr <= 0) - return null - else - if (pr >= 100) - return n - var/te = n - var/t = "" - n = length(n) - var/p = null - p = 1 - while(p <= n) - if ((copytext(te, p, p + 1) == " " || prob(pr))) - t = text("[][]", t, copytext(te, p, p + 1)) - else - t = text("[]*", t) - p++ - return sanitize(t) - -/proc/slur(n,var/strength=50) - strength = min(strength,50) - var/phrase = html_decode(n) - var/leng = length(phrase) - var/counter=length(phrase) - var/newphrase="" - var/newletter="" - while(counter>=1) - newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) - if(rand(1,100)<=strength*0.5) - if(lowertext(newletter)=="o") - newletter="u" - if(lowertext(newletter)=="s") - newletter="ch" - if(lowertext(newletter)=="a") - newletter="ah" - if(lowertext(newletter)=="u") - newletter="oo" - if(lowertext(newletter)=="c") - newletter="k" - if(rand(1,100) <= strength*0.25) - if(newletter==" ") - newletter="...huuuhhh..." - if(newletter==".") - newletter=" BURP!" - if(rand(1,100) <= strength*0.5) - if(rand(1,5) == 1) - newletter+="'" - if(rand(1,5) == 1) - newletter+="[newletter]" - if(rand(1,5) == 1) - newletter+="[newletter][newletter]" - newphrase+="[newletter]";counter-=1 - return newphrase - - -/proc/cultslur(n) // Inflicted on victims of a stun talisman - var/phrase = html_decode(n) - var/leng = length(phrase) - var/counter=length(phrase) - var/newphrase="" - var/newletter="" - while(counter>=1) - newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) - if(rand(1,2)==2) - if(lowertext(newletter)=="o") - newletter="u" - if(lowertext(newletter)=="t") - newletter="ch" - if(lowertext(newletter)=="a") - newletter="ah" - if(lowertext(newletter)=="u") - newletter="oo" - if(lowertext(newletter)=="c") - newletter=" NAR " - if(lowertext(newletter)=="s") - newletter=" SIE " - if(rand(1,4)==4) - if(newletter==" ") - newletter=" no hope... " - if(newletter=="H") - newletter=" IT COMES... " - - switch(rand(1,15)) - if(1) - newletter="'" - if(2) - newletter+="agn" - if(3) - newletter="fth" - if(4) - newletter="nglu" - if(5) - newletter="glor" - newphrase+="[newletter]";counter-=1 - return newphrase - - -/proc/stutter(n) - var/te = html_decode(n) - var/t = ""//placed before the message. Not really sure what it's for. - n = length(n)//length of the entire word - var/p = null - p = 1//1 is the start of any word - while(p <= n)//while P, which starts at 1 is less or equal to N which is the length. - var/n_letter = copytext(te, p, p + 1)//copies text from a certain distance. In this case, only one letter at a time. - if (prob(80) && (ckey(n_letter) in list("b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"))) - if (prob(10)) - n_letter = text("[n_letter]-[n_letter]-[n_letter]-[n_letter]")//replaces the current letter with this instead. - else - if (prob(20)) - n_letter = text("[n_letter]-[n_letter]-[n_letter]") - else - if (prob(5)) - n_letter = null - else - n_letter = text("[n_letter]-[n_letter]") - t = text("[t][n_letter]")//since the above is ran through for each letter, the text just adds up back to the original word. - p++//for each letter p is increased to find where the next letter will be. - return copytext(sanitize(t),1,MAX_MESSAGE_LEN) - -/proc/derpspeech(message, stuttering) - message = replacetext(message, " am ", " ") - message = replacetext(message, " is ", " ") - message = replacetext(message, " are ", " ") - message = replacetext(message, "you", "u") - message = replacetext(message, "help", "halp") - message = replacetext(message, "grief", "grife") - message = replacetext(message, "space", "spess") - message = replacetext(message, "carp", "crap") - message = replacetext(message, "reason", "raisin") - if(prob(50)) - message = uppertext(message) - message += "[stutter(pick("!", "!!", "!!!"))]" - if(!stuttering && prob(15)) - message = stutter(message) - return message - - -/proc/Gibberish(t, p)//t is the inputted message, and any value higher than 70 for p will cause letters to be replaced instead of added - /* Turn text into complete gibberish! */ - var/returntext = "" - for(var/i = 1, i <= length(t), i++) - - var/letter = copytext(t, i, i+1) - if(prob(50)) - if(p >= 70) - letter = "" - - for(var/j = 1, j <= rand(0, 2), j++) - letter += pick("#","@","*","&","%","$","/", "<", ">", ";","*","*","*","*","*","*","*") - - returntext += letter - - return returntext - - -/proc/ninjaspeak(n) //NINJACODE -/* -The difference with stutter is that this proc can stutter more than 1 letter -The issue here is that anything that does not have a space is treated as one word (in many instances). For instance, "LOOKING," is a word, including the comma. -It's fairly easy to fix if dealing with single letters but not so much with compounds of letters./N -*/ - var/te = html_decode(n) - var/t = "" - n = length(n) - var/p = 1 - while(p <= n) - var/n_letter - var/n_mod = rand(1,4) - if(p+n_mod>n+1) - n_letter = copytext(te, p, n+1) - else - n_letter = copytext(te, p, p+n_mod) - if (prob(50)) - if (prob(30)) - n_letter = text("[n_letter]-[n_letter]-[n_letter]") - else - n_letter = text("[n_letter]-[n_letter]") - else - n_letter = text("[n_letter]") - t = text("[t][n_letter]") - p=p+n_mod - return copytext(sanitize(t),1,MAX_MESSAGE_LEN) - - -/proc/shake_camera(mob/M, duration, strength=1) - if(!M || !M.client || duration < 1) - return - var/client/C = M.client - var/oldx = C.pixel_x - var/oldy = C.pixel_y - var/max = strength*world.icon_size - var/min = -(strength*world.icon_size) - - for(var/i in 0 to duration-1) - if (i == 0) - animate(C, pixel_x=rand(min,max), pixel_y=rand(min,max), time=1) - else - animate(pixel_x=rand(min,max), pixel_y=rand(min,max), time=1) - animate(pixel_x=oldx, pixel_y=oldy, time=1) - - - -/proc/findname(msg) - if(!istext(msg)) - msg = "[msg]" - for(var/i in GLOB.mob_list) - var/mob/M = i - if(M.real_name == msg) - return M - return 0 - -/mob/proc/first_name() - var/static/regex/firstname = new("^\[^\\s-\]+") //First word before whitespace or "-" - firstname.Find(real_name) - return firstname.match - - -//change a mob's act-intent. Input the intent as a string such as "help" or use "right"/"left -/mob/verb/a_intent_change(input as text) - set name = "a-intent" - set hidden = 1 - - if(!possible_a_intents || !possible_a_intents.len) - return - - if(input in possible_a_intents) - a_intent = input - else - var/current_intent = possible_a_intents.Find(a_intent) - - if(!current_intent) - // Failsafe. Just in case some badmin was playing with VV. - current_intent = 1 - - if(input == INTENT_HOTKEY_RIGHT) - current_intent += 1 - if(input == INTENT_HOTKEY_LEFT) - current_intent -= 1 - - // Handle looping - if(current_intent < 1) - current_intent = possible_a_intents.len - if(current_intent > possible_a_intents.len) - current_intent = 1 - - a_intent = possible_a_intents[current_intent] - - if(hud_used && hud_used.action_intent) - hud_used.action_intent.icon_state = "[a_intent]" - - -/proc/is_blind(A) - if(ismob(A)) - var/mob/B = A - return B.eye_blind - return FALSE - -/mob/proc/hallucinating() - return FALSE - -/proc/is_special_character(mob/M) // returns 1 for special characters and 2 for heroes of gamemode //moved out of admins.dm because things other than admin procs were calling this. - if(!SSticker.HasRoundStarted()) - return FALSE - if(!istype(M)) - return FALSE - if(issilicon(M)) - if(iscyborg(M)) //For cyborgs, returns 1 if the cyborg has a law 0 and special_role. Returns 0 if the borg is merely slaved to an AI traitor. - return FALSE - else if(isAI(M)) - var/mob/living/silicon/ai/A = M - if(A.laws && A.laws.zeroth && A.mind && A.mind.special_role) - return TRUE - return FALSE - if(M.mind && M.mind.special_role)//If they have a mind and special role, they are some type of traitor or antagonist. - switch(SSticker.mode.config_tag) - if("revolution") - if(is_revolutionary(M)) - return 2 - if("cult") - if(M.mind in SSticker.mode.cult) - return 2 - if("nuclear") - if(M.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE)) - return 2 - if("changeling") - if(M.mind.has_antag_datum(/datum/antagonist/changeling,TRUE)) - return 2 - if("wizard") - if(iswizard(M)) - return 2 - if("apprentice") - if(M.mind in SSticker.mode.apprentices) - return 2 - if("monkey") - if(isliving(M)) - var/mob/living/L = M - if(L.diseases && (locate(/datum/disease/transformation/jungle_fever) in L.diseases)) - return 2 - return TRUE - if(M.mind && LAZYLEN(M.mind.antag_datums)) //they have an antag datum! - return TRUE - return FALSE - -/mob/proc/reagent_check(datum/reagent/R) // utilized in the species code - return 1 - -/proc/notify_ghosts(message, ghost_sound, enter_link, atom/source, mutable_appearance/alert_overlay, action = NOTIFY_JUMP, flashwindow = TRUE, ignore_mapload = TRUE, ignore_key, ignore_dnr_observers = FALSE) //Easy notification of ghosts. - if(ignore_mapload && SSatoms.initialized != INITIALIZATION_INNEW_REGULAR) //don't notify for objects created during a map load - return - for(var/mob/dead/observer/O in GLOB.player_list) - if(O.client) - if ((ignore_key && (O.ckey in GLOB.poll_ignore[ignore_key])) || (ignore_dnr_observers && !O.can_reenter_round(TRUE))) - continue - to_chat(O, "[message][(enter_link) ? " [enter_link]" : ""]") - if(ghost_sound) - SEND_SOUND(O, sound(ghost_sound)) - if(flashwindow) - window_flash(O.client) - if(source) - var/obj/screen/alert/notify_action/A = O.throw_alert("[REF(source)]_notify_action", /obj/screen/alert/notify_action) - if(A) - if(O.client.prefs && O.client.prefs.UI_style) - A.icon = ui_style2icon(O.client.prefs.UI_style) - A.desc = message - A.action = action - A.target = source - if(!alert_overlay) - alert_overlay = new(source) - alert_overlay.layer = FLOAT_LAYER - alert_overlay.plane = FLOAT_PLANE - A.add_overlay(alert_overlay) - -/proc/item_heal_robotic(mob/living/carbon/human/H, mob/user, brute_heal, burn_heal) - var/obj/item/bodypart/affecting = H.get_bodypart(check_zone(user.zone_selected)) - if(affecting && affecting.status == BODYPART_ROBOTIC) - var/dam //changes repair text based on how much brute/burn was supplied - if(brute_heal > burn_heal) - dam = 1 - else - dam = 0 - if((brute_heal > 0 && affecting.brute_dam > 0) || (burn_heal > 0 && affecting.burn_dam > 0)) - if(affecting.heal_damage(brute_heal, burn_heal, 0, TRUE, FALSE)) - H.update_damage_overlays() - user.visible_message("[user] has fixed some of the [dam ? "dents on" : "burnt wires in"] [H]'s [affecting.name].", \ - "You fix some of the [dam ? "dents on" : "burnt wires in"] [H]'s [affecting.name].") - return 1 //successful heal - else - to_chat(user, "[affecting] is already in good condition!") - - -/proc/IsAdminGhost(var/mob/user) - if(!user) //Are they a mob? Auto interface updates call this with a null src - return - if(!user.client) // Do they have a client? - return - if(!isobserver(user)) // Are they a ghost? - return - if(!check_rights_for(user.client, R_ADMIN)) // Are they allowed? - return - if(!user.client.AI_Interact) // Do they have it enabled? - return - return TRUE - -/proc/offer_control(mob/M) - to_chat(M, "Control of your mob has been offered to dead players.") - if(usr) - log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.") - message_admins("[key_name_admin(usr)] has offered control of ([ADMIN_LOOKUPFLW(M)]) to ghosts") - var/poll_message = "Do you want to play as [M.real_name]?" - if(M.mind && M.mind.assigned_role) - poll_message = "[poll_message] Job:[M.mind.assigned_role]." - if(M.mind && M.mind.special_role) - poll_message = "[poll_message] Status:[M.mind.special_role]." - else if(M.mind) - var/datum/antagonist/A = M.mind.has_antag_datum(/datum/antagonist/) - if(A) - poll_message = "[poll_message] Status:[A.name]." - var/list/mob/dead/observer/candidates = pollCandidatesForMob(poll_message, ROLE_PAI, null, FALSE, 100, M) - - if(LAZYLEN(candidates)) - var/mob/dead/observer/C = pick(candidates) - to_chat(M, "Your mob has been taken over by a ghost!") - message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(M)])") - M.ghostize(FALSE, TRUE) - C.transfer_ckey(M, FALSE) - return TRUE - else - to_chat(M, "There were no ghosts willing to take control.") - message_admins("No ghosts were willing to take control of [ADMIN_LOOKUPFLW(M)])") - return FALSE - -/mob/proc/is_flying(mob/M = src) - if(M.movement_type & FLYING) - return 1 - else - return 0 - -/mob/proc/click_random_mob() - var/list/nearby_mobs = list() - for(var/mob/living/L in range(1, src)) - if(L!=src) - nearby_mobs |= L - if(nearby_mobs.len) - var/mob/living/T = pick(nearby_mobs) - ClickOn(T) - -// Logs a message in a mob's individual log, and in the global logs as well if log_globally is true -/mob/log_message(message, message_type, color=null, log_globally = TRUE) - if(!LAZYLEN(message)) - stack_trace("Empty message") - return - - // Cannot use the list as a map if the key is a number, so we stringify it (thank you BYOND) - var/smessage_type = num2text(message_type) - - if(client) - if(!islist(client.player_details.logging[smessage_type])) - client.player_details.logging[smessage_type] = list() - - if(!islist(logging[smessage_type])) - logging[smessage_type] = list() - - var/colored_message = message - if(color) - if(color[1] == "#") - colored_message = "[message]" - else - colored_message = "[message]" - - var/list/timestamped_message = list("[LAZYLEN(logging[smessage_type]) + 1]\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(src)] [loc_name(src)]" = colored_message) - - logging[smessage_type] += timestamped_message - - if(client) - client.player_details.logging[smessage_type] += timestamped_message - - ..() - -/mob/proc/can_hear() - . = TRUE - -/proc/bloodtype_to_color(var/type) - . = BLOOD_COLOR_HUMAN - switch(type) - if("U")//Universal blood; a bit orange - . = BLOOD_COLOR_UNIVERSAL - if("SY")//Synthetics blood; blue - . = BLOOD_COLOR_SYNTHETIC - if("L")//lizard, a bit pink/purple - . = BLOOD_COLOR_LIZARD - if("X*")//xeno blood; greenish yellow - . = BLOOD_COLOR_XENO - if("HF")// Oil/Hydraulic blood. something something why not. reee - . = BLOOD_COLOR_OIL - if("GEL")// slimepeople blood, rgb 0, 255, 144 - . = BLOOD_COLOR_SLIME - if("BUG")// yellowish, like, y'know bug guts I guess. - . = BLOOD_COLOR_BUG - //add more stuff to the switch if you have more blood colors for different types - // the defines are in _DEFINES/misc.dm - -//Examine text for traits shared by multiple types. I wish examine was less copypasted. -/mob/proc/common_trait_examine() - if(HAS_TRAIT(src, TRAIT_DISSECTED)) - var/dissectionmsg = "" - if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Extraterrestrial Dissection")) - dissectionmsg = " via Extraterrestrial Dissection. It is no longer worth experimenting on" - else if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Experimental Dissection")) - dissectionmsg = " via Experimental Dissection" - else if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Thorough Dissection")) - dissectionmsg = " via Thorough Dissection" - . += "This body has been dissected and analyzed[dissectionmsg].
                " - -//gets ID card object from special clothes slot or null. -/mob/proc/get_idcard(hand_first = TRUE) - var/obj/item/held_item = get_active_held_item() - . = held_item ? held_item.GetID() : null - if(!.) //If so, then check the inactive hand - held_item = get_inactive_held_item() - . = held_item ? held_item.GetID() : null - -/mob/proc/get_id_in_hand() - var/obj/item/held_item = get_active_held_item() - if(!held_item) - return - return held_item.GetID() - -//Can the mob see reagents inside of containers? -/mob/proc/can_see_reagents() - return stat == DEAD || has_unlimited_silicon_privilege //Dead guys and silicons can always see reagents + +// see _DEFINES/is_helpers.dm for mob type checks + +/mob/proc/lowest_buckled_mob() + . = src + if(buckled && ismob(buckled)) + var/mob/Buckled = buckled + . = Buckled.lowest_buckled_mob() + +/proc/check_zone(zone) + if(!zone) + return BODY_ZONE_CHEST + switch(zone) + if(BODY_ZONE_PRECISE_EYES) + zone = BODY_ZONE_HEAD + if(BODY_ZONE_PRECISE_MOUTH) + zone = BODY_ZONE_HEAD + if(BODY_ZONE_PRECISE_L_HAND) + zone = BODY_ZONE_L_ARM + if(BODY_ZONE_PRECISE_R_HAND) + zone = BODY_ZONE_R_ARM + if(BODY_ZONE_PRECISE_L_FOOT) + zone = BODY_ZONE_L_LEG + if(BODY_ZONE_PRECISE_R_FOOT) + zone = BODY_ZONE_R_LEG + if(BODY_ZONE_PRECISE_GROIN) + zone = BODY_ZONE_CHEST + return zone + + +/proc/ran_zone(zone, probability = 80) + if(prob(probability)) + zone = check_zone(zone) + else + zone = pickweight(list(BODY_ZONE_HEAD = 6, BODY_ZONE_CHEST = 6, BODY_ZONE_L_ARM = 22, BODY_ZONE_R_ARM = 22, BODY_ZONE_L_LEG = 22, BODY_ZONE_R_LEG = 22)) + return zone + +/proc/above_neck(zone) + var/list/zones = list(BODY_ZONE_HEAD, BODY_ZONE_PRECISE_MOUTH, BODY_ZONE_PRECISE_EYES) + if(zones.Find(zone)) + return 1 + else + return 0 + +/proc/stars(n, pr) + n = html_encode(n) + if (pr == null) + pr = 25 + if (pr <= 0) + return null + else + if (pr >= 100) + return n + var/te = n + var/t = "" + n = length(n) + var/p = null + p = 1 + while(p <= n) + if ((copytext(te, p, p + 1) == " " || prob(pr))) + t = text("[][]", t, copytext(te, p, p + 1)) + else + t = text("[]*", t) + p++ + return sanitize(t) + +/proc/slur(n,var/strength=50) + strength = min(strength,50) + var/phrase = html_decode(n) + var/leng = length(phrase) + var/counter=length(phrase) + var/newphrase="" + var/newletter="" + while(counter>=1) + newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) + if(rand(1,100)<=strength*0.5) + if(lowertext(newletter)=="o") + newletter="u" + if(lowertext(newletter)=="s") + newletter="ch" + if(lowertext(newletter)=="a") + newletter="ah" + if(lowertext(newletter)=="u") + newletter="oo" + if(lowertext(newletter)=="c") + newletter="k" + if(rand(1,100) <= strength*0.25) + if(newletter==" ") + newletter="...huuuhhh..." + if(newletter==".") + newletter=" BURP!" + if(rand(1,100) <= strength*0.5) + if(rand(1,5) == 1) + newletter+="'" + if(rand(1,5) == 1) + newletter+="[newletter]" + if(rand(1,5) == 1) + newletter+="[newletter][newletter]" + newphrase+="[newletter]";counter-=1 + return newphrase + + +/proc/cultslur(n) // Inflicted on victims of a stun talisman + var/phrase = html_decode(n) + var/leng = length(phrase) + var/counter=length(phrase) + var/newphrase="" + var/newletter="" + while(counter>=1) + newletter=copytext(phrase,(leng-counter)+1,(leng-counter)+2) + if(rand(1,2)==2) + if(lowertext(newletter)=="o") + newletter="u" + if(lowertext(newletter)=="t") + newletter="ch" + if(lowertext(newletter)=="a") + newletter="ah" + if(lowertext(newletter)=="u") + newletter="oo" + if(lowertext(newletter)=="c") + newletter=" NAR " + if(lowertext(newletter)=="s") + newletter=" SIE " + if(rand(1,4)==4) + if(newletter==" ") + newletter=" no hope... " + if(newletter=="H") + newletter=" IT COMES... " + + switch(rand(1,15)) + if(1) + newletter="'" + if(2) + newletter+="agn" + if(3) + newletter="fth" + if(4) + newletter="nglu" + if(5) + newletter="glor" + newphrase+="[newletter]";counter-=1 + return newphrase + + +/proc/stutter(n) + var/te = html_decode(n) + var/t = ""//placed before the message. Not really sure what it's for. + n = length(n)//length of the entire word + var/p = null + p = 1//1 is the start of any word + while(p <= n)//while P, which starts at 1 is less or equal to N which is the length. + var/n_letter = copytext(te, p, p + 1)//copies text from a certain distance. In this case, only one letter at a time. + if (prob(80) && (ckey(n_letter) in list("b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"))) + if (prob(10)) + n_letter = text("[n_letter]-[n_letter]-[n_letter]-[n_letter]")//replaces the current letter with this instead. + else + if (prob(20)) + n_letter = text("[n_letter]-[n_letter]-[n_letter]") + else + if (prob(5)) + n_letter = null + else + n_letter = text("[n_letter]-[n_letter]") + t = text("[t][n_letter]")//since the above is ran through for each letter, the text just adds up back to the original word. + p++//for each letter p is increased to find where the next letter will be. + return copytext(sanitize(t),1,MAX_MESSAGE_LEN) + +/proc/derpspeech(message, stuttering) + message = replacetext(message, " am ", " ") + message = replacetext(message, " is ", " ") + message = replacetext(message, " are ", " ") + message = replacetext(message, "you", "u") + message = replacetext(message, "help", "halp") + message = replacetext(message, "grief", "grife") + message = replacetext(message, "space", "spess") + message = replacetext(message, "carp", "crap") + message = replacetext(message, "reason", "raisin") + if(prob(50)) + message = uppertext(message) + message += "[stutter(pick("!", "!!", "!!!"))]" + if(!stuttering && prob(15)) + message = stutter(message) + return message + + +/proc/Gibberish(t, p)//t is the inputted message, and any value higher than 70 for p will cause letters to be replaced instead of added + /* Turn text into complete gibberish! */ + var/returntext = "" + for(var/i = 1, i <= length(t), i++) + + var/letter = copytext(t, i, i+1) + if(prob(50)) + if(p >= 70) + letter = "" + + for(var/j = 1, j <= rand(0, 2), j++) + letter += pick("#","@","*","&","%","$","/", "<", ">", ";","*","*","*","*","*","*","*") + + returntext += letter + + return returntext + + +/proc/ninjaspeak(n) //NINJACODE +/* +The difference with stutter is that this proc can stutter more than 1 letter +The issue here is that anything that does not have a space is treated as one word (in many instances). For instance, "LOOKING," is a word, including the comma. +It's fairly easy to fix if dealing with single letters but not so much with compounds of letters./N +*/ + var/te = html_decode(n) + var/t = "" + n = length(n) + var/p = 1 + while(p <= n) + var/n_letter + var/n_mod = rand(1,4) + if(p+n_mod>n+1) + n_letter = copytext(te, p, n+1) + else + n_letter = copytext(te, p, p+n_mod) + if (prob(50)) + if (prob(30)) + n_letter = text("[n_letter]-[n_letter]-[n_letter]") + else + n_letter = text("[n_letter]-[n_letter]") + else + n_letter = text("[n_letter]") + t = text("[t][n_letter]") + p=p+n_mod + return copytext(sanitize(t),1,MAX_MESSAGE_LEN) + + +/proc/shake_camera(mob/M, duration, strength=1) + if(!M || !M.client || duration < 1) + return + var/client/C = M.client + var/oldx = C.pixel_x + var/oldy = C.pixel_y + var/max = strength*world.icon_size + var/min = -(strength*world.icon_size) + + for(var/i in 0 to duration-1) + if (i == 0) + animate(C, pixel_x=rand(min,max), pixel_y=rand(min,max), time=1) + else + animate(pixel_x=rand(min,max), pixel_y=rand(min,max), time=1) + animate(pixel_x=oldx, pixel_y=oldy, time=1) + + + +/proc/findname(msg) + if(!istext(msg)) + msg = "[msg]" + for(var/i in GLOB.mob_list) + var/mob/M = i + if(M.real_name == msg) + return M + return 0 + +/mob/proc/first_name() + var/static/regex/firstname = new("^\[^\\s-\]+") //First word before whitespace or "-" + firstname.Find(real_name) + return firstname.match + + +//change a mob's act-intent. Input the intent as a string such as "help" or use "right"/"left +/mob/verb/a_intent_change(input as text) + set name = "a-intent" + set hidden = 1 + + if(!possible_a_intents || !possible_a_intents.len) + return + + if(input in possible_a_intents) + a_intent = input + else + var/current_intent = possible_a_intents.Find(a_intent) + + if(!current_intent) + // Failsafe. Just in case some badmin was playing with VV. + current_intent = 1 + + if(input == INTENT_HOTKEY_RIGHT) + current_intent += 1 + if(input == INTENT_HOTKEY_LEFT) + current_intent -= 1 + + // Handle looping + if(current_intent < 1) + current_intent = possible_a_intents.len + if(current_intent > possible_a_intents.len) + current_intent = 1 + + a_intent = possible_a_intents[current_intent] + + if(hud_used && hud_used.action_intent) + hud_used.action_intent.icon_state = "[a_intent]" + + +/proc/is_blind(A) + if(ismob(A)) + var/mob/B = A + return B.eye_blind + return FALSE + +/mob/proc/hallucinating() + return FALSE + +/proc/is_special_character(mob/M) // returns 1 for special characters and 2 for heroes of gamemode //moved out of admins.dm because things other than admin procs were calling this. + if(!SSticker.HasRoundStarted()) + return FALSE + if(!istype(M)) + return FALSE + if(issilicon(M)) + if(iscyborg(M)) //For cyborgs, returns 1 if the cyborg has a law 0 and special_role. Returns 0 if the borg is merely slaved to an AI traitor. + return FALSE + else if(isAI(M)) + var/mob/living/silicon/ai/A = M + if(A.laws && A.laws.zeroth && A.mind && A.mind.special_role) + return TRUE + return FALSE + if(M.mind && M.mind.special_role)//If they have a mind and special role, they are some type of traitor or antagonist. + switch(SSticker.mode.config_tag) + if("revolution") + if(is_revolutionary(M)) + return 2 + if("cult") + if(M.mind in SSticker.mode.cult) + return 2 + if("nuclear") + if(M.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE)) + return 2 + if("changeling") + if(M.mind.has_antag_datum(/datum/antagonist/changeling,TRUE)) + return 2 + if("wizard") + if(iswizard(M)) + return 2 + if("apprentice") + if(M.mind in SSticker.mode.apprentices) + return 2 + if("monkey") + if(isliving(M)) + var/mob/living/L = M + if(L.diseases && (locate(/datum/disease/transformation/jungle_fever) in L.diseases)) + return 2 + return TRUE + if(M.mind && LAZYLEN(M.mind.antag_datums)) //they have an antag datum! + return TRUE + return FALSE + +/mob/proc/reagent_check(datum/reagent/R) // utilized in the species code + return 1 + +/proc/notify_ghosts(message, ghost_sound, enter_link, atom/source, mutable_appearance/alert_overlay, action = NOTIFY_JUMP, flashwindow = TRUE, ignore_mapload = TRUE, ignore_key, ignore_dnr_observers = FALSE) //Easy notification of ghosts. + if(ignore_mapload && SSatoms.initialized != INITIALIZATION_INNEW_REGULAR) //don't notify for objects created during a map load + return + for(var/mob/dead/observer/O in GLOB.player_list) + if(O.client) + if ((ignore_key && (O.ckey in GLOB.poll_ignore[ignore_key])) || (ignore_dnr_observers && !O.can_reenter_round(TRUE))) + continue + to_chat(O, "[message][(enter_link) ? " [enter_link]" : ""]") + if(ghost_sound) + SEND_SOUND(O, sound(ghost_sound)) + if(flashwindow) + window_flash(O.client) + if(source) + var/obj/screen/alert/notify_action/A = O.throw_alert("[REF(source)]_notify_action", /obj/screen/alert/notify_action) + if(A) + if(O.client.prefs && O.client.prefs.UI_style) + A.icon = ui_style2icon(O.client.prefs.UI_style) + A.desc = message + A.action = action + A.target = source + if(!alert_overlay) + alert_overlay = new(source) + alert_overlay.layer = FLOAT_LAYER + alert_overlay.plane = FLOAT_PLANE + A.add_overlay(alert_overlay) + +/proc/item_heal_robotic(mob/living/carbon/human/H, mob/user, brute_heal, burn_heal) + var/obj/item/bodypart/affecting = H.get_bodypart(check_zone(user.zone_selected)) + if(affecting && affecting.status == BODYPART_ROBOTIC) + var/dam //changes repair text based on how much brute/burn was supplied + if(brute_heal > burn_heal) + dam = 1 + else + dam = 0 + if((brute_heal > 0 && affecting.brute_dam > 0) || (burn_heal > 0 && affecting.burn_dam > 0)) + if(affecting.heal_damage(brute_heal, burn_heal, 0, TRUE, FALSE)) + H.update_damage_overlays() + user.visible_message("[user] has fixed some of the [dam ? "dents on" : "burnt wires in"] [H]'s [affecting.name].", \ + "You fix some of the [dam ? "dents on" : "burnt wires in"] [H]'s [affecting.name].") + return 1 //successful heal + else + to_chat(user, "[affecting] is already in good condition!") + + +/proc/IsAdminGhost(var/mob/user) + if(!user) //Are they a mob? Auto interface updates call this with a null src + return + if(!user.client) // Do they have a client? + return + if(!isobserver(user)) // Are they a ghost? + return + if(!check_rights_for(user.client, R_ADMIN)) // Are they allowed? + return + if(!user.client.AI_Interact) // Do they have it enabled? + return + return TRUE + +/proc/offer_control(mob/M) + to_chat(M, "Control of your mob has been offered to dead players.") + if(usr) + log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.") + message_admins("[key_name_admin(usr)] has offered control of ([ADMIN_LOOKUPFLW(M)]) to ghosts") + var/poll_message = "Do you want to play as [M.real_name]?" + if(M.mind && M.mind.assigned_role) + poll_message = "[poll_message] Job:[M.mind.assigned_role]." + if(M.mind && M.mind.special_role) + poll_message = "[poll_message] Status:[M.mind.special_role]." + else if(M.mind) + var/datum/antagonist/A = M.mind.has_antag_datum(/datum/antagonist/) + if(A) + poll_message = "[poll_message] Status:[A.name]." + var/list/mob/dead/observer/candidates = pollCandidatesForMob(poll_message, ROLE_PAI, null, FALSE, 100, M) + + if(LAZYLEN(candidates)) + var/mob/dead/observer/C = pick(candidates) + to_chat(M, "Your mob has been taken over by a ghost!") + message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(M)])") + M.ghostize(FALSE, TRUE) + C.transfer_ckey(M, FALSE) + return TRUE + else + to_chat(M, "There were no ghosts willing to take control.") + message_admins("No ghosts were willing to take control of [ADMIN_LOOKUPFLW(M)])") + return FALSE + +/mob/proc/is_flying(mob/M = src) + if(M.movement_type & FLYING) + return 1 + else + return 0 + +/mob/proc/click_random_mob() + var/list/nearby_mobs = list() + for(var/mob/living/L in range(1, src)) + if(L!=src) + nearby_mobs |= L + if(nearby_mobs.len) + var/mob/living/T = pick(nearby_mobs) + ClickOn(T) + +// Logs a message in a mob's individual log, and in the global logs as well if log_globally is true +/mob/log_message(message, message_type, color=null, log_globally = TRUE) + if(!LAZYLEN(message)) + stack_trace("Empty message") + return + + // Cannot use the list as a map if the key is a number, so we stringify it (thank you BYOND) + var/smessage_type = num2text(message_type) + + if(client) + if(!islist(client.player_details.logging[smessage_type])) + client.player_details.logging[smessage_type] = list() + + if(!islist(logging[smessage_type])) + logging[smessage_type] = list() + + var/colored_message = message + if(color) + if(color[1] == "#") + colored_message = "[message]" + else + colored_message = "[message]" + + var/list/timestamped_message = list("[LAZYLEN(logging[smessage_type]) + 1]\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(src)] [loc_name(src)]" = colored_message) + + logging[smessage_type] += timestamped_message + + if(client) + client.player_details.logging[smessage_type] += timestamped_message + + ..() + +/mob/proc/can_hear() + . = TRUE + +/proc/bloodtype_to_color(var/type) + . = BLOOD_COLOR_HUMAN + switch(type) + if("U")//Universal blood; a bit orange + . = BLOOD_COLOR_UNIVERSAL + if("SY")//Synthetics blood; blue + . = BLOOD_COLOR_SYNTHETIC + if("L")//lizard, a bit pink/purple + . = BLOOD_COLOR_LIZARD + if("X*")//xeno blood; greenish yellow + . = BLOOD_COLOR_XENO + if("HF")// Oil/Hydraulic blood. something something why not. reee + . = BLOOD_COLOR_OIL + if("GEL")// slimepeople blood, rgb 0, 255, 144 + . = BLOOD_COLOR_SLIME + if("BUG")// yellowish, like, y'know bug guts I guess. + . = BLOOD_COLOR_BUG + //add more stuff to the switch if you have more blood colors for different types + // the defines are in _DEFINES/misc.dm + +//Examine text for traits shared by multiple types. I wish examine was less copypasted. +/mob/proc/common_trait_examine() + if(HAS_TRAIT(src, TRAIT_DISSECTED)) + var/dissectionmsg = "" + if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Extraterrestrial Dissection")) + dissectionmsg = " via Extraterrestrial Dissection. It is no longer worth experimenting on" + else if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Experimental Dissection")) + dissectionmsg = " via Experimental Dissection" + else if(HAS_TRAIT_FROM(src, TRAIT_DISSECTED,"Thorough Dissection")) + dissectionmsg = " via Thorough Dissection" + . += "This body has been dissected and analyzed[dissectionmsg].
                " + +//gets ID card object from special clothes slot or null. +/mob/proc/get_idcard(hand_first = TRUE) + var/obj/item/held_item = get_active_held_item() + . = held_item ? held_item.GetID() : null + if(!.) //If so, then check the inactive hand + held_item = get_inactive_held_item() + . = held_item ? held_item.GetID() : null + +/mob/proc/get_id_in_hand() + var/obj/item/held_item = get_active_held_item() + if(!held_item) + return + return held_item.GetID() + +//Can the mob see reagents inside of containers? +/mob/proc/can_see_reagents() + return stat == DEAD || has_unlimited_silicon_privilege //Dead guys and silicons can always see reagents diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 04d47da56e..f61d65146e 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -1,407 +1,407 @@ -/mob/CanPass(atom/movable/mover, turf/target) - if((mover.pass_flags & PASSMOB)) - return TRUE - if(istype(mover, /obj/item/projectile) || mover.throwing) - return (!density || lying) - if(buckled == mover) - return TRUE - if(ismob(mover)) - if (mover in buckled_mobs) - return TRUE - return (!mover.density || !density || lying || (mover.throwing && mover.throwing.thrower == src && !ismob(mover))) - -//DO NOT USE THIS UNLESS YOU ABSOLUTELY HAVE TO. THIS IS BEING PHASED OUT FOR THE MOVESPEED MODIFICATION SYSTEM. -//See mob_movespeed.dm -/mob/proc/movement_delay() //update /living/movement_delay() if you change this - return cached_multiplicative_slowdown - -/client/verb/drop_item() - set hidden = 1 - if(!iscyborg(mob) && mob.stat == CONSCIOUS) - mob.dropItemToGround(mob.get_active_held_item()) - return - -/client/proc/Move_object(direct) - if(mob && mob.control_object) - if(mob.control_object.density) - step(mob.control_object,direct) - if(!mob.control_object) - return - mob.control_object.setDir(direct) - else - mob.control_object.forceMove(get_step(mob.control_object,direct)) - -#define MOVEMENT_DELAY_BUFFER 0.75 -#define MOVEMENT_DELAY_BUFFER_DELTA 1.25 - -/client/Move(n, direct) - if(world.time < move_delay) //do not move anything ahead of this check please - return FALSE - else - next_move_dir_add = 0 - next_move_dir_sub = 0 - var/old_move_delay = move_delay - move_delay = world.time + world.tick_lag //this is here because Move() can now be called mutiple times per tick - if(!mob || !mob.loc) - return FALSE - if(!n || !direct) - return FALSE - if(mob.notransform) - return FALSE //This is sota the goto stop mobs from moving var - if(mob.control_object) - return Move_object(direct) - if(!isliving(mob)) - return mob.Move(n, direct) - if(mob.stat == DEAD) - mob.ghostize() - return FALSE - if(mob.force_moving) - return FALSE - - var/mob/living/L = mob //Already checked for isliving earlier - if(L.incorporeal_move) //Move though walls - Process_Incorpmove(direct) - return FALSE - - if(mob.remote_control) //we're controlling something, our movement is relayed to it - return mob.remote_control.relaymove(mob, direct) - - if(isAI(mob)) - return AIMove(n,direct,mob) - - if(Process_Grab()) //are we restrained by someone's grip? - return - - if(mob.buckled) //if we're buckled to something, tell it we moved. - return mob.buckled.relaymove(mob, direct) - - if(!mob.canmove) - return FALSE - - if(isobj(mob.loc) || ismob(mob.loc)) //Inside an object, tell it we moved - var/atom/O = mob.loc - return O.relaymove(mob, direct) - - if(!mob.Process_Spacemove(direct)) - return FALSE - //We are now going to move - var/add_delay = mob.movement_delay() - if(old_move_delay + (add_delay*MOVEMENT_DELAY_BUFFER_DELTA) + MOVEMENT_DELAY_BUFFER > world.time) - move_delay = old_move_delay - else - move_delay = world.time - var/oldloc = mob.loc - - if(L.confused) - var/newdir = 0 - if(L.confused > 40) - newdir = pick(GLOB.alldirs) - else if(prob(L.confused * 1.5)) - newdir = angle2dir(dir2angle(direct) + pick(90, -90)) - else if(prob(L.confused * 3)) - newdir = angle2dir(dir2angle(direct) + pick(45, -45)) - if(newdir) - direct = newdir - n = get_step(L, direct) - - . = ..() - - if((direct & (direct - 1)) && mob.loc == n) //moved diagonally successfully - add_delay *= 2 - if(mob.loc != oldloc) - move_delay += add_delay - if(.) // If mob is null here, we deserve the runtime - if(mob.throwing) - mob.throwing.finalize(FALSE) - - for(var/obj/O in mob.user_movement_hooks) - O.intercept_user_move(direct, mob, n, oldloc) - - var/atom/movable/P = mob.pulling - if(P && !ismob(P) && P.density) - mob.setDir(turn(mob.dir, 180)) - -///Process_Grab() -///Called by client/Move() -///Checks to see if you are being grabbed and if so attemps to break it -/client/proc/Process_Grab() - if(mob.pulledby) - if(mob.incapacitated(ignore_restraints = 1)) - move_delay = world.time + 10 - return TRUE - else if(mob.restrained(ignore_grab = 1)) - move_delay = world.time + 10 - to_chat(src, "You're restrained! You can't move!") - return TRUE - else - return mob.resist_grab(1) - -///Process_Incorpmove -///Called by client/Move() -///Allows mobs to run though walls -/client/proc/Process_Incorpmove(direct) - var/turf/mobloc = get_turf(mob) - if(!isliving(mob)) - return - var/mob/living/L = mob - switch(L.incorporeal_move) - if(INCORPOREAL_MOVE_BASIC) - var/T = get_step(L,direct) - if(T) - L.forceMove(T) - L.setDir(direct) - if(INCORPOREAL_MOVE_SHADOW) - if(prob(50)) - var/locx - var/locy - switch(direct) - if(NORTH) - locx = mobloc.x - locy = (mobloc.y+2) - if(locy>world.maxy) - return - if(SOUTH) - locx = mobloc.x - locy = (mobloc.y-2) - if(locy<1) - return - if(EAST) - locy = mobloc.y - locx = (mobloc.x+2) - if(locx>world.maxx) - return - if(WEST) - locy = mobloc.y - locx = (mobloc.x-2) - if(locx<1) - return - else - return - var/target = locate(locx,locy,mobloc.z) - if(target) - L.loc = target - var/limit = 2//For only two trailing shadows. - for(var/turf/T in getline(mobloc, L.loc)) - new /obj/effect/temp_visual/dir_setting/ninja/shadow(T, L.dir) - limit-- - if(limit<=0) - break - else - new /obj/effect/temp_visual/dir_setting/ninja/shadow(mobloc, L.dir) - var/T = get_step(L,direct) - if(T) - L.forceMove(T) - L.setDir(direct) - if(INCORPOREAL_MOVE_JAUNT) //Incorporeal move, but blocked by holy-watered tiles and salt piles. - var/turf/open/floor/stepTurf = get_step(L, direct) - if(stepTurf) - for(var/obj/effect/decal/cleanable/salt/S in stepTurf) - to_chat(L, "[S] bars your passage!") - if(isrevenant(L)) - var/mob/living/simple_animal/revenant/R = L - R.reveal(20) - R.stun(20) - return - if(stepTurf.flags_1 & NOJAUNT_1) - to_chat(L, "Some strange aura is blocking the way.") - return - if (locate(/obj/effect/blessing, stepTurf)) - to_chat(L, "Holy energies block your path!") - return - - L.forceMove(stepTurf) - L.setDir(direct) - return TRUE - - -///Process_Spacemove -///Called by /client/Move() -///For moving in space -///return TRUE for movement 0 for none -/mob/Process_Spacemove(movement_dir = 0) - if(spacewalk || ..()) - 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))) //You're pushing off something movable, so it moves - to_chat(src, "You push off of [backup] to propel yourself.") - return TRUE - return FALSE - -/mob/get_spacemove_backup() - for(var/A in orange(1, get_turf(src))) - if(isarea(A)) - continue - else if(isturf(A)) - var/turf/turf = A - if(isspaceturf(turf)) - continue - if(!turf.density && !mob_negates_gravity()) - continue - return A - else - var/atom/movable/AM = A - if(AM == buckled) - continue - if(ismob(AM)) - var/mob/M = AM - if(M.buckled) - continue - if(!AM.CanPass(src) || AM.density) - if(AM.anchored) - return AM - if(pulling == AM) - continue - . = AM - -/mob/proc/mob_has_gravity() - return has_gravity() - -/mob/proc/mob_negates_gravity() - return FALSE - - -/mob/proc/slip(s_amount, w_amount, obj/O, lube) - return - -/mob/proc/update_gravity() - return - -//bodypart selection - Cyberboss -//8 toggles through head - eyes - mouth -//4: r-arm 5: chest 6: l-arm -//1: r-leg 2: groin 3: l-leg - -/client/proc/check_has_body_select() - return mob && mob.hud_used && mob.hud_used.zone_select && istype(mob.hud_used.zone_select, /obj/screen/zone_sel) - -/client/verb/body_toggle_head() - set name = "body-toggle-head" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/next_in_line - switch(mob.zone_selected) - if(BODY_ZONE_HEAD) - next_in_line = BODY_ZONE_PRECISE_EYES - if(BODY_ZONE_PRECISE_EYES) - next_in_line = BODY_ZONE_PRECISE_MOUTH - else - next_in_line = BODY_ZONE_HEAD - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(next_in_line, mob) - -/client/verb/body_r_arm() - set name = "body-r-arm" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(BODY_ZONE_R_ARM, mob) - -/client/verb/body_chest() - set name = "body-chest" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(BODY_ZONE_CHEST, mob) - -/client/verb/body_l_arm() - set name = "body-l-arm" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(BODY_ZONE_L_ARM, mob) - -/client/verb/body_r_leg() - set name = "body-r-leg" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(BODY_ZONE_R_LEG, mob) - -/client/verb/body_groin() - set name = "body-groin" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(BODY_ZONE_PRECISE_GROIN, mob) - -/client/verb/body_l_leg() - set name = "body-l-leg" - set hidden = 1 - - if(!check_has_body_select()) - return - - var/obj/screen/zone_sel/selector = mob.hud_used.zone_select - selector.set_selected_zone(BODY_ZONE_L_LEG, mob) - -/client/verb/toggle_walk_run() - set name = "toggle-walk-run" - set hidden = TRUE - set instant = TRUE - if(mob) - mob.toggle_move_intent(usr) - -/mob/proc/toggle_move_intent(mob/user) - if(m_intent == MOVE_INTENT_RUN) - m_intent = MOVE_INTENT_WALK - else - if (HAS_TRAIT(src,TRAIT_NORUNNING)) // FULPSTATION 7/10/19 So you can't run during fortitude. - to_chat(src, "You find yourself unable to run.") - return FALSE - m_intent = MOVE_INTENT_RUN - if(hud_used && hud_used.static_inventory) - for(var/obj/screen/mov_intent/selector in hud_used.static_inventory) - selector.update_icon() - -/mob/verb/up() - set name = "Move Upwards" - set category = "IC" - - if(zMove(UP, TRUE)) - to_chat(src, "You move upwards.") - -/mob/verb/down() - set name = "Move Down" - set category = "IC" - - if(zMove(DOWN, TRUE)) - to_chat(src, "You move down.") - -/mob/proc/zMove(dir, feedback = FALSE) - if(dir != UP && dir != DOWN) - return FALSE - var/turf/target = get_step_multiz(src, dir) - if(!target) - if(feedback) - to_chat(src, "There's nothing in that direction!") - return FALSE - if(!canZMove(dir, target)) - if(feedback) - to_chat(src, "You couldn't move there!") - return FALSE - forceMove(target) - return TRUE - -/mob/proc/canZMove(direction, turf/target) - return FALSE +/mob/CanPass(atom/movable/mover, turf/target) + if((mover.pass_flags & PASSMOB)) + return TRUE + if(istype(mover, /obj/item/projectile) || mover.throwing) + return (!density || lying) + if(buckled == mover) + return TRUE + if(ismob(mover)) + if (mover in buckled_mobs) + return TRUE + return (!mover.density || !density || lying || (mover.throwing && mover.throwing.thrower == src && !ismob(mover))) + +//DO NOT USE THIS UNLESS YOU ABSOLUTELY HAVE TO. THIS IS BEING PHASED OUT FOR THE MOVESPEED MODIFICATION SYSTEM. +//See mob_movespeed.dm +/mob/proc/movement_delay() //update /living/movement_delay() if you change this + return cached_multiplicative_slowdown + +/client/verb/drop_item() + set hidden = 1 + if(!iscyborg(mob) && mob.stat == CONSCIOUS) + mob.dropItemToGround(mob.get_active_held_item()) + return + +/client/proc/Move_object(direct) + if(mob && mob.control_object) + if(mob.control_object.density) + step(mob.control_object,direct) + if(!mob.control_object) + return + mob.control_object.setDir(direct) + else + mob.control_object.forceMove(get_step(mob.control_object,direct)) + +#define MOVEMENT_DELAY_BUFFER 0.75 +#define MOVEMENT_DELAY_BUFFER_DELTA 1.25 + +/client/Move(n, direct) + if(world.time < move_delay) //do not move anything ahead of this check please + return FALSE + else + next_move_dir_add = 0 + next_move_dir_sub = 0 + var/old_move_delay = move_delay + move_delay = world.time + world.tick_lag //this is here because Move() can now be called mutiple times per tick + if(!mob || !mob.loc) + return FALSE + if(!n || !direct) + return FALSE + if(mob.notransform) + return FALSE //This is sota the goto stop mobs from moving var + if(mob.control_object) + return Move_object(direct) + if(!isliving(mob)) + return mob.Move(n, direct) + if(mob.stat == DEAD) + mob.ghostize() + return FALSE + if(mob.force_moving) + return FALSE + + var/mob/living/L = mob //Already checked for isliving earlier + if(L.incorporeal_move) //Move though walls + Process_Incorpmove(direct) + return FALSE + + if(mob.remote_control) //we're controlling something, our movement is relayed to it + return mob.remote_control.relaymove(mob, direct) + + if(isAI(mob)) + return AIMove(n,direct,mob) + + if(Process_Grab()) //are we restrained by someone's grip? + return + + if(mob.buckled) //if we're buckled to something, tell it we moved. + return mob.buckled.relaymove(mob, direct) + + if(!mob.canmove) + return FALSE + + if(isobj(mob.loc) || ismob(mob.loc)) //Inside an object, tell it we moved + var/atom/O = mob.loc + return O.relaymove(mob, direct) + + if(!mob.Process_Spacemove(direct)) + return FALSE + //We are now going to move + var/add_delay = mob.movement_delay() + if(old_move_delay + (add_delay*MOVEMENT_DELAY_BUFFER_DELTA) + MOVEMENT_DELAY_BUFFER > world.time) + move_delay = old_move_delay + else + move_delay = world.time + var/oldloc = mob.loc + + if(L.confused) + var/newdir = 0 + if(L.confused > 40) + newdir = pick(GLOB.alldirs) + else if(prob(L.confused * 1.5)) + newdir = angle2dir(dir2angle(direct) + pick(90, -90)) + else if(prob(L.confused * 3)) + newdir = angle2dir(dir2angle(direct) + pick(45, -45)) + if(newdir) + direct = newdir + n = get_step(L, direct) + + . = ..() + + if((direct & (direct - 1)) && mob.loc == n) //moved diagonally successfully + add_delay *= 2 + if(mob.loc != oldloc) + move_delay += add_delay + if(.) // If mob is null here, we deserve the runtime + if(mob.throwing) + mob.throwing.finalize(FALSE) + + for(var/obj/O in mob.user_movement_hooks) + O.intercept_user_move(direct, mob, n, oldloc) + + var/atom/movable/P = mob.pulling + if(P && !ismob(P) && P.density) + mob.setDir(turn(mob.dir, 180)) + +///Process_Grab() +///Called by client/Move() +///Checks to see if you are being grabbed and if so attemps to break it +/client/proc/Process_Grab() + if(mob.pulledby) + if(mob.incapacitated(ignore_restraints = 1)) + move_delay = world.time + 10 + return TRUE + else if(mob.restrained(ignore_grab = 1)) + move_delay = world.time + 10 + to_chat(src, "You're restrained! You can't move!") + return TRUE + else + return mob.resist_grab(1) + +///Process_Incorpmove +///Called by client/Move() +///Allows mobs to run though walls +/client/proc/Process_Incorpmove(direct) + var/turf/mobloc = get_turf(mob) + if(!isliving(mob)) + return + var/mob/living/L = mob + switch(L.incorporeal_move) + if(INCORPOREAL_MOVE_BASIC) + var/T = get_step(L,direct) + if(T) + L.forceMove(T) + L.setDir(direct) + if(INCORPOREAL_MOVE_SHADOW) + if(prob(50)) + var/locx + var/locy + switch(direct) + if(NORTH) + locx = mobloc.x + locy = (mobloc.y+2) + if(locy>world.maxy) + return + if(SOUTH) + locx = mobloc.x + locy = (mobloc.y-2) + if(locy<1) + return + if(EAST) + locy = mobloc.y + locx = (mobloc.x+2) + if(locx>world.maxx) + return + if(WEST) + locy = mobloc.y + locx = (mobloc.x-2) + if(locx<1) + return + else + return + var/target = locate(locx,locy,mobloc.z) + if(target) + L.loc = target + var/limit = 2//For only two trailing shadows. + for(var/turf/T in getline(mobloc, L.loc)) + new /obj/effect/temp_visual/dir_setting/ninja/shadow(T, L.dir) + limit-- + if(limit<=0) + break + else + new /obj/effect/temp_visual/dir_setting/ninja/shadow(mobloc, L.dir) + var/T = get_step(L,direct) + if(T) + L.forceMove(T) + L.setDir(direct) + if(INCORPOREAL_MOVE_JAUNT) //Incorporeal move, but blocked by holy-watered tiles and salt piles. + var/turf/open/floor/stepTurf = get_step(L, direct) + if(stepTurf) + for(var/obj/effect/decal/cleanable/salt/S in stepTurf) + to_chat(L, "[S] bars your passage!") + if(isrevenant(L)) + var/mob/living/simple_animal/revenant/R = L + R.reveal(20) + R.stun(20) + return + if(stepTurf.flags_1 & NOJAUNT_1) + to_chat(L, "Some strange aura is blocking the way.") + return + if (locate(/obj/effect/blessing, stepTurf)) + to_chat(L, "Holy energies block your path!") + return + + L.forceMove(stepTurf) + L.setDir(direct) + return TRUE + + +///Process_Spacemove +///Called by /client/Move() +///For moving in space +///return TRUE for movement 0 for none +/mob/Process_Spacemove(movement_dir = 0) + if(spacewalk || ..()) + 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))) //You're pushing off something movable, so it moves + to_chat(src, "You push off of [backup] to propel yourself.") + return TRUE + return FALSE + +/mob/get_spacemove_backup() + for(var/A in orange(1, get_turf(src))) + if(isarea(A)) + continue + else if(isturf(A)) + var/turf/turf = A + if(isspaceturf(turf)) + continue + if(!turf.density && !mob_negates_gravity()) + continue + return A + else + var/atom/movable/AM = A + if(AM == buckled) + continue + if(ismob(AM)) + var/mob/M = AM + if(M.buckled) + continue + if(!AM.CanPass(src) || AM.density) + if(AM.anchored) + return AM + if(pulling == AM) + continue + . = AM + +/mob/proc/mob_has_gravity() + return has_gravity() + +/mob/proc/mob_negates_gravity() + return FALSE + + +/mob/proc/slip(s_amount, w_amount, obj/O, lube) + return + +/mob/proc/update_gravity() + return + +//bodypart selection - Cyberboss +//8 toggles through head - eyes - mouth +//4: r-arm 5: chest 6: l-arm +//1: r-leg 2: groin 3: l-leg + +/client/proc/check_has_body_select() + return mob && mob.hud_used && mob.hud_used.zone_select && istype(mob.hud_used.zone_select, /obj/screen/zone_sel) + +/client/verb/body_toggle_head() + set name = "body-toggle-head" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/next_in_line + switch(mob.zone_selected) + if(BODY_ZONE_HEAD) + next_in_line = BODY_ZONE_PRECISE_EYES + if(BODY_ZONE_PRECISE_EYES) + next_in_line = BODY_ZONE_PRECISE_MOUTH + else + next_in_line = BODY_ZONE_HEAD + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(next_in_line, mob) + +/client/verb/body_r_arm() + set name = "body-r-arm" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(BODY_ZONE_R_ARM, mob) + +/client/verb/body_chest() + set name = "body-chest" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(BODY_ZONE_CHEST, mob) + +/client/verb/body_l_arm() + set name = "body-l-arm" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(BODY_ZONE_L_ARM, mob) + +/client/verb/body_r_leg() + set name = "body-r-leg" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(BODY_ZONE_R_LEG, mob) + +/client/verb/body_groin() + set name = "body-groin" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(BODY_ZONE_PRECISE_GROIN, mob) + +/client/verb/body_l_leg() + set name = "body-l-leg" + set hidden = 1 + + if(!check_has_body_select()) + return + + var/obj/screen/zone_sel/selector = mob.hud_used.zone_select + selector.set_selected_zone(BODY_ZONE_L_LEG, mob) + +/client/verb/toggle_walk_run() + set name = "toggle-walk-run" + set hidden = TRUE + set instant = TRUE + if(mob) + mob.toggle_move_intent(usr) + +/mob/proc/toggle_move_intent(mob/user) + if(m_intent == MOVE_INTENT_RUN) + m_intent = MOVE_INTENT_WALK + else + if (HAS_TRAIT(src,TRAIT_NORUNNING)) // FULPSTATION 7/10/19 So you can't run during fortitude. + to_chat(src, "You find yourself unable to run.") + return FALSE + m_intent = MOVE_INTENT_RUN + if(hud_used && hud_used.static_inventory) + for(var/obj/screen/mov_intent/selector in hud_used.static_inventory) + selector.update_icon() + +/mob/verb/up() + set name = "Move Upwards" + set category = "IC" + + if(zMove(UP, TRUE)) + to_chat(src, "You move upwards.") + +/mob/verb/down() + set name = "Move Down" + set category = "IC" + + if(zMove(DOWN, TRUE)) + to_chat(src, "You move down.") + +/mob/proc/zMove(dir, feedback = FALSE) + if(dir != UP && dir != DOWN) + return FALSE + var/turf/target = get_step_multiz(src, dir) + if(!target) + if(feedback) + to_chat(src, "There's nothing in that direction!") + return FALSE + if(!canZMove(dir, target)) + if(feedback) + to_chat(src, "You couldn't move there!") + return FALSE + forceMove(target) + return TRUE + +/mob/proc/canZMove(direction, turf/target) + return FALSE diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 9b8f100907..8f7983bb83 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -1,104 +1,104 @@ -//Speech verbs. -/mob/verb/say_verb(message as text) - set name = "Say" - set category = "IC" - if(GLOB.say_disabled) //This is here to try to identify lag problems - to_chat(usr, "Speech is currently admin-disabled.") - return - if(message) - say(message) - -/mob/say_mod(input, message_mode) - var/customsayverb = findtext(input, "*") - if(customsayverb && message_mode != MODE_WHISPER_CRIT) - message_mode = MODE_CUSTOM_SAY - return lowertext(copytext(input, 1, customsayverb)) - else - return ..() - -/mob/verb/whisper_verb(message as text) - set name = "Whisper" - set category = "IC" - if(GLOB.say_disabled) //This is here to try to identify lag problems - to_chat(usr, "Speech is currently admin-disabled.") - return - whisper(message) - -/mob/proc/whisper(message, datum/language/language=null) - say(message, language) //only living mobs actually whisper, everything else just talks - -/mob/verb/me_verb(message as message) - set name = "Me" - set category = "IC" - - if(GLOB.say_disabled) //This is here to try to identify lag problems - to_chat(usr, "Speech is currently admin-disabled.") - return - - message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) - - usr.emote("me",1,message,TRUE) - -/mob/proc/say_dead(var/message) - var/name = real_name - var/alt_name = "" - - if(GLOB.say_disabled) //This is here to try to identify lag problems - to_chat(usr, "Speech is currently admin-disabled.") - return - - var/jb = jobban_isbanned(src, "OOC") - if(QDELETED(src)) - return - - if(jb) - to_chat(src, "You have been banned from deadchat.") - return - - - - if (src.client) - if(src.client.prefs.muted & MUTE_DEADCHAT) - to_chat(src, "You cannot talk in deadchat (muted).") - return - - if(src.client.handle_spam_prevention(message,MUTE_DEADCHAT)) - return - - var/mob/dead/observer/O = src - if(isobserver(src) && O.deadchat_name) - name = "[O.deadchat_name]" - else - if(mind && mind.name) - name = "[mind.name]" - else - name = real_name - if(name != real_name) - alt_name = " (died as [real_name])" - - var/spanned = say_quote(message) - message = emoji_parse(message) - var/rendered = "DEAD: [name][alt_name] [emoji_parse(spanned)]" - log_talk(message, LOG_SAY, tag="DEAD") - deadchat_broadcast(rendered, follow_target = src, speaker_key = key) - -/mob/proc/check_emote(message) - if(copytext(message, 1, 2) == "*") - emote(copytext(message, 2), intentional = TRUE) - return 1 - -/mob/proc/hivecheck() - return 0 - -/mob/proc/lingcheck() - return LINGHIVE_NONE - -/mob/proc/get_message_mode(message) - var/key = copytext(message, 1, 2) - if(key == "#") - return MODE_WHISPER - else if(key == ";") - return MODE_HEADSET - else if(length(message) > 2 && (key in GLOB.department_radio_prefixes)) - var/key_symbol = lowertext(copytext(message, 2, 3)) - return GLOB.department_radio_keys[key_symbol] +//Speech verbs. +/mob/verb/say_verb(message as text) + set name = "Say" + set category = "IC" + if(GLOB.say_disabled) //This is here to try to identify lag problems + to_chat(usr, "Speech is currently admin-disabled.") + return + if(message) + say(message) + +/mob/say_mod(input, message_mode) + var/customsayverb = findtext(input, "*") + if(customsayverb && message_mode != MODE_WHISPER_CRIT) + message_mode = MODE_CUSTOM_SAY + return lowertext(copytext(input, 1, customsayverb)) + else + return ..() + +/mob/verb/whisper_verb(message as text) + set name = "Whisper" + set category = "IC" + if(GLOB.say_disabled) //This is here to try to identify lag problems + to_chat(usr, "Speech is currently admin-disabled.") + return + whisper(message) + +/mob/proc/whisper(message, datum/language/language=null) + say(message, language) //only living mobs actually whisper, everything else just talks + +/mob/verb/me_verb(message as message) + set name = "Me" + set category = "IC" + + if(GLOB.say_disabled) //This is here to try to identify lag problems + to_chat(usr, "Speech is currently admin-disabled.") + return + + message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) + + usr.emote("me",1,message,TRUE) + +/mob/proc/say_dead(var/message) + var/name = real_name + var/alt_name = "" + + if(GLOB.say_disabled) //This is here to try to identify lag problems + to_chat(usr, "Speech is currently admin-disabled.") + return + + var/jb = jobban_isbanned(src, "OOC") + if(QDELETED(src)) + return + + if(jb) + to_chat(src, "You have been banned from deadchat.") + return + + + + if (src.client) + if(src.client.prefs.muted & MUTE_DEADCHAT) + to_chat(src, "You cannot talk in deadchat (muted).") + return + + if(src.client.handle_spam_prevention(message,MUTE_DEADCHAT)) + return + + var/mob/dead/observer/O = src + if(isobserver(src) && O.deadchat_name) + name = "[O.deadchat_name]" + else + if(mind && mind.name) + name = "[mind.name]" + else + name = real_name + if(name != real_name) + alt_name = " (died as [real_name])" + + var/spanned = say_quote(message) + message = emoji_parse(message) + var/rendered = "DEAD: [name][alt_name] [emoji_parse(spanned)]" + log_talk(message, LOG_SAY, tag="DEAD") + deadchat_broadcast(rendered, follow_target = src, speaker_key = key) + +/mob/proc/check_emote(message) + if(copytext(message, 1, 2) == "*") + emote(copytext(message, 2), intentional = TRUE) + return 1 + +/mob/proc/hivecheck() + return 0 + +/mob/proc/lingcheck() + return LINGHIVE_NONE + +/mob/proc/get_message_mode(message) + var/key = copytext(message, 1, 2) + if(key == "#") + return MODE_WHISPER + else if(key == ";") + return MODE_HEADSET + else if(length(message) > 2 && (key in GLOB.department_radio_prefixes)) + var/key_symbol = lowertext(copytext(message, 2, 3)) + return GLOB.department_radio_keys[key_symbol] diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm index 6394b45aa7..4af6b4d644 100644 --- a/code/modules/mob/transform_procs.dm +++ b/code/modules/mob/transform_procs.dm @@ -1,586 +1,586 @@ -/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG)) - if (notransform) - return - //Handle items on mob - - //first implants & organs - var/list/stored_implants = list() - var/list/int_organs = list() - - if (tr_flags & TR_KEEPIMPLANTS) - for(var/X in implants) - var/obj/item/implant/IMP = X - stored_implants += IMP - IMP.removed(src, 1, 1) - - var/list/missing_bodyparts_zones = get_missing_limbs() - - var/obj/item/cavity_object - - var/obj/item/bodypart/chest/CH = get_bodypart(BODY_ZONE_CHEST) - if(CH.cavity_item) - cavity_object = CH.cavity_item - CH.cavity_item = null - - if(tr_flags & TR_KEEPITEMS) - var/Itemlist = get_equipped_items(TRUE) - Itemlist += held_items - for(var/obj/item/W in Itemlist) - dropItemToGround(W) - - //Make mob invisible and spawn animation - notransform = 1 - canmove = 0 - Stun(22, ignore_canstun = TRUE) - icon = null - cut_overlays() - invisibility = INVISIBILITY_MAXIMUM - - new /obj/effect/temp_visual/monkeyify(loc) - sleep(22) - var/mob/living/carbon/monkey/O = new /mob/living/carbon/monkey( loc ) - - // hash the original name? - if(tr_flags & TR_HASHNAME) - O.name = "monkey ([copytext(md5(real_name), 2, 6)])" - O.real_name = "monkey ([copytext(md5(real_name), 2, 6)])" - - //handle DNA and other attributes - dna.transfer_identity(O) - O.updateappearance(icon_update=0) - - if(tr_flags & TR_KEEPSE) - O.dna.struc_enzymes = dna.struc_enzymes - var/datum/mutation/human/race/R = GLOB.mutations_list[RACEMUT] - O.dna.struc_enzymes = R.set_se(O.dna.struc_enzymes, on=1)//we don't want to keep the race block inactive - - if(suiciding) - O.suiciding = suiciding - if(hellbound) - O.hellbound = hellbound - O.a_intent = INTENT_HARM - - //keep viruses? - if (tr_flags & TR_KEEPVIRUS) - O.diseases = diseases - diseases = list() - for(var/thing in O.diseases) - var/datum/disease/D = thing - D.affected_mob = O - - //keep damage? - if (tr_flags & TR_KEEPDAMAGE) - O.setToxLoss(getToxLoss(), 0) - O.adjustBruteLoss(getBruteLoss(), 0) - O.setOxyLoss(getOxyLoss(), 0) - O.setCloneLoss(getCloneLoss(), 0) - O.adjustFireLoss(getFireLoss(), 0) - O.setOrganLoss(ORGAN_SLOT_BRAIN, getOrganLoss(ORGAN_SLOT_BRAIN), 0) - O.adjustStaminaLoss(getStaminaLoss(), 0)//CIT CHANGE - makes monkey transformations inherit stamina - O.updatehealth() - O.radiation = radiation - - //re-add implants to new mob - if (tr_flags & TR_KEEPIMPLANTS) - for(var/Y in implants) - var/obj/item/implant/IMP = Y - IMP.implant(O, null, 1) - - //re-add organs to new mob. this order prevents moving the mind to a brain at any point - if(tr_flags & TR_KEEPORGANS) - for(var/X in O.internal_organs) - var/obj/item/organ/I = X - I.Remove(O, 1) - - if(mind) - mind.transfer_to(O) - var/datum/antagonist/changeling/changeling = O.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - var/obj/effect/proc_holder/changeling/humanform/HF = new /obj/effect/proc_holder/changeling/humanform(null) - changeling.purchasedpowers += HF - HF.action.Grant(O) - - for(var/X in internal_organs) - var/obj/item/organ/I = X - int_organs += I - I.Remove(src, 1) - - for(var/X in int_organs) - var/obj/item/organ/I = X - I.Insert(O, 1) - - var/obj/item/bodypart/chest/torso = O.get_bodypart(BODY_ZONE_CHEST) - if(cavity_object) - torso.cavity_item = cavity_object //cavity item is given to the new chest - cavity_object.forceMove(O) - - for(var/missing_zone in missing_bodyparts_zones) - var/obj/item/bodypart/BP = O.get_bodypart(missing_zone) - BP.drop_limb(1) - if(!(tr_flags & TR_KEEPORGANS)) //we didn't already get rid of the organs of the newly spawned mob - for(var/X in O.internal_organs) - var/obj/item/organ/G = X - if(BP.body_zone == check_zone(G.zone)) - if(mind && mind.has_antag_datum(/datum/antagonist/changeling) && istype(G, /obj/item/organ/brain)) - continue //so headless changelings don't lose their brain when transforming - qdel(G) //we lose the organs in the missing limbs - qdel(BP) - - //transfer mind if we didn't yet - if(mind) - mind.transfer_to(O) - var/datum/antagonist/changeling/changeling = O.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - var/obj/effect/proc_holder/changeling/humanform/HF = new /obj/effect/proc_holder/changeling/humanform(null) - changeling.purchasedpowers += HF - HF.action.Grant(O) - - if (tr_flags & TR_DEFAULTMSG) - to_chat(O, "You are now a monkey.") - - for(var/A in loc.vars) - if(loc.vars[A] == src) - loc.vars[A] = O - - transfer_observers_to(O) - - . = O - - qdel(src) - -////////////////////////// Humanize ////////////////////////////// -//Could probably be merged with monkeyize but other transformations got their own procs, too - -/mob/living/carbon/proc/humanize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG)) - if (notransform) - return - //Handle items on mob - - //first implants & organs - var/list/stored_implants = list() - var/list/int_organs = list() - - if (tr_flags & TR_KEEPIMPLANTS) - for(var/X in implants) - var/obj/item/implant/IMP = X - stored_implants += IMP - IMP.removed(src, 1, 1) - - var/list/missing_bodyparts_zones = get_missing_limbs() - - var/obj/item/cavity_object - - var/obj/item/bodypart/chest/CH = get_bodypart(BODY_ZONE_CHEST) - if(CH.cavity_item) - cavity_object = CH.cavity_item - CH.cavity_item = null - - //now the rest - if (tr_flags & TR_KEEPITEMS) - var/Itemlist = get_equipped_items(TRUE) - Itemlist += held_items - for(var/obj/item/W in Itemlist) - dropItemToGround(W, TRUE) - if (client) - client.screen -= W - - - - //Make mob invisible and spawn animation - notransform = 1 - canmove = 0 - Stun(22, ignore_canstun = TRUE) - icon = null - cut_overlays() - invisibility = INVISIBILITY_MAXIMUM - new /obj/effect/temp_visual/monkeyify/humanify(loc) - sleep(22) - var/mob/living/carbon/human/O = new( loc ) - for(var/obj/item/C in O.loc) - O.equip_to_appropriate_slot(C) - - dna.transfer_identity(O) - O.updateappearance(mutcolor_update=1) - - if(cmptext("monkey",copytext(O.dna.real_name,1,7))) - O.real_name = random_unique_name(O.gender) - O.dna.generate_unique_enzymes(O) - else - O.real_name = O.dna.real_name - O.name = O.real_name - - if(tr_flags & TR_KEEPSE) - O.dna.struc_enzymes = dna.struc_enzymes - var/datum/mutation/human/race/R = GLOB.mutations_list[RACEMUT] - O.dna.struc_enzymes = R.set_se(O.dna.struc_enzymes, on=0)//we don't want to keep the race block active - O.domutcheck() - - if(suiciding) - O.suiciding = suiciding - if(hellbound) - O.hellbound = hellbound - - //keep viruses? - if (tr_flags & TR_KEEPVIRUS) - O.diseases = diseases - diseases = list() - for(var/thing in O.diseases) - var/datum/disease/D = thing - D.affected_mob = O - O.med_hud_set_status() - - //keep damage? - if (tr_flags & TR_KEEPDAMAGE) - O.setToxLoss(getToxLoss(), 0) - O.adjustBruteLoss(getBruteLoss(), 0) - O.setOxyLoss(getOxyLoss(), 0) - O.setCloneLoss(getCloneLoss(), 0) - O.adjustFireLoss(getFireLoss(), 0) - O.setOrganLoss(ORGAN_SLOT_BRAIN, getOrganLoss(ORGAN_SLOT_BRAIN), 0) - O.adjustStaminaLoss(getStaminaLoss(), 0)//CIT CHANGE - makes monkey transformations inherit stamina - O.updatehealth() - O.radiation = radiation - - //re-add implants to new mob - if (tr_flags & TR_KEEPIMPLANTS) - for(var/Y in implants) - var/obj/item/implant/IMP = Y - IMP.implant(O, null, 1) - - if(tr_flags & TR_KEEPORGANS) - for(var/X in O.internal_organs) - var/obj/item/organ/I = X - I.Remove(O, 1) - - if(mind) - mind.transfer_to(O) - var/datum/antagonist/changeling/changeling = O.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - for(var/obj/effect/proc_holder/changeling/humanform/HF in changeling.purchasedpowers) - changeling.purchasedpowers -= HF - - for(var/X in internal_organs) - var/obj/item/organ/I = X - int_organs += I - I.Remove(src, 1) - - for(var/X in int_organs) - var/obj/item/organ/I = X - I.Insert(O, 1) - - - var/obj/item/bodypart/chest/torso = get_bodypart(BODY_ZONE_CHEST) - if(cavity_object) - torso.cavity_item = cavity_object //cavity item is given to the new chest - cavity_object.forceMove(O) - - for(var/missing_zone in missing_bodyparts_zones) - var/obj/item/bodypart/BP = O.get_bodypart(missing_zone) - BP.drop_limb(1) - if(!(tr_flags & TR_KEEPORGANS)) //we didn't already get rid of the organs of the newly spawned mob - for(var/X in O.internal_organs) - var/obj/item/organ/G = X - if(BP.body_zone == check_zone(G.zone)) - if(mind && mind.has_antag_datum(/datum/antagonist/changeling) && istype(G, /obj/item/organ/brain)) - continue //so headless changelings don't lose their brain when transforming - qdel(G) //we lose the organs in the missing limbs - qdel(BP) - - if(mind) - mind.transfer_to(O) - var/datum/antagonist/changeling/changeling = O.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - for(var/obj/effect/proc_holder/changeling/humanform/HF in changeling.purchasedpowers) - changeling.purchasedpowers -= HF - - O.a_intent = INTENT_HELP - if (tr_flags & TR_DEFAULTMSG) - to_chat(O, "You are now a human.") - - transfer_observers_to(O) - - . = O - - for(var/A in loc.vars) - if(loc.vars[A] == src) - loc.vars[A] = O - - qdel(src) - -/mob/living/carbon/human/AIize() - if (notransform) - return - for(var/t in bodyparts) - qdel(t) - - return ..() - -/mob/living/carbon/AIize() - if (notransform) - return - for(var/obj/item/W in src) - dropItemToGround(W) - regenerate_icons() - notransform = 1 - canmove = 0 - icon = null - invisibility = INVISIBILITY_MAXIMUM - return ..() - -/mob/proc/AIize(transfer_after = TRUE) - var/list/turf/landmark_loc = list() - for(var/obj/effect/landmark/start/ai/sloc in GLOB.landmarks_list) - if(locate(/mob/living/silicon/ai) in sloc.loc) - continue - if(sloc.primary_ai) - LAZYCLEARLIST(landmark_loc) - landmark_loc += sloc.loc - break - landmark_loc += sloc.loc - if(!landmark_loc.len) - to_chat(src, "Oh god sorry we can't find an unoccupied AI spawn location, so we're spawning you on top of someone.") - for(var/obj/effect/landmark/start/ai/sloc in GLOB.landmarks_list) - landmark_loc += sloc.loc - - if(!landmark_loc.len) - message_admins("Could not find ai landmark for [src]. Yell at a mapper! We are spawning them at their current location.") - landmark_loc += loc - - if(client) - stop_sound_channel(CHANNEL_LOBBYMUSIC) - - if(!transfer_after) - mind.active = FALSE - - . = new /mob/living/silicon/ai(pick(landmark_loc), null, src) - - qdel(src) - -/mob/living/carbon/human/proc/Robotize(delete_items = 0, transfer_after = TRUE) - if (notransform) - return - for(var/obj/item/W in src) - if(delete_items) - qdel(W) - else - dropItemToGround(W) - regenerate_icons() - notransform = 1 - canmove = 0 - icon = null - invisibility = INVISIBILITY_MAXIMUM - for(var/t in bodyparts) - qdel(t) - - var/mob/living/silicon/robot/R = new /mob/living/silicon/robot(loc) - - R.gender = gender - R.invisibility = 0 - - if(mind) //TODO - if(!transfer_after) - mind.active = FALSE - mind.transfer_to(R) - else if(transfer_after) - transfer_ckey(R) - - R.apply_pref_name("cyborg") - - if(R.mmi) - R.mmi.name = "Man-Machine Interface: [real_name]" - if(R.mmi.brain) - R.mmi.brain.name = "[real_name]'s brain" - if(R.mmi.brainmob) - R.mmi.brainmob.real_name = real_name //the name of the brain inside the cyborg is the robotized human's name. - R.mmi.brainmob.name = real_name - - R.job = "Cyborg" - R.notify_ai(NEW_BORG) - - . = R - qdel(src) - -//human -> alien -/mob/living/carbon/human/proc/Alienize(mind_transfer = TRUE) - if (notransform) - return - for(var/obj/item/W in src) - dropItemToGround(W) - regenerate_icons() - notransform = 1 - canmove = 0 - icon = null - invisibility = INVISIBILITY_MAXIMUM - for(var/t in bodyparts) - qdel(t) - - var/alien_caste = pick("Hunter","Sentinel","Drone") - var/mob/living/carbon/alien/humanoid/new_xeno - switch(alien_caste) - if("Hunter") - new_xeno = new /mob/living/carbon/alien/humanoid/hunter(loc) - if("Sentinel") - new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(loc) - if("Drone") - new_xeno = new /mob/living/carbon/alien/humanoid/drone(loc) - - new_xeno.a_intent = INTENT_HARM - if(mind && mind_transfer) - mind.transfer_to(new_xeno) - else - transfer_ckey(new_xeno) - - to_chat(new_xeno, "You are now an alien.") - . = new_xeno - qdel(src) - -/mob/living/carbon/human/proc/slimeize(reproduce, mind_transfer = TRUE) - if (notransform) - return - for(var/obj/item/W in src) - dropItemToGround(W) - regenerate_icons() - notransform = 1 - canmove = 0 - icon = null - invisibility = INVISIBILITY_MAXIMUM - for(var/t in bodyparts) - qdel(t) - - var/mob/living/simple_animal/slime/new_slime - if(reproduce) - var/number = pick(14;2,3,4) //reproduce (has a small chance of producing 3 or 4 offspring) - var/list/babies = list() - for(var/i=1,i<=number,i++) - var/mob/living/simple_animal/slime/M = new/mob/living/simple_animal/slime(loc) - M.nutrition = round(nutrition/number) - step_away(M,src) - babies += M - new_slime = pick(babies) - else - new_slime = new /mob/living/simple_animal/slime(loc) - new_slime.a_intent = INTENT_HARM - if(mind && mind_transfer) - mind.transfer_to(new_slime) - else - transfer_ckey(new_slime) - - to_chat(new_slime, "You are now a slime. Skreee!") - . = new_slime - qdel(src) - -/mob/proc/become_overmind(starting_points = 60, mind_transfer = FALSE) - var/mob/camera/blob/B = new /mob/camera/blob(get_turf(src), starting_points) - if(mind && mind_transfer) - mind.transfer_to(B) - else - transfer_ckey(B) - . = B - qdel(src) - - -/mob/living/carbon/human/proc/corgize(mind_transfer = TRUE) - if (notransform) - return - for(var/obj/item/W in src) - dropItemToGround(W) - regenerate_icons() - notransform = 1 - canmove = 0 - icon = null - invisibility = INVISIBILITY_MAXIMUM - for(var/t in bodyparts) //this really should not be necessary - qdel(t) - - var/mob/living/simple_animal/pet/dog/corgi/new_corgi = new /mob/living/simple_animal/pet/dog/corgi (loc) - new_corgi.a_intent = INTENT_HARM - if(mind && mind_transfer) - mind.transfer_to(new_corgi) - else - transfer_ckey(new_corgi) - - to_chat(new_corgi, "You are now a Corgi. Yap Yap!") - . = new_corgi - qdel(src) - -/mob/living/carbon/proc/gorillize(mind_transfer = TRUE) - if(notransform) - return - - SSblackbox.record_feedback("amount", "gorillas_created", 1) - - var/Itemlist = get_equipped_items(TRUE) - Itemlist += held_items - for(var/obj/item/W in Itemlist) - dropItemToGround(W, TRUE) - - regenerate_icons() - notransform = TRUE - canmove = FALSE - icon = null - invisibility = INVISIBILITY_MAXIMUM - var/mob/living/simple_animal/hostile/gorilla/new_gorilla = new (get_turf(src)) - new_gorilla.a_intent = INTENT_HARM - if(mind && mind_transfer) - mind.transfer_to(new_gorilla) - else - transfer_ckey(new_gorilla) - to_chat(new_gorilla, "You are now a gorilla. Ooga ooga!") - . = new_gorilla - qdel(src) - -/mob/living/carbon/human/Animalize(mind_transfer = TRUE) - - var/list/mobtypes = typesof(/mob/living/simple_animal) - var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") as null|anything in mobtypes - if(!mobpath) - return - if(mind) - mind_transfer = alert("Want to transfer their mind into the new mob", "Mind Transfer", "Yes", "No") == "Yes" ? TRUE : FALSE - - if(notransform) - return - for(var/obj/item/W in src) - dropItemToGround(W) - - regenerate_icons() - notransform = TRUE - canmove = FALSE - icon = null - invisibility = INVISIBILITY_MAXIMUM - - for(var/t in bodyparts) - qdel(t) - - var/mob/new_mob = new mobpath(src.loc) - if(mind && mind_transfer) - mind.transfer_to(new_mob) - else - transfer_ckey(new_mob) - new_mob.a_intent = INTENT_HARM - - - to_chat(new_mob, "You suddenly feel more... animalistic.") - . = new_mob - qdel(src) - -/mob/proc/Animalize(mind_transfer = TRUE) - - var/list/mobtypes = typesof(/mob/living/simple_animal) - var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") as null|anything in mobtypes - if(!mobpath) - return - if(mind) - mind_transfer = alert("Want to transfer their mind into the new mob", "Mind Transfer", "Yes", "No") == "Yes" ? TRUE : FALSE - - var/mob/new_mob = new mobpath(src.loc) - - if(mind && mind_transfer) - mind.transfer_to(new_mob) - else - transfer_ckey(new_mob) - new_mob.a_intent = INTENT_HARM - to_chat(new_mob, "You feel more... animalistic") - - . = new_mob - qdel(src) +/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG)) + if (notransform) + return + //Handle items on mob + + //first implants & organs + var/list/stored_implants = list() + var/list/int_organs = list() + + if (tr_flags & TR_KEEPIMPLANTS) + for(var/X in implants) + var/obj/item/implant/IMP = X + stored_implants += IMP + IMP.removed(src, 1, 1) + + var/list/missing_bodyparts_zones = get_missing_limbs() + + var/obj/item/cavity_object + + var/obj/item/bodypart/chest/CH = get_bodypart(BODY_ZONE_CHEST) + if(CH.cavity_item) + cavity_object = CH.cavity_item + CH.cavity_item = null + + if(tr_flags & TR_KEEPITEMS) + var/Itemlist = get_equipped_items(TRUE) + Itemlist += held_items + for(var/obj/item/W in Itemlist) + dropItemToGround(W) + + //Make mob invisible and spawn animation + notransform = 1 + canmove = 0 + Stun(22, ignore_canstun = TRUE) + icon = null + cut_overlays() + invisibility = INVISIBILITY_MAXIMUM + + new /obj/effect/temp_visual/monkeyify(loc) + sleep(22) + var/mob/living/carbon/monkey/O = new /mob/living/carbon/monkey( loc ) + + // hash the original name? + if(tr_flags & TR_HASHNAME) + O.name = "monkey ([copytext(md5(real_name), 2, 6)])" + O.real_name = "monkey ([copytext(md5(real_name), 2, 6)])" + + //handle DNA and other attributes + dna.transfer_identity(O) + O.updateappearance(icon_update=0) + + if(tr_flags & TR_KEEPSE) + O.dna.struc_enzymes = dna.struc_enzymes + var/datum/mutation/human/race/R = GLOB.mutations_list[RACEMUT] + O.dna.struc_enzymes = R.set_se(O.dna.struc_enzymes, on=1)//we don't want to keep the race block inactive + + if(suiciding) + O.suiciding = suiciding + if(hellbound) + O.hellbound = hellbound + O.a_intent = INTENT_HARM + + //keep viruses? + if (tr_flags & TR_KEEPVIRUS) + O.diseases = diseases + diseases = list() + for(var/thing in O.diseases) + var/datum/disease/D = thing + D.affected_mob = O + + //keep damage? + if (tr_flags & TR_KEEPDAMAGE) + O.setToxLoss(getToxLoss(), 0) + O.adjustBruteLoss(getBruteLoss(), 0) + O.setOxyLoss(getOxyLoss(), 0) + O.setCloneLoss(getCloneLoss(), 0) + O.adjustFireLoss(getFireLoss(), 0) + O.setOrganLoss(ORGAN_SLOT_BRAIN, getOrganLoss(ORGAN_SLOT_BRAIN), 0) + O.adjustStaminaLoss(getStaminaLoss(), 0)//CIT CHANGE - makes monkey transformations inherit stamina + O.updatehealth() + O.radiation = radiation + + //re-add implants to new mob + if (tr_flags & TR_KEEPIMPLANTS) + for(var/Y in implants) + var/obj/item/implant/IMP = Y + IMP.implant(O, null, 1) + + //re-add organs to new mob. this order prevents moving the mind to a brain at any point + if(tr_flags & TR_KEEPORGANS) + for(var/X in O.internal_organs) + var/obj/item/organ/I = X + I.Remove(O, 1) + + if(mind) + mind.transfer_to(O) + var/datum/antagonist/changeling/changeling = O.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling) + var/obj/effect/proc_holder/changeling/humanform/HF = new /obj/effect/proc_holder/changeling/humanform(null) + changeling.purchasedpowers += HF + HF.action.Grant(O) + + for(var/X in internal_organs) + var/obj/item/organ/I = X + int_organs += I + I.Remove(src, 1) + + for(var/X in int_organs) + var/obj/item/organ/I = X + I.Insert(O, 1) + + var/obj/item/bodypart/chest/torso = O.get_bodypart(BODY_ZONE_CHEST) + if(cavity_object) + torso.cavity_item = cavity_object //cavity item is given to the new chest + cavity_object.forceMove(O) + + for(var/missing_zone in missing_bodyparts_zones) + var/obj/item/bodypart/BP = O.get_bodypart(missing_zone) + BP.drop_limb(1) + if(!(tr_flags & TR_KEEPORGANS)) //we didn't already get rid of the organs of the newly spawned mob + for(var/X in O.internal_organs) + var/obj/item/organ/G = X + if(BP.body_zone == check_zone(G.zone)) + if(mind && mind.has_antag_datum(/datum/antagonist/changeling) && istype(G, /obj/item/organ/brain)) + continue //so headless changelings don't lose their brain when transforming + qdel(G) //we lose the organs in the missing limbs + qdel(BP) + + //transfer mind if we didn't yet + if(mind) + mind.transfer_to(O) + var/datum/antagonist/changeling/changeling = O.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling) + var/obj/effect/proc_holder/changeling/humanform/HF = new /obj/effect/proc_holder/changeling/humanform(null) + changeling.purchasedpowers += HF + HF.action.Grant(O) + + if (tr_flags & TR_DEFAULTMSG) + to_chat(O, "You are now a monkey.") + + for(var/A in loc.vars) + if(loc.vars[A] == src) + loc.vars[A] = O + + transfer_observers_to(O) + + . = O + + qdel(src) + +////////////////////////// Humanize ////////////////////////////// +//Could probably be merged with monkeyize but other transformations got their own procs, too + +/mob/living/carbon/proc/humanize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_DEFAULTMSG)) + if (notransform) + return + //Handle items on mob + + //first implants & organs + var/list/stored_implants = list() + var/list/int_organs = list() + + if (tr_flags & TR_KEEPIMPLANTS) + for(var/X in implants) + var/obj/item/implant/IMP = X + stored_implants += IMP + IMP.removed(src, 1, 1) + + var/list/missing_bodyparts_zones = get_missing_limbs() + + var/obj/item/cavity_object + + var/obj/item/bodypart/chest/CH = get_bodypart(BODY_ZONE_CHEST) + if(CH.cavity_item) + cavity_object = CH.cavity_item + CH.cavity_item = null + + //now the rest + if (tr_flags & TR_KEEPITEMS) + var/Itemlist = get_equipped_items(TRUE) + Itemlist += held_items + for(var/obj/item/W in Itemlist) + dropItemToGround(W, TRUE) + if (client) + client.screen -= W + + + + //Make mob invisible and spawn animation + notransform = 1 + canmove = 0 + Stun(22, ignore_canstun = TRUE) + icon = null + cut_overlays() + invisibility = INVISIBILITY_MAXIMUM + new /obj/effect/temp_visual/monkeyify/humanify(loc) + sleep(22) + var/mob/living/carbon/human/O = new( loc ) + for(var/obj/item/C in O.loc) + O.equip_to_appropriate_slot(C) + + dna.transfer_identity(O) + O.updateappearance(mutcolor_update=1) + + if(cmptext("monkey",copytext(O.dna.real_name,1,7))) + O.real_name = random_unique_name(O.gender) + O.dna.generate_unique_enzymes(O) + else + O.real_name = O.dna.real_name + O.name = O.real_name + + if(tr_flags & TR_KEEPSE) + O.dna.struc_enzymes = dna.struc_enzymes + var/datum/mutation/human/race/R = GLOB.mutations_list[RACEMUT] + O.dna.struc_enzymes = R.set_se(O.dna.struc_enzymes, on=0)//we don't want to keep the race block active + O.domutcheck() + + if(suiciding) + O.suiciding = suiciding + if(hellbound) + O.hellbound = hellbound + + //keep viruses? + if (tr_flags & TR_KEEPVIRUS) + O.diseases = diseases + diseases = list() + for(var/thing in O.diseases) + var/datum/disease/D = thing + D.affected_mob = O + O.med_hud_set_status() + + //keep damage? + if (tr_flags & TR_KEEPDAMAGE) + O.setToxLoss(getToxLoss(), 0) + O.adjustBruteLoss(getBruteLoss(), 0) + O.setOxyLoss(getOxyLoss(), 0) + O.setCloneLoss(getCloneLoss(), 0) + O.adjustFireLoss(getFireLoss(), 0) + O.setOrganLoss(ORGAN_SLOT_BRAIN, getOrganLoss(ORGAN_SLOT_BRAIN), 0) + O.adjustStaminaLoss(getStaminaLoss(), 0)//CIT CHANGE - makes monkey transformations inherit stamina + O.updatehealth() + O.radiation = radiation + + //re-add implants to new mob + if (tr_flags & TR_KEEPIMPLANTS) + for(var/Y in implants) + var/obj/item/implant/IMP = Y + IMP.implant(O, null, 1) + + if(tr_flags & TR_KEEPORGANS) + for(var/X in O.internal_organs) + var/obj/item/organ/I = X + I.Remove(O, 1) + + if(mind) + mind.transfer_to(O) + var/datum/antagonist/changeling/changeling = O.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling) + for(var/obj/effect/proc_holder/changeling/humanform/HF in changeling.purchasedpowers) + changeling.purchasedpowers -= HF + + for(var/X in internal_organs) + var/obj/item/organ/I = X + int_organs += I + I.Remove(src, 1) + + for(var/X in int_organs) + var/obj/item/organ/I = X + I.Insert(O, 1) + + + var/obj/item/bodypart/chest/torso = get_bodypart(BODY_ZONE_CHEST) + if(cavity_object) + torso.cavity_item = cavity_object //cavity item is given to the new chest + cavity_object.forceMove(O) + + for(var/missing_zone in missing_bodyparts_zones) + var/obj/item/bodypart/BP = O.get_bodypart(missing_zone) + BP.drop_limb(1) + if(!(tr_flags & TR_KEEPORGANS)) //we didn't already get rid of the organs of the newly spawned mob + for(var/X in O.internal_organs) + var/obj/item/organ/G = X + if(BP.body_zone == check_zone(G.zone)) + if(mind && mind.has_antag_datum(/datum/antagonist/changeling) && istype(G, /obj/item/organ/brain)) + continue //so headless changelings don't lose their brain when transforming + qdel(G) //we lose the organs in the missing limbs + qdel(BP) + + if(mind) + mind.transfer_to(O) + var/datum/antagonist/changeling/changeling = O.mind.has_antag_datum(/datum/antagonist/changeling) + if(changeling) + for(var/obj/effect/proc_holder/changeling/humanform/HF in changeling.purchasedpowers) + changeling.purchasedpowers -= HF + + O.a_intent = INTENT_HELP + if (tr_flags & TR_DEFAULTMSG) + to_chat(O, "You are now a human.") + + transfer_observers_to(O) + + . = O + + for(var/A in loc.vars) + if(loc.vars[A] == src) + loc.vars[A] = O + + qdel(src) + +/mob/living/carbon/human/AIize() + if (notransform) + return + for(var/t in bodyparts) + qdel(t) + + return ..() + +/mob/living/carbon/AIize() + if (notransform) + return + for(var/obj/item/W in src) + dropItemToGround(W) + regenerate_icons() + notransform = 1 + canmove = 0 + icon = null + invisibility = INVISIBILITY_MAXIMUM + return ..() + +/mob/proc/AIize(transfer_after = TRUE) + var/list/turf/landmark_loc = list() + for(var/obj/effect/landmark/start/ai/sloc in GLOB.landmarks_list) + if(locate(/mob/living/silicon/ai) in sloc.loc) + continue + if(sloc.primary_ai) + LAZYCLEARLIST(landmark_loc) + landmark_loc += sloc.loc + break + landmark_loc += sloc.loc + if(!landmark_loc.len) + to_chat(src, "Oh god sorry we can't find an unoccupied AI spawn location, so we're spawning you on top of someone.") + for(var/obj/effect/landmark/start/ai/sloc in GLOB.landmarks_list) + landmark_loc += sloc.loc + + if(!landmark_loc.len) + message_admins("Could not find ai landmark for [src]. Yell at a mapper! We are spawning them at their current location.") + landmark_loc += loc + + if(client) + stop_sound_channel(CHANNEL_LOBBYMUSIC) + + if(!transfer_after) + mind.active = FALSE + + . = new /mob/living/silicon/ai(pick(landmark_loc), null, src) + + qdel(src) + +/mob/living/carbon/human/proc/Robotize(delete_items = 0, transfer_after = TRUE) + if (notransform) + return + for(var/obj/item/W in src) + if(delete_items) + qdel(W) + else + dropItemToGround(W) + regenerate_icons() + notransform = 1 + canmove = 0 + icon = null + invisibility = INVISIBILITY_MAXIMUM + for(var/t in bodyparts) + qdel(t) + + var/mob/living/silicon/robot/R = new /mob/living/silicon/robot(loc) + + R.gender = gender + R.invisibility = 0 + + if(mind) //TODO + if(!transfer_after) + mind.active = FALSE + mind.transfer_to(R) + else if(transfer_after) + transfer_ckey(R) + + R.apply_pref_name("cyborg") + + if(R.mmi) + R.mmi.name = "Man-Machine Interface: [real_name]" + if(R.mmi.brain) + R.mmi.brain.name = "[real_name]'s brain" + if(R.mmi.brainmob) + R.mmi.brainmob.real_name = real_name //the name of the brain inside the cyborg is the robotized human's name. + R.mmi.brainmob.name = real_name + + R.job = "Cyborg" + R.notify_ai(NEW_BORG) + + . = R + qdel(src) + +//human -> alien +/mob/living/carbon/human/proc/Alienize(mind_transfer = TRUE) + if (notransform) + return + for(var/obj/item/W in src) + dropItemToGround(W) + regenerate_icons() + notransform = 1 + canmove = 0 + icon = null + invisibility = INVISIBILITY_MAXIMUM + for(var/t in bodyparts) + qdel(t) + + var/alien_caste = pick("Hunter","Sentinel","Drone") + var/mob/living/carbon/alien/humanoid/new_xeno + switch(alien_caste) + if("Hunter") + new_xeno = new /mob/living/carbon/alien/humanoid/hunter(loc) + if("Sentinel") + new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(loc) + if("Drone") + new_xeno = new /mob/living/carbon/alien/humanoid/drone(loc) + + new_xeno.a_intent = INTENT_HARM + if(mind && mind_transfer) + mind.transfer_to(new_xeno) + else + transfer_ckey(new_xeno) + + to_chat(new_xeno, "You are now an alien.") + . = new_xeno + qdel(src) + +/mob/living/carbon/human/proc/slimeize(reproduce, mind_transfer = TRUE) + if (notransform) + return + for(var/obj/item/W in src) + dropItemToGround(W) + regenerate_icons() + notransform = 1 + canmove = 0 + icon = null + invisibility = INVISIBILITY_MAXIMUM + for(var/t in bodyparts) + qdel(t) + + var/mob/living/simple_animal/slime/new_slime + if(reproduce) + var/number = pick(14;2,3,4) //reproduce (has a small chance of producing 3 or 4 offspring) + var/list/babies = list() + for(var/i=1,i<=number,i++) + var/mob/living/simple_animal/slime/M = new/mob/living/simple_animal/slime(loc) + M.nutrition = round(nutrition/number) + step_away(M,src) + babies += M + new_slime = pick(babies) + else + new_slime = new /mob/living/simple_animal/slime(loc) + new_slime.a_intent = INTENT_HARM + if(mind && mind_transfer) + mind.transfer_to(new_slime) + else + transfer_ckey(new_slime) + + to_chat(new_slime, "You are now a slime. Skreee!") + . = new_slime + qdel(src) + +/mob/proc/become_overmind(starting_points = 60, mind_transfer = FALSE) + var/mob/camera/blob/B = new /mob/camera/blob(get_turf(src), starting_points) + if(mind && mind_transfer) + mind.transfer_to(B) + else + transfer_ckey(B) + . = B + qdel(src) + + +/mob/living/carbon/human/proc/corgize(mind_transfer = TRUE) + if (notransform) + return + for(var/obj/item/W in src) + dropItemToGround(W) + regenerate_icons() + notransform = 1 + canmove = 0 + icon = null + invisibility = INVISIBILITY_MAXIMUM + for(var/t in bodyparts) //this really should not be necessary + qdel(t) + + var/mob/living/simple_animal/pet/dog/corgi/new_corgi = new /mob/living/simple_animal/pet/dog/corgi (loc) + new_corgi.a_intent = INTENT_HARM + if(mind && mind_transfer) + mind.transfer_to(new_corgi) + else + transfer_ckey(new_corgi) + + to_chat(new_corgi, "You are now a Corgi. Yap Yap!") + . = new_corgi + qdel(src) + +/mob/living/carbon/proc/gorillize(mind_transfer = TRUE) + if(notransform) + return + + SSblackbox.record_feedback("amount", "gorillas_created", 1) + + var/Itemlist = get_equipped_items(TRUE) + Itemlist += held_items + for(var/obj/item/W in Itemlist) + dropItemToGround(W, TRUE) + + regenerate_icons() + notransform = TRUE + canmove = FALSE + icon = null + invisibility = INVISIBILITY_MAXIMUM + var/mob/living/simple_animal/hostile/gorilla/new_gorilla = new (get_turf(src)) + new_gorilla.a_intent = INTENT_HARM + if(mind && mind_transfer) + mind.transfer_to(new_gorilla) + else + transfer_ckey(new_gorilla) + to_chat(new_gorilla, "You are now a gorilla. Ooga ooga!") + . = new_gorilla + qdel(src) + +/mob/living/carbon/human/Animalize(mind_transfer = TRUE) + + var/list/mobtypes = typesof(/mob/living/simple_animal) + var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") as null|anything in mobtypes + if(!mobpath) + return + if(mind) + mind_transfer = alert("Want to transfer their mind into the new mob", "Mind Transfer", "Yes", "No") == "Yes" ? TRUE : FALSE + + if(notransform) + return + for(var/obj/item/W in src) + dropItemToGround(W) + + regenerate_icons() + notransform = TRUE + canmove = FALSE + icon = null + invisibility = INVISIBILITY_MAXIMUM + + for(var/t in bodyparts) + qdel(t) + + var/mob/new_mob = new mobpath(src.loc) + if(mind && mind_transfer) + mind.transfer_to(new_mob) + else + transfer_ckey(new_mob) + new_mob.a_intent = INTENT_HARM + + + to_chat(new_mob, "You suddenly feel more... animalistic.") + . = new_mob + qdel(src) + +/mob/proc/Animalize(mind_transfer = TRUE) + + var/list/mobtypes = typesof(/mob/living/simple_animal) + var/mobpath = input("Which type of mob should [src] turn into?", "Choose a type") as null|anything in mobtypes + if(!mobpath) + return + if(mind) + mind_transfer = alert("Want to transfer their mind into the new mob", "Mind Transfer", "Yes", "No") == "Yes" ? TRUE : FALSE + + var/mob/new_mob = new mobpath(src.loc) + + if(mind && mind_transfer) + mind.transfer_to(new_mob) + else + transfer_ckey(new_mob) + new_mob.a_intent = INTENT_HARM + to_chat(new_mob, "You feel more... animalistic") + + . = new_mob + qdel(src) diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm index 137a87d1e7..f75569ef7a 100644 --- a/code/modules/paperwork/clipboard.dm +++ b/code/modules/paperwork/clipboard.dm @@ -1,126 +1,126 @@ -/obj/item/clipboard - name = "clipboard" - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "clipboard" - item_state = "clipboard" - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 7 - var/obj/item/pen/haspen //The stored pen. - var/obj/item/paper/toppaper //The topmost piece of paper. - slot_flags = ITEM_SLOT_BELT - resistance_flags = FLAMMABLE - -/obj/item/clipboard/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins putting [user.p_their()] head into the clip of \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS//the clipboard's clip is very strong. industrial duty. can kill a man easily. - -/obj/item/clipboard/Initialize() - update_icon() - . = ..() - -/obj/item/clipboard/Destroy() - QDEL_NULL(haspen) - QDEL_NULL(toppaper) //let movable/Destroy handle the rest - return ..() - -/obj/item/clipboard/update_icon() - cut_overlays() - if(toppaper) - add_overlay(toppaper.icon_state) - copy_overlays(toppaper) - if(haspen) - add_overlay("clipboard_pen") - add_overlay("clipboard_over") - - -/obj/item/clipboard/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/paper)) - if(!user.transferItemToLoc(W, src)) - return - toppaper = W - to_chat(user, "You clip the paper onto \the [src].") - update_icon() - else if(toppaper) - toppaper.attackby(user.get_active_held_item(), user) - update_icon() - - -/obj/item/clipboard/attack_self(mob/user) - var/dat = "Clipboard" - if(haspen) - dat += "Remove Pen


                " - else - dat += "Add Pen

                " - - //The topmost paper. You can't organise contents directly in byond, so this is what we're stuck with. -Pete - if(toppaper) - var/obj/item/paper/P = toppaper - dat += "Write Remove - [P.name]

                " - - for(P in src) - if(P == toppaper) - continue - dat += "Write Remove Move to top - [P.name]
                " - user << browse(dat, "window=clipboard") - onclose(user, "clipboard") - add_fingerprint(usr) - - -/obj/item/clipboard/Topic(href, href_list) - ..() - if(usr.stat || usr.restrained()) - return - - if(usr.contents.Find(src)) - - if(href_list["pen"]) - if(haspen) - haspen.forceMove(usr.loc) - usr.put_in_hands(haspen) - haspen = null - - if(href_list["addpen"]) - if(!haspen) - var/obj/item/held = usr.get_active_held_item() - if(istype(held, /obj/item/pen)) - var/obj/item/pen/W = held - if(!usr.transferItemToLoc(W, src)) - return - haspen = W - to_chat(usr, "You slot [W] into [src].") - - if(href_list["write"]) - var/obj/item/P = locate(href_list["write"]) - if(istype(P) && P.loc == src) - if(usr.get_active_held_item()) - P.attackby(usr.get_active_held_item(), usr) - - if(href_list["remove"]) - var/obj/item/P = locate(href_list["remove"]) - if(istype(P) && P.loc == src) - P.forceMove(usr.loc) - usr.put_in_hands(P) - if(P == toppaper) - toppaper = null - var/obj/item/paper/newtop = locate(/obj/item/paper) in src - if(newtop && (newtop != P)) - toppaper = newtop - else - toppaper = null - - if(href_list["read"]) - var/obj/item/paper/P = locate(href_list["read"]) - if(istype(P) && P.loc == src) - usr.examinate(P) - - if(href_list["top"]) - var/obj/item/P = locate(href_list["top"]) - if(istype(P) && P.loc == src) - toppaper = P - to_chat(usr, "You move [P.name] to the top.") - - //Update everything - attack_self(usr) - update_icon() +/obj/item/clipboard + name = "clipboard" + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "clipboard" + item_state = "clipboard" + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 7 + var/obj/item/pen/haspen //The stored pen. + var/obj/item/paper/toppaper //The topmost piece of paper. + slot_flags = ITEM_SLOT_BELT + resistance_flags = FLAMMABLE + +/obj/item/clipboard/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins putting [user.p_their()] head into the clip of \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS//the clipboard's clip is very strong. industrial duty. can kill a man easily. + +/obj/item/clipboard/Initialize() + update_icon() + . = ..() + +/obj/item/clipboard/Destroy() + QDEL_NULL(haspen) + QDEL_NULL(toppaper) //let movable/Destroy handle the rest + return ..() + +/obj/item/clipboard/update_icon() + cut_overlays() + if(toppaper) + add_overlay(toppaper.icon_state) + copy_overlays(toppaper) + if(haspen) + add_overlay("clipboard_pen") + add_overlay("clipboard_over") + + +/obj/item/clipboard/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/paper)) + if(!user.transferItemToLoc(W, src)) + return + toppaper = W + to_chat(user, "You clip the paper onto \the [src].") + update_icon() + else if(toppaper) + toppaper.attackby(user.get_active_held_item(), user) + update_icon() + + +/obj/item/clipboard/attack_self(mob/user) + var/dat = "Clipboard" + if(haspen) + dat += "Remove Pen

                " + else + dat += "Add Pen

                " + + //The topmost paper. You can't organise contents directly in byond, so this is what we're stuck with. -Pete + if(toppaper) + var/obj/item/paper/P = toppaper + dat += "Write Remove - [P.name]

                " + + for(P in src) + if(P == toppaper) + continue + dat += "Write Remove Move to top - [P.name]
                " + user << browse(dat, "window=clipboard") + onclose(user, "clipboard") + add_fingerprint(usr) + + +/obj/item/clipboard/Topic(href, href_list) + ..() + if(usr.stat || usr.restrained()) + return + + if(usr.contents.Find(src)) + + if(href_list["pen"]) + if(haspen) + haspen.forceMove(usr.loc) + usr.put_in_hands(haspen) + haspen = null + + if(href_list["addpen"]) + if(!haspen) + var/obj/item/held = usr.get_active_held_item() + if(istype(held, /obj/item/pen)) + var/obj/item/pen/W = held + if(!usr.transferItemToLoc(W, src)) + return + haspen = W + to_chat(usr, "You slot [W] into [src].") + + if(href_list["write"]) + var/obj/item/P = locate(href_list["write"]) + if(istype(P) && P.loc == src) + if(usr.get_active_held_item()) + P.attackby(usr.get_active_held_item(), usr) + + if(href_list["remove"]) + var/obj/item/P = locate(href_list["remove"]) + if(istype(P) && P.loc == src) + P.forceMove(usr.loc) + usr.put_in_hands(P) + if(P == toppaper) + toppaper = null + var/obj/item/paper/newtop = locate(/obj/item/paper) in src + if(newtop && (newtop != P)) + toppaper = newtop + else + toppaper = null + + if(href_list["read"]) + var/obj/item/paper/P = locate(href_list["read"]) + if(istype(P) && P.loc == src) + usr.examinate(P) + + if(href_list["top"]) + var/obj/item/P = locate(href_list["top"]) + if(istype(P) && P.loc == src) + toppaper = P + to_chat(usr, "You move [P.name] to the top.") + + //Update everything + attack_self(usr) + update_icon() diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm index ccbcf34d8c..aa1fd7dbfb 100644 --- a/code/modules/paperwork/filingcabinet.dm +++ b/code/modules/paperwork/filingcabinet.dm @@ -1,225 +1,225 @@ -/* Filing cabinets! - * Contains: - * Filing Cabinets - * Security Record Cabinets - * Medical Record Cabinets - * Employment Contract Cabinets - */ - - -/* - * Filing Cabinets - */ -/obj/structure/filingcabinet - name = "filing cabinet" - desc = "A large cabinet with drawers." - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "filingcabinet" - density = TRUE - anchored = TRUE - -/obj/structure/filingcabinet/chestdrawer - name = "chest drawer" - icon_state = "chestdrawer" - -/obj/structure/filingcabinet/chestdrawer/wheeled - name = "rolling chest drawer" - desc = "A small cabinet with drawers. This one has wheels!" - anchored = FALSE - -/obj/structure/filingcabinet/filingcabinet //not changing the path to avoid unnecessary map issues, but please don't name stuff like this in the future -Pete - icon_state = "tallcabinet" - - -/obj/structure/filingcabinet/Initialize(mapload) - . = ..() - if(mapload) - for(var/obj/item/I in loc) - if(istype(I, /obj/item/paper) || istype(I, /obj/item/folder) || istype(I, /obj/item/photo)) - I.forceMove(src) - -/obj/structure/filingcabinet/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc, 2) - for(var/obj/item/I in src) - I.forceMove(loc) - qdel(src) - -/obj/structure/filingcabinet/attackby(obj/item/P, mob/user, params) - if(istype(P, /obj/item/paper) || istype(P, /obj/item/folder) || istype(P, /obj/item/photo) || istype(P, /obj/item/documents)) - if(!user.transferItemToLoc(P, src)) - return - to_chat(user, "You put [P] in [src].") - icon_state = "[initial(icon_state)]-open" - sleep(5) - icon_state = initial(icon_state) - updateUsrDialog() - else if(istype(P, /obj/item/wrench)) - to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].") - anchored = !anchored - else if(user.a_intent != INTENT_HARM) - to_chat(user, "You can't put [P] in [src]!") - else - return ..() - - -/obj/structure/filingcabinet/ui_interact(mob/user) - . = ..() - if(isobserver(user)) - return - - if(contents.len <= 0) - to_chat(user, "[src] is empty.") - return - - var/dat = "

                Name

                Status

                Location

                Control

                [Bot.hacked ? "(!)" : ""] [Bot.name] ([Bot.model])[bot_mode][get_area_name(Bot, TRUE)]InterfaceCall
                " - var/i - for(i=contents.len, i>=1, i--) - var/obj/item/P = contents[i] - dat += "" - dat += "
                [P.name]
                " - user << browse("[name][dat]", "window=filingcabinet;size=350x300") - -/obj/structure/filingcabinet/attack_tk(mob/user) - if(anchored) - attack_self_tk(user) - else - ..() - -/obj/structure/filingcabinet/attack_self_tk(mob/user) - if(contents.len) - if(prob(40 + contents.len * 5)) - var/obj/item/I = pick(contents) - I.forceMove(loc) - if(prob(25)) - step_rand(I) - to_chat(user, "You pull \a [I] out of [src] at random.") - return - to_chat(user, "You find nothing in [src].") - -/obj/structure/filingcabinet/Topic(href, href_list) - if(href_list["retrieve"]) - usr << browse("", "window=filingcabinet") // Close the menu - - var/obj/item/P = locate(href_list["retrieve"])//contents[retrieveindex] - if(istype(P) && P.loc == src && in_range(src, usr)) - usr.put_in_hands(P) - updateUsrDialog() - icon_state = "[initial(icon_state)]-open" - sleep(5) - icon_state = initial(icon_state) - - -/* - * Security Record Cabinets - */ -/obj/structure/filingcabinet/security - var/virgin = 1 - -/obj/structure/filingcabinet/security/proc/populate() - if(virgin) - for(var/datum/data/record/G in GLOB.data_core.general) - var/datum/data/record/S = find_record("name", G.fields["name"], GLOB.data_core.security) - if(!S) - continue - var/obj/item/paper/P = new /obj/item/paper(src) - P.info = "
                Security Record

                " - P.info += "Name: [G.fields["name"]] ID: [G.fields["id"]]
                \nSex: [G.fields["sex"]]
                \nAge: [G.fields["age"]]
                \nFingerprint: [G.fields["fingerprint"]]
                \nPhysical Status: [G.fields["p_stat"]]
                \nMental Status: [G.fields["m_stat"]]
                " - P.info += "
                \n
                Security Data

                \nCriminal Status: [S.fields["criminal"]]
                \n
                \nMinor Crimes: [S.fields["mi_crim"]]
                \nDetails: [S.fields["mi_crim_d"]]
                \n
                \nMajor Crimes: [S.fields["ma_crim"]]
                \nDetails: [S.fields["ma_crim_d"]]
                \n
                \nImportant Notes:
                \n\t[S.fields["notes"]]
                \n
                \n
                Comments/Log

                " - var/counter = 1 - while(S.fields["com_[counter]"]) - P.info += "[S.fields["com_[counter]"]]
                " - counter++ - P.info += "
                " - P.name = "paper - '[G.fields["name"]]'" - virgin = 0 //tabbing here is correct- it's possible for people to try and use it - //before the records have been generated, so we do this inside the loop. - -/obj/structure/filingcabinet/security/attack_hand() - populate() - . = ..() - -/obj/structure/filingcabinet/security/attack_tk() - populate() - ..() - -/* - * Medical Record Cabinets - */ -/obj/structure/filingcabinet/medical - var/virgin = 1 - -/obj/structure/filingcabinet/medical/proc/populate() - if(virgin) - for(var/datum/data/record/G in GLOB.data_core.general) - var/datum/data/record/M = find_record("name", G.fields["name"], GLOB.data_core.medical) - if(!M) - continue - var/obj/item/paper/P = new /obj/item/paper(src) - P.info = "
                Medical Record

                " - P.info += "Name: [G.fields["name"]] ID: [G.fields["id"]]
                \nSex: [G.fields["sex"]]
                \nAge: [G.fields["age"]]
                \nFingerprint: [G.fields["fingerprint"]]
                \nPhysical Status: [G.fields["p_stat"]]
                \nMental Status: [G.fields["m_stat"]]
                " - P.info += "
                \n
                Medical Data

                \nBlood Type: [M.fields["blood_type"]]
                \nDNA: [M.fields["b_dna"]]
                \n
                \nMinor Disabilities: [M.fields["mi_dis"]]
                \nDetails: [M.fields["mi_dis_d"]]
                \n
                \nMajor Disabilities: [M.fields["ma_dis"]]
                \nDetails: [M.fields["ma_dis_d"]]
                \n
                \nAllergies: [M.fields["alg"]]
                \nDetails: [M.fields["alg_d"]]
                \n
                \nCurrent Diseases: [M.fields["cdi"]] (per disease info placed in log/comment section)
                \nDetails: [M.fields["cdi_d"]]
                \n
                \nImportant Notes:
                \n\t[M.fields["notes"]]
                \n
                \n
                Comments/Log

                " - var/counter = 1 - while(M.fields["com_[counter]"]) - P.info += "[M.fields["com_[counter]"]]
                " - counter++ - P.info += "
                " - P.name = "paper - '[G.fields["name"]]'" - virgin = 0 //tabbing here is correct- it's possible for people to try and use it - //before the records have been generated, so we do this inside the loop. - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/structure/filingcabinet/medical/attack_hand() - populate() - . = ..() - -/obj/structure/filingcabinet/medical/attack_tk() - populate() - ..() - -/* - * Employment contract Cabinets - */ - -GLOBAL_LIST_EMPTY(employmentCabinets) - -/obj/structure/filingcabinet/employment - var/cooldown = 0 - icon_state = "employmentcabinet" - var/virgin = 1 - -/obj/structure/filingcabinet/employment/Initialize() - . = ..() - GLOB.employmentCabinets += src - -/obj/structure/filingcabinet/employment/Destroy() - GLOB.employmentCabinets -= src - return ..() - -/obj/structure/filingcabinet/employment/proc/fillCurrent() - //This proc fills the cabinet with the current crew. - for(var/record in GLOB.data_core.locked) - var/datum/data/record/G = record - if(!G) - continue - var/datum/mind/M = G.fields["mindref"] - if(M && ishuman(M.current)) - addFile(M.current) - - -/obj/structure/filingcabinet/employment/proc/addFile(mob/living/carbon/human/employee) - new /obj/item/paper/contract/employment(src, employee) - -/obj/structure/filingcabinet/employment/interact(mob/user) - if(!cooldown) - if(virgin) - fillCurrent() - virgin = 0 - cooldown = 1 - sleep(100) // prevents the devil from just instantly emptying the cabinet, ensuring an easy win. - cooldown = 0 - else - to_chat(user, "[src] is jammed, give it a few seconds.") - ..() +/* Filing cabinets! + * Contains: + * Filing Cabinets + * Security Record Cabinets + * Medical Record Cabinets + * Employment Contract Cabinets + */ + + +/* + * Filing Cabinets + */ +/obj/structure/filingcabinet + name = "filing cabinet" + desc = "A large cabinet with drawers." + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "filingcabinet" + density = TRUE + anchored = TRUE + +/obj/structure/filingcabinet/chestdrawer + name = "chest drawer" + icon_state = "chestdrawer" + +/obj/structure/filingcabinet/chestdrawer/wheeled + name = "rolling chest drawer" + desc = "A small cabinet with drawers. This one has wheels!" + anchored = FALSE + +/obj/structure/filingcabinet/filingcabinet //not changing the path to avoid unnecessary map issues, but please don't name stuff like this in the future -Pete + icon_state = "tallcabinet" + + +/obj/structure/filingcabinet/Initialize(mapload) + . = ..() + if(mapload) + for(var/obj/item/I in loc) + if(istype(I, /obj/item/paper) || istype(I, /obj/item/folder) || istype(I, /obj/item/photo)) + I.forceMove(src) + +/obj/structure/filingcabinet/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal(loc, 2) + for(var/obj/item/I in src) + I.forceMove(loc) + qdel(src) + +/obj/structure/filingcabinet/attackby(obj/item/P, mob/user, params) + if(istype(P, /obj/item/paper) || istype(P, /obj/item/folder) || istype(P, /obj/item/photo) || istype(P, /obj/item/documents)) + if(!user.transferItemToLoc(P, src)) + return + to_chat(user, "You put [P] in [src].") + icon_state = "[initial(icon_state)]-open" + sleep(5) + icon_state = initial(icon_state) + updateUsrDialog() + else if(istype(P, /obj/item/wrench)) + to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].") + anchored = !anchored + else if(user.a_intent != INTENT_HARM) + to_chat(user, "You can't put [P] in [src]!") + else + return ..() + + +/obj/structure/filingcabinet/ui_interact(mob/user) + . = ..() + if(isobserver(user)) + return + + if(contents.len <= 0) + to_chat(user, "[src] is empty.") + return + + var/dat = "
                " + var/i + for(i=contents.len, i>=1, i--) + var/obj/item/P = contents[i] + dat += "" + dat += "
                [P.name]
                " + user << browse("[name][dat]", "window=filingcabinet;size=350x300") + +/obj/structure/filingcabinet/attack_tk(mob/user) + if(anchored) + attack_self_tk(user) + else + ..() + +/obj/structure/filingcabinet/attack_self_tk(mob/user) + if(contents.len) + if(prob(40 + contents.len * 5)) + var/obj/item/I = pick(contents) + I.forceMove(loc) + if(prob(25)) + step_rand(I) + to_chat(user, "You pull \a [I] out of [src] at random.") + return + to_chat(user, "You find nothing in [src].") + +/obj/structure/filingcabinet/Topic(href, href_list) + if(href_list["retrieve"]) + usr << browse("", "window=filingcabinet") // Close the menu + + var/obj/item/P = locate(href_list["retrieve"])//contents[retrieveindex] + if(istype(P) && P.loc == src && in_range(src, usr)) + usr.put_in_hands(P) + updateUsrDialog() + icon_state = "[initial(icon_state)]-open" + sleep(5) + icon_state = initial(icon_state) + + +/* + * Security Record Cabinets + */ +/obj/structure/filingcabinet/security + var/virgin = 1 + +/obj/structure/filingcabinet/security/proc/populate() + if(virgin) + for(var/datum/data/record/G in GLOB.data_core.general) + var/datum/data/record/S = find_record("name", G.fields["name"], GLOB.data_core.security) + if(!S) + continue + var/obj/item/paper/P = new /obj/item/paper(src) + P.info = "
                Security Record

                " + P.info += "Name: [G.fields["name"]] ID: [G.fields["id"]]
                \nSex: [G.fields["sex"]]
                \nAge: [G.fields["age"]]
                \nFingerprint: [G.fields["fingerprint"]]
                \nPhysical Status: [G.fields["p_stat"]]
                \nMental Status: [G.fields["m_stat"]]
                " + P.info += "
                \n
                Security Data

                \nCriminal Status: [S.fields["criminal"]]
                \n
                \nMinor Crimes: [S.fields["mi_crim"]]
                \nDetails: [S.fields["mi_crim_d"]]
                \n
                \nMajor Crimes: [S.fields["ma_crim"]]
                \nDetails: [S.fields["ma_crim_d"]]
                \n
                \nImportant Notes:
                \n\t[S.fields["notes"]]
                \n
                \n
                Comments/Log

                " + var/counter = 1 + while(S.fields["com_[counter]"]) + P.info += "[S.fields["com_[counter]"]]
                " + counter++ + P.info += "" + P.name = "paper - '[G.fields["name"]]'" + virgin = 0 //tabbing here is correct- it's possible for people to try and use it + //before the records have been generated, so we do this inside the loop. + +/obj/structure/filingcabinet/security/attack_hand() + populate() + . = ..() + +/obj/structure/filingcabinet/security/attack_tk() + populate() + ..() + +/* + * Medical Record Cabinets + */ +/obj/structure/filingcabinet/medical + var/virgin = 1 + +/obj/structure/filingcabinet/medical/proc/populate() + if(virgin) + for(var/datum/data/record/G in GLOB.data_core.general) + var/datum/data/record/M = find_record("name", G.fields["name"], GLOB.data_core.medical) + if(!M) + continue + var/obj/item/paper/P = new /obj/item/paper(src) + P.info = "
                Medical Record

                " + P.info += "Name: [G.fields["name"]] ID: [G.fields["id"]]
                \nSex: [G.fields["sex"]]
                \nAge: [G.fields["age"]]
                \nFingerprint: [G.fields["fingerprint"]]
                \nPhysical Status: [G.fields["p_stat"]]
                \nMental Status: [G.fields["m_stat"]]
                " + P.info += "
                \n
                Medical Data

                \nBlood Type: [M.fields["blood_type"]]
                \nDNA: [M.fields["b_dna"]]
                \n
                \nMinor Disabilities: [M.fields["mi_dis"]]
                \nDetails: [M.fields["mi_dis_d"]]
                \n
                \nMajor Disabilities: [M.fields["ma_dis"]]
                \nDetails: [M.fields["ma_dis_d"]]
                \n
                \nAllergies: [M.fields["alg"]]
                \nDetails: [M.fields["alg_d"]]
                \n
                \nCurrent Diseases: [M.fields["cdi"]] (per disease info placed in log/comment section)
                \nDetails: [M.fields["cdi_d"]]
                \n
                \nImportant Notes:
                \n\t[M.fields["notes"]]
                \n
                \n
                Comments/Log

                " + var/counter = 1 + while(M.fields["com_[counter]"]) + P.info += "[M.fields["com_[counter]"]]
                " + counter++ + P.info += "" + P.name = "paper - '[G.fields["name"]]'" + virgin = 0 //tabbing here is correct- it's possible for people to try and use it + //before the records have been generated, so we do this inside the loop. + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/structure/filingcabinet/medical/attack_hand() + populate() + . = ..() + +/obj/structure/filingcabinet/medical/attack_tk() + populate() + ..() + +/* + * Employment contract Cabinets + */ + +GLOBAL_LIST_EMPTY(employmentCabinets) + +/obj/structure/filingcabinet/employment + var/cooldown = 0 + icon_state = "employmentcabinet" + var/virgin = 1 + +/obj/structure/filingcabinet/employment/Initialize() + . = ..() + GLOB.employmentCabinets += src + +/obj/structure/filingcabinet/employment/Destroy() + GLOB.employmentCabinets -= src + return ..() + +/obj/structure/filingcabinet/employment/proc/fillCurrent() + //This proc fills the cabinet with the current crew. + for(var/record in GLOB.data_core.locked) + var/datum/data/record/G = record + if(!G) + continue + var/datum/mind/M = G.fields["mindref"] + if(M && ishuman(M.current)) + addFile(M.current) + + +/obj/structure/filingcabinet/employment/proc/addFile(mob/living/carbon/human/employee) + new /obj/item/paper/contract/employment(src, employee) + +/obj/structure/filingcabinet/employment/interact(mob/user) + if(!cooldown) + if(virgin) + fillCurrent() + virgin = 0 + cooldown = 1 + sleep(100) // prevents the devil from just instantly emptying the cabinet, ensuring an easy win. + cooldown = 0 + else + to_chat(user, "[src] is jammed, give it a few seconds.") + ..() diff --git a/code/modules/paperwork/folders.dm b/code/modules/paperwork/folders.dm index 038cba8936..0a4bdf0c1c 100644 --- a/code/modules/paperwork/folders.dm +++ b/code/modules/paperwork/folders.dm @@ -1,117 +1,117 @@ -/obj/item/folder - name = "folder" - desc = "A folder." - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "folder" - w_class = WEIGHT_CLASS_SMALL - pressure_resistance = 2 - resistance_flags = FLAMMABLE - -/obj/item/folder/suicide_act(mob/living/user) - user.visible_message("[user] begins filing an imaginary death warrant! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/folder/blue - desc = "A blue folder." - icon_state = "folder_blue" - -/obj/item/folder/red - desc = "A red folder." - icon_state = "folder_red" - -/obj/item/folder/yellow - desc = "A yellow folder." - icon_state = "folder_yellow" - -/obj/item/folder/white - desc = "A white folder." - icon_state = "folder_white" - - -/obj/item/folder/update_icon() - cut_overlays() - if(contents.len) - add_overlay("folder_paper") - - -/obj/item/folder/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/paper) || istype(W, /obj/item/photo) || istype(W, /obj/item/documents)) - if(!user.transferItemToLoc(W, src)) - return - to_chat(user, "You put [W] into [src].") - update_icon() - else if(istype(W, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the cover of [src]!") - return - var/n_name = copytext(sanitize(input(user, "What would you like to label the folder?", "Folder Labelling", null) as text), 1, MAX_NAME_LEN) - if(user.canUseTopic(src, BE_CLOSE)) - name = "folder[(n_name ? " - '[n_name]'" : null)]" - - -/obj/item/folder/attack_self(mob/user) - var/dat = "[name]" - - for(var/obj/item/I in src) - dat += "Remove - [I.name]
                " - user << browse(dat, "window=folder") - onclose(user, "folder") - add_fingerprint(usr) - - -/obj/item/folder/Topic(href, href_list) - ..() - if(usr.stat || usr.restrained()) - return - - if(usr.contents.Find(src)) - - if(href_list["remove"]) - var/obj/item/I = locate(href_list["remove"]) - if(istype(I) && I.loc == src) - I.forceMove(usr.loc) - usr.put_in_hands(I) - - if(href_list["read"]) - var/obj/item/I = locate(href_list["read"]) - if(istype(I) && I.loc == src) - usr.examinate(I) - - //Update everything - attack_self(usr) - update_icon() - -/obj/item/folder/documents - name = "folder- 'TOP SECRET'" - desc = "A folder stamped \"Top Secret - Property of Nanotrasen Corporation. Unauthorized distribution is punishable by death.\"" - -/obj/item/folder/documents/Initialize() - . = ..() - new /obj/item/documents/nanotrasen(src) - update_icon() - -/obj/item/folder/syndicate - icon_state = "folder_syndie" - name = "folder- 'TOP SECRET'" - desc = "A folder stamped \"Top Secret - Property of The Syndicate.\"" - -/obj/item/folder/syndicate/red - icon_state = "folder_sred" - -/obj/item/folder/syndicate/red/Initialize() - . = ..() - new /obj/item/documents/syndicate/red(src) - update_icon() - -/obj/item/folder/syndicate/blue - icon_state = "folder_sblue" - -/obj/item/folder/syndicate/blue/Initialize() - . = ..() - new /obj/item/documents/syndicate/blue(src) - update_icon() - -/obj/item/folder/syndicate/mining/Initialize() - . = ..() - new /obj/item/documents/syndicate/mining(src) - update_icon() +/obj/item/folder + name = "folder" + desc = "A folder." + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "folder" + w_class = WEIGHT_CLASS_SMALL + pressure_resistance = 2 + resistance_flags = FLAMMABLE + +/obj/item/folder/suicide_act(mob/living/user) + user.visible_message("[user] begins filing an imaginary death warrant! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/folder/blue + desc = "A blue folder." + icon_state = "folder_blue" + +/obj/item/folder/red + desc = "A red folder." + icon_state = "folder_red" + +/obj/item/folder/yellow + desc = "A yellow folder." + icon_state = "folder_yellow" + +/obj/item/folder/white + desc = "A white folder." + icon_state = "folder_white" + + +/obj/item/folder/update_icon() + cut_overlays() + if(contents.len) + add_overlay("folder_paper") + + +/obj/item/folder/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/paper) || istype(W, /obj/item/photo) || istype(W, /obj/item/documents)) + if(!user.transferItemToLoc(W, src)) + return + to_chat(user, "You put [W] into [src].") + update_icon() + else if(istype(W, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the cover of [src]!") + return + var/n_name = copytext(sanitize(input(user, "What would you like to label the folder?", "Folder Labelling", null) as text), 1, MAX_NAME_LEN) + if(user.canUseTopic(src, BE_CLOSE)) + name = "folder[(n_name ? " - '[n_name]'" : null)]" + + +/obj/item/folder/attack_self(mob/user) + var/dat = "[name]" + + for(var/obj/item/I in src) + dat += "Remove - [I.name]
                " + user << browse(dat, "window=folder") + onclose(user, "folder") + add_fingerprint(usr) + + +/obj/item/folder/Topic(href, href_list) + ..() + if(usr.stat || usr.restrained()) + return + + if(usr.contents.Find(src)) + + if(href_list["remove"]) + var/obj/item/I = locate(href_list["remove"]) + if(istype(I) && I.loc == src) + I.forceMove(usr.loc) + usr.put_in_hands(I) + + if(href_list["read"]) + var/obj/item/I = locate(href_list["read"]) + if(istype(I) && I.loc == src) + usr.examinate(I) + + //Update everything + attack_self(usr) + update_icon() + +/obj/item/folder/documents + name = "folder- 'TOP SECRET'" + desc = "A folder stamped \"Top Secret - Property of Nanotrasen Corporation. Unauthorized distribution is punishable by death.\"" + +/obj/item/folder/documents/Initialize() + . = ..() + new /obj/item/documents/nanotrasen(src) + update_icon() + +/obj/item/folder/syndicate + icon_state = "folder_syndie" + name = "folder- 'TOP SECRET'" + desc = "A folder stamped \"Top Secret - Property of The Syndicate.\"" + +/obj/item/folder/syndicate/red + icon_state = "folder_sred" + +/obj/item/folder/syndicate/red/Initialize() + . = ..() + new /obj/item/documents/syndicate/red(src) + update_icon() + +/obj/item/folder/syndicate/blue + icon_state = "folder_sblue" + +/obj/item/folder/syndicate/blue/Initialize() + . = ..() + new /obj/item/documents/syndicate/blue(src) + update_icon() + +/obj/item/folder/syndicate/mining/Initialize() + . = ..() + new /obj/item/documents/syndicate/mining(src) + update_icon() diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm index 6fc65e1771..5648bc7af8 100644 --- a/code/modules/paperwork/paperbin.dm +++ b/code/modules/paperwork/paperbin.dm @@ -1,174 +1,174 @@ -/obj/item/paper_bin - name = "paper bin" - desc = "Contains all the paper you'll never need." - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "paper_bin1" - item_state = "sheet-metal" - lefthand_file = 'icons/mob/inhands/misc/sheets_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/sheets_righthand.dmi' - throwforce = 0 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 3 - throw_range = 7 - pressure_resistance = 8 - var/papertype = /obj/item/paper - var/total_paper = 30 - var/list/papers = list() - var/obj/item/pen/bin_pen - -/obj/item/paper_bin/Initialize(mapload) - . = ..() - interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP - if(!mapload) - return - var/obj/item/pen/P = locate(/obj/item/pen) in src.loc - if(P && !bin_pen) - P.forceMove(src) - bin_pen = P - update_icon() - -/obj/item/paper_bin/Destroy() - if(papers) - for(var/i in papers) - qdel(i) - papers = null - . = ..() - -/obj/item/paper_bin/fire_act(exposed_temperature, exposed_volume) - if(total_paper) - total_paper = 0 - update_icon() - ..() - -/obj/item/paper_bin/MouseDrop(atom/over_object) - var/mob/living/M = usr - if(!istype(M) || M.incapacitated() || !Adjacent(M)) - return - - if(over_object == M) - M.put_in_hands(src) - - else if(istype(over_object, /obj/screen/inventory/hand)) - var/obj/screen/inventory/hand/H = over_object - M.putItemFromInventoryInHandIfPossible(src, H.held_index) - - else - . = ..() - - add_fingerprint(M) - -/obj/item/paper_bin/attack_paw(mob/user) - return attack_hand(user) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/paper_bin/attack_hand(mob/user) - if(user.lying) - return - user.changeNext_move(CLICK_CD_MELEE) - if(bin_pen) - var/obj/item/pen/P = bin_pen - P.add_fingerprint(user) - P.forceMove(user.loc) - user.put_in_hands(P) - to_chat(user, "You take [P] out of \the [src].") - bin_pen = null - update_icon() - else if(total_paper >= 1) - total_paper-- - update_icon() - // If there's any custom paper on the stack, use that instead of creating a new paper. - var/obj/item/paper/P - if(papers.len > 0) - P = papers[papers.len] - papers.Remove(P) - else - P = new papertype(src) - if(SSevents.holidays && SSevents.holidays[APRIL_FOOLS]) - if(prob(30)) - P.info = "HONK HONK HONK HONK HONK HONK HONK
                HOOOOOOOOOOOOOOOOOOOOOONK
                APRIL FOOLS
                " - P.rigged = 1 - P.updateinfolinks() - - P.add_fingerprint(user) - P.forceMove(user.loc) - user.put_in_hands(P) - to_chat(user, "You take [P] out of \the [src].") - else - to_chat(user, "[src] is empty!") - add_fingerprint(user) - return ..() - -/obj/item/paper_bin/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/paper)) - var/obj/item/paper/P = I - if(!user.transferItemToLoc(P, src)) - return - to_chat(user, "You put [P] in [src].") - papers.Add(P) - total_paper++ - update_icon() - else if(istype(I, /obj/item/pen) && !bin_pen) - var/obj/item/pen/P = I - if(!user.transferItemToLoc(P, src)) - return - to_chat(user, "You put [P] in [src].") - bin_pen = P - update_icon() - else - return ..() - -/obj/item/paper_bin/examine(mob/user) - . = ..() - if(total_paper) - . += "It contains [total_paper > 1 ? "[total_paper] papers" : " one paper"]." - else - . += "It doesn't contain anything." - - -/obj/item/paper_bin/update_icon() - if(total_paper < 1) - icon_state = "paper_bin0" - else - icon_state = "[initial(icon_state)]" - cut_overlays() - if(bin_pen) - add_overlay(mutable_appearance(bin_pen.icon, bin_pen.icon_state)) - -/obj/item/paper_bin/construction - name = "construction paper bin" - desc = "Contains all the paper you'll never need, IN COLOR!" - icon_state = "paper_binc" - papertype = /obj/item/paper/construction - -/obj/item/paper_bin/bundlenatural - name = "natural paper bundle" - desc = "A bundle of paper created using traditional methods." - icon_state = "paper_bundle" - papertype = /obj/item/paper/natural - resistance_flags = FLAMMABLE - -/obj/item/paper_bin/bundlenatural/attack_hand(mob/user) - ..() - if(total_paper < 1) - qdel(src) - -/obj/item/paper_bin/bundlenatural/fire_act(exposed_temperature, exposed_volume) - qdel(src) - -/obj/item/paper_bin/bundlenatural/attackby(obj/item/W, mob/user) - if(W.get_sharpness()) - to_chat(user, "You snip \the [src], spilling paper everywhere.") - var/turf/T = get_turf(src.loc) - while(total_paper > 0) - total_paper-- - var/obj/item/paper/P - if(papers.len > 0) - P = papers[papers.len] - papers -= P - else - P = new papertype() - P.forceMove(T) - CHECK_TICK - qdel(src) - else - ..() +/obj/item/paper_bin + name = "paper bin" + desc = "Contains all the paper you'll never need." + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "paper_bin1" + item_state = "sheet-metal" + lefthand_file = 'icons/mob/inhands/misc/sheets_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/sheets_righthand.dmi' + throwforce = 0 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 3 + throw_range = 7 + pressure_resistance = 8 + var/papertype = /obj/item/paper + var/total_paper = 30 + var/list/papers = list() + var/obj/item/pen/bin_pen + +/obj/item/paper_bin/Initialize(mapload) + . = ..() + interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP + if(!mapload) + return + var/obj/item/pen/P = locate(/obj/item/pen) in src.loc + if(P && !bin_pen) + P.forceMove(src) + bin_pen = P + update_icon() + +/obj/item/paper_bin/Destroy() + if(papers) + for(var/i in papers) + qdel(i) + papers = null + . = ..() + +/obj/item/paper_bin/fire_act(exposed_temperature, exposed_volume) + if(total_paper) + total_paper = 0 + update_icon() + ..() + +/obj/item/paper_bin/MouseDrop(atom/over_object) + var/mob/living/M = usr + if(!istype(M) || M.incapacitated() || !Adjacent(M)) + return + + if(over_object == M) + M.put_in_hands(src) + + else if(istype(over_object, /obj/screen/inventory/hand)) + var/obj/screen/inventory/hand/H = over_object + M.putItemFromInventoryInHandIfPossible(src, H.held_index) + + else + . = ..() + + add_fingerprint(M) + +/obj/item/paper_bin/attack_paw(mob/user) + return attack_hand(user) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/paper_bin/attack_hand(mob/user) + if(user.lying) + return + user.changeNext_move(CLICK_CD_MELEE) + if(bin_pen) + var/obj/item/pen/P = bin_pen + P.add_fingerprint(user) + P.forceMove(user.loc) + user.put_in_hands(P) + to_chat(user, "You take [P] out of \the [src].") + bin_pen = null + update_icon() + else if(total_paper >= 1) + total_paper-- + update_icon() + // If there's any custom paper on the stack, use that instead of creating a new paper. + var/obj/item/paper/P + if(papers.len > 0) + P = papers[papers.len] + papers.Remove(P) + else + P = new papertype(src) + if(SSevents.holidays && SSevents.holidays[APRIL_FOOLS]) + if(prob(30)) + P.info = "HONK HONK HONK HONK HONK HONK HONK
                HOOOOOOOOOOOOOOOOOOOOOONK
                APRIL FOOLS
                " + P.rigged = 1 + P.updateinfolinks() + + P.add_fingerprint(user) + P.forceMove(user.loc) + user.put_in_hands(P) + to_chat(user, "You take [P] out of \the [src].") + else + to_chat(user, "[src] is empty!") + add_fingerprint(user) + return ..() + +/obj/item/paper_bin/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/paper)) + var/obj/item/paper/P = I + if(!user.transferItemToLoc(P, src)) + return + to_chat(user, "You put [P] in [src].") + papers.Add(P) + total_paper++ + update_icon() + else if(istype(I, /obj/item/pen) && !bin_pen) + var/obj/item/pen/P = I + if(!user.transferItemToLoc(P, src)) + return + to_chat(user, "You put [P] in [src].") + bin_pen = P + update_icon() + else + return ..() + +/obj/item/paper_bin/examine(mob/user) + . = ..() + if(total_paper) + . += "It contains [total_paper > 1 ? "[total_paper] papers" : " one paper"]." + else + . += "It doesn't contain anything." + + +/obj/item/paper_bin/update_icon() + if(total_paper < 1) + icon_state = "paper_bin0" + else + icon_state = "[initial(icon_state)]" + cut_overlays() + if(bin_pen) + add_overlay(mutable_appearance(bin_pen.icon, bin_pen.icon_state)) + +/obj/item/paper_bin/construction + name = "construction paper bin" + desc = "Contains all the paper you'll never need, IN COLOR!" + icon_state = "paper_binc" + papertype = /obj/item/paper/construction + +/obj/item/paper_bin/bundlenatural + name = "natural paper bundle" + desc = "A bundle of paper created using traditional methods." + icon_state = "paper_bundle" + papertype = /obj/item/paper/natural + resistance_flags = FLAMMABLE + +/obj/item/paper_bin/bundlenatural/attack_hand(mob/user) + ..() + if(total_paper < 1) + qdel(src) + +/obj/item/paper_bin/bundlenatural/fire_act(exposed_temperature, exposed_volume) + qdel(src) + +/obj/item/paper_bin/bundlenatural/attackby(obj/item/W, mob/user) + if(W.get_sharpness()) + to_chat(user, "You snip \the [src], spilling paper everywhere.") + var/turf/T = get_turf(src.loc) + while(total_paper > 0) + total_paper-- + var/obj/item/paper/P + if(papers.len > 0) + P = papers[papers.len] + papers -= P + else + P = new papertype() + P.forceMove(T) + CHECK_TICK + qdel(src) + else + ..() diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm index 8172a57399..2650da8c7a 100644 --- a/code/modules/paperwork/pen.dm +++ b/code/modules/paperwork/pen.dm @@ -1,220 +1,220 @@ -/* Pens! - * Contains: - * Pens - * Sleepy Pens - * Parapens - * Edaggers - */ - - -/* - * Pens - */ -/obj/item/pen - desc = "It's a normal black ink pen." - name = "pen" - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "pen" - item_state = "pen" - slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_EARS - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - materials = list(MAT_METAL=10) - pressure_resistance = 2 - grind_results = list(/datum/reagent/iron = 2, /datum/reagent/iodine = 1) - var/colour = "black" //what colour the ink is! - var/degrees = 0 - var/font = PEN_FONT - -/obj/item/pen/suicide_act(mob/user) - user.visible_message("[user] is scribbling numbers all over [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit sudoku...") - return(BRUTELOSS) - -/obj/item/pen/blue - desc = "It's a normal blue ink pen." - icon_state = "pen_blue" - colour = "blue" - -/obj/item/pen/red - desc = "It's a normal red ink pen." - icon_state = "pen_red" - colour = "red" - -/obj/item/pen/invisible - desc = "It's an invisible pen marker." - icon_state = "pen" - colour = "white" - -/obj/item/pen/fourcolor - desc = "It's a fancy four-color ink pen, set to black." - name = "four-color pen" - colour = "black" - -/obj/item/pen/fourcolor/attack_self(mob/living/carbon/user) - switch(colour) - if("black") - colour = "red" - if("red") - colour = "green" - if("green") - colour = "blue" - else - colour = "black" - to_chat(user, "\The [src] will now write in [colour].") - desc = "It's a fancy four-color ink pen, set to [colour]." - -/obj/item/pen/fountain - name = "fountain pen" - desc = "It's a common fountain pen, with a faux wood body." - icon_state = "pen-fountain" - font = FOUNTAIN_PEN_FONT - -/obj/item/pen/fountain/captain - name = "captain's fountain pen" - desc = "It's an expensive Oak fountain pen. The nib is quite sharp." - icon_state = "pen-fountain-o" - force = 5 - throwforce = 5 - throw_speed = 4 - colour = "crimson" - materials = list(MAT_GOLD = 750) - sharpness = IS_SHARP - resistance_flags = FIRE_PROOF - unique_reskin = list("Oak" = "pen-fountain-o", - "Gold" = "pen-fountain-g", - "Rosewood" = "pen-fountain-r", - "Black and Silver" = "pen-fountain-b", - "Command Blue" = "pen-fountain-cb" - ) - -/obj/item/pen/fountain/captain/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 200, 115) //the pen is mightier than the sword - -/obj/item/pen/fountain/captain/reskin_obj(mob/M) - ..() - if(current_skin) - desc = "It's an expensive [current_skin] fountain pen. The nib is quite sharp." - -/obj/item/pen/attack_self(mob/living/carbon/user) - var/deg = input(user, "What angle would you like to rotate the pen head to? (1-360)", "Rotate Pen Head") as null|num - if(deg && (deg > 0 && deg <= 360)) - degrees = deg - to_chat(user, "You rotate the top of the pen to [degrees] degrees.") - SEND_SIGNAL(src, COMSIG_PEN_ROTATED, deg, user) - -/obj/item/pen/attack(mob/living/M, mob/user,stealth) - if(!istype(M)) - return - - if(!force) - if(M.can_inject(user, 1)) - to_chat(user, "You stab [M] with the pen.") - if(!stealth) - to_chat(M, "You feel a tiny prick!") - . = 1 - - log_combat(user, M, "stabbed", src) - - else - . = ..() - -/obj/item/pen/afterattack(obj/O, mob/living/user, proximity) - . = ..() - //Changing Name/Description of items. Only works if they have the 'unique_rename' flag set - if(isobj(O) && proximity && (O.obj_flags & UNIQUE_RENAME)) - var/penchoice = input(user, "What would you like to edit?", "Rename or change description?") as null|anything in list("Rename","Change description") - if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE)) - return - if(penchoice == "Rename") - var/input = stripped_input(user,"What do you want to name \the [O.name]?", ,"", MAX_NAME_LEN) - var/oldname = O.name - if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE)) - return - if(oldname == input) - to_chat(user, "You changed \the [O.name] to... well... \the [O.name].") - else - O.name = input - to_chat(user, "\The [oldname] has been successfully been renamed to \the [input].") - O.renamedByPlayer = TRUE - - if(penchoice == "Change description") - var/input = stripped_input(user,"Describe \the [O.name] here", ,"", 100) - if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE)) - return - O.desc = input - to_chat(user, "You have successfully changed \the [O.name]'s description.") - -/* - * Sleepypens - */ - -/obj/item/pen/sleepy/attack(mob/living/M, mob/user) - if(!istype(M)) - return - - if(..()) - if(reagents.total_volume) - if(M.reagents) - reagents.reaction(M, INJECT) - reagents.trans_to(M, reagents.total_volume) - - -/obj/item/pen/sleepy/Initialize() - . = ..() - create_reagents(45, OPENCONTAINER) - reagents.add_reagent(/datum/reagent/toxin/chloralhydrate, 20) - reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 15) - reagents.add_reagent(/datum/reagent/toxin/staminatoxin, 10) - -/* - * (Alan) Edaggers - */ -/obj/item/pen/edagger - attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") //these wont show up if the pen is off - sharpness = IS_SHARP - var/on = FALSE - -/obj/item/pen/edagger/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 60, 100, 0, 'sound/weapons/blade1.ogg') - -/obj/item/pen/edagger/get_sharpness() - return on * sharpness - -/obj/item/pen/edagger/attack_self(mob/living/user) - if(on) - on = FALSE - force = initial(force) - w_class = initial(w_class) - name = initial(name) - hitsound = initial(hitsound) - embedding = embedding.setRating(embed_chance = EMBED_CHANCE) - throwforce = initial(throwforce) - playsound(user, 'sound/weapons/saberoff.ogg', 5, 1) - to_chat(user, "[src] can now be concealed.") - else - on = TRUE - force = 18 - w_class = WEIGHT_CLASS_NORMAL - name = "energy dagger" - hitsound = 'sound/weapons/blade1.ogg' - embedding = embedding.setRating(embed_chance = 100) //rule of cool - throwforce = 35 - playsound(user, 'sound/weapons/saberon.ogg', 5, 1) - to_chat(user, "[src] is now active.") - update_icon() - -/obj/item/pen/edagger/update_icon() - if(on) - icon_state = "edagger" - item_state = "edagger" - lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' - else - icon_state = initial(icon_state) //looks like a normal pen when off. - item_state = initial(item_state) - lefthand_file = initial(lefthand_file) - righthand_file = initial(righthand_file) +/* Pens! + * Contains: + * Pens + * Sleepy Pens + * Parapens + * Edaggers + */ + + +/* + * Pens + */ +/obj/item/pen + desc = "It's a normal black ink pen." + name = "pen" + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "pen" + item_state = "pen" + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_EARS + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + materials = list(MAT_METAL=10) + pressure_resistance = 2 + grind_results = list(/datum/reagent/iron = 2, /datum/reagent/iodine = 1) + var/colour = "black" //what colour the ink is! + var/degrees = 0 + var/font = PEN_FONT + +/obj/item/pen/suicide_act(mob/user) + user.visible_message("[user] is scribbling numbers all over [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit sudoku...") + return(BRUTELOSS) + +/obj/item/pen/blue + desc = "It's a normal blue ink pen." + icon_state = "pen_blue" + colour = "blue" + +/obj/item/pen/red + desc = "It's a normal red ink pen." + icon_state = "pen_red" + colour = "red" + +/obj/item/pen/invisible + desc = "It's an invisible pen marker." + icon_state = "pen" + colour = "white" + +/obj/item/pen/fourcolor + desc = "It's a fancy four-color ink pen, set to black." + name = "four-color pen" + colour = "black" + +/obj/item/pen/fourcolor/attack_self(mob/living/carbon/user) + switch(colour) + if("black") + colour = "red" + if("red") + colour = "green" + if("green") + colour = "blue" + else + colour = "black" + to_chat(user, "\The [src] will now write in [colour].") + desc = "It's a fancy four-color ink pen, set to [colour]." + +/obj/item/pen/fountain + name = "fountain pen" + desc = "It's a common fountain pen, with a faux wood body." + icon_state = "pen-fountain" + font = FOUNTAIN_PEN_FONT + +/obj/item/pen/fountain/captain + name = "captain's fountain pen" + desc = "It's an expensive Oak fountain pen. The nib is quite sharp." + icon_state = "pen-fountain-o" + force = 5 + throwforce = 5 + throw_speed = 4 + colour = "crimson" + materials = list(MAT_GOLD = 750) + sharpness = IS_SHARP + resistance_flags = FIRE_PROOF + unique_reskin = list("Oak" = "pen-fountain-o", + "Gold" = "pen-fountain-g", + "Rosewood" = "pen-fountain-r", + "Black and Silver" = "pen-fountain-b", + "Command Blue" = "pen-fountain-cb" + ) + +/obj/item/pen/fountain/captain/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 200, 115) //the pen is mightier than the sword + +/obj/item/pen/fountain/captain/reskin_obj(mob/M) + ..() + if(current_skin) + desc = "It's an expensive [current_skin] fountain pen. The nib is quite sharp." + +/obj/item/pen/attack_self(mob/living/carbon/user) + var/deg = input(user, "What angle would you like to rotate the pen head to? (1-360)", "Rotate Pen Head") as null|num + if(deg && (deg > 0 && deg <= 360)) + degrees = deg + to_chat(user, "You rotate the top of the pen to [degrees] degrees.") + SEND_SIGNAL(src, COMSIG_PEN_ROTATED, deg, user) + +/obj/item/pen/attack(mob/living/M, mob/user,stealth) + if(!istype(M)) + return + + if(!force) + if(M.can_inject(user, 1)) + to_chat(user, "You stab [M] with the pen.") + if(!stealth) + to_chat(M, "You feel a tiny prick!") + . = 1 + + log_combat(user, M, "stabbed", src) + + else + . = ..() + +/obj/item/pen/afterattack(obj/O, mob/living/user, proximity) + . = ..() + //Changing Name/Description of items. Only works if they have the 'unique_rename' flag set + if(isobj(O) && proximity && (O.obj_flags & UNIQUE_RENAME)) + var/penchoice = input(user, "What would you like to edit?", "Rename or change description?") as null|anything in list("Rename","Change description") + if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE)) + return + if(penchoice == "Rename") + var/input = stripped_input(user,"What do you want to name \the [O.name]?", ,"", MAX_NAME_LEN) + var/oldname = O.name + if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE)) + return + if(oldname == input) + to_chat(user, "You changed \the [O.name] to... well... \the [O.name].") + else + O.name = input + to_chat(user, "\The [oldname] has been successfully been renamed to \the [input].") + O.renamedByPlayer = TRUE + + if(penchoice == "Change description") + var/input = stripped_input(user,"Describe \the [O.name] here", ,"", 100) + if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE)) + return + O.desc = input + to_chat(user, "You have successfully changed \the [O.name]'s description.") + +/* + * Sleepypens + */ + +/obj/item/pen/sleepy/attack(mob/living/M, mob/user) + if(!istype(M)) + return + + if(..()) + if(reagents.total_volume) + if(M.reagents) + reagents.reaction(M, INJECT) + reagents.trans_to(M, reagents.total_volume) + + +/obj/item/pen/sleepy/Initialize() + . = ..() + create_reagents(45, OPENCONTAINER) + reagents.add_reagent(/datum/reagent/toxin/chloralhydrate, 20) + reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 15) + reagents.add_reagent(/datum/reagent/toxin/staminatoxin, 10) + +/* + * (Alan) Edaggers + */ +/obj/item/pen/edagger + attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") //these wont show up if the pen is off + sharpness = IS_SHARP + var/on = FALSE + +/obj/item/pen/edagger/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 60, 100, 0, 'sound/weapons/blade1.ogg') + +/obj/item/pen/edagger/get_sharpness() + return on * sharpness + +/obj/item/pen/edagger/attack_self(mob/living/user) + if(on) + on = FALSE + force = initial(force) + w_class = initial(w_class) + name = initial(name) + hitsound = initial(hitsound) + embedding = embedding.setRating(embed_chance = EMBED_CHANCE) + throwforce = initial(throwforce) + playsound(user, 'sound/weapons/saberoff.ogg', 5, 1) + to_chat(user, "[src] can now be concealed.") + else + on = TRUE + force = 18 + w_class = WEIGHT_CLASS_NORMAL + name = "energy dagger" + hitsound = 'sound/weapons/blade1.ogg' + embedding = embedding.setRating(embed_chance = 100) //rule of cool + throwforce = 35 + playsound(user, 'sound/weapons/saberon.ogg', 5, 1) + to_chat(user, "[src] is now active.") + update_icon() + +/obj/item/pen/edagger/update_icon() + if(on) + icon_state = "edagger" + item_state = "edagger" + lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi' + else + icon_state = initial(icon_state) //looks like a normal pen when off. + item_state = initial(item_state) + lefthand_file = initial(lefthand_file) + righthand_file = initial(righthand_file) diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm index e8d318697c..182806388a 100644 --- a/code/modules/paperwork/photocopier.dm +++ b/code/modules/paperwork/photocopier.dm @@ -1,340 +1,340 @@ -/* Photocopiers! - * Contains: - * Photocopier - * Toner Cartridge - */ - -/* - * Photocopier - */ -/obj/machinery/photocopier - name = "photocopier" - desc = "Used to copy important documents and anatomy studies." - icon = 'icons/obj/library.dmi' - icon_state = "photocopier" - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 30 - active_power_usage = 200 - power_channel = EQUIP - max_integrity = 300 - integrity_failure = 100 - var/obj/item/paper/copy = null //what's in the copier! - var/obj/item/photo/photocopy = null - var/obj/item/documents/doccopy = null - var/copies = 1 //how many copies to print! - var/toner = 40 //how much toner is left! woooooo~ - var/maxcopies = 10 //how many copies can be copied at once- idea shamelessly stolen from bs12's copier! - var/greytoggle = "Greyscale" - var/mob/living/ass //i can't believe i didn't write a stupid-ass comment about this var when i first coded asscopy. - var/busy = FALSE - -/obj/machinery/photocopier/ui_interact(mob/user) - . = ..() - var/dat = "Photocopier

                " - if(copy || photocopy || doccopy || (ass && (ass.loc == src.loc))) - dat += "Remove Paper
                " - if(toner) - dat += "Copy
                " - dat += "Printing: [copies] copies." - dat += "- " - dat += "+

                " - if(photocopy) - dat += "Printing in [greytoggle]

                " - else if(toner) - dat += "Please insert paper to copy.

                " - if(isAI(user)) - dat += "Print photo from database

                " - dat += "Current toner level: [toner]" - if(!toner) - dat +="
                Please insert a new toner cartridge!" - user << browse(dat, "window=copier") - onclose(user, "copier") - -/obj/machinery/photocopier/Topic(href, href_list) - if(..()) - return - if(href_list["copy"]) - if(copy) - for(var/i = 0, i < copies, i++) - if(toner > 0 && !busy && copy) - var/copy_as_paper = 1 - if(istype(copy, /obj/item/paper/contract/employment)) - var/obj/item/paper/contract/employment/E = copy - var/obj/item/paper/contract/employment/C = new /obj/item/paper/contract/employment (loc, E.target.current) - if(C) - copy_as_paper = 0 - if(copy_as_paper) - var/obj/item/paper/c = new /obj/item/paper (loc) - if(length(copy.info) > 0) //Only print and add content if the copied doc has words on it - if(toner > 10) //lots of toner, make it dark - c.info = "" - else //no toner? shitty copies for you! - c.info = "" - var/copied = copy.info - copied = replacetext(copied, "" - c.name = copy.name - c.fields = copy.fields - c.update_icon() - c.updateinfolinks() - c.stamps = copy.stamps - if(copy.stamped) - c.stamped = copy.stamped.Copy() - c.copy_overlays(copy, TRUE) - toner-- - busy = TRUE - sleep(15) - busy = FALSE - else - break - updateUsrDialog() - else if(photocopy) - for(var/i = 0, i < copies, i++) - if(toner >= 5 && !busy && photocopy) //Was set to = 0, but if there was say 3 toner left and this ran, you would get -2 which would be weird for ink - new /obj/item/photo (loc, photocopy.picture.Copy(greytoggle == "Greyscale"? TRUE : FALSE)) - busy = TRUE - sleep(15) - busy = FALSE - else - break - else if(doccopy) - for(var/i = 0, i < copies, i++) - if(toner > 5 && !busy && doccopy) - new /obj/item/documents/photocopy(loc, doccopy) - toner-= 6 // the sprite shows 6 papers, yes I checked - busy = TRUE - sleep(15) - busy = FALSE - else - break - updateUsrDialog() - else if(ass) //ASS COPY. By Miauw - for(var/i = 0, i < copies, i++) - var/icon/temp_img - if(ishuman(ass) && (ass.get_item_by_slot(SLOT_W_UNIFORM) || ass.get_item_by_slot(SLOT_WEAR_SUIT))) - to_chat(usr, "You feel kind of silly, copying [ass == usr ? "your" : ass][ass == usr ? "" : "\'s"] ass with [ass == usr ? "your" : "[ass.p_their()]"] clothes on." ) - break - else if(toner >= 5 && !busy && check_ass()) //You have to be sitting on the copier and either be a xeno or a human without clothes on. - if(isalienadult(ass) || istype(ass, /mob/living/simple_animal/hostile/alien)) //Xenos have their own asses, thanks to Pybro. - temp_img = icon('icons/ass/assalien.png') - else if(ishuman(ass)) //Suit checks are in check_ass - if(ass.gender == MALE) - temp_img = icon('icons/ass/assmale.png') - else if(ass.gender == FEMALE) - temp_img = icon('icons/ass/assfemale.png') - else //In case anyone ever makes the generic ass. For now I'll be using male asses. - temp_img = icon('icons/ass/assmale.png') - else if(isdrone(ass)) //Drones are hot - temp_img = icon('icons/ass/assdrone.png') - else - break - var/obj/item/photo/p = new /obj/item/photo (loc) - p.pixel_x = rand(-10, 10) - p.pixel_y = rand(-10, 10) - p.picture = new(null, "You see [ass]'s ass on the photo.", temp_img) - p.picture.psize_x = 128 - p.picture.psize_y = 128 - p.update_icon() - toner -= 5 - busy = TRUE - sleep(15) - busy = FALSE - else - break - updateUsrDialog() - else if(href_list["remove"]) - if(copy) - remove_photocopy(copy, usr) - copy = null - else if(photocopy) - remove_photocopy(photocopy, usr) - photocopy = null - else if(doccopy) - remove_photocopy(doccopy, usr) - doccopy = null - else if(check_ass()) - to_chat(ass, "You feel a slight pressure on your ass.") - updateUsrDialog() - else if(href_list["min"]) - if(copies > 1) - copies-- - updateUsrDialog() - else if(href_list["add"]) - if(copies < maxcopies) - copies++ - updateUsrDialog() - else if(href_list["aipic"]) - if(!isAI(usr)) - return - if(toner >= 5 && !busy) - var/mob/living/silicon/ai/tempAI = usr - if(tempAI.aicamera.stored.len == 0) - to_chat(usr, "No images saved") - return - var/datum/picture/selection = tempAI.aicamera.selectpicture(usr) - var/obj/item/photo/photo = new(loc, selection) - photo.pixel_x = rand(-10, 10) - photo.pixel_y = rand(-10, 10) - toner -= 5 //AI prints color pictures only, thus they can do it more efficiently - busy = TRUE - sleep(15) - busy = FALSE - updateUsrDialog() - else if(href_list["colortoggle"]) - if(greytoggle == "Greyscale") - greytoggle = "Color" - else - greytoggle = "Greyscale" - updateUsrDialog() - -/obj/machinery/photocopier/proc/do_insertion(obj/item/O, mob/user) - O.forceMove(src) - to_chat(user, "You insert [O] into [src].") - flick("photocopier1", src) - updateUsrDialog() - -/obj/machinery/photocopier/proc/remove_photocopy(obj/item/O, mob/user) - if(!issilicon(user)) //surprised this check didn't exist before, putting stuff in AI's hand is bad - O.forceMove(user.loc) - user.put_in_hands(O) - else - O.forceMove(drop_location()) - to_chat(user, "You take [O] out of [src].") - -/obj/machinery/photocopier/attackby(obj/item/O, mob/user, params) - if(default_unfasten_wrench(user, O)) - return - - else if(istype(O, /obj/item/paper)) - if(copier_empty()) - if(istype(O, /obj/item/paper/contract/infernal)) - to_chat(user, "[src] smokes, smelling of brimstone!") - resistance_flags |= FLAMMABLE - fire_act() - else - if(!user.temporarilyRemoveItemFromInventory(O)) - return - copy = O - do_insertion(O, user) - else - to_chat(user, "There is already something in [src]!") - - else if(istype(O, /obj/item/photo)) - if(copier_empty()) - if(!user.temporarilyRemoveItemFromInventory(O)) - return - photocopy = O - do_insertion(O, user) - else - to_chat(user, "There is already something in [src]!") - - else if(istype(O, /obj/item/documents)) - if(copier_empty()) - if(!user.temporarilyRemoveItemFromInventory(O)) - return - doccopy = O - do_insertion(O, user) - else - to_chat(user, "There is already something in [src]!") - - else if(istype(O, /obj/item/toner)) - if(toner <= 0) - if(!user.temporarilyRemoveItemFromInventory(O)) - return - qdel(O) - toner = 40 - to_chat(user, "You insert [O] into [src].") - updateUsrDialog() - else - to_chat(user, "This cartridge is not yet ready for replacement! Use up the rest of the toner.") - - else if(istype(O, /obj/item/areaeditor/blueprints)) - to_chat(user, "The Blueprint is too large to put into the copier. You need to find something else to record the document") - else - return ..() - -/obj/machinery/photocopier/obj_break(damage_flag) - if(!(flags_1 & NODECONSTRUCT_1)) - if(toner > 0) - new /obj/effect/decal/cleanable/oil(get_turf(src)) - toner = 0 - -/obj/machinery/photocopier/MouseDrop_T(mob/target, mob/user) - check_ass() //Just to make sure that you can re-drag somebody onto it after they moved off. - if (!istype(target) || target.anchored || target.buckled || !Adjacent(target) || !user.canUseTopic(src, BE_CLOSE) || target == ass || copier_blocked()) - return - src.add_fingerprint(user) - if(target == user) - user.visible_message("[user] starts climbing onto the photocopier!", "You start climbing onto the photocopier...") - else - user.visible_message("[user] starts putting [target] onto the photocopier!", "You start putting [target] onto the photocopier...") - - if(do_after(user, 20, target = src)) - if(!target || QDELETED(target) || QDELETED(src) || !Adjacent(target)) //check if the photocopier/target still exists. - return - - if(target == user) - user.visible_message("[user] climbs onto the photocopier!", "You climb onto the photocopier.") - else - user.visible_message("[user] puts [target] onto the photocopier!", "You put [target] onto the photocopier.") - - target.forceMove(drop_location()) - ass = target - - if(photocopy) - photocopy.forceMove(drop_location()) - visible_message("[photocopy] is shoved out of the way by [ass]!") - photocopy = null - - else if(copy) - copy.forceMove(drop_location()) - visible_message("[copy] is shoved out of the way by [ass]!") - copy = null - updateUsrDialog() - -/obj/machinery/photocopier/proc/check_ass() //I'm not sure wether I made this proc because it's good form or because of the name. - if(!ass) - return 0 - if(ass.loc != src.loc) - ass = null - updateUsrDialog() - return 0 - else if(ishuman(ass)) - if(!ass.get_item_by_slot(SLOT_W_UNIFORM) && !ass.get_item_by_slot(SLOT_WEAR_SUIT)) - return 1 - else - return 0 - else - return 1 - -/obj/machinery/photocopier/proc/copier_blocked() - if(QDELETED(src)) - return - if(loc.density) - return 1 - for(var/atom/movable/AM in loc) - if(AM == src) - continue - if(AM.density) - return 1 - return 0 - -/obj/machinery/photocopier/proc/copier_empty() - if(copy || photocopy || check_ass()) - return 0 - else - return 1 - -/* - * Toner cartridge - */ -/obj/item/toner - name = "toner cartridge" - icon = 'icons/obj/device.dmi' - icon_state = "tonercartridge" - grind_results = list(/datum/reagent/iodine = 40, /datum/reagent/iron = 10) - var/charges = 5 - var/max_charges = 5 +/* Photocopiers! + * Contains: + * Photocopier + * Toner Cartridge + */ + +/* + * Photocopier + */ +/obj/machinery/photocopier + name = "photocopier" + desc = "Used to copy important documents and anatomy studies." + icon = 'icons/obj/library.dmi' + icon_state = "photocopier" + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 30 + active_power_usage = 200 + power_channel = EQUIP + max_integrity = 300 + integrity_failure = 100 + var/obj/item/paper/copy = null //what's in the copier! + var/obj/item/photo/photocopy = null + var/obj/item/documents/doccopy = null + var/copies = 1 //how many copies to print! + var/toner = 40 //how much toner is left! woooooo~ + var/maxcopies = 10 //how many copies can be copied at once- idea shamelessly stolen from bs12's copier! + var/greytoggle = "Greyscale" + var/mob/living/ass //i can't believe i didn't write a stupid-ass comment about this var when i first coded asscopy. + var/busy = FALSE + +/obj/machinery/photocopier/ui_interact(mob/user) + . = ..() + var/dat = "Photocopier

                " + if(copy || photocopy || doccopy || (ass && (ass.loc == src.loc))) + dat += "Remove Paper
                " + if(toner) + dat += "Copy
                " + dat += "Printing: [copies] copies." + dat += "- " + dat += "+

                " + if(photocopy) + dat += "Printing in [greytoggle]

                " + else if(toner) + dat += "Please insert paper to copy.

                " + if(isAI(user)) + dat += "Print photo from database

                " + dat += "Current toner level: [toner]" + if(!toner) + dat +="
                Please insert a new toner cartridge!" + user << browse(dat, "window=copier") + onclose(user, "copier") + +/obj/machinery/photocopier/Topic(href, href_list) + if(..()) + return + if(href_list["copy"]) + if(copy) + for(var/i = 0, i < copies, i++) + if(toner > 0 && !busy && copy) + var/copy_as_paper = 1 + if(istype(copy, /obj/item/paper/contract/employment)) + var/obj/item/paper/contract/employment/E = copy + var/obj/item/paper/contract/employment/C = new /obj/item/paper/contract/employment (loc, E.target.current) + if(C) + copy_as_paper = 0 + if(copy_as_paper) + var/obj/item/paper/c = new /obj/item/paper (loc) + if(length(copy.info) > 0) //Only print and add content if the copied doc has words on it + if(toner > 10) //lots of toner, make it dark + c.info = "" + else //no toner? shitty copies for you! + c.info = "" + var/copied = copy.info + copied = replacetext(copied, "" + c.name = copy.name + c.fields = copy.fields + c.update_icon() + c.updateinfolinks() + c.stamps = copy.stamps + if(copy.stamped) + c.stamped = copy.stamped.Copy() + c.copy_overlays(copy, TRUE) + toner-- + busy = TRUE + sleep(15) + busy = FALSE + else + break + updateUsrDialog() + else if(photocopy) + for(var/i = 0, i < copies, i++) + if(toner >= 5 && !busy && photocopy) //Was set to = 0, but if there was say 3 toner left and this ran, you would get -2 which would be weird for ink + new /obj/item/photo (loc, photocopy.picture.Copy(greytoggle == "Greyscale"? TRUE : FALSE)) + busy = TRUE + sleep(15) + busy = FALSE + else + break + else if(doccopy) + for(var/i = 0, i < copies, i++) + if(toner > 5 && !busy && doccopy) + new /obj/item/documents/photocopy(loc, doccopy) + toner-= 6 // the sprite shows 6 papers, yes I checked + busy = TRUE + sleep(15) + busy = FALSE + else + break + updateUsrDialog() + else if(ass) //ASS COPY. By Miauw + for(var/i = 0, i < copies, i++) + var/icon/temp_img + if(ishuman(ass) && (ass.get_item_by_slot(SLOT_W_UNIFORM) || ass.get_item_by_slot(SLOT_WEAR_SUIT))) + to_chat(usr, "You feel kind of silly, copying [ass == usr ? "your" : ass][ass == usr ? "" : "\'s"] ass with [ass == usr ? "your" : "[ass.p_their()]"] clothes on." ) + break + else if(toner >= 5 && !busy && check_ass()) //You have to be sitting on the copier and either be a xeno or a human without clothes on. + if(isalienadult(ass) || istype(ass, /mob/living/simple_animal/hostile/alien)) //Xenos have their own asses, thanks to Pybro. + temp_img = icon('icons/ass/assalien.png') + else if(ishuman(ass)) //Suit checks are in check_ass + if(ass.gender == MALE) + temp_img = icon('icons/ass/assmale.png') + else if(ass.gender == FEMALE) + temp_img = icon('icons/ass/assfemale.png') + else //In case anyone ever makes the generic ass. For now I'll be using male asses. + temp_img = icon('icons/ass/assmale.png') + else if(isdrone(ass)) //Drones are hot + temp_img = icon('icons/ass/assdrone.png') + else + break + var/obj/item/photo/p = new /obj/item/photo (loc) + p.pixel_x = rand(-10, 10) + p.pixel_y = rand(-10, 10) + p.picture = new(null, "You see [ass]'s ass on the photo.", temp_img) + p.picture.psize_x = 128 + p.picture.psize_y = 128 + p.update_icon() + toner -= 5 + busy = TRUE + sleep(15) + busy = FALSE + else + break + updateUsrDialog() + else if(href_list["remove"]) + if(copy) + remove_photocopy(copy, usr) + copy = null + else if(photocopy) + remove_photocopy(photocopy, usr) + photocopy = null + else if(doccopy) + remove_photocopy(doccopy, usr) + doccopy = null + else if(check_ass()) + to_chat(ass, "You feel a slight pressure on your ass.") + updateUsrDialog() + else if(href_list["min"]) + if(copies > 1) + copies-- + updateUsrDialog() + else if(href_list["add"]) + if(copies < maxcopies) + copies++ + updateUsrDialog() + else if(href_list["aipic"]) + if(!isAI(usr)) + return + if(toner >= 5 && !busy) + var/mob/living/silicon/ai/tempAI = usr + if(tempAI.aicamera.stored.len == 0) + to_chat(usr, "No images saved") + return + var/datum/picture/selection = tempAI.aicamera.selectpicture(usr) + var/obj/item/photo/photo = new(loc, selection) + photo.pixel_x = rand(-10, 10) + photo.pixel_y = rand(-10, 10) + toner -= 5 //AI prints color pictures only, thus they can do it more efficiently + busy = TRUE + sleep(15) + busy = FALSE + updateUsrDialog() + else if(href_list["colortoggle"]) + if(greytoggle == "Greyscale") + greytoggle = "Color" + else + greytoggle = "Greyscale" + updateUsrDialog() + +/obj/machinery/photocopier/proc/do_insertion(obj/item/O, mob/user) + O.forceMove(src) + to_chat(user, "You insert [O] into [src].") + flick("photocopier1", src) + updateUsrDialog() + +/obj/machinery/photocopier/proc/remove_photocopy(obj/item/O, mob/user) + if(!issilicon(user)) //surprised this check didn't exist before, putting stuff in AI's hand is bad + O.forceMove(user.loc) + user.put_in_hands(O) + else + O.forceMove(drop_location()) + to_chat(user, "You take [O] out of [src].") + +/obj/machinery/photocopier/attackby(obj/item/O, mob/user, params) + if(default_unfasten_wrench(user, O)) + return + + else if(istype(O, /obj/item/paper)) + if(copier_empty()) + if(istype(O, /obj/item/paper/contract/infernal)) + to_chat(user, "[src] smokes, smelling of brimstone!") + resistance_flags |= FLAMMABLE + fire_act() + else + if(!user.temporarilyRemoveItemFromInventory(O)) + return + copy = O + do_insertion(O, user) + else + to_chat(user, "There is already something in [src]!") + + else if(istype(O, /obj/item/photo)) + if(copier_empty()) + if(!user.temporarilyRemoveItemFromInventory(O)) + return + photocopy = O + do_insertion(O, user) + else + to_chat(user, "There is already something in [src]!") + + else if(istype(O, /obj/item/documents)) + if(copier_empty()) + if(!user.temporarilyRemoveItemFromInventory(O)) + return + doccopy = O + do_insertion(O, user) + else + to_chat(user, "There is already something in [src]!") + + else if(istype(O, /obj/item/toner)) + if(toner <= 0) + if(!user.temporarilyRemoveItemFromInventory(O)) + return + qdel(O) + toner = 40 + to_chat(user, "You insert [O] into [src].") + updateUsrDialog() + else + to_chat(user, "This cartridge is not yet ready for replacement! Use up the rest of the toner.") + + else if(istype(O, /obj/item/areaeditor/blueprints)) + to_chat(user, "The Blueprint is too large to put into the copier. You need to find something else to record the document") + else + return ..() + +/obj/machinery/photocopier/obj_break(damage_flag) + if(!(flags_1 & NODECONSTRUCT_1)) + if(toner > 0) + new /obj/effect/decal/cleanable/oil(get_turf(src)) + toner = 0 + +/obj/machinery/photocopier/MouseDrop_T(mob/target, mob/user) + check_ass() //Just to make sure that you can re-drag somebody onto it after they moved off. + if (!istype(target) || target.anchored || target.buckled || !Adjacent(target) || !user.canUseTopic(src, BE_CLOSE) || target == ass || copier_blocked()) + return + src.add_fingerprint(user) + if(target == user) + user.visible_message("[user] starts climbing onto the photocopier!", "You start climbing onto the photocopier...") + else + user.visible_message("[user] starts putting [target] onto the photocopier!", "You start putting [target] onto the photocopier...") + + if(do_after(user, 20, target = src)) + if(!target || QDELETED(target) || QDELETED(src) || !Adjacent(target)) //check if the photocopier/target still exists. + return + + if(target == user) + user.visible_message("[user] climbs onto the photocopier!", "You climb onto the photocopier.") + else + user.visible_message("[user] puts [target] onto the photocopier!", "You put [target] onto the photocopier.") + + target.forceMove(drop_location()) + ass = target + + if(photocopy) + photocopy.forceMove(drop_location()) + visible_message("[photocopy] is shoved out of the way by [ass]!") + photocopy = null + + else if(copy) + copy.forceMove(drop_location()) + visible_message("[copy] is shoved out of the way by [ass]!") + copy = null + updateUsrDialog() + +/obj/machinery/photocopier/proc/check_ass() //I'm not sure wether I made this proc because it's good form or because of the name. + if(!ass) + return 0 + if(ass.loc != src.loc) + ass = null + updateUsrDialog() + return 0 + else if(ishuman(ass)) + if(!ass.get_item_by_slot(SLOT_W_UNIFORM) && !ass.get_item_by_slot(SLOT_WEAR_SUIT)) + return 1 + else + return 0 + else + return 1 + +/obj/machinery/photocopier/proc/copier_blocked() + if(QDELETED(src)) + return + if(loc.density) + return 1 + for(var/atom/movable/AM in loc) + if(AM == src) + continue + if(AM.density) + return 1 + return 0 + +/obj/machinery/photocopier/proc/copier_empty() + if(copy || photocopy || check_ass()) + return 0 + else + return 1 + +/* + * Toner cartridge + */ +/obj/item/toner + name = "toner cartridge" + icon = 'icons/obj/device.dmi' + icon_state = "tonercartridge" + grind_results = list(/datum/reagent/iodine = 40, /datum/reagent/iron = 10) + var/charges = 5 + var/max_charges = 5 diff --git a/code/modules/paperwork/stamps.dm b/code/modules/paperwork/stamps.dm index 7235e0e658..1fe59623fb 100644 --- a/code/modules/paperwork/stamps.dm +++ b/code/modules/paperwork/stamps.dm @@ -1,71 +1,71 @@ -/obj/item/stamp - name = "\improper GRANTED rubber stamp" - desc = "A rubber stamp for stamping important documents." - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "stamp-ok" - item_state = "stamp" - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - materials = list(MAT_METAL=60) - item_color = "cargo" - pressure_resistance = 2 - attack_verb = list("stamped") - -/obj/item/stamp/suicide_act(mob/user) - user.visible_message("[user] stamps 'VOID' on [user.p_their()] forehead, then promptly falls over, dead.") - return (OXYLOSS) - -/obj/item/stamp/qm - name = "quartermaster's rubber stamp" - icon_state = "stamp-qm" - item_color = "qm" - -/obj/item/stamp/law - name = "law office's rubber stamp" - icon_state = "stamp-law" - item_color = "cargo" - -/obj/item/stamp/captain - name = "captain's rubber stamp" - icon_state = "stamp-cap" - item_color = "captain" - -/obj/item/stamp/hop - name = "head of personnel's rubber stamp" - icon_state = "stamp-hop" - item_color = "hop" - -/obj/item/stamp/hos - name = "head of security's rubber stamp" - icon_state = "stamp-hos" - item_color = "hosred" - -/obj/item/stamp/ce - name = "chief engineer's rubber stamp" - icon_state = "stamp-ce" - item_color = "chief" - -/obj/item/stamp/rd - name = "research director's rubber stamp" - icon_state = "stamp-rd" - item_color = "director" - -/obj/item/stamp/cmo - name = "chief medical officer's rubber stamp" - icon_state = "stamp-cmo" - item_color = "cmo" - -/obj/item/stamp/denied - name = "\improper DENIED rubber stamp" - icon_state = "stamp-deny" - item_color = "redcoat" - -/obj/item/stamp/clown - name = "clown's rubber stamp" - icon_state = "stamp-clown" - item_color = "clown" - -/obj/item/stamp/attack_paw(mob/user) - return attack_hand(user) +/obj/item/stamp + name = "\improper GRANTED rubber stamp" + desc = "A rubber stamp for stamping important documents." + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "stamp-ok" + item_state = "stamp" + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + materials = list(MAT_METAL=60) + item_color = "cargo" + pressure_resistance = 2 + attack_verb = list("stamped") + +/obj/item/stamp/suicide_act(mob/user) + user.visible_message("[user] stamps 'VOID' on [user.p_their()] forehead, then promptly falls over, dead.") + return (OXYLOSS) + +/obj/item/stamp/qm + name = "quartermaster's rubber stamp" + icon_state = "stamp-qm" + item_color = "qm" + +/obj/item/stamp/law + name = "law office's rubber stamp" + icon_state = "stamp-law" + item_color = "cargo" + +/obj/item/stamp/captain + name = "captain's rubber stamp" + icon_state = "stamp-cap" + item_color = "captain" + +/obj/item/stamp/hop + name = "head of personnel's rubber stamp" + icon_state = "stamp-hop" + item_color = "hop" + +/obj/item/stamp/hos + name = "head of security's rubber stamp" + icon_state = "stamp-hos" + item_color = "hosred" + +/obj/item/stamp/ce + name = "chief engineer's rubber stamp" + icon_state = "stamp-ce" + item_color = "chief" + +/obj/item/stamp/rd + name = "research director's rubber stamp" + icon_state = "stamp-rd" + item_color = "director" + +/obj/item/stamp/cmo + name = "chief medical officer's rubber stamp" + icon_state = "stamp-cmo" + item_color = "cmo" + +/obj/item/stamp/denied + name = "\improper DENIED rubber stamp" + icon_state = "stamp-deny" + item_color = "redcoat" + +/obj/item/stamp/clown + name = "clown's rubber stamp" + icon_state = "stamp-clown" + item_color = "clown" + +/obj/item/stamp/attack_paw(mob/user) + return attack_hand(user) diff --git a/code/modules/photography/_pictures.dm b/code/modules/photography/_pictures.dm index 9f5babbbbc..62fc01efda 100644 --- a/code/modules/photography/_pictures.dm +++ b/code/modules/photography/_pictures.dm @@ -1,171 +1,171 @@ -/datum/picture - var/picture_name = "picture" - var/picture_desc = "This is a picture." - var/caption - var/icon/picture_image - var/icon/picture_icon - var/psize_x = 96 - var/psize_y = 96 - var/has_blueprints = FALSE - var/logpath //If the picture has been logged this is the path. - var/id //this var is NOT protected because the worst you can do with this that you couldn't do otherwise is overwrite photos, and photos aren't going to be used as attack logs/investigations anytime soon. - -/datum/picture/New(name, desc, image, icon, size_x, size_y, bp, caption_, autogenerate_icon) - if(!isnull(name)) - picture_name = name - if(!isnull(desc)) - picture_desc = desc - if(!isnull(image)) - picture_image = image - if(!isnull(icon)) - picture_icon = icon - if(!isnull(psize_x)) - psize_x = size_x - if(!isnull(psize_y)) - psize_y = size_y - if(!isnull(bp)) - has_blueprints = bp - if(!isnull(caption_)) - caption = caption_ - if(autogenerate_icon && !picture_icon && picture_image) - regenerate_small_icon() - -/datum/picture/proc/get_small_icon() - if(!picture_icon) - regenerate_small_icon() - return picture_icon - -/datum/picture/proc/regenerate_small_icon() - if(!picture_image) - return - var/icon/small_img = icon(picture_image) - var/icon/ic = icon('icons/obj/items_and_weapons.dmi', "photo") - small_img.Scale(8, 8) - ic.Blend(small_img,ICON_OVERLAY, 13, 13) - picture_icon = ic - -/datum/picture/serialize_list(list/options) - . = list() - .["id"] = id - .["desc"] = picture_desc - .["name"] = picture_name - .["caption"] = caption - .["pixel_size_x"] = psize_x - .["pixel_size_y"] = psize_y - .["blueprints"] = has_blueprints - .["logpath"] = logpath - -/datum/picture/deserialize_list(list/input, list/options) - if(!input["logpath"] || !fexists(input["logpath"]) || !input["id"] || !input["pixel_size_x"] || !input["pixel_size_y"]) - return - picture_image = icon(file(input["logpath"])) - logpath = input["logpath"] - id = input["id"] - psize_x = input["pixel_size_x"] - psize_y = input["pixel_size_y"] - if(input["blueprints"]) - has_blueprints = input["blueprints"] - if(input["caption"]) - caption = input["caption"] - if(input["desc"]) - picture_desc = input["desc"] - if(input["name"]) - picture_name = input["name"] - return src - -/proc/load_photo_from_disk(id, location) - var/datum/picture/P = load_picture_from_disk(id) - if(istype(P)) - var/obj/item/photo/p = new(location, P) - return p - -/proc/load_picture_from_disk(id) - var/pathstring = log_path_from_picture_ID(id) - if(!pathstring) - return - var/path = file(pathstring) - if(!fexists(path)) - return - var/dir_index = findlasttext(pathstring, "/") - var/dir = copytext(pathstring, 1, dir_index) - var/json_path = file("[dir]/metadata.json") - if(!fexists(json_path)) - return - var/list/json = json_decode(file2text(json_path)) - if(!json[id]) - return - var/datum/picture/P = new - P.deserialize_json(json[id]) - return P - -/proc/log_path_from_picture_ID(id) - if(!istext(id)) - return - . = "data/picture_logs/" - var/list/data = splittext(id, "_") - if(data.len < 3) - return null - var/mode = data[1] - switch(mode) - if("L") - if(data.len < 5) - return null - var/timestamp = data[2] - var/year = copytext(timestamp, 1, 5) - var/month = copytext(timestamp, 5, 7) - var/day = copytext(timestamp, 7, 9) - var/round = data[4] - . += "[year]/[month]/[day]/round-[round]" - if("O") - var/list/path = data.Copy(2, data.len) - . += path.Join("") - else - return null - var/n = data[data.len] - . += "/[n].png" - -//BE VERY CAREFUL WITH THIS PROC, TO AVOID DUPLICATION. -/datum/picture/proc/log_to_file() - if(!picture_image) - return - if(!CONFIG_GET(flag/log_pictures)) - return - if(logpath) - return //we're already logged - var/number = GLOB.picture_logging_id++ - var/finalpath = "[GLOB.picture_log_directory]/[number].png" - fcopy(icon(picture_image, dir = SOUTH, frame = 1), finalpath) - logpath = finalpath - id = "[GLOB.picture_logging_prefix][number]" - var/jsonpath = "[GLOB.picture_log_directory]/metadata.json" - jsonpath = file(jsonpath) - var/list/json - if(fexists(jsonpath)) - json = json_decode(file2text(jsonpath)) - fdel(jsonpath) - else - json = list() - json[id] = serialize_json() - WRITE_FILE(jsonpath, json_encode(json)) - -/datum/picture/proc/Copy(greyscale = FALSE, cropx = 0, cropy = 0) - var/datum/picture/P = new - P.picture_name = picture_name - P.picture_desc = picture_desc - if(picture_image) - P.picture_image = icon(picture_image) //Copy, not reference. - if(picture_icon) - P.picture_icon = icon(picture_icon) - P.psize_x = psize_x - cropx * 2 - P.psize_y = psize_y - cropy * 2 - P.has_blueprints = has_blueprints - if(greyscale) - if(picture_image) - P.picture_image.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) - if(picture_icon) - P.picture_icon.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) - if(cropx || cropy) - if(picture_image) - P.picture_image.Crop(cropx, cropy, psize_x - cropx, psize_y - cropy) - P.regenerate_small_icon() - return P +/datum/picture + var/picture_name = "picture" + var/picture_desc = "This is a picture." + var/caption + var/icon/picture_image + var/icon/picture_icon + var/psize_x = 96 + var/psize_y = 96 + var/has_blueprints = FALSE + var/logpath //If the picture has been logged this is the path. + var/id //this var is NOT protected because the worst you can do with this that you couldn't do otherwise is overwrite photos, and photos aren't going to be used as attack logs/investigations anytime soon. + +/datum/picture/New(name, desc, image, icon, size_x, size_y, bp, caption_, autogenerate_icon) + if(!isnull(name)) + picture_name = name + if(!isnull(desc)) + picture_desc = desc + if(!isnull(image)) + picture_image = image + if(!isnull(icon)) + picture_icon = icon + if(!isnull(psize_x)) + psize_x = size_x + if(!isnull(psize_y)) + psize_y = size_y + if(!isnull(bp)) + has_blueprints = bp + if(!isnull(caption_)) + caption = caption_ + if(autogenerate_icon && !picture_icon && picture_image) + regenerate_small_icon() + +/datum/picture/proc/get_small_icon() + if(!picture_icon) + regenerate_small_icon() + return picture_icon + +/datum/picture/proc/regenerate_small_icon() + if(!picture_image) + return + var/icon/small_img = icon(picture_image) + var/icon/ic = icon('icons/obj/items_and_weapons.dmi', "photo") + small_img.Scale(8, 8) + ic.Blend(small_img,ICON_OVERLAY, 13, 13) + picture_icon = ic + +/datum/picture/serialize_list(list/options) + . = list() + .["id"] = id + .["desc"] = picture_desc + .["name"] = picture_name + .["caption"] = caption + .["pixel_size_x"] = psize_x + .["pixel_size_y"] = psize_y + .["blueprints"] = has_blueprints + .["logpath"] = logpath + +/datum/picture/deserialize_list(list/input, list/options) + if(!input["logpath"] || !fexists(input["logpath"]) || !input["id"] || !input["pixel_size_x"] || !input["pixel_size_y"]) + return + picture_image = icon(file(input["logpath"])) + logpath = input["logpath"] + id = input["id"] + psize_x = input["pixel_size_x"] + psize_y = input["pixel_size_y"] + if(input["blueprints"]) + has_blueprints = input["blueprints"] + if(input["caption"]) + caption = input["caption"] + if(input["desc"]) + picture_desc = input["desc"] + if(input["name"]) + picture_name = input["name"] + return src + +/proc/load_photo_from_disk(id, location) + var/datum/picture/P = load_picture_from_disk(id) + if(istype(P)) + var/obj/item/photo/p = new(location, P) + return p + +/proc/load_picture_from_disk(id) + var/pathstring = log_path_from_picture_ID(id) + if(!pathstring) + return + var/path = file(pathstring) + if(!fexists(path)) + return + var/dir_index = findlasttext(pathstring, "/") + var/dir = copytext(pathstring, 1, dir_index) + var/json_path = file("[dir]/metadata.json") + if(!fexists(json_path)) + return + var/list/json = json_decode(file2text(json_path)) + if(!json[id]) + return + var/datum/picture/P = new + P.deserialize_json(json[id]) + return P + +/proc/log_path_from_picture_ID(id) + if(!istext(id)) + return + . = "data/picture_logs/" + var/list/data = splittext(id, "_") + if(data.len < 3) + return null + var/mode = data[1] + switch(mode) + if("L") + if(data.len < 5) + return null + var/timestamp = data[2] + var/year = copytext(timestamp, 1, 5) + var/month = copytext(timestamp, 5, 7) + var/day = copytext(timestamp, 7, 9) + var/round = data[4] + . += "[year]/[month]/[day]/round-[round]" + if("O") + var/list/path = data.Copy(2, data.len) + . += path.Join("") + else + return null + var/n = data[data.len] + . += "/[n].png" + +//BE VERY CAREFUL WITH THIS PROC, TO AVOID DUPLICATION. +/datum/picture/proc/log_to_file() + if(!picture_image) + return + if(!CONFIG_GET(flag/log_pictures)) + return + if(logpath) + return //we're already logged + var/number = GLOB.picture_logging_id++ + var/finalpath = "[GLOB.picture_log_directory]/[number].png" + fcopy(icon(picture_image, dir = SOUTH, frame = 1), finalpath) + logpath = finalpath + id = "[GLOB.picture_logging_prefix][number]" + var/jsonpath = "[GLOB.picture_log_directory]/metadata.json" + jsonpath = file(jsonpath) + var/list/json + if(fexists(jsonpath)) + json = json_decode(file2text(jsonpath)) + fdel(jsonpath) + else + json = list() + json[id] = serialize_json() + WRITE_FILE(jsonpath, json_encode(json)) + +/datum/picture/proc/Copy(greyscale = FALSE, cropx = 0, cropy = 0) + var/datum/picture/P = new + P.picture_name = picture_name + P.picture_desc = picture_desc + if(picture_image) + P.picture_image = icon(picture_image) //Copy, not reference. + if(picture_icon) + P.picture_icon = icon(picture_icon) + P.psize_x = psize_x - cropx * 2 + P.psize_y = psize_y - cropy * 2 + P.has_blueprints = has_blueprints + if(greyscale) + if(picture_image) + P.picture_image.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) + if(picture_icon) + P.picture_icon.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) + if(cropx || cropy) + if(picture_image) + P.picture_image.Crop(cropx, cropy, psize_x - cropx, psize_y - cropy) + P.regenerate_small_icon() + return P diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm index 96738e5021..125219d809 100644 --- a/code/modules/photography/camera/camera.dm +++ b/code/modules/photography/camera/camera.dm @@ -1,215 +1,215 @@ - -#define CAMERA_PICTURE_SIZE_HARD_LIMIT 21 - -/obj/item/camera - name = "camera" - icon = 'icons/obj/items_and_weapons.dmi' - desc = "A polaroid camera." - icon_state = "camera" - item_state = "camera" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - light_color = LIGHT_COLOR_WHITE - light_power = FLASH_LIGHT_POWER - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_NECK - materials = list(MAT_METAL = 50, MAT_GLASS = 150) - var/flash_enabled = TRUE - var/state_on = "camera" - var/state_off = "camera_off" - var/pictures_max = 10 - var/pictures_left = 10 - var/on = TRUE - var/cooldown = 64 - var/blending = FALSE //lets not take pictures while the previous is still processing! - var/see_ghosts = CAMERA_NO_GHOSTS //for the spoop of it - var/obj/item/disk/holodisk/disk - var/sound/custom_sound - var/silent = FALSE - var/picture_size_x = 2 - var/picture_size_y = 2 - var/picture_size_x_min = 1 - var/picture_size_y_min = 1 - var/picture_size_x_max = 4 - var/picture_size_y_max = 4 - -/obj/item/camera/attack_self(mob/user) - if(!disk) - return - to_chat(user, "You eject [disk] out the back of [src].") - user.put_in_hands(disk) - disk = null - -/obj/item/camera/examine(mob/user) - . = ..() - . += "Alt-click to change its focusing, allowing you to set how big of an area it will capture." - -/obj/item/camera/AltClick(mob/user) - . = ..() - if(!user.canUseTopic(src, BE_CLOSE)) - return - var/desired_x = input(user, "How high do you want the camera to shoot, between [picture_size_x_min] and [picture_size_x_max]?", "Zoom", picture_size_x) as num - var/desired_y = input(user, "How wide do you want the camera to shoot, between [picture_size_y_min] and [picture_size_y_max]?", "Zoom", picture_size_y) as num - picture_size_x = min(CLAMP(desired_x, picture_size_x_min, picture_size_x_max), CAMERA_PICTURE_SIZE_HARD_LIMIT) - picture_size_y = min(CLAMP(desired_y, picture_size_y_min, picture_size_y_max), CAMERA_PICTURE_SIZE_HARD_LIMIT) - return TRUE - -/obj/item/camera/attack(mob/living/carbon/human/M, mob/user) - return - -/obj/item/camera/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/camera_film)) - if(pictures_left) - to_chat(user, "[src] still has some film in it!") - return - if(!user.temporarilyRemoveItemFromInventory(I)) - return - to_chat(user, "You insert [I] into [src].") - qdel(I) - pictures_left = pictures_max - return - if(istype(I, /obj/item/disk/holodisk)) - if (!disk) - if(!user.transferItemToLoc(I, src)) - to_chat(user, "[I] is stuck to your hand!") - return TRUE - to_chat(user, "You slide [I] into the back of [src].") - disk = I - else - to_chat(user, "There's already a disk inside [src].") - return TRUE //no afterattack - ..() - -/obj/item/camera/examine(mob/user) - . = ..() - . += "It has [pictures_left] photos left." - -//user can be atom or mob -/obj/item/camera/proc/can_target(atom/target, mob/user, prox_flag) - if(!on || blending || !pictures_left) - return FALSE - var/turf/T = get_turf(target) - if(!T) - return FALSE - if(istype(user)) - if(isAI(user) && !GLOB.cameranet.checkTurfVis(T)) - return FALSE - else if(user.client && !(get_turf(target) in get_hear(user.client.view, user))) - return FALSE - else if(!(get_turf(target) in get_hear(world.view, user))) - return FALSE - else //user is an atom - if(!(get_turf(target) in view(world.view, user))) - return FALSE - return TRUE - -/obj/item/camera/afterattack(atom/target, mob/user, flag) - if (disk) - if(ismob(target)) - if (disk.record) - QDEL_NULL(disk.record) - - disk.record = new - var/mob/M = target - disk.record.caller_name = M.name - disk.record.set_caller_image(M) - else - to_chat(user, "Invalid holodisk target.") - return - - if(!can_target(target, user, flag)) - return - - on = FALSE - - var/realcooldown = cooldown - var/mob/living/carbon/human/H = user - if (HAS_TRAIT(H, TRAIT_PHOTOGRAPHER)) - realcooldown *= 0.5 - addtimer(CALLBACK(src, .proc/cooldown), realcooldown) - - icon_state = state_off - - INVOKE_ASYNC(src, .proc/captureimage, target, user, flag, picture_size_x - 1, picture_size_y - 1) - - -/obj/item/camera/proc/cooldown() - UNTIL(!blending) - icon_state = state_on - on = TRUE - -/obj/item/camera/proc/show_picture(mob/user, datum/picture/selection) - var/obj/item/photo/P = new(src, selection) - P.show(user) - to_chat(user, P.desc) - qdel(P) - -/obj/item/camera/proc/captureimage(atom/target, mob/user, flag, size_x = 1, size_y = 1) - if(flash_enabled) - flash_lighting_fx(8, light_power, light_color) - blending = TRUE - var/turf/target_turf = get_turf(target) - if(!isturf(target_turf)) - blending = FALSE - return FALSE - size_x = CLAMP(size_x, 0, CAMERA_PICTURE_SIZE_HARD_LIMIT) - size_y = CLAMP(size_y, 0, CAMERA_PICTURE_SIZE_HARD_LIMIT) - var/list/desc = list("This is a photo of an area of [size_x+1] meters by [size_y+1] meters.") - var/ai_user = isAI(user) - var/list/seen - var/list/viewlist = (user && user.client)? getviewsize(user.client.view) : getviewsize(world.view) - var/viewr = max(viewlist[1], viewlist[2]) + max(size_x, size_y) - var/viewc = user.client? user.client.eye : target - seen = get_hear(viewr, viewc) - var/list/turfs = list() - var/list/mobs = list() - var/blueprints = FALSE - var/clone_area = SSmapping.RequestBlockReservation(size_x * 2 + 1, size_y * 2 + 1) - for(var/turf/T in block(locate(target_turf.x - size_x, target_turf.y - size_y, target_turf.z), locate(target_turf.x + size_x, target_turf.y + size_y, target_turf.z))) - if((ai_user && GLOB.cameranet.checkTurfVis(T)) || (T in seen)) - turfs += T - for(var/mob/M in T) - mobs += M - if(locate(/obj/item/areaeditor/blueprints) in T) - blueprints = TRUE - for(var/i in mobs) - var/mob/M = i - desc += M.get_photo_description(src) - - var/psize_x = (size_x * 2 + 1) * world.icon_size - var/psize_y = (size_y * 2 + 1) * world.icon_size - var/get_icon = camera_get_icon(turfs, target_turf, psize_x, psize_y, clone_area, size_x, size_y, (size_x * 2 + 1), (size_y * 2 + 1)) - qdel(clone_area) - var/icon/temp = icon('icons/effects/96x96.dmi',"") - temp.Blend("#000", ICON_OVERLAY) - temp.Scale(psize_x, psize_y) - temp.Blend(get_icon, ICON_OVERLAY) - - var/datum/picture/P = new("picture", desc.Join(" "), temp, null, psize_x, psize_y, blueprints) - after_picture(user, P, flag) - blending = FALSE - -/obj/item/camera/proc/after_picture(mob/user, datum/picture/picture, proximity_flag) - printpicture(user, picture) - -/obj/item/camera/proc/printpicture(mob/user, datum/picture/picture) //Normal camera proc for creating photos - var/obj/item/photo/p = new(get_turf(src), picture) - if(in_range(src, user)) //needed because of TK - user.put_in_hands(p) - pictures_left-- - to_chat(user, "[pictures_left] photos left.") - var/customize = alert(user, "Do you want to customize the photo?", "Customization", "Yes", "No") - if(customize == "Yes") - var/name1 = stripped_input(user, "Set a name for this photo, or leave blank. 32 characters max.", "Name", max_length = 32) - var/desc1 = stripped_input(user, "Set a description to add to photo, or leave blank. 128 characters max.", "Caption", max_length = 128) - var/caption = stripped_input(user, "Set a caption for this photo, or leave blank. 256 characters max.", "Caption", max_length = 256) - if(name1) - picture.picture_name = name1 - if(desc1) - picture.picture_desc = "[desc1] - [picture.picture_desc]" - if(caption) - picture.caption = caption - p.set_picture(picture, TRUE, TRUE) - if(CONFIG_GET(flag/picture_logging_camera)) + +#define CAMERA_PICTURE_SIZE_HARD_LIMIT 21 + +/obj/item/camera + name = "camera" + icon = 'icons/obj/items_and_weapons.dmi' + desc = "A polaroid camera." + icon_state = "camera" + item_state = "camera" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + light_color = LIGHT_COLOR_WHITE + light_power = FLASH_LIGHT_POWER + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_NECK + materials = list(MAT_METAL = 50, MAT_GLASS = 150) + var/flash_enabled = TRUE + var/state_on = "camera" + var/state_off = "camera_off" + var/pictures_max = 10 + var/pictures_left = 10 + var/on = TRUE + var/cooldown = 64 + var/blending = FALSE //lets not take pictures while the previous is still processing! + var/see_ghosts = CAMERA_NO_GHOSTS //for the spoop of it + var/obj/item/disk/holodisk/disk + var/sound/custom_sound + var/silent = FALSE + var/picture_size_x = 2 + var/picture_size_y = 2 + var/picture_size_x_min = 1 + var/picture_size_y_min = 1 + var/picture_size_x_max = 4 + var/picture_size_y_max = 4 + +/obj/item/camera/attack_self(mob/user) + if(!disk) + return + to_chat(user, "You eject [disk] out the back of [src].") + user.put_in_hands(disk) + disk = null + +/obj/item/camera/examine(mob/user) + . = ..() + . += "Alt-click to change its focusing, allowing you to set how big of an area it will capture." + +/obj/item/camera/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, BE_CLOSE)) + return + var/desired_x = input(user, "How high do you want the camera to shoot, between [picture_size_x_min] and [picture_size_x_max]?", "Zoom", picture_size_x) as num + var/desired_y = input(user, "How wide do you want the camera to shoot, between [picture_size_y_min] and [picture_size_y_max]?", "Zoom", picture_size_y) as num + picture_size_x = min(CLAMP(desired_x, picture_size_x_min, picture_size_x_max), CAMERA_PICTURE_SIZE_HARD_LIMIT) + picture_size_y = min(CLAMP(desired_y, picture_size_y_min, picture_size_y_max), CAMERA_PICTURE_SIZE_HARD_LIMIT) + return TRUE + +/obj/item/camera/attack(mob/living/carbon/human/M, mob/user) + return + +/obj/item/camera/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/camera_film)) + if(pictures_left) + to_chat(user, "[src] still has some film in it!") + return + if(!user.temporarilyRemoveItemFromInventory(I)) + return + to_chat(user, "You insert [I] into [src].") + qdel(I) + pictures_left = pictures_max + return + if(istype(I, /obj/item/disk/holodisk)) + if (!disk) + if(!user.transferItemToLoc(I, src)) + to_chat(user, "[I] is stuck to your hand!") + return TRUE + to_chat(user, "You slide [I] into the back of [src].") + disk = I + else + to_chat(user, "There's already a disk inside [src].") + return TRUE //no afterattack + ..() + +/obj/item/camera/examine(mob/user) + . = ..() + . += "It has [pictures_left] photos left." + +//user can be atom or mob +/obj/item/camera/proc/can_target(atom/target, mob/user, prox_flag) + if(!on || blending || !pictures_left) + return FALSE + var/turf/T = get_turf(target) + if(!T) + return FALSE + if(istype(user)) + if(isAI(user) && !GLOB.cameranet.checkTurfVis(T)) + return FALSE + else if(user.client && !(get_turf(target) in get_hear(user.client.view, user))) + return FALSE + else if(!(get_turf(target) in get_hear(world.view, user))) + return FALSE + else //user is an atom + if(!(get_turf(target) in view(world.view, user))) + return FALSE + return TRUE + +/obj/item/camera/afterattack(atom/target, mob/user, flag) + if (disk) + if(ismob(target)) + if (disk.record) + QDEL_NULL(disk.record) + + disk.record = new + var/mob/M = target + disk.record.caller_name = M.name + disk.record.set_caller_image(M) + else + to_chat(user, "Invalid holodisk target.") + return + + if(!can_target(target, user, flag)) + return + + on = FALSE + + var/realcooldown = cooldown + var/mob/living/carbon/human/H = user + if (HAS_TRAIT(H, TRAIT_PHOTOGRAPHER)) + realcooldown *= 0.5 + addtimer(CALLBACK(src, .proc/cooldown), realcooldown) + + icon_state = state_off + + INVOKE_ASYNC(src, .proc/captureimage, target, user, flag, picture_size_x - 1, picture_size_y - 1) + + +/obj/item/camera/proc/cooldown() + UNTIL(!blending) + icon_state = state_on + on = TRUE + +/obj/item/camera/proc/show_picture(mob/user, datum/picture/selection) + var/obj/item/photo/P = new(src, selection) + P.show(user) + to_chat(user, P.desc) + qdel(P) + +/obj/item/camera/proc/captureimage(atom/target, mob/user, flag, size_x = 1, size_y = 1) + if(flash_enabled) + flash_lighting_fx(8, light_power, light_color) + blending = TRUE + var/turf/target_turf = get_turf(target) + if(!isturf(target_turf)) + blending = FALSE + return FALSE + size_x = CLAMP(size_x, 0, CAMERA_PICTURE_SIZE_HARD_LIMIT) + size_y = CLAMP(size_y, 0, CAMERA_PICTURE_SIZE_HARD_LIMIT) + var/list/desc = list("This is a photo of an area of [size_x+1] meters by [size_y+1] meters.") + var/ai_user = isAI(user) + var/list/seen + var/list/viewlist = (user && user.client)? getviewsize(user.client.view) : getviewsize(world.view) + var/viewr = max(viewlist[1], viewlist[2]) + max(size_x, size_y) + var/viewc = user.client? user.client.eye : target + seen = get_hear(viewr, viewc) + var/list/turfs = list() + var/list/mobs = list() + var/blueprints = FALSE + var/clone_area = SSmapping.RequestBlockReservation(size_x * 2 + 1, size_y * 2 + 1) + for(var/turf/T in block(locate(target_turf.x - size_x, target_turf.y - size_y, target_turf.z), locate(target_turf.x + size_x, target_turf.y + size_y, target_turf.z))) + if((ai_user && GLOB.cameranet.checkTurfVis(T)) || (T in seen)) + turfs += T + for(var/mob/M in T) + mobs += M + if(locate(/obj/item/areaeditor/blueprints) in T) + blueprints = TRUE + for(var/i in mobs) + var/mob/M = i + desc += M.get_photo_description(src) + + var/psize_x = (size_x * 2 + 1) * world.icon_size + var/psize_y = (size_y * 2 + 1) * world.icon_size + var/get_icon = camera_get_icon(turfs, target_turf, psize_x, psize_y, clone_area, size_x, size_y, (size_x * 2 + 1), (size_y * 2 + 1)) + qdel(clone_area) + var/icon/temp = icon('icons/effects/96x96.dmi',"") + temp.Blend("#000", ICON_OVERLAY) + temp.Scale(psize_x, psize_y) + temp.Blend(get_icon, ICON_OVERLAY) + + var/datum/picture/P = new("picture", desc.Join(" "), temp, null, psize_x, psize_y, blueprints) + after_picture(user, P, flag) + blending = FALSE + +/obj/item/camera/proc/after_picture(mob/user, datum/picture/picture, proximity_flag) + printpicture(user, picture) + +/obj/item/camera/proc/printpicture(mob/user, datum/picture/picture) //Normal camera proc for creating photos + var/obj/item/photo/p = new(get_turf(src), picture) + if(in_range(src, user)) //needed because of TK + user.put_in_hands(p) + pictures_left-- + to_chat(user, "[pictures_left] photos left.") + var/customize = alert(user, "Do you want to customize the photo?", "Customization", "Yes", "No") + if(customize == "Yes") + var/name1 = stripped_input(user, "Set a name for this photo, or leave blank. 32 characters max.", "Name", max_length = 32) + var/desc1 = stripped_input(user, "Set a description to add to photo, or leave blank. 128 characters max.", "Caption", max_length = 128) + var/caption = stripped_input(user, "Set a caption for this photo, or leave blank. 256 characters max.", "Caption", max_length = 256) + if(name1) + picture.picture_name = name1 + if(desc1) + picture.picture_desc = "[desc1] - [picture.picture_desc]" + if(caption) + picture.caption = caption + p.set_picture(picture, TRUE, TRUE) + if(CONFIG_GET(flag/picture_logging_camera)) picture.log_to_file() \ No newline at end of file diff --git a/code/modules/photography/camera/camera_image_capturing.dm b/code/modules/photography/camera/camera_image_capturing.dm index e7072026f5..f25d80c050 100644 --- a/code/modules/photography/camera/camera_image_capturing.dm +++ b/code/modules/photography/camera/camera_image_capturing.dm @@ -1,89 +1,89 @@ -/obj/effect/appearance_clone - -/obj/effect/appearance_clone/New(loc, atom/A) //Intentionally not Initialize(), to make sure the clone assumes the intended appearance in time for the camera getFlatIcon. - if(istype(A)) - appearance = A.appearance - dir = A.dir - if(ismovableatom(A)) - var/atom/movable/AM = A - step_x = AM.step_x - step_y = AM.step_y - . = ..() - -/obj/item/camera/proc/camera_get_icon(list/turfs, turf/center, psize_x = 96, psize_y = 96, datum/turf_reservation/clone_area, size_x, size_y, total_x, total_y) - var/list/atoms = list() - var/skip_normal = FALSE - var/wipe_atoms = FALSE - - if(istype(clone_area) && total_x == clone_area.width && total_y == clone_area.height && size_x >= 0 && size_y > 0) - var/cloned_center_x = round(clone_area.bottom_left_coords[1] + ((total_x - 1) / 2)) - var/cloned_center_y = round(clone_area.bottom_left_coords[2] + ((total_y - 1) / 2)) - for(var/t in turfs) - var/turf/T = t - var/offset_x = T.x - center.x - var/offset_y = T.y - center.y - var/turf/newT = locate(cloned_center_x + offset_x, cloned_center_y + offset_y, clone_area.bottom_left_coords[3]) - if(!(newT in clone_area.reserved_turfs)) //sanity check so we don't overwrite other areas somehow - continue - atoms += new /obj/effect/appearance_clone(newT, T) - if(T.loc.icon_state) - atoms += new /obj/effect/appearance_clone(newT, T.loc) - for(var/i in T.contents) - var/atom/A = i - if(!A.invisibility || (see_ghosts && isobserver(A))) - atoms += new /obj/effect/appearance_clone(newT, A) - skip_normal = TRUE - wipe_atoms = TRUE - center = locate(cloned_center_x, cloned_center_y, clone_area.bottom_left_coords[3]) - - if(!skip_normal) - for(var/i in turfs) - var/turf/T = i - atoms += T - for(var/atom/movable/A in T) - if(A.invisibility) - if(!(see_ghosts && isobserver(A))) - continue - atoms += A - CHECK_TICK - - var/icon/res = icon('icons/effects/96x96.dmi', "") - res.Scale(psize_x, psize_y) - - var/list/sorted = list() - var/j - for(var/i in 1 to atoms.len) - var/atom/c = atoms[i] - for(j = sorted.len, j > 0, --j) - var/atom/c2 = sorted[j] - if(c2.layer <= c.layer) - break - sorted.Insert(j+1, c) - CHECK_TICK - - var/xcomp = FLOOR(psize_x / 2, 1) - 15 - var/ycomp = FLOOR(psize_y / 2, 1) - 15 - - - for(var/atom/A in sorted) - var/xo = (A.x - center.x) * world.icon_size + A.pixel_x + xcomp - var/yo = (A.y - center.y) * world.icon_size + A.pixel_y + ycomp - if(ismovableatom(A)) - var/atom/movable/AM = A - xo += AM.step_x - yo += AM.step_y - var/icon/img = getFlatIcon(A) - if(img) - res.Blend(img, blendMode2iconMode(A.blend_mode), xo, yo) - CHECK_TICK - - if(!silent) - if(istype(custom_sound)) //This is where the camera actually finishes its exposure. - playsound(loc, custom_sound, 75, 1, -3) - else - playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, 1, -3) - - if(wipe_atoms) - QDEL_LIST(atoms) - - return res +/obj/effect/appearance_clone + +/obj/effect/appearance_clone/New(loc, atom/A) //Intentionally not Initialize(), to make sure the clone assumes the intended appearance in time for the camera getFlatIcon. + if(istype(A)) + appearance = A.appearance + dir = A.dir + if(ismovableatom(A)) + var/atom/movable/AM = A + step_x = AM.step_x + step_y = AM.step_y + . = ..() + +/obj/item/camera/proc/camera_get_icon(list/turfs, turf/center, psize_x = 96, psize_y = 96, datum/turf_reservation/clone_area, size_x, size_y, total_x, total_y) + var/list/atoms = list() + var/skip_normal = FALSE + var/wipe_atoms = FALSE + + if(istype(clone_area) && total_x == clone_area.width && total_y == clone_area.height && size_x >= 0 && size_y > 0) + var/cloned_center_x = round(clone_area.bottom_left_coords[1] + ((total_x - 1) / 2)) + var/cloned_center_y = round(clone_area.bottom_left_coords[2] + ((total_y - 1) / 2)) + for(var/t in turfs) + var/turf/T = t + var/offset_x = T.x - center.x + var/offset_y = T.y - center.y + var/turf/newT = locate(cloned_center_x + offset_x, cloned_center_y + offset_y, clone_area.bottom_left_coords[3]) + if(!(newT in clone_area.reserved_turfs)) //sanity check so we don't overwrite other areas somehow + continue + atoms += new /obj/effect/appearance_clone(newT, T) + if(T.loc.icon_state) + atoms += new /obj/effect/appearance_clone(newT, T.loc) + for(var/i in T.contents) + var/atom/A = i + if(!A.invisibility || (see_ghosts && isobserver(A))) + atoms += new /obj/effect/appearance_clone(newT, A) + skip_normal = TRUE + wipe_atoms = TRUE + center = locate(cloned_center_x, cloned_center_y, clone_area.bottom_left_coords[3]) + + if(!skip_normal) + for(var/i in turfs) + var/turf/T = i + atoms += T + for(var/atom/movable/A in T) + if(A.invisibility) + if(!(see_ghosts && isobserver(A))) + continue + atoms += A + CHECK_TICK + + var/icon/res = icon('icons/effects/96x96.dmi', "") + res.Scale(psize_x, psize_y) + + var/list/sorted = list() + var/j + for(var/i in 1 to atoms.len) + var/atom/c = atoms[i] + for(j = sorted.len, j > 0, --j) + var/atom/c2 = sorted[j] + if(c2.layer <= c.layer) + break + sorted.Insert(j+1, c) + CHECK_TICK + + var/xcomp = FLOOR(psize_x / 2, 1) - 15 + var/ycomp = FLOOR(psize_y / 2, 1) - 15 + + + for(var/atom/A in sorted) + var/xo = (A.x - center.x) * world.icon_size + A.pixel_x + xcomp + var/yo = (A.y - center.y) * world.icon_size + A.pixel_y + ycomp + if(ismovableatom(A)) + var/atom/movable/AM = A + xo += AM.step_x + yo += AM.step_y + var/icon/img = getFlatIcon(A) + if(img) + res.Blend(img, blendMode2iconMode(A.blend_mode), xo, yo) + CHECK_TICK + + if(!silent) + if(istype(custom_sound)) //This is where the camera actually finishes its exposure. + playsound(loc, custom_sound, 75, 1, -3) + else + playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, 1, -3) + + if(wipe_atoms) + QDEL_LIST(atoms) + + return res diff --git a/code/modules/photography/camera/film.dm b/code/modules/photography/camera/film.dm index 5d69824bad..b1cd6bae66 100644 --- a/code/modules/photography/camera/film.dm +++ b/code/modules/photography/camera/film.dm @@ -1,14 +1,14 @@ -/* - * Film - */ -/obj/item/camera_film - name = "film cartridge" - icon = 'icons/obj/items_and_weapons.dmi' - desc = "A camera film cartridge. Insert it into a camera to reload it." - icon_state = "film" - item_state = "electropack" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - materials = list(MAT_METAL = 10, MAT_GLASS = 10) +/* + * Film + */ +/obj/item/camera_film + name = "film cartridge" + icon = 'icons/obj/items_and_weapons.dmi' + desc = "A camera film cartridge. Insert it into a camera to reload it." + icon_state = "film" + item_state = "electropack" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + materials = list(MAT_METAL = 10, MAT_GLASS = 10) diff --git a/code/modules/photography/camera/other.dm b/code/modules/photography/camera/other.dm index 3695559e97..0245c63ca8 100644 --- a/code/modules/photography/camera/other.dm +++ b/code/modules/photography/camera/other.dm @@ -1,21 +1,21 @@ -/obj/item/camera/spooky - name = "camera obscura" - desc = "A polaroid camera, some say it can see ghosts!" - see_ghosts = CAMERA_SEE_GHOSTS_BASIC - -/obj/item/camera/spooky/badmin - desc = "A polaroid camera, some say it can see ghosts! It seems to have an extra magnifier on the end." - see_ghosts = CAMERA_SEE_GHOSTS_ORBIT - -/obj/item/camera/detective - name = "Detective's camera" - desc = "A polaroid camera with extra capacity for crime investigations." - pictures_max = 30 - pictures_left = 30 - -/obj/item/camera/spooky/family - name = "fancy camera" - desc = "A fancy camera that has the latest magnifier mods and double the film load! With a complex double lens set with holy water to be able to see the dead, at laest to the Chaplain..." - see_ghosts = CAMERA_SEE_GHOSTS_ORBIT - pictures_max = 30 - pictures_left = 30 +/obj/item/camera/spooky + name = "camera obscura" + desc = "A polaroid camera, some say it can see ghosts!" + see_ghosts = CAMERA_SEE_GHOSTS_BASIC + +/obj/item/camera/spooky/badmin + desc = "A polaroid camera, some say it can see ghosts! It seems to have an extra magnifier on the end." + see_ghosts = CAMERA_SEE_GHOSTS_ORBIT + +/obj/item/camera/detective + name = "Detective's camera" + desc = "A polaroid camera with extra capacity for crime investigations." + pictures_max = 30 + pictures_left = 30 + +/obj/item/camera/spooky/family + name = "fancy camera" + desc = "A fancy camera that has the latest magnifier mods and double the film load! With a complex double lens set with holy water to be able to see the dead, at laest to the Chaplain..." + see_ghosts = CAMERA_SEE_GHOSTS_ORBIT + pictures_max = 30 + pictures_left = 30 diff --git a/code/modules/photography/camera/silicon_camera.dm b/code/modules/photography/camera/silicon_camera.dm index 8e1c9338c6..28a080d5f1 100644 --- a/code/modules/photography/camera/silicon_camera.dm +++ b/code/modules/photography/camera/silicon_camera.dm @@ -1,99 +1,99 @@ - -/obj/item/camera/siliconcam - name = "silicon photo camera" - var/in_camera_mode = FALSE - var/list/datum/picture/stored = list() - -/obj/item/camera/siliconcam/ai_camera - name = "AI photo camera" - flash_enabled = FALSE - -/obj/item/camera/siliconcam/proc/toggle_camera_mode(mob/user) - if(in_camera_mode) - camera_mode_off(user) - else - camera_mode_on(user) - -/obj/item/camera/siliconcam/proc/camera_mode_off(mob/user) - in_camera_mode = FALSE - to_chat(user, "Camera Mode deactivated") - -/obj/item/camera/siliconcam/proc/camera_mode_on(mob/user) - in_camera_mode = TRUE - to_chat(user, "Camera Mode activated") - -/obj/item/camera/siliconcam/proc/selectpicture(mob/user) - var/list/nametemp = list() - var/find - if(!stored.len) - to_chat(usr, "No images saved") - return - var/list/temp = list() - for(var/i in stored) - var/datum/picture/p = i - nametemp += p.picture_name - temp[p.picture_name] = p - find = input(user, "Select image") in nametemp|null - if(!find) - return - return temp[find] - -/obj/item/camera/siliconcam/proc/viewpictures(mob/user) - var/datum/picture/selection = selectpicture(user) - if(istype(selection)) - show_picture(user, selection) - -/obj/item/camera/siliconcam/ai_camera/after_picture(mob/user, datum/picture/picture, proximity_flag) - var/number = stored.len - picture.picture_name = "Image [number] (taken by [loc.name])" - stored[picture] = TRUE - to_chat(usr, "Image recorded") - -/obj/item/camera/siliconcam/robot_camera - name = "Cyborg photo camera" - var/printcost = 2 - -/obj/item/camera/siliconcam/robot_camera/after_picture(mob/user, datum/picture/picture, proximity_flag) - var/mob/living/silicon/robot/C = loc - if(istype(C) && istype(C.connected_ai)) - var/number = C.connected_ai.aicamera.stored.len - picture.picture_name = "Image [number] (taken by [loc.name])" - C.connected_ai.aicamera.stored[picture] = TRUE - to_chat(usr, "Image recorded and saved to remote database") - else - var/number = stored.len - picture.picture_name = "Image [number] (taken by [loc.name])" - stored[picture] = TRUE - to_chat(usr, "Image recorded and saved to local storage. Upload will happen automatically if unit is lawsynced.") - -/obj/item/camera/siliconcam/robot_camera/selectpicture(mob/user) - var/mob/living/silicon/robot/R = loc - if(istype(R) && R.connected_ai) - R.picturesync() - return R.connected_ai.aicamera.selectpicture(user) - else - return ..() - -/obj/item/camera/siliconcam/robot_camera/verb/borgprinting() - set category ="Robot Commands" - set name = "Print Image" - set src in usr - if(usr.stat == DEAD) - return - borgprint(usr) - -/obj/item/camera/siliconcam/robot_camera/proc/borgprint(mob/user) - var/mob/living/silicon/robot/C = loc - if(!istype(C) || C.toner < 20) - to_chat(user, "Insufficent toner to print image.") - return - var/datum/picture/selection = selectpicture(user) - if(!istype(selection)) - to_chat(user, "Invalid Image.") - return - var/obj/item/photo/p = new /obj/item/photo(C.loc, selection) - p.pixel_x = rand(-10, 10) - p.pixel_y = rand(-10, 10) - C.toner -= printcost //All fun allowed. - visible_message("[C.name] spits out a photograph from a narrow slot on its chassis.") - to_chat(usr, "You print a photograph.") + +/obj/item/camera/siliconcam + name = "silicon photo camera" + var/in_camera_mode = FALSE + var/list/datum/picture/stored = list() + +/obj/item/camera/siliconcam/ai_camera + name = "AI photo camera" + flash_enabled = FALSE + +/obj/item/camera/siliconcam/proc/toggle_camera_mode(mob/user) + if(in_camera_mode) + camera_mode_off(user) + else + camera_mode_on(user) + +/obj/item/camera/siliconcam/proc/camera_mode_off(mob/user) + in_camera_mode = FALSE + to_chat(user, "Camera Mode deactivated") + +/obj/item/camera/siliconcam/proc/camera_mode_on(mob/user) + in_camera_mode = TRUE + to_chat(user, "Camera Mode activated") + +/obj/item/camera/siliconcam/proc/selectpicture(mob/user) + var/list/nametemp = list() + var/find + if(!stored.len) + to_chat(usr, "No images saved") + return + var/list/temp = list() + for(var/i in stored) + var/datum/picture/p = i + nametemp += p.picture_name + temp[p.picture_name] = p + find = input(user, "Select image") in nametemp|null + if(!find) + return + return temp[find] + +/obj/item/camera/siliconcam/proc/viewpictures(mob/user) + var/datum/picture/selection = selectpicture(user) + if(istype(selection)) + show_picture(user, selection) + +/obj/item/camera/siliconcam/ai_camera/after_picture(mob/user, datum/picture/picture, proximity_flag) + var/number = stored.len + picture.picture_name = "Image [number] (taken by [loc.name])" + stored[picture] = TRUE + to_chat(usr, "Image recorded") + +/obj/item/camera/siliconcam/robot_camera + name = "Cyborg photo camera" + var/printcost = 2 + +/obj/item/camera/siliconcam/robot_camera/after_picture(mob/user, datum/picture/picture, proximity_flag) + var/mob/living/silicon/robot/C = loc + if(istype(C) && istype(C.connected_ai)) + var/number = C.connected_ai.aicamera.stored.len + picture.picture_name = "Image [number] (taken by [loc.name])" + C.connected_ai.aicamera.stored[picture] = TRUE + to_chat(usr, "Image recorded and saved to remote database") + else + var/number = stored.len + picture.picture_name = "Image [number] (taken by [loc.name])" + stored[picture] = TRUE + to_chat(usr, "Image recorded and saved to local storage. Upload will happen automatically if unit is lawsynced.") + +/obj/item/camera/siliconcam/robot_camera/selectpicture(mob/user) + var/mob/living/silicon/robot/R = loc + if(istype(R) && R.connected_ai) + R.picturesync() + return R.connected_ai.aicamera.selectpicture(user) + else + return ..() + +/obj/item/camera/siliconcam/robot_camera/verb/borgprinting() + set category ="Robot Commands" + set name = "Print Image" + set src in usr + if(usr.stat == DEAD) + return + borgprint(usr) + +/obj/item/camera/siliconcam/robot_camera/proc/borgprint(mob/user) + var/mob/living/silicon/robot/C = loc + if(!istype(C) || C.toner < 20) + to_chat(user, "Insufficent toner to print image.") + return + var/datum/picture/selection = selectpicture(user) + if(!istype(selection)) + to_chat(user, "Invalid Image.") + return + var/obj/item/photo/p = new /obj/item/photo(C.loc, selection) + p.pixel_x = rand(-10, 10) + p.pixel_y = rand(-10, 10) + C.toner -= printcost //All fun allowed. + visible_message("[C.name] spits out a photograph from a narrow slot on its chassis.") + to_chat(usr, "You print a photograph.") diff --git a/code/modules/photography/photos/album.dm b/code/modules/photography/photos/album.dm index 3400ed6de0..8361accba2 100644 --- a/code/modules/photography/photos/album.dm +++ b/code/modules/photography/photos/album.dm @@ -1,75 +1,75 @@ -/* - * Photo album - */ -/obj/item/storage/photo_album - name = "photo album" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "album" - item_state = "briefcase" - lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' - resistance_flags = FLAMMABLE - var/persistence_id - -/obj/item/storage/photo_album/Initialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.can_hold = typecacheof(list(/obj/item/photo)) - STR.max_combined_w_class = 42 - STR.max_items = 21 - LAZYADD(SSpersistence.photo_albums, src) - -/obj/item/storage/photo_album/Destroy() - LAZYREMOVE(SSpersistence.photo_albums, src) - return ..() - -/obj/item/storage/photo_album/proc/get_picture_id_list() - var/list/L = list() - for(var/i in contents) - if(istype(i, /obj/item/photo)) - L += i - if(!L.len) - return - . = list() - for(var/i in L) - var/obj/item/photo/P = i - if(!istype(P.picture)) - continue - . |= P.picture.id - -//Manual loading, DO NOT USE FOR HARDCODED/MAPPED IN ALBUMS. This is for if an album needs to be loaded mid-round from an ID. -/obj/item/storage/photo_album/proc/persistence_load() - var/list/data = SSpersistence.GetPhotoAlbums() - if(data[persistence_id]) - populate_from_id_list(data[persistence_id]) - -/obj/item/storage/photo_album/proc/populate_from_id_list(list/ids) - var/list/current_ids = get_picture_id_list() - for(var/i in ids) - if(i in current_ids) - continue - var/obj/item/photo/P = load_photo_from_disk(i) - if(istype(P)) - if(!SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, P, null, TRUE, TRUE)) - qdel(P) - -/obj/item/storage/photo_album/HoS - persistence_id = "HoS" - -/obj/item/storage/photo_album/RD - persistence_id = "RD" - -/obj/item/storage/photo_album/HoP - persistence_id = "HoP" - -/obj/item/storage/photo_album/Captain - persistence_id = "Captain" - -/obj/item/storage/photo_album/CMO - persistence_id = "CMO" - -/obj/item/storage/photo_album/QM - persistence_id = "QM" - -/obj/item/storage/photo_album/CE - persistence_id = "CE" +/* + * Photo album + */ +/obj/item/storage/photo_album + name = "photo album" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "album" + item_state = "briefcase" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + resistance_flags = FLAMMABLE + var/persistence_id + +/obj/item/storage/photo_album/Initialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.can_hold = typecacheof(list(/obj/item/photo)) + STR.max_combined_w_class = 42 + STR.max_items = 21 + LAZYADD(SSpersistence.photo_albums, src) + +/obj/item/storage/photo_album/Destroy() + LAZYREMOVE(SSpersistence.photo_albums, src) + return ..() + +/obj/item/storage/photo_album/proc/get_picture_id_list() + var/list/L = list() + for(var/i in contents) + if(istype(i, /obj/item/photo)) + L += i + if(!L.len) + return + . = list() + for(var/i in L) + var/obj/item/photo/P = i + if(!istype(P.picture)) + continue + . |= P.picture.id + +//Manual loading, DO NOT USE FOR HARDCODED/MAPPED IN ALBUMS. This is for if an album needs to be loaded mid-round from an ID. +/obj/item/storage/photo_album/proc/persistence_load() + var/list/data = SSpersistence.GetPhotoAlbums() + if(data[persistence_id]) + populate_from_id_list(data[persistence_id]) + +/obj/item/storage/photo_album/proc/populate_from_id_list(list/ids) + var/list/current_ids = get_picture_id_list() + for(var/i in ids) + if(i in current_ids) + continue + var/obj/item/photo/P = load_photo_from_disk(i) + if(istype(P)) + if(!SEND_SIGNAL(src, COMSIG_TRY_STORAGE_INSERT, P, null, TRUE, TRUE)) + qdel(P) + +/obj/item/storage/photo_album/HoS + persistence_id = "HoS" + +/obj/item/storage/photo_album/RD + persistence_id = "RD" + +/obj/item/storage/photo_album/HoP + persistence_id = "HoP" + +/obj/item/storage/photo_album/Captain + persistence_id = "Captain" + +/obj/item/storage/photo_album/CMO + persistence_id = "CMO" + +/obj/item/storage/photo_album/QM + persistence_id = "QM" + +/obj/item/storage/photo_album/CE + persistence_id = "CE" diff --git a/code/modules/photography/photos/frame.dm b/code/modules/photography/photos/frame.dm index 5986da1ce8..f86cae8095 100644 --- a/code/modules/photography/photos/frame.dm +++ b/code/modules/photography/photos/frame.dm @@ -1,163 +1,163 @@ -// Picture frames - -/obj/item/wallframe/picture - name = "picture frame" - desc = "The perfect showcase for your favorite deathtrap memories." - icon = 'icons/obj/decals.dmi' - materials = list() - flags_1 = 0 - icon_state = "frame-empty" - result_path = /obj/structure/sign/picture_frame - var/obj/item/photo/displayed - -/obj/item/wallframe/picture/attackby(obj/item/I, mob/user) - if(istype(I, /obj/item/photo)) - if(!displayed) - if(!user.transferItemToLoc(I, src)) - return - displayed = I - update_icon() - else - to_chat(user, "\The [src] already contains a photo.") - ..() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/wallframe/picture/attack_hand(mob/user) - if(user.get_inactive_held_item() != src) - ..() - return - if(contents.len) - var/obj/item/I = pick(contents) - user.put_in_hands(I) - to_chat(user, "You carefully remove the photo from \the [src].") - displayed = null - update_icon() - return ..() - -/obj/item/wallframe/picture/attack_self(mob/user) - user.examinate(src) - -/obj/item/wallframe/picture/examine(mob/user) - if(user.is_holding(src) && displayed) - displayed.show(user) - return list() - return ..() - -/obj/item/wallframe/picture/update_icon() - cut_overlays() - if(displayed) - add_overlay(image(displayed)) - -/obj/item/wallframe/picture/after_attach(obj/O) - ..() - var/obj/structure/sign/picture_frame/PF = O - PF.copy_overlays(src) - if(displayed) - PF.framed = displayed - if(contents.len) - var/obj/item/I = pick(contents) - I.forceMove(PF) - -/obj/structure/sign/picture_frame - name = "picture frame" - desc = "Every time you look it makes you laugh." - icon = 'icons/obj/decals.dmi' - icon_state = "frame-empty" - var/obj/item/photo/framed - var/persistence_id - var/can_decon = TRUE - -#define FRAME_DEFINE(id) /obj/structure/sign/picture_frame/##id/persistence_id = #id - -//Put default persistent frame defines here! - -#undef FRAME_DEFINE - -/obj/structure/sign/picture_frame/Initialize(mapload, dir, building) - . = ..() - LAZYADD(SSpersistence.photo_frames, src) - if(dir) - setDir(dir) - if(building) - pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30) - pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 - -/obj/structure/sign/picture_frame/Destroy() - LAZYREMOVE(SSpersistence.photo_frames, src) - return ..() - -/obj/structure/sign/picture_frame/proc/get_photo_id() - if(istype(framed) && istype(framed.picture)) - return framed.picture.id - -//Manual loading, DO NOT USE FOR HARDCODED/MAPPED IN ALBUMS. This is for if an album needs to be loaded mid-round from an ID. -/obj/structure/sign/picture_frame/proc/persistence_load() - var/list/data = SSpersistence.GetPhotoFrames() - if(data[persistence_id]) - load_from_id(data[persistence_id]) - -/obj/structure/sign/picture_frame/proc/load_from_id(id) - var/obj/item/photo/P = load_photo_from_disk(id) - if(istype(P)) - if(istype(framed)) - framed.forceMove(drop_location()) - else - qdel(framed) - framed = P - update_icon() - -/obj/structure/sign/picture_frame/examine(mob/user) - if(in_range(src, user) && framed) - framed.show(user) - return list() - return ..() - -/obj/structure/sign/picture_frame/attackby(obj/item/I, mob/user, params) - if(can_decon && (istype(I, /obj/item/screwdriver) || istype(I, /obj/item/wrench))) - to_chat(user, "You start unsecuring [name]...") - if(I.use_tool(src, user, 30, volume=50)) - playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) - to_chat(user, "You unsecure [name].") - deconstruct() - - else if(istype(I, /obj/item/wirecutters) && framed) - framed.forceMove(drop_location()) - framed = null - user.visible_message("[user] cuts away [framed] from [src]!") - return - - else if(istype(I, /obj/item/photo)) - if(!framed) - var/obj/item/photo/P = I - if(!user.transferItemToLoc(P, src)) - return - framed = P - update_icon() - else - to_chat(user, "\The [src] already contains a photo.") - - ..() - -/obj/structure/sign/picture_frame/attack_hand(mob/user) - . = ..() - if(.) - return - if(framed) - framed.show(user) - -/obj/structure/sign/picture_frame/update_icon() - cut_overlays() - if(framed) - add_overlay(image(framed)) - -/obj/structure/sign/picture_frame/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - var/obj/item/wallframe/picture/F = new /obj/item/wallframe/picture(loc) - if(framed) - F.displayed = framed - framed = null - if(contents.len) - var/obj/item/I = pick(contents) - I.forceMove(F) - F.update_icon() - qdel(src) +// Picture frames + +/obj/item/wallframe/picture + name = "picture frame" + desc = "The perfect showcase for your favorite deathtrap memories." + icon = 'icons/obj/decals.dmi' + materials = list() + flags_1 = 0 + icon_state = "frame-empty" + result_path = /obj/structure/sign/picture_frame + var/obj/item/photo/displayed + +/obj/item/wallframe/picture/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/photo)) + if(!displayed) + if(!user.transferItemToLoc(I, src)) + return + displayed = I + update_icon() + else + to_chat(user, "\The [src] already contains a photo.") + ..() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/wallframe/picture/attack_hand(mob/user) + if(user.get_inactive_held_item() != src) + ..() + return + if(contents.len) + var/obj/item/I = pick(contents) + user.put_in_hands(I) + to_chat(user, "You carefully remove the photo from \the [src].") + displayed = null + update_icon() + return ..() + +/obj/item/wallframe/picture/attack_self(mob/user) + user.examinate(src) + +/obj/item/wallframe/picture/examine(mob/user) + if(user.is_holding(src) && displayed) + displayed.show(user) + return list() + return ..() + +/obj/item/wallframe/picture/update_icon() + cut_overlays() + if(displayed) + add_overlay(image(displayed)) + +/obj/item/wallframe/picture/after_attach(obj/O) + ..() + var/obj/structure/sign/picture_frame/PF = O + PF.copy_overlays(src) + if(displayed) + PF.framed = displayed + if(contents.len) + var/obj/item/I = pick(contents) + I.forceMove(PF) + +/obj/structure/sign/picture_frame + name = "picture frame" + desc = "Every time you look it makes you laugh." + icon = 'icons/obj/decals.dmi' + icon_state = "frame-empty" + var/obj/item/photo/framed + var/persistence_id + var/can_decon = TRUE + +#define FRAME_DEFINE(id) /obj/structure/sign/picture_frame/##id/persistence_id = #id + +//Put default persistent frame defines here! + +#undef FRAME_DEFINE + +/obj/structure/sign/picture_frame/Initialize(mapload, dir, building) + . = ..() + LAZYADD(SSpersistence.photo_frames, src) + if(dir) + setDir(dir) + if(building) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30) + pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 + +/obj/structure/sign/picture_frame/Destroy() + LAZYREMOVE(SSpersistence.photo_frames, src) + return ..() + +/obj/structure/sign/picture_frame/proc/get_photo_id() + if(istype(framed) && istype(framed.picture)) + return framed.picture.id + +//Manual loading, DO NOT USE FOR HARDCODED/MAPPED IN ALBUMS. This is for if an album needs to be loaded mid-round from an ID. +/obj/structure/sign/picture_frame/proc/persistence_load() + var/list/data = SSpersistence.GetPhotoFrames() + if(data[persistence_id]) + load_from_id(data[persistence_id]) + +/obj/structure/sign/picture_frame/proc/load_from_id(id) + var/obj/item/photo/P = load_photo_from_disk(id) + if(istype(P)) + if(istype(framed)) + framed.forceMove(drop_location()) + else + qdel(framed) + framed = P + update_icon() + +/obj/structure/sign/picture_frame/examine(mob/user) + if(in_range(src, user) && framed) + framed.show(user) + return list() + return ..() + +/obj/structure/sign/picture_frame/attackby(obj/item/I, mob/user, params) + if(can_decon && (istype(I, /obj/item/screwdriver) || istype(I, /obj/item/wrench))) + to_chat(user, "You start unsecuring [name]...") + if(I.use_tool(src, user, 30, volume=50)) + playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) + to_chat(user, "You unsecure [name].") + deconstruct() + + else if(istype(I, /obj/item/wirecutters) && framed) + framed.forceMove(drop_location()) + framed = null + user.visible_message("[user] cuts away [framed] from [src]!") + return + + else if(istype(I, /obj/item/photo)) + if(!framed) + var/obj/item/photo/P = I + if(!user.transferItemToLoc(P, src)) + return + framed = P + update_icon() + else + to_chat(user, "\The [src] already contains a photo.") + + ..() + +/obj/structure/sign/picture_frame/attack_hand(mob/user) + . = ..() + if(.) + return + if(framed) + framed.show(user) + +/obj/structure/sign/picture_frame/update_icon() + cut_overlays() + if(framed) + add_overlay(image(framed)) + +/obj/structure/sign/picture_frame/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + var/obj/item/wallframe/picture/F = new /obj/item/wallframe/picture(loc) + if(framed) + F.displayed = framed + framed = null + if(contents.len) + var/obj/item/I = pick(contents) + I.forceMove(F) + F.update_icon() + qdel(src) diff --git a/code/modules/photography/photos/photo.dm b/code/modules/photography/photos/photo.dm index 49f315d998..b15aa308a3 100644 --- a/code/modules/photography/photos/photo.dm +++ b/code/modules/photography/photos/photo.dm @@ -1,92 +1,92 @@ -/* - * Photo - */ -/obj/item/photo - name = "photo" - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "photo" - item_state = "paper" - w_class = WEIGHT_CLASS_TINY - resistance_flags = FLAMMABLE - max_integrity = 50 - grind_results = list(/datum/reagent/iodine = 4) - var/datum/picture/picture - var/scribble //Scribble on the back. - -/obj/item/photo/Initialize(mapload, datum/picture/P, datum_name = TRUE, datum_desc = TRUE) - set_picture(P, datum_name, datum_desc, TRUE) - return ..() - -/obj/item/photo/proc/set_picture(datum/picture/P, setname, setdesc, name_override = FALSE) - if(!istype(P)) - return - picture = P - update_icon() - if(P.caption) - scribble = P.caption - if(setname && P.picture_name) - if(name_override) - name = P.picture_name - else - name = "photo - [P.picture_name]" - if(setdesc && P.picture_desc) - desc = P.picture_desc - -/obj/item/photo/update_icon() - if(!istype(picture) || !picture.picture_image) - return - var/icon/I = picture.get_small_icon() - if(I) - icon = I - -/obj/item/photo/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is taking one last look at \the [src]! It looks like [user.p_theyre()] giving in to death!")//when you wanna look at photo of waifu one last time before you die... - if (user.gender == MALE) - playsound(user, 'sound/voice/human/manlaugh1.ogg', 50, 1)//EVERY TIME I DO IT MAKES ME LAUGH - else if (user.gender == FEMALE) - playsound(user, 'sound/voice/human/womanlaugh.ogg', 50, 1) - return OXYLOSS - -/obj/item/photo/attack_self(mob/user) - user.examinate(src) - -/obj/item/photo/attackby(obj/item/P, mob/user, params) - if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on [src]!") - return - var/txt = sanitize(input(user, "What would you like to write on the back?", "Photo Writing", null) as text) - txt = copytext(txt, 1, 128) - if(user.canUseTopic(src, BE_CLOSE)) - scribble = txt - ..() - -/obj/item/photo/examine(mob/user) - . = ..() - if(in_range(src, user)) - show(user) - else - . += "You need to get closer to get a good look at this photo!" - -/obj/item/photo/proc/show(mob/user) - if(!istype(picture) || !picture.picture_image) - to_chat(user, "[src] seems to be blank...") - return - user << browse_rsc(picture.picture_image, "tmp_photo.png") - user << browse("[name]" \ - + "" \ - + "" \ - + "[scribble ? "
                Written on the back:
                [scribble]" : ""]"\ - + "", "window=photo_showing;size=480x608") - onclose(user, "[name]") - -/obj/item/photo/verb/rename() - set name = "Rename photo" - set category = "Object" - set src in usr - - var/n_name = copytext(sanitize(input(usr, "What would you like to label the photo?", "Photo Labelling", null) as text), 1, MAX_NAME_LEN) - //loc.loc check is for making possible renaming photos in clipboards - if((loc == usr || loc.loc && loc.loc == usr) && usr.stat == CONSCIOUS && usr.canmove && !usr.restrained()) - name = "photo[(n_name ? text("- '[n_name]'") : null)]" - add_fingerprint(usr) +/* + * Photo + */ +/obj/item/photo + name = "photo" + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "photo" + item_state = "paper" + w_class = WEIGHT_CLASS_TINY + resistance_flags = FLAMMABLE + max_integrity = 50 + grind_results = list(/datum/reagent/iodine = 4) + var/datum/picture/picture + var/scribble //Scribble on the back. + +/obj/item/photo/Initialize(mapload, datum/picture/P, datum_name = TRUE, datum_desc = TRUE) + set_picture(P, datum_name, datum_desc, TRUE) + return ..() + +/obj/item/photo/proc/set_picture(datum/picture/P, setname, setdesc, name_override = FALSE) + if(!istype(P)) + return + picture = P + update_icon() + if(P.caption) + scribble = P.caption + if(setname && P.picture_name) + if(name_override) + name = P.picture_name + else + name = "photo - [P.picture_name]" + if(setdesc && P.picture_desc) + desc = P.picture_desc + +/obj/item/photo/update_icon() + if(!istype(picture) || !picture.picture_image) + return + var/icon/I = picture.get_small_icon() + if(I) + icon = I + +/obj/item/photo/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is taking one last look at \the [src]! It looks like [user.p_theyre()] giving in to death!")//when you wanna look at photo of waifu one last time before you die... + if (user.gender == MALE) + playsound(user, 'sound/voice/human/manlaugh1.ogg', 50, 1)//EVERY TIME I DO IT MAKES ME LAUGH + else if (user.gender == FEMALE) + playsound(user, 'sound/voice/human/womanlaugh.ogg', 50, 1) + return OXYLOSS + +/obj/item/photo/attack_self(mob/user) + user.examinate(src) + +/obj/item/photo/attackby(obj/item/P, mob/user, params) + if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on [src]!") + return + var/txt = sanitize(input(user, "What would you like to write on the back?", "Photo Writing", null) as text) + txt = copytext(txt, 1, 128) + if(user.canUseTopic(src, BE_CLOSE)) + scribble = txt + ..() + +/obj/item/photo/examine(mob/user) + . = ..() + if(in_range(src, user)) + show(user) + else + . += "You need to get closer to get a good look at this photo!" + +/obj/item/photo/proc/show(mob/user) + if(!istype(picture) || !picture.picture_image) + to_chat(user, "[src] seems to be blank...") + return + user << browse_rsc(picture.picture_image, "tmp_photo.png") + user << browse("[name]" \ + + "" \ + + "" \ + + "[scribble ? "
                Written on the back:
                [scribble]" : ""]"\ + + "", "window=photo_showing;size=480x608") + onclose(user, "[name]") + +/obj/item/photo/verb/rename() + set name = "Rename photo" + set category = "Object" + set src in usr + + var/n_name = copytext(sanitize(input(usr, "What would you like to label the photo?", "Photo Labelling", null) as text), 1, MAX_NAME_LEN) + //loc.loc check is for making possible renaming photos in clipboards + if((loc == usr || loc.loc && loc.loc == usr) && usr.stat == CONSCIOUS && usr.canmove && !usr.restrained()) + name = "photo[(n_name ? text("- '[n_name]'") : null)]" + add_fingerprint(usr) diff --git a/code/modules/power/antimatter/containment_jar.dm b/code/modules/power/antimatter/containment_jar.dm index a9dfa6666c..234808b461 100644 --- a/code/modules/power/antimatter/containment_jar.dm +++ b/code/modules/power/antimatter/containment_jar.dm @@ -1,40 +1,40 @@ -/obj/item/am_containment - name = "antimatter containment jar" - desc = "Holds antimatter." - icon = 'icons/obj/machines/antimatter.dmi' - icon_state = "jar" - density = FALSE - anchored = FALSE - force = 8 - throwforce = 10 - throw_speed = 1 - throw_range = 2 - - var/fuel = 10000 - var/fuel_max = 10000//Lets try this for now - var/stability = 100//TODO: add all the stability things to this so its not very safe if you keep hitting in on things - - -/obj/item/am_containment/ex_act(severity, target) - switch(severity) - if(1) - explosion(get_turf(src), 1, 2, 3, 5)//Should likely be larger but this works fine for now I guess - if(src) - qdel(src) - if(2) - if(prob((fuel/10)-stability)) - explosion(get_turf(src), 1, 2, 3, 5) - if(src) - qdel(src) - return - stability -= 40 - if(3) - stability -= 20 - //check_stability() - return - -/obj/item/am_containment/proc/usefuel(wanted) - if(fuel < wanted) - wanted = fuel - fuel -= wanted +/obj/item/am_containment + name = "antimatter containment jar" + desc = "Holds antimatter." + icon = 'icons/obj/machines/antimatter.dmi' + icon_state = "jar" + density = FALSE + anchored = FALSE + force = 8 + throwforce = 10 + throw_speed = 1 + throw_range = 2 + + var/fuel = 10000 + var/fuel_max = 10000//Lets try this for now + var/stability = 100//TODO: add all the stability things to this so its not very safe if you keep hitting in on things + + +/obj/item/am_containment/ex_act(severity, target) + switch(severity) + if(1) + explosion(get_turf(src), 1, 2, 3, 5)//Should likely be larger but this works fine for now I guess + if(src) + qdel(src) + if(2) + if(prob((fuel/10)-stability)) + explosion(get_turf(src), 1, 2, 3, 5) + if(src) + qdel(src) + return + stability -= 40 + if(3) + stability -= 20 + //check_stability() + return + +/obj/item/am_containment/proc/usefuel(wanted) + if(fuel < wanted) + wanted = fuel + fuel -= wanted return wanted \ No newline at end of file diff --git a/code/modules/power/antimatter/control.dm b/code/modules/power/antimatter/control.dm index 099edc1909..1ff398bdd3 100644 --- a/code/modules/power/antimatter/control.dm +++ b/code/modules/power/antimatter/control.dm @@ -1,357 +1,357 @@ -/obj/machinery/power/am_control_unit - name = "antimatter control unit" - desc = "This device injects antimatter into connected shielding units, the more antimatter injected the more power produced. Wrench the device to set it up." - icon = 'icons/obj/machines/antimatter.dmi' - icon_state = "control" - anchored = FALSE - density = TRUE - use_power = IDLE_POWER_USE - idle_power_usage = 100 - active_power_usage = 1000 - - interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT | INTERACT_ATOM_REQUIRES_ANCHORED - - var/list/obj/machinery/am_shielding/linked_shielding - var/list/obj/machinery/am_shielding/linked_cores - var/obj/item/am_containment/fueljar - var/update_shield_icons = 0 - var/stability = 100 - var/exploding = 0 - - var/active = 0//On or not - var/fuel_injection = 2//How much fuel to inject - var/shield_icon_delay = 0//delays resetting for a short time - var/reported_core_efficiency = 0 - - var/power_cycle = 0 - var/power_cycle_delay = 4//How many ticks till produce_power is called - var/stored_core_stability = 0 - var/stored_core_stability_delay = 0 - - var/stored_power = 0//Power to deploy per tick - - -/obj/machinery/power/am_control_unit/Initialize() - . = ..() - linked_shielding = list() - linked_cores = list() - - -/obj/machinery/power/am_control_unit/Destroy()//Perhaps damage and run stability checks rather than just del on the others - for(var/obj/machinery/am_shielding/AMS in linked_shielding) - AMS.control_unit = null - qdel(AMS) - QDEL_NULL(fueljar) - return ..() - - -/obj/machinery/power/am_control_unit/process() - if(exploding) - explosion(get_turf(src),8,12,18,12) - if(src) - qdel(src) - - if(update_shield_icons && !shield_icon_delay) - check_shield_icons() - update_shield_icons = 0 - - if(stat & (NOPOWER|BROKEN) || !active)//can update the icons even without power - return - - if(!fueljar)//No fuel but we are on, shutdown - toggle_power() - playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, 0) - return - - add_avail(stored_power) - - power_cycle++ - if(power_cycle >= power_cycle_delay) - produce_power() - power_cycle = 0 - - return - - -/obj/machinery/power/am_control_unit/proc/produce_power() - playsound(src.loc, 'sound/effects/bang.ogg', 25, 1) - var/core_power = reported_core_efficiency//Effectively how much fuel we can safely deal with - if(core_power <= 0) - return 0//Something is wrong - var/core_damage = 0 - var/fuel = fueljar.usefuel(fuel_injection) - - stored_power = (fuel/core_power)*fuel*200000 - //Now check if the cores could deal with it safely, this is done after so you can overload for more power if needed, still a bad idea - if(fuel > (2*core_power))//More fuel has been put in than the current cores can deal with - if(prob(50)) - core_damage = 1//Small chance of damage - if((fuel-core_power) > 5) - core_damage = 5//Now its really starting to overload the cores - if((fuel-core_power) > 10) - core_damage = 20//Welp now you did it, they wont stand much of this - if(core_damage == 0) - return - for(var/obj/machinery/am_shielding/AMS in linked_cores) - AMS.stability -= core_damage - AMS.check_stability(1) - playsound(src.loc, 'sound/effects/bang.ogg', 50, 1) - return - - -/obj/machinery/power/am_control_unit/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - switch(severity) - if(1) - if(active) - toggle_power() - stability -= rand(15,30) - if(2) - if(active) - toggle_power() - stability -= rand(10,20) - -/obj/machinery/power/am_control_unit/blob_act() - stability -= 20 - if(prob(100-stability))//Might infect the rest of the machine - for(var/obj/machinery/am_shielding/AMS in linked_shielding) - AMS.blob_act() - qdel(src) - return - check_stability() - return - - -/obj/machinery/power/am_control_unit/ex_act(severity, target) - stability -= (80 - (severity * 20)) - check_stability() - return - - -/obj/machinery/power/am_control_unit/bullet_act(obj/item/projectile/Proj) - . = ..() - if(Proj.flag != "bullet") - stability -= Proj.force - check_stability() - - -/obj/machinery/power/am_control_unit/power_change() - ..() - if(stat & NOPOWER) - if(active) - toggle_power(1) - else - use_power = NO_POWER_USE - - else if(!stat && anchored) - use_power = IDLE_POWER_USE - - return - - -/obj/machinery/power/am_control_unit/update_icon() - if(active) - icon_state = "control_on" - else icon_state = "control" - //No other icons for it atm - - -/obj/machinery/power/am_control_unit/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/wrench)) - if(!anchored) - W.play_tool_sound(src, 75) - user.visible_message("[user.name] secures the [src.name] to the floor.", \ - "You secure the anchor bolts to the floor.", \ - "You hear a ratchet.") - src.anchored = TRUE - connect_to_network() - else if(!linked_shielding.len > 0) - W.play_tool_sound(src, 75) - user.visible_message("[user.name] unsecures the [src.name].", \ - "You remove the anchor bolts.", \ - "You hear a ratchet.") - src.anchored = FALSE - disconnect_from_network() - else - to_chat(user, "Once bolted and linked to a shielding unit it the [src.name] is unable to be moved!") - - else if(istype(W, /obj/item/am_containment)) - if(fueljar) - to_chat(user, "There is already a [fueljar] inside!") - return - - if(!user.transferItemToLoc(W, src)) - return - fueljar = W - user.visible_message("[user.name] loads an [W.name] into the [src.name].", \ - "You load an [W.name].", \ - "You hear a thunk.") - else - return ..() - - -/obj/machinery/power/am_control_unit/take_damage(damage, damage_type = BRUTE, sound_effect = 1) - switch(damage_type) - if(BRUTE) - if(sound_effect) - if(damage) - playsound(loc, 'sound/weapons/smash.ogg', 50, 1) - else - playsound(loc, 'sound/weapons/tap.ogg', 50, 1) - if(BURN) - if(sound_effect) - playsound(src.loc, 'sound/items/welder.ogg', 100, 1) - else - return - if(damage >= 20) - stability -= damage/2 - check_stability() - -/obj/machinery/power/am_control_unit/proc/add_shielding(obj/machinery/am_shielding/AMS, AMS_linking = 0) - if(!istype(AMS)) - return 0 - if(!anchored) - return 0 - if(!AMS_linking && !AMS.link_control(src)) - return 0 - linked_shielding.Add(AMS) - update_shield_icons = 1 - return 1 - - -/obj/machinery/power/am_control_unit/proc/remove_shielding(obj/machinery/am_shielding/AMS) - if(!istype(AMS)) - return 0 - linked_shielding.Remove(AMS) - update_shield_icons = 2 - if(active) - toggle_power() - return 1 - - -/obj/machinery/power/am_control_unit/proc/check_stability()//TODO: make it break when low also might want to add a way to fix it like a part or such that can be replaced - if(stability <= 0) - qdel(src) - return - - -/obj/machinery/power/am_control_unit/proc/toggle_power(powerfail = 0) - active = !active - if(active) - use_power = ACTIVE_POWER_USE - visible_message("The [src.name] starts up.") - else - use_power = !powerfail - visible_message("The [src.name] shuts down.") - update_icon() - return - - -/obj/machinery/power/am_control_unit/proc/check_shield_icons()//Forces icon_update for all shields - if(shield_icon_delay) - return - shield_icon_delay = 1 - if(update_shield_icons == 2)//2 means to clear everything and rebuild - for(var/obj/machinery/am_shielding/AMS in linked_shielding) - if(AMS.processing) - AMS.shutdown_core() - AMS.control_unit = null - addtimer(CALLBACK(AMS, /obj/machinery/am_shielding.proc/controllerscan), 10) - linked_shielding = list() - else - for(var/obj/machinery/am_shielding/AMS in linked_shielding) - AMS.update_icon() - addtimer(CALLBACK(src, .proc/reset_shield_icon_delay), 20) - -/obj/machinery/power/am_control_unit/proc/reset_shield_icon_delay() - shield_icon_delay = 0 - -/obj/machinery/power/am_control_unit/proc/check_core_stability() - if(stored_core_stability_delay || linked_cores.len <= 0) - return - stored_core_stability_delay = 1 - stored_core_stability = 0 - for(var/obj/machinery/am_shielding/AMS in linked_cores) - stored_core_stability += AMS.stability - stored_core_stability/=linked_cores.len - addtimer(CALLBACK(src, .proc/reset_stored_core_stability_delay), 40) - -/obj/machinery/power/am_control_unit/proc/reset_stored_core_stability_delay() - stored_core_stability_delay = 0 - -/obj/machinery/power/am_control_unit/ui_interact(mob/user) - . = ..() - if((get_dist(src, user) > 1) || (stat & (BROKEN|NOPOWER))) - if(!isAI(user)) - user.unset_machine() - user << browse(null, "window=AMcontrol") - return - - var/dat = "" - dat += "AntiMatter Control Panel
                " - dat += "Close
                " - dat += "Refresh
                " - dat += "Force Shielding Update

                " - dat += "Status: [(active?"Injecting":"Standby")]
                " - dat += "Toggle Status
                " - - dat += "Stability: [stability]%
                " - dat += "Reactor parts: [linked_shielding.len]
                "//TODO: perhaps add some sort of stability check - dat += "Cores: [linked_cores.len]

                " - dat += "-Current Efficiency: [reported_core_efficiency]
                " - dat += "-Average Stability: [stored_core_stability] (update)
                " - dat += "Last Produced: [DisplayPower(stored_power)]
                " - - dat += "Fuel: " - if(!fueljar) - dat += "
                No fuel receptacle detected." - else - dat += "Eject
                " - dat += "- [fueljar.fuel]/[fueljar.fuel_max] Units
                " - - dat += "- Injecting: [fuel_injection] units
                " - dat += "- --|++

                " - - - user << browse(dat, "window=AMcontrol;size=420x500") - onclose(user, "AMcontrol") - return - - -/obj/machinery/power/am_control_unit/Topic(href, href_list) - if(..()) - return - - if(href_list["close"]) - usr << browse(null, "window=AMcontrol") - usr.unset_machine() - return - - if(href_list["togglestatus"]) - toggle_power() - - if(href_list["refreshicons"]) - update_shield_icons = 1 - - if(href_list["ejectjar"]) - if(fueljar) - fueljar.forceMove(drop_location()) - fueljar = null - //fueljar.control_unit = null currently it does not care where it is - //update_icon() when we have the icon for it - - if(href_list["strengthup"]) - fuel_injection++ - - if(href_list["strengthdown"]) - fuel_injection-- - if(fuel_injection < 0) - fuel_injection = 0 - - if(href_list["refreshstability"]) - check_core_stability() - - updateDialog() - return +/obj/machinery/power/am_control_unit + name = "antimatter control unit" + desc = "This device injects antimatter into connected shielding units, the more antimatter injected the more power produced. Wrench the device to set it up." + icon = 'icons/obj/machines/antimatter.dmi' + icon_state = "control" + anchored = FALSE + density = TRUE + use_power = IDLE_POWER_USE + idle_power_usage = 100 + active_power_usage = 1000 + + interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT | INTERACT_ATOM_REQUIRES_ANCHORED + + var/list/obj/machinery/am_shielding/linked_shielding + var/list/obj/machinery/am_shielding/linked_cores + var/obj/item/am_containment/fueljar + var/update_shield_icons = 0 + var/stability = 100 + var/exploding = 0 + + var/active = 0//On or not + var/fuel_injection = 2//How much fuel to inject + var/shield_icon_delay = 0//delays resetting for a short time + var/reported_core_efficiency = 0 + + var/power_cycle = 0 + var/power_cycle_delay = 4//How many ticks till produce_power is called + var/stored_core_stability = 0 + var/stored_core_stability_delay = 0 + + var/stored_power = 0//Power to deploy per tick + + +/obj/machinery/power/am_control_unit/Initialize() + . = ..() + linked_shielding = list() + linked_cores = list() + + +/obj/machinery/power/am_control_unit/Destroy()//Perhaps damage and run stability checks rather than just del on the others + for(var/obj/machinery/am_shielding/AMS in linked_shielding) + AMS.control_unit = null + qdel(AMS) + QDEL_NULL(fueljar) + return ..() + + +/obj/machinery/power/am_control_unit/process() + if(exploding) + explosion(get_turf(src),8,12,18,12) + if(src) + qdel(src) + + if(update_shield_icons && !shield_icon_delay) + check_shield_icons() + update_shield_icons = 0 + + if(stat & (NOPOWER|BROKEN) || !active)//can update the icons even without power + return + + if(!fueljar)//No fuel but we are on, shutdown + toggle_power() + playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, 0) + return + + add_avail(stored_power) + + power_cycle++ + if(power_cycle >= power_cycle_delay) + produce_power() + power_cycle = 0 + + return + + +/obj/machinery/power/am_control_unit/proc/produce_power() + playsound(src.loc, 'sound/effects/bang.ogg', 25, 1) + var/core_power = reported_core_efficiency//Effectively how much fuel we can safely deal with + if(core_power <= 0) + return 0//Something is wrong + var/core_damage = 0 + var/fuel = fueljar.usefuel(fuel_injection) + + stored_power = (fuel/core_power)*fuel*200000 + //Now check if the cores could deal with it safely, this is done after so you can overload for more power if needed, still a bad idea + if(fuel > (2*core_power))//More fuel has been put in than the current cores can deal with + if(prob(50)) + core_damage = 1//Small chance of damage + if((fuel-core_power) > 5) + core_damage = 5//Now its really starting to overload the cores + if((fuel-core_power) > 10) + core_damage = 20//Welp now you did it, they wont stand much of this + if(core_damage == 0) + return + for(var/obj/machinery/am_shielding/AMS in linked_cores) + AMS.stability -= core_damage + AMS.check_stability(1) + playsound(src.loc, 'sound/effects/bang.ogg', 50, 1) + return + + +/obj/machinery/power/am_control_unit/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + switch(severity) + if(1) + if(active) + toggle_power() + stability -= rand(15,30) + if(2) + if(active) + toggle_power() + stability -= rand(10,20) + +/obj/machinery/power/am_control_unit/blob_act() + stability -= 20 + if(prob(100-stability))//Might infect the rest of the machine + for(var/obj/machinery/am_shielding/AMS in linked_shielding) + AMS.blob_act() + qdel(src) + return + check_stability() + return + + +/obj/machinery/power/am_control_unit/ex_act(severity, target) + stability -= (80 - (severity * 20)) + check_stability() + return + + +/obj/machinery/power/am_control_unit/bullet_act(obj/item/projectile/Proj) + . = ..() + if(Proj.flag != "bullet") + stability -= Proj.force + check_stability() + + +/obj/machinery/power/am_control_unit/power_change() + ..() + if(stat & NOPOWER) + if(active) + toggle_power(1) + else + use_power = NO_POWER_USE + + else if(!stat && anchored) + use_power = IDLE_POWER_USE + + return + + +/obj/machinery/power/am_control_unit/update_icon() + if(active) + icon_state = "control_on" + else icon_state = "control" + //No other icons for it atm + + +/obj/machinery/power/am_control_unit/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/wrench)) + if(!anchored) + W.play_tool_sound(src, 75) + user.visible_message("[user.name] secures the [src.name] to the floor.", \ + "You secure the anchor bolts to the floor.", \ + "You hear a ratchet.") + src.anchored = TRUE + connect_to_network() + else if(!linked_shielding.len > 0) + W.play_tool_sound(src, 75) + user.visible_message("[user.name] unsecures the [src.name].", \ + "You remove the anchor bolts.", \ + "You hear a ratchet.") + src.anchored = FALSE + disconnect_from_network() + else + to_chat(user, "Once bolted and linked to a shielding unit it the [src.name] is unable to be moved!") + + else if(istype(W, /obj/item/am_containment)) + if(fueljar) + to_chat(user, "There is already a [fueljar] inside!") + return + + if(!user.transferItemToLoc(W, src)) + return + fueljar = W + user.visible_message("[user.name] loads an [W.name] into the [src.name].", \ + "You load an [W.name].", \ + "You hear a thunk.") + else + return ..() + + +/obj/machinery/power/am_control_unit/take_damage(damage, damage_type = BRUTE, sound_effect = 1) + switch(damage_type) + if(BRUTE) + if(sound_effect) + if(damage) + playsound(loc, 'sound/weapons/smash.ogg', 50, 1) + else + playsound(loc, 'sound/weapons/tap.ogg', 50, 1) + if(BURN) + if(sound_effect) + playsound(src.loc, 'sound/items/welder.ogg', 100, 1) + else + return + if(damage >= 20) + stability -= damage/2 + check_stability() + +/obj/machinery/power/am_control_unit/proc/add_shielding(obj/machinery/am_shielding/AMS, AMS_linking = 0) + if(!istype(AMS)) + return 0 + if(!anchored) + return 0 + if(!AMS_linking && !AMS.link_control(src)) + return 0 + linked_shielding.Add(AMS) + update_shield_icons = 1 + return 1 + + +/obj/machinery/power/am_control_unit/proc/remove_shielding(obj/machinery/am_shielding/AMS) + if(!istype(AMS)) + return 0 + linked_shielding.Remove(AMS) + update_shield_icons = 2 + if(active) + toggle_power() + return 1 + + +/obj/machinery/power/am_control_unit/proc/check_stability()//TODO: make it break when low also might want to add a way to fix it like a part or such that can be replaced + if(stability <= 0) + qdel(src) + return + + +/obj/machinery/power/am_control_unit/proc/toggle_power(powerfail = 0) + active = !active + if(active) + use_power = ACTIVE_POWER_USE + visible_message("The [src.name] starts up.") + else + use_power = !powerfail + visible_message("The [src.name] shuts down.") + update_icon() + return + + +/obj/machinery/power/am_control_unit/proc/check_shield_icons()//Forces icon_update for all shields + if(shield_icon_delay) + return + shield_icon_delay = 1 + if(update_shield_icons == 2)//2 means to clear everything and rebuild + for(var/obj/machinery/am_shielding/AMS in linked_shielding) + if(AMS.processing) + AMS.shutdown_core() + AMS.control_unit = null + addtimer(CALLBACK(AMS, /obj/machinery/am_shielding.proc/controllerscan), 10) + linked_shielding = list() + else + for(var/obj/machinery/am_shielding/AMS in linked_shielding) + AMS.update_icon() + addtimer(CALLBACK(src, .proc/reset_shield_icon_delay), 20) + +/obj/machinery/power/am_control_unit/proc/reset_shield_icon_delay() + shield_icon_delay = 0 + +/obj/machinery/power/am_control_unit/proc/check_core_stability() + if(stored_core_stability_delay || linked_cores.len <= 0) + return + stored_core_stability_delay = 1 + stored_core_stability = 0 + for(var/obj/machinery/am_shielding/AMS in linked_cores) + stored_core_stability += AMS.stability + stored_core_stability/=linked_cores.len + addtimer(CALLBACK(src, .proc/reset_stored_core_stability_delay), 40) + +/obj/machinery/power/am_control_unit/proc/reset_stored_core_stability_delay() + stored_core_stability_delay = 0 + +/obj/machinery/power/am_control_unit/ui_interact(mob/user) + . = ..() + if((get_dist(src, user) > 1) || (stat & (BROKEN|NOPOWER))) + if(!isAI(user)) + user.unset_machine() + user << browse(null, "window=AMcontrol") + return + + var/dat = "" + dat += "AntiMatter Control Panel
                " + dat += "Close
                " + dat += "Refresh
                " + dat += "Force Shielding Update

                " + dat += "Status: [(active?"Injecting":"Standby")]
                " + dat += "Toggle Status
                " + + dat += "Stability: [stability]%
                " + dat += "Reactor parts: [linked_shielding.len]
                "//TODO: perhaps add some sort of stability check + dat += "Cores: [linked_cores.len]

                " + dat += "-Current Efficiency: [reported_core_efficiency]
                " + dat += "-Average Stability: [stored_core_stability] (update)
                " + dat += "Last Produced: [DisplayPower(stored_power)]
                " + + dat += "Fuel: " + if(!fueljar) + dat += "
                No fuel receptacle detected." + else + dat += "Eject
                " + dat += "- [fueljar.fuel]/[fueljar.fuel_max] Units
                " + + dat += "- Injecting: [fuel_injection] units
                " + dat += "- --|++

                " + + + user << browse(dat, "window=AMcontrol;size=420x500") + onclose(user, "AMcontrol") + return + + +/obj/machinery/power/am_control_unit/Topic(href, href_list) + if(..()) + return + + if(href_list["close"]) + usr << browse(null, "window=AMcontrol") + usr.unset_machine() + return + + if(href_list["togglestatus"]) + toggle_power() + + if(href_list["refreshicons"]) + update_shield_icons = 1 + + if(href_list["ejectjar"]) + if(fueljar) + fueljar.forceMove(drop_location()) + fueljar = null + //fueljar.control_unit = null currently it does not care where it is + //update_icon() when we have the icon for it + + if(href_list["strengthup"]) + fuel_injection++ + + if(href_list["strengthdown"]) + fuel_injection-- + if(fuel_injection < 0) + fuel_injection = 0 + + if(href_list["refreshstability"]) + check_core_stability() + + updateDialog() + return diff --git a/code/modules/power/antimatter/shielding.dm b/code/modules/power/antimatter/shielding.dm index 43b2400299..32950a2199 100644 --- a/code/modules/power/antimatter/shielding.dm +++ b/code/modules/power/antimatter/shielding.dm @@ -1,253 +1,253 @@ -//like orange but only checks north/south/east/west for one step -/proc/cardinalrange(var/center) - var/list/things = list() - for(var/direction in GLOB.cardinals) - var/turf/T = get_step(center, direction) - if(!T) - continue - things += T.contents - return things - -/obj/machinery/am_shielding - name = "antimatter reactor section" - desc = "This device was built using a plasma life-form that seems to increase plasma's natural ability to react with neutrinos while reducing the combustibility." - - icon = 'icons/obj/machines/antimatter.dmi' - icon_state = "shield" - density = TRUE - dir = NORTH - use_power = NO_POWER_USE//Living things generally dont use power - idle_power_usage = 0 - active_power_usage = 0 - - var/obj/machinery/power/am_control_unit/control_unit = null - var/processing = FALSE//To track if we are in the update list or not, we need to be when we are damaged and if we ever - var/stability = 100//If this gets low bad things tend to happen - var/efficiency = 1//How many cores this core counts for when doing power processing, plasma in the air and stability could affect this - var/coredirs = 0 - var/dirs = 0 - - -/obj/machinery/am_shielding/Initialize() - . = ..() - addtimer(CALLBACK(src, .proc/controllerscan), 10) - -/obj/machinery/am_shielding/proc/overheat() - visible_message("[src] melts!") - new /obj/effect/hotspot(loc) - qdel(src) - -/obj/machinery/am_shielding/proc/collapse() - visible_message("[src] collapses back into a container!") - new /obj/item/am_shielding_container(drop_location()) - qdel(src) - -/obj/machinery/am_shielding/proc/controllerscan(priorscan = 0) - //Make sure we are the only one here - if(!isturf(loc)) - collapse() - for(var/obj/machinery/am_shielding/AMS in loc.contents) - if(AMS == src) - continue - collapse() - return - - //Search for shielding first - for(var/obj/machinery/am_shielding/AMS in cardinalrange(src)) - if(AMS && AMS.control_unit && link_control(AMS.control_unit)) - break - - if(!control_unit)//No other guys nearby look for a control unit - for(var/direction in GLOB.cardinals) - for(var/obj/machinery/power/am_control_unit/AMC in cardinalrange(src)) - if(AMC.add_shielding(src)) - break - - if(!control_unit) - if(!priorscan) - addtimer(CALLBACK(src, .proc/controllerscan, 1), 20) - return - collapse() - - -/obj/machinery/am_shielding/Destroy() - if(control_unit) - control_unit.remove_shielding(src) - if(processing) - shutdown_core() - //Might want to have it leave a mess on the floor but no sprites for now - return ..() - - -/obj/machinery/am_shielding/CanPass(atom/movable/mover, turf/target) - return 0 - - -/obj/machinery/am_shielding/process() - if(!processing) - . = PROCESS_KILL - //TODO: core functions and stability - //TODO: think about checking the airmix for plasma and increasing power output - return - - -/obj/machinery/am_shielding/emp_act()//Immune due to not really much in the way of electronics. - return - -/obj/machinery/am_shielding/ex_act(severity, target) - stability -= (80 - (severity * 20)) - check_stability() - return - - -/obj/machinery/am_shielding/bullet_act(obj/item/projectile/Proj) - . = ..() - if(Proj.flag != "bullet") - stability -= Proj.force/2 - check_stability() - - -/obj/machinery/am_shielding/update_icon() - dirs = 0 - coredirs = 0 - cut_overlays() - for(var/direction in GLOB.alldirs) - var/turf/T = get_step(loc, direction) - for(var/obj/machinery/machine in T) - if(istype(machine, /obj/machinery/am_shielding)) - var/obj/machinery/am_shielding/shield = machine - if(shield.control_unit == control_unit) - if(shield.processing) - coredirs |= direction - if(direction in GLOB.cardinals) - dirs |= direction - - else - if(istype(machine, /obj/machinery/power/am_control_unit) && (direction in GLOB.cardinals)) - var/obj/machinery/power/am_control_unit/control = machine - if(control == control_unit) - dirs |= direction - - - var/prefix = "" - var/icondirs=dirs - - if(coredirs) - prefix="core" - - icon_state = "[prefix]shield_[icondirs]" - - if(core_check()) - add_overlay("core[control_unit && control_unit.active]") - if(!processing) - setup_core() - else if(processing) - shutdown_core() - - -/obj/machinery/am_shielding/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) - switch(damage_type) - if(BRUTE) - if(sound_effect) - if(damage_amount) - playsound(loc, 'sound/weapons/smash.ogg', 50, 1) - else - playsound(loc, 'sound/weapons/tap.ogg', 50, 1) - if(BURN) - if(sound_effect) - playsound(src.loc, 'sound/items/welder.ogg', 100, 1) - else - return - if(damage_amount >= 10) - stability -= damage_amount/2 - check_stability() - - -//Call this to link a detected shilding unit to the controller -/obj/machinery/am_shielding/proc/link_control(obj/machinery/power/am_control_unit/AMC) - if(!istype(AMC)) - return 0 - if(control_unit && control_unit != AMC) - return 0//Already have one - control_unit = AMC - control_unit.add_shielding(src,1) - return 1 - - -//Scans cards for shields or the control unit and if all there it -/obj/machinery/am_shielding/proc/core_check() - for(var/direction in GLOB.alldirs) - var/found_am_device=0 - for(var/obj/machinery/machine in get_step(loc, direction)) - if(!machine) - continue//Need all for a core - if(istype(machine, /obj/machinery/am_shielding) || istype(machine, /obj/machinery/power/am_control_unit)) - found_am_device = 1 - break - if(!found_am_device) - return 0 - return 1 - - -/obj/machinery/am_shielding/proc/setup_core() - processing = TRUE - GLOB.machines |= src - START_PROCESSING(SSmachines, src) - if(!control_unit) - return - control_unit.linked_cores.Add(src) - control_unit.reported_core_efficiency += efficiency - return - - -/obj/machinery/am_shielding/proc/shutdown_core() - processing = FALSE - if(!control_unit) - return - control_unit.linked_cores.Remove(src) - control_unit.reported_core_efficiency -= efficiency - return - - -/obj/machinery/am_shielding/proc/check_stability(injecting_fuel = 0) - if(stability > 0) - return - if(injecting_fuel && control_unit) - control_unit.exploding = 1 - if(src) - overheat() - return - - -/obj/machinery/am_shielding/proc/recalc_efficiency(new_efficiency)//tbh still not 100% sure how I want to deal with efficiency so this is likely temp - if(!control_unit || !processing) - return - if(stability < 50) - new_efficiency /= 2 - control_unit.reported_core_efficiency += (new_efficiency - efficiency) - efficiency = new_efficiency - return - - - -/obj/item/am_shielding_container - name = "packaged antimatter reactor section" - desc = "A small storage unit containing an antimatter reactor section. To use place near an antimatter control unit or deployed antimatter reactor section and use a multitool to activate this package." - icon = 'icons/obj/machines/antimatter.dmi' - icon_state = "box" - item_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 - materials = list(MAT_METAL=100) - -/obj/item/am_shielding_container/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/multitool) && istype(src.loc, /turf)) - new/obj/machinery/am_shielding(src.loc) - qdel(src) - else - return ..() +//like orange but only checks north/south/east/west for one step +/proc/cardinalrange(var/center) + var/list/things = list() + for(var/direction in GLOB.cardinals) + var/turf/T = get_step(center, direction) + if(!T) + continue + things += T.contents + return things + +/obj/machinery/am_shielding + name = "antimatter reactor section" + desc = "This device was built using a plasma life-form that seems to increase plasma's natural ability to react with neutrinos while reducing the combustibility." + + icon = 'icons/obj/machines/antimatter.dmi' + icon_state = "shield" + density = TRUE + dir = NORTH + use_power = NO_POWER_USE//Living things generally dont use power + idle_power_usage = 0 + active_power_usage = 0 + + var/obj/machinery/power/am_control_unit/control_unit = null + var/processing = FALSE//To track if we are in the update list or not, we need to be when we are damaged and if we ever + var/stability = 100//If this gets low bad things tend to happen + var/efficiency = 1//How many cores this core counts for when doing power processing, plasma in the air and stability could affect this + var/coredirs = 0 + var/dirs = 0 + + +/obj/machinery/am_shielding/Initialize() + . = ..() + addtimer(CALLBACK(src, .proc/controllerscan), 10) + +/obj/machinery/am_shielding/proc/overheat() + visible_message("[src] melts!") + new /obj/effect/hotspot(loc) + qdel(src) + +/obj/machinery/am_shielding/proc/collapse() + visible_message("[src] collapses back into a container!") + new /obj/item/am_shielding_container(drop_location()) + qdel(src) + +/obj/machinery/am_shielding/proc/controllerscan(priorscan = 0) + //Make sure we are the only one here + if(!isturf(loc)) + collapse() + for(var/obj/machinery/am_shielding/AMS in loc.contents) + if(AMS == src) + continue + collapse() + return + + //Search for shielding first + for(var/obj/machinery/am_shielding/AMS in cardinalrange(src)) + if(AMS && AMS.control_unit && link_control(AMS.control_unit)) + break + + if(!control_unit)//No other guys nearby look for a control unit + for(var/direction in GLOB.cardinals) + for(var/obj/machinery/power/am_control_unit/AMC in cardinalrange(src)) + if(AMC.add_shielding(src)) + break + + if(!control_unit) + if(!priorscan) + addtimer(CALLBACK(src, .proc/controllerscan, 1), 20) + return + collapse() + + +/obj/machinery/am_shielding/Destroy() + if(control_unit) + control_unit.remove_shielding(src) + if(processing) + shutdown_core() + //Might want to have it leave a mess on the floor but no sprites for now + return ..() + + +/obj/machinery/am_shielding/CanPass(atom/movable/mover, turf/target) + return 0 + + +/obj/machinery/am_shielding/process() + if(!processing) + . = PROCESS_KILL + //TODO: core functions and stability + //TODO: think about checking the airmix for plasma and increasing power output + return + + +/obj/machinery/am_shielding/emp_act()//Immune due to not really much in the way of electronics. + return + +/obj/machinery/am_shielding/ex_act(severity, target) + stability -= (80 - (severity * 20)) + check_stability() + return + + +/obj/machinery/am_shielding/bullet_act(obj/item/projectile/Proj) + . = ..() + if(Proj.flag != "bullet") + stability -= Proj.force/2 + check_stability() + + +/obj/machinery/am_shielding/update_icon() + dirs = 0 + coredirs = 0 + cut_overlays() + for(var/direction in GLOB.alldirs) + var/turf/T = get_step(loc, direction) + for(var/obj/machinery/machine in T) + if(istype(machine, /obj/machinery/am_shielding)) + var/obj/machinery/am_shielding/shield = machine + if(shield.control_unit == control_unit) + if(shield.processing) + coredirs |= direction + if(direction in GLOB.cardinals) + dirs |= direction + + else + if(istype(machine, /obj/machinery/power/am_control_unit) && (direction in GLOB.cardinals)) + var/obj/machinery/power/am_control_unit/control = machine + if(control == control_unit) + dirs |= direction + + + var/prefix = "" + var/icondirs=dirs + + if(coredirs) + prefix="core" + + icon_state = "[prefix]shield_[icondirs]" + + if(core_check()) + add_overlay("core[control_unit && control_unit.active]") + if(!processing) + setup_core() + else if(processing) + shutdown_core() + + +/obj/machinery/am_shielding/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) + switch(damage_type) + if(BRUTE) + if(sound_effect) + if(damage_amount) + playsound(loc, 'sound/weapons/smash.ogg', 50, 1) + else + playsound(loc, 'sound/weapons/tap.ogg', 50, 1) + if(BURN) + if(sound_effect) + playsound(src.loc, 'sound/items/welder.ogg', 100, 1) + else + return + if(damage_amount >= 10) + stability -= damage_amount/2 + check_stability() + + +//Call this to link a detected shilding unit to the controller +/obj/machinery/am_shielding/proc/link_control(obj/machinery/power/am_control_unit/AMC) + if(!istype(AMC)) + return 0 + if(control_unit && control_unit != AMC) + return 0//Already have one + control_unit = AMC + control_unit.add_shielding(src,1) + return 1 + + +//Scans cards for shields or the control unit and if all there it +/obj/machinery/am_shielding/proc/core_check() + for(var/direction in GLOB.alldirs) + var/found_am_device=0 + for(var/obj/machinery/machine in get_step(loc, direction)) + if(!machine) + continue//Need all for a core + if(istype(machine, /obj/machinery/am_shielding) || istype(machine, /obj/machinery/power/am_control_unit)) + found_am_device = 1 + break + if(!found_am_device) + return 0 + return 1 + + +/obj/machinery/am_shielding/proc/setup_core() + processing = TRUE + GLOB.machines |= src + START_PROCESSING(SSmachines, src) + if(!control_unit) + return + control_unit.linked_cores.Add(src) + control_unit.reported_core_efficiency += efficiency + return + + +/obj/machinery/am_shielding/proc/shutdown_core() + processing = FALSE + if(!control_unit) + return + control_unit.linked_cores.Remove(src) + control_unit.reported_core_efficiency -= efficiency + return + + +/obj/machinery/am_shielding/proc/check_stability(injecting_fuel = 0) + if(stability > 0) + return + if(injecting_fuel && control_unit) + control_unit.exploding = 1 + if(src) + overheat() + return + + +/obj/machinery/am_shielding/proc/recalc_efficiency(new_efficiency)//tbh still not 100% sure how I want to deal with efficiency so this is likely temp + if(!control_unit || !processing) + return + if(stability < 50) + new_efficiency /= 2 + control_unit.reported_core_efficiency += (new_efficiency - efficiency) + efficiency = new_efficiency + return + + + +/obj/item/am_shielding_container + name = "packaged antimatter reactor section" + desc = "A small storage unit containing an antimatter reactor section. To use place near an antimatter control unit or deployed antimatter reactor section and use a multitool to activate this package." + icon = 'icons/obj/machines/antimatter.dmi' + icon_state = "box" + item_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 + materials = list(MAT_METAL=100) + +/obj/item/am_shielding_container/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/multitool) && istype(src.loc, /turf)) + new/obj/machinery/am_shielding(src.loc) + qdel(src) + else + return ..() diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index fde2ce4178..b1c2d225ee 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -1,851 +1,851 @@ -GLOBAL_LIST_INIT(cable_colors, list( - "yellow" = "#ffff00", - "green" = "#00aa00", - "blue" = "#1919c8", - "pink" = "#ff3cc8", - "orange" = "#ff8000", - "cyan" = "#00ffff", - "white" = "#ffffff", - "red" = "#ff0000" - )) - -/////////////////////////////// -//CABLE STRUCTURE -/////////////////////////////// - - -//////////////////////////////// -// Definitions -//////////////////////////////// - -/* Cable directions (d1 and d2) - - - 9 1 5 - \ | / - 8 - 0 - 4 - / | \ - 10 2 6 - -If d1 = 0 and d2 = 0, there's no cable -If d1 = 0 and d2 = dir, it's a O-X cable, getting from the center of the tile to dir (knot cable) -If d1 = dir1 and d2 = dir2, it's a full X-X cable, getting from dir1 to dir2 -By design, d1 is the smallest direction and d2 is the highest -*/ - -/obj/structure/cable - name = "power cable" - desc = "A flexible, superconducting insulated cable for heavy-duty power transfer." - icon = 'icons/obj/power_cond/cables.dmi' - icon_state = "0-1" - level = 1 //is underfloor - layer = WIRE_LAYER //Above hidden pipes, GAS_PIPE_HIDDEN_LAYER - anchored = TRUE - obj_flags = CAN_BE_HIT | ON_BLUEPRINTS - var/d1 = 0 // cable direction 1 (see above) - var/d2 = 1 // cable direction 2 (see above) - var/datum/powernet/powernet - var/obj/item/stack/cable_coil/stored - - var/cable_color = "red" - color = "#ff0000" - -/obj/structure/cable/yellow - cable_color = "yellow" - color = "#ffff00" - -/obj/structure/cable/green - cable_color = "green" - color = "#00aa00" - -/obj/structure/cable/blue - cable_color = "blue" - color = "#1919c8" - -/obj/structure/cable/pink - cable_color = "pink" - color = "#ff3cc8" - -/obj/structure/cable/orange - cable_color = "orange" - color = "#ff8000" - -/obj/structure/cable/cyan - cable_color = "cyan" - color = "#00ffff" - -/obj/structure/cable/white - cable_color = "white" - color = "#ffffff" - -// the power cable object -/obj/structure/cable/Initialize(mapload, param_color) - . = ..() - - // ensure d1 & d2 reflect the icon_state for entering and exiting cable - var/dash = findtext(icon_state, "-") - d1 = text2num( copytext( icon_state, 1, dash ) ) - d2 = text2num( copytext( icon_state, dash+1 ) ) - - var/turf/T = get_turf(src) // hide if turf is not intact - if(level==1) - hide(T.intact) - GLOB.cable_list += src //add it to the global cable list - - if(d1) - stored = new/obj/item/stack/cable_coil(null,2,cable_color) - else - stored = new/obj/item/stack/cable_coil(null,1,cable_color) - - var/list/cable_colors = GLOB.cable_colors - cable_color = param_color || cable_color || pick(cable_colors) - if(cable_colors[cable_color]) - cable_color = cable_colors[cable_color] - update_icon() - -/obj/structure/cable/Destroy() // called when a cable is deleted - if(powernet) - cut_cable_from_powernet() // update the powernets - GLOB.cable_list -= src //remove it from global cable list - return ..() // then go ahead and delete the cable - -/obj/structure/cable/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - var/turf/T = loc - stored.forceMove(T) - qdel(src) - -/////////////////////////////////// -// General procedures -/////////////////////////////////// - -//If underfloor, hide the cable -/obj/structure/cable/hide(i) - - if(level == 1 && isturf(loc)) - invisibility = i ? INVISIBILITY_MAXIMUM : 0 - update_icon() - -/obj/structure/cable/update_icon() - icon_state = "[d1]-[d2]" - color = null - add_atom_colour(cable_color, FIXED_COLOUR_PRIORITY) - -/obj/structure/cable/proc/handlecable(obj/item/W, mob/user, params) - var/turf/T = get_turf(src) - if(T.intact) - return - if(istype(W, /obj/item/wirecutters)) - if (shock(user, 50)) - return - user.visible_message("[user] cuts the cable.", "You cut the cable.") - stored.add_fingerprint(user) - investigate_log("was cut by [key_name(usr)] in [AREACOORD(src)]", INVESTIGATE_WIRES) - deconstruct() - return - - else if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/coil = W - if (coil.get_amount() < 1) - to_chat(user, "Not enough cable!") - return - coil.cable_join(src, user) - - else if(istype(W, /obj/item/twohanded/rcl)) - var/obj/item/twohanded/rcl/R = W - if(R.loaded) - R.loaded.cable_join(src, user) - R.is_empty(user) - - else if(istype(W, /obj/item/multitool)) - if(powernet && (powernet.avail > 0)) // is it powered? - to_chat(user, "[DisplayPower(powernet.avail)] in power network.") - else - to_chat(user, "The cable is not powered.") - shock(user, 5, 0.2) - - src.add_fingerprint(user) - -// Items usable on a cable : -// - Wirecutters : cut it duh ! -// - Cable coil : merge cables -// - Multitool : get the power currently passing through the cable -// -/obj/structure/cable/attackby(obj/item/W, mob/user, params) - handlecable(W, user, params) - - -// shock the user with probability prb -/obj/structure/cable/proc/shock(mob/user, prb, siemens_coeff = 1) - if(!prob(prb)) - return 0 - if (electrocute_mob(user, powernet, src, siemens_coeff)) - do_sparks(5, TRUE, src) - return 1 - else - return 0 - -/obj/structure/cable/singularity_pull(S, current_size) - ..() - if(current_size >= STAGE_FIVE) - deconstruct() - -/obj/structure/cable/proc/update_stored(length = 1, colorC = "red") - stored.amount = length - stored.item_color = colorC - stored.update_icon() - -//////////////////////////////////////////// -// Power related -/////////////////////////////////////////// - -// All power generation handled in add_avail() -// Machines should use add_load(), surplus(), avail() -// Non-machines should use add_delayedload(), delayed_surplus(), newavail() - -/obj/structure/cable/proc/add_avail(amount) - if(powernet) - powernet.newavail += amount - -/obj/structure/cable/proc/add_load(amount) - if(powernet) - powernet.load += amount - -/obj/structure/cable/proc/surplus() - if(powernet) - return CLAMP(powernet.avail-powernet.load, 0, powernet.avail) - else - return 0 - -/obj/structure/cable/proc/avail(amount) - if(powernet) - return amount ? powernet.avail >= amount : powernet.avail - else - return 0 - -/obj/structure/cable/proc/add_delayedload(amount) - if(powernet) - powernet.delayedload += amount - -/obj/structure/cable/proc/delayed_surplus() - if(powernet) - return CLAMP(powernet.newavail - powernet.delayedload, 0, powernet.newavail) - else - return 0 - -/obj/structure/cable/proc/newavail() - if(powernet) - return powernet.newavail - else - return 0 - -///////////////////////////////////////////////// -// Cable laying helpers -//////////////////////////////////////////////// - -//handles merging diagonally matching cables -//for info : direction^3 is flipping horizontally, direction^12 is flipping vertically -/obj/structure/cable/proc/mergeDiagonalsNetworks(direction) - - //search for and merge diagonally matching cables from the first direction component (north/south) - var/turf/T = get_step(src, direction&3)//go north/south - - for(var/obj/structure/cable/C in T) - - if(!C) - continue - - if(src == C) - continue - - if(C.d1 == (direction^3) || C.d2 == (direction^3)) //we've got a diagonally matching cable - if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) - var/datum/powernet/newPN = new() - newPN.add_cable(C) - - if(powernet) //if we already have a powernet, then merge the two powernets - merge_powernets(powernet,C.powernet) - else - C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet - - //the same from the second direction component (east/west) - T = get_step(src, direction&12)//go east/west - - for(var/obj/structure/cable/C in T) - - if(!C) - continue - - if(src == C) - continue - if(C.d1 == (direction^12) || C.d2 == (direction^12)) //we've got a diagonally matching cable - if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) - var/datum/powernet/newPN = new() - newPN.add_cable(C) - - if(powernet) //if we already have a powernet, then merge the two powernets - merge_powernets(powernet,C.powernet) - else - C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet - -// merge with the powernets of power objects in the given direction -/obj/structure/cable/proc/mergeConnectedNetworks(direction) - - var/fdir = (!direction)? 0 : turn(direction, 180) //flip the direction, to match with the source position on its turf - - if(!(d1 == direction || d2 == direction)) //if the cable is not pointed in this direction, do nothing - return - - var/turf/TB = get_step(src, direction) - - for(var/obj/structure/cable/C in TB) - - if(!C) - continue - - if(src == C) - continue - - if(C.d1 == fdir || C.d2 == fdir) //we've got a matching cable in the neighbor turf - if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) - var/datum/powernet/newPN = new() - newPN.add_cable(C) - - if(powernet) //if we already have a powernet, then merge the two powernets - merge_powernets(powernet,C.powernet) - else - C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet - -// merge with the powernets of power objects in the source turf -/obj/structure/cable/proc/mergeConnectedNetworksOnTurf() - var/list/to_connect = list() - - if(!powernet) //if we somehow have no powernet, make one (should not happen for cables) - var/datum/powernet/newPN = new() - newPN.add_cable(src) - - //first let's add turf cables to our powernet - //then we'll connect machines on turf with a node cable is present - for(var/AM in loc) - if(istype(AM, /obj/structure/cable)) - var/obj/structure/cable/C = AM - if(C.d1 == d1 || C.d2 == d1 || C.d1 == d2 || C.d2 == d2) //only connected if they have a common direction - if(C.powernet == powernet) - continue - if(C.powernet) - merge_powernets(powernet, C.powernet) - else - powernet.add_cable(C) //the cable was powernetless, let's just add it to our powernet - - else if(istype(AM, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/N = AM - if(!N.terminal) - continue // APC are connected through their terminal - - if(N.terminal.powernet == powernet) - continue - - to_connect += N.terminal //we'll connect the machines after all cables are merged - - else if(istype(AM, /obj/machinery/power)) //other power machines - var/obj/machinery/power/M = AM - - if(M.powernet == powernet) - continue - - to_connect += M //we'll connect the machines after all cables are merged - - //now that cables are done, let's connect found machines - for(var/obj/machinery/power/PM in to_connect) - if(!PM.connect_to_network()) - PM.disconnect_from_network() //if we somehow can't connect the machine to the new powernet, remove it from the old nonetheless - -////////////////////////////////////////////// -// Powernets handling helpers -////////////////////////////////////////////// - -//if powernetless_only = 1, will only get connections without powernet -/obj/structure/cable/proc/get_connections(powernetless_only = 0) - . = list() // this will be a list of all connected power objects - var/turf/T - - //get matching cables from the first direction - if(d1) //if not a node cable - T = get_step(src, d1) - if(T) - . += power_list(T, src, turn(d1, 180), powernetless_only) //get adjacents matching cables - - if(d1&(d1-1)) //diagonal direction, must check the 4 possibles adjacents tiles - T = get_step(src,d1&3) // go north/south - if(T) - . += power_list(T, src, d1 ^ 3, powernetless_only) //get diagonally matching cables - T = get_step(src,d1&12) // go east/west - if(T) - . += power_list(T, src, d1 ^ 12, powernetless_only) //get diagonally matching cables - - . += power_list(loc, src, d1, powernetless_only) //get on turf matching cables - - //do the same on the second direction (which can't be 0) - T = get_step(src, d2) - if(T) - . += power_list(T, src, turn(d2, 180), powernetless_only) //get adjacents matching cables - - if(d2&(d2-1)) //diagonal direction, must check the 4 possibles adjacents tiles - T = get_step(src,d2&3) // go north/south - if(T) - . += power_list(T, src, d2 ^ 3, powernetless_only) //get diagonally matching cables - T = get_step(src,d2&12) // go east/west - if(T) - . += power_list(T, src, d2 ^ 12, powernetless_only) //get diagonally matching cables - . += power_list(loc, src, d2, powernetless_only) //get on turf matching cables - - return . - -//should be called after placing a cable which extends another cable, creating a "smooth" cable that no longer terminates in the centre of a turf. -//needed as this can, unlike other placements, disconnect cables -/obj/structure/cable/proc/denode() - var/turf/T1 = loc - if(!T1) - return - - var/list/powerlist = power_list(T1,src,0,0) //find the other cables that ended in the centre of the turf, with or without a powernet - if(powerlist.len>0) - var/datum/powernet/PN = new() - propagate_network(powerlist[1],PN) //propagates the new powernet beginning at the source cable - - if(PN.is_empty()) //can happen with machines made nodeless when smoothing cables - qdel(PN) - -/obj/structure/cable/proc/auto_propogate_cut_cable(obj/O) - if(O && !QDELETED(O)) - var/datum/powernet/newPN = new()// creates a new powernet... - propagate_network(O, newPN)//... and propagates it to the other side of the cable - -// cut the cable's powernet at this cable and updates the powergrid -/obj/structure/cable/proc/cut_cable_from_powernet(remove=TRUE) - var/turf/T1 = loc - var/list/P_list - if(!T1) - return - if(d1) - T1 = get_step(T1, d1) - P_list = power_list(T1, src, turn(d1,180),0,cable_only = 1) // what adjacently joins on to cut cable... - - P_list += power_list(loc, src, d1, 0, cable_only = 1)//... and on turf - - - if(P_list.len == 0)//if nothing in both list, then the cable was a lone cable, just delete it and its powernet - powernet.remove_cable(src) - - for(var/obj/machinery/power/P in T1)//check if it was powering a machine - if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to - P.disconnect_from_network() //remove from current network (and delete powernet) - return - - var/obj/O = P_list[1] - // remove the cut cable from its turf and powernet, so that it doesn't get count in propagate_network worklist - if(remove) - moveToNullspace() - powernet.remove_cable(src) //remove the cut cable from its powernet - - addtimer(CALLBACK(O, .proc/auto_propogate_cut_cable, O), 0) //so we don't rebuild the network X times when singulo/explosion destroys a line of X cables - - // Disconnect machines connected to nodes - if(d1 == 0) // if we cut a node (O-X) cable - for(var/obj/machinery/power/P in T1) - if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to - P.disconnect_from_network() //remove from current network - - -/////////////////////////////////////////////// -// The cable coil object, used for laying cable -/////////////////////////////////////////////// - -//////////////////////////////// -// Definitions -//////////////////////////////// - -GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe("cable restraints", /obj/item/restraints/handcuffs/cable, 15))) - -/obj/item/stack/cable_coil - name = "cable coil" - gender = NEUTER //That's a cable coil sounds better than that's some cable coils - icon = 'icons/obj/power.dmi' - icon_state = "coil" - item_state = "coil" - lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' - max_amount = MAXCOIL - amount = MAXCOIL - merge_type = /obj/item/stack/cable_coil // This is here to let its children merge between themselves - item_color = "red" - desc = "A coil of insulated power cable." - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 5 - materials = list(MAT_METAL=10, MAT_GLASS=5) - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - attack_verb = list("whipped", "lashed", "disciplined", "flogged") - singular_name = "cable piece" - full_w_class = WEIGHT_CLASS_SMALL - grind_results = list(/datum/reagent/copper = 2) //2 copper per cable in the coil - usesound = 'sound/items/deconstruct.ogg' - -/obj/item/stack/cable_coil/cyborg - is_cyborg = 1 - materials = list() - cost = 1 - -/obj/item/stack/cable_coil/cyborg/attack_self(mob/user) - var/cable_color = input(user,"Pick a cable color.","Cable Color") in list("red","yellow","green","blue","pink","orange","cyan","white") - item_color = cable_color - update_icon() - -/obj/item/stack/cable_coil/suicide_act(mob/user) - if(locate(/obj/structure/chair/stool) in get_turf(user)) - user.visible_message("[user] is making a noose with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - else - 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/stack/cable_coil/Initialize(mapload, new_amount = null, param_color = null) - . = ..() - - var/list/cable_colors = GLOB.cable_colors - item_color = param_color || item_color || pick(cable_colors) - if(cable_colors[item_color]) - item_color = cable_colors[item_color] - - pixel_x = rand(-2,2) - pixel_y = rand(-2,2) - update_icon() - recipes = GLOB.cable_coil_recipes - -/////////////////////////////////// -// General procedures -/////////////////////////////////// - - -//you can use wires to heal robotics -/obj/item/stack/cable_coil/attack(mob/living/carbon/human/H, mob/user) - if(!istype(H)) - return ..() - - var/obj/item/bodypart/affecting = H.get_bodypart(check_zone(user.zone_selected)) - if(affecting && affecting.status == BODYPART_ROBOTIC) - if(user == H) - user.visible_message("[user] starts to fix some of the wires in [H]'s [affecting.name].", "You start fixing some of the wires in [H]'s [affecting.name].") - if(!do_mob(user, H, 50)) - return - if(item_heal_robotic(H, user, 0, 15)) - use(1) - return - else - return ..() - - -/obj/item/stack/cable_coil/update_icon() - icon_state = "[initial(item_state)][amount < 3 ? amount : ""]" - name = "cable [amount < 3 ? "piece" : "coil"]" - color = null - add_atom_colour(item_color, FIXED_COLOUR_PRIORITY) - -/obj/item/stack/cable_coil/attack_hand(mob/user) - . = ..() - if(.) - return - var/obj/item/stack/cable_coil/new_cable = ..() - if(istype(new_cable)) - new_cable.item_color = item_color - new_cable.update_icon() - -//add cables to the stack -/obj/item/stack/cable_coil/proc/give(extra) - if(amount + extra > max_amount) - amount = max_amount - else - amount += extra - update_icon() - - - -/////////////////////////////////////////////// -// Cable laying procedures -////////////////////////////////////////////// - -/obj/item/stack/cable_coil/proc/get_new_cable(location) - var/path = /obj/structure/cable - return new path(location, item_color) - -// called when cable_coil is clicked on a turf -/obj/item/stack/cable_coil/proc/place_turf(turf/T, mob/user, dirnew) - if(!isturf(user.loc)) - return - - if(!isturf(T) || T.intact || !T.can_have_cabling()) - to_chat(user, "You can only lay cables on catwalks and plating!") - return - - if(get_amount() < 1) // Out of cable - to_chat(user, "There is no cable left!") - return - - if(get_dist(T,user) > 1) // Too far - to_chat(user, "You can't lay cable at a place that far away!") - return - - var/dirn - if(!dirnew) //If we weren't given a direction, come up with one! (Called as null from catwalk.dm and floor.dm) - if(user.loc == T) - dirn = user.dir //If laying on the tile we're on, lay in the direction we're facing - else - dirn = get_dir(T, user) - else - dirn = dirnew - - for(var/obj/structure/cable/LC in T) - if(LC.d2 == dirn && LC.d1 == 0) - to_chat(user, "There's already a cable at that position!") - return - - var/obj/structure/cable/C = get_new_cable(T) - - //set up the new cable - C.d1 = 0 //it's a O-X node cable - C.d2 = dirn - C.add_fingerprint(user) - C.update_icon() - - //create a new powernet with the cable, if needed it will be merged later - var/datum/powernet/PN = new() - PN.add_cable(C) - - C.mergeConnectedNetworks(C.d2) //merge the powernet with adjacents powernets - C.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets - - if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions - C.mergeDiagonalsNetworks(C.d2) - - use(1) - - if(C.shock(user, 50)) - if(prob(50)) //fail - new /obj/item/stack/cable_coil(get_turf(C), 1, C.color) - C.deconstruct() - - return C - -// called when cable_coil is click on an installed obj/cable -// or click on a turf that already contains a "node" cable -/obj/item/stack/cable_coil/proc/cable_join(obj/structure/cable/C, mob/user, showerror = TRUE, forceddir) - var/turf/U = user.loc - if(!isturf(U)) - return - - var/turf/T = C.loc - - if(!isturf(T) || T.intact) // sanity checks, also stop use interacting with T-scanner revealed cable - return - - if(get_dist(C, user) > 1) // make sure it's close enough - to_chat(user, "You can't lay cable at a place that far away!") - return - - - if(U == T && !forceddir) //if clicked on the turf we're standing on and a direction wasn't supplied, try to put a cable in the direction we're facing - place_turf(T,user) - return - - var/dirn = get_dir(C, user) - - // one end of the clicked cable is pointing towards us and no direction was supplied - if((C.d1 == dirn || C.d2 == dirn) && !forceddir) - if(!U.can_have_cabling()) //checking if it's a plating or catwalk - if (showerror) - to_chat(user, "You can only lay cables on catwalks and plating!") - return - if(U.intact) //can't place a cable if it's a plating with a tile on it - to_chat(user, "You can't lay cable there unless the floor tiles are removed!") - return - else - // cable is pointing at us, we're standing on an open tile - // so create a stub pointing at the clicked cable on our tile - - var/fdirn = turn(dirn, 180) // the opposite direction - - for(var/obj/structure/cable/LC in U) // check to make sure there's not a cable there already - if(LC.d1 == fdirn || LC.d2 == fdirn) - if (showerror) - to_chat(user, "There's already a cable at that position!") - return - - var/obj/structure/cable/NC = get_new_cable (U) - - NC.d1 = 0 - NC.d2 = fdirn - NC.add_fingerprint(user) - NC.update_icon() - - //create a new powernet with the cable, if needed it will be merged later - var/datum/powernet/newPN = new() - newPN.add_cable(NC) - - NC.mergeConnectedNetworks(NC.d2) //merge the powernet with adjacents powernets - NC.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets - - if(NC.d2 & (NC.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions - NC.mergeDiagonalsNetworks(NC.d2) - - use(1) - - if (NC.shock(user, 50)) - if (prob(50)) //fail - NC.deconstruct() - - return - - // exisiting cable doesn't point at our position or we have a supplied direction, so see if it's a stub - else if(C.d1 == 0) - // if so, make it a full cable pointing from it's old direction to our dirn - var/nd1 = C.d2 // these will be the new directions - var/nd2 = dirn - - - if(nd1 > nd2) // swap directions to match icons/states - nd1 = dirn - nd2 = C.d2 - - - for(var/obj/structure/cable/LC in T) // check to make sure there's no matching cable - if(LC == C) // skip the cable we're interacting with - continue - if((LC.d1 == nd1 && LC.d2 == nd2) || (LC.d1 == nd2 && LC.d2 == nd1) ) // make sure no cable matches either direction - if (showerror) - to_chat(user, "There's already a cable at that position!") - - return - - - C.update_icon() - - C.d1 = nd1 - C.d2 = nd2 - - //updates the stored cable coil - C.update_stored(2, item_color) - - C.add_fingerprint(user) - C.update_icon() - - - C.mergeConnectedNetworks(C.d1) //merge the powernets... - C.mergeConnectedNetworks(C.d2) //...in the two new cable directions - C.mergeConnectedNetworksOnTurf() - - if(C.d1 & (C.d1 - 1))// if the cable is layed diagonally, check the others 2 possible directions - C.mergeDiagonalsNetworks(C.d1) - - if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions - C.mergeDiagonalsNetworks(C.d2) - - use(1) - - if (C.shock(user, 50)) - if (prob(50)) //fail - C.deconstruct() - return - - C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets. - return - -////////////////////////////// -// Misc. -///////////////////////////// - -/obj/item/stack/cable_coil/red - item_color = "red" - color = "#ff0000" - -/obj/item/stack/cable_coil/yellow - item_color = "yellow" - color = "#ffff00" - -/obj/item/stack/cable_coil/blue - item_color = "blue" - color = "#1919c8" - -/obj/item/stack/cable_coil/green - item_color = "green" - color = "#00aa00" - -/obj/item/stack/cable_coil/pink - item_color = "pink" - color = "#ff3ccd" - -/obj/item/stack/cable_coil/orange - item_color = "orange" - color = "#ff8000" - -/obj/item/stack/cable_coil/cyan - item_color = "cyan" - color = "#00ffff" - -/obj/item/stack/cable_coil/white - item_color = "white" - -/obj/item/stack/cable_coil/random - item_color = null - color = "#ffffff" - - -/obj/item/stack/cable_coil/random/five - amount = 5 - -/obj/item/stack/cable_coil/cut - amount = null - icon_state = "coil2" - -/obj/item/stack/cable_coil/cut/Initialize(mapload) - . = ..() - if(!amount) - amount = rand(1,2) - pixel_x = rand(-2,2) - pixel_y = rand(-2,2) - update_icon() - -/obj/item/stack/cable_coil/cut/red - item_color = "red" - color = "#ff0000" - -/obj/item/stack/cable_coil/cut/yellow - item_color = "yellow" - color = "#ffff00" - -/obj/item/stack/cable_coil/cut/blue - item_color = "blue" - color = "#1919c8" - -/obj/item/stack/cable_coil/cut/green - item_color = "green" - color = "#00aa00" - -/obj/item/stack/cable_coil/cut/pink - item_color = "pink" - color = "#ff3ccd" - -/obj/item/stack/cable_coil/cut/orange - item_color = "orange" - color = "#ff8000" - -/obj/item/stack/cable_coil/cut/cyan - item_color = "cyan" - color = "#00ffff" - -/obj/item/stack/cable_coil/cut/white - item_color = "white" - -/obj/item/stack/cable_coil/cut/random - item_color = null +GLOBAL_LIST_INIT(cable_colors, list( + "yellow" = "#ffff00", + "green" = "#00aa00", + "blue" = "#1919c8", + "pink" = "#ff3cc8", + "orange" = "#ff8000", + "cyan" = "#00ffff", + "white" = "#ffffff", + "red" = "#ff0000" + )) + +/////////////////////////////// +//CABLE STRUCTURE +/////////////////////////////// + + +//////////////////////////////// +// Definitions +//////////////////////////////// + +/* Cable directions (d1 and d2) + + + 9 1 5 + \ | / + 8 - 0 - 4 + / | \ + 10 2 6 + +If d1 = 0 and d2 = 0, there's no cable +If d1 = 0 and d2 = dir, it's a O-X cable, getting from the center of the tile to dir (knot cable) +If d1 = dir1 and d2 = dir2, it's a full X-X cable, getting from dir1 to dir2 +By design, d1 is the smallest direction and d2 is the highest +*/ + +/obj/structure/cable + name = "power cable" + desc = "A flexible, superconducting insulated cable for heavy-duty power transfer." + icon = 'icons/obj/power_cond/cables.dmi' + icon_state = "0-1" + level = 1 //is underfloor + layer = WIRE_LAYER //Above hidden pipes, GAS_PIPE_HIDDEN_LAYER + anchored = TRUE + obj_flags = CAN_BE_HIT | ON_BLUEPRINTS + var/d1 = 0 // cable direction 1 (see above) + var/d2 = 1 // cable direction 2 (see above) + var/datum/powernet/powernet + var/obj/item/stack/cable_coil/stored + + var/cable_color = "red" + color = "#ff0000" + +/obj/structure/cable/yellow + cable_color = "yellow" + color = "#ffff00" + +/obj/structure/cable/green + cable_color = "green" + color = "#00aa00" + +/obj/structure/cable/blue + cable_color = "blue" + color = "#1919c8" + +/obj/structure/cable/pink + cable_color = "pink" + color = "#ff3cc8" + +/obj/structure/cable/orange + cable_color = "orange" + color = "#ff8000" + +/obj/structure/cable/cyan + cable_color = "cyan" + color = "#00ffff" + +/obj/structure/cable/white + cable_color = "white" + color = "#ffffff" + +// the power cable object +/obj/structure/cable/Initialize(mapload, param_color) + . = ..() + + // ensure d1 & d2 reflect the icon_state for entering and exiting cable + var/dash = findtext(icon_state, "-") + d1 = text2num( copytext( icon_state, 1, dash ) ) + d2 = text2num( copytext( icon_state, dash+1 ) ) + + var/turf/T = get_turf(src) // hide if turf is not intact + if(level==1) + hide(T.intact) + GLOB.cable_list += src //add it to the global cable list + + if(d1) + stored = new/obj/item/stack/cable_coil(null,2,cable_color) + else + stored = new/obj/item/stack/cable_coil(null,1,cable_color) + + var/list/cable_colors = GLOB.cable_colors + cable_color = param_color || cable_color || pick(cable_colors) + if(cable_colors[cable_color]) + cable_color = cable_colors[cable_color] + update_icon() + +/obj/structure/cable/Destroy() // called when a cable is deleted + if(powernet) + cut_cable_from_powernet() // update the powernets + GLOB.cable_list -= src //remove it from global cable list + return ..() // then go ahead and delete the cable + +/obj/structure/cable/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + var/turf/T = loc + stored.forceMove(T) + qdel(src) + +/////////////////////////////////// +// General procedures +/////////////////////////////////// + +//If underfloor, hide the cable +/obj/structure/cable/hide(i) + + if(level == 1 && isturf(loc)) + invisibility = i ? INVISIBILITY_MAXIMUM : 0 + update_icon() + +/obj/structure/cable/update_icon() + icon_state = "[d1]-[d2]" + color = null + add_atom_colour(cable_color, FIXED_COLOUR_PRIORITY) + +/obj/structure/cable/proc/handlecable(obj/item/W, mob/user, params) + var/turf/T = get_turf(src) + if(T.intact) + return + if(istype(W, /obj/item/wirecutters)) + if (shock(user, 50)) + return + user.visible_message("[user] cuts the cable.", "You cut the cable.") + stored.add_fingerprint(user) + investigate_log("was cut by [key_name(usr)] in [AREACOORD(src)]", INVESTIGATE_WIRES) + deconstruct() + return + + else if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/coil = W + if (coil.get_amount() < 1) + to_chat(user, "Not enough cable!") + return + coil.cable_join(src, user) + + else if(istype(W, /obj/item/twohanded/rcl)) + var/obj/item/twohanded/rcl/R = W + if(R.loaded) + R.loaded.cable_join(src, user) + R.is_empty(user) + + else if(istype(W, /obj/item/multitool)) + if(powernet && (powernet.avail > 0)) // is it powered? + to_chat(user, "[DisplayPower(powernet.avail)] in power network.") + else + to_chat(user, "The cable is not powered.") + shock(user, 5, 0.2) + + src.add_fingerprint(user) + +// Items usable on a cable : +// - Wirecutters : cut it duh ! +// - Cable coil : merge cables +// - Multitool : get the power currently passing through the cable +// +/obj/structure/cable/attackby(obj/item/W, mob/user, params) + handlecable(W, user, params) + + +// shock the user with probability prb +/obj/structure/cable/proc/shock(mob/user, prb, siemens_coeff = 1) + if(!prob(prb)) + return 0 + if (electrocute_mob(user, powernet, src, siemens_coeff)) + do_sparks(5, TRUE, src) + return 1 + else + return 0 + +/obj/structure/cable/singularity_pull(S, current_size) + ..() + if(current_size >= STAGE_FIVE) + deconstruct() + +/obj/structure/cable/proc/update_stored(length = 1, colorC = "red") + stored.amount = length + stored.item_color = colorC + stored.update_icon() + +//////////////////////////////////////////// +// Power related +/////////////////////////////////////////// + +// All power generation handled in add_avail() +// Machines should use add_load(), surplus(), avail() +// Non-machines should use add_delayedload(), delayed_surplus(), newavail() + +/obj/structure/cable/proc/add_avail(amount) + if(powernet) + powernet.newavail += amount + +/obj/structure/cable/proc/add_load(amount) + if(powernet) + powernet.load += amount + +/obj/structure/cable/proc/surplus() + if(powernet) + return CLAMP(powernet.avail-powernet.load, 0, powernet.avail) + else + return 0 + +/obj/structure/cable/proc/avail(amount) + if(powernet) + return amount ? powernet.avail >= amount : powernet.avail + else + return 0 + +/obj/structure/cable/proc/add_delayedload(amount) + if(powernet) + powernet.delayedload += amount + +/obj/structure/cable/proc/delayed_surplus() + if(powernet) + return CLAMP(powernet.newavail - powernet.delayedload, 0, powernet.newavail) + else + return 0 + +/obj/structure/cable/proc/newavail() + if(powernet) + return powernet.newavail + else + return 0 + +///////////////////////////////////////////////// +// Cable laying helpers +//////////////////////////////////////////////// + +//handles merging diagonally matching cables +//for info : direction^3 is flipping horizontally, direction^12 is flipping vertically +/obj/structure/cable/proc/mergeDiagonalsNetworks(direction) + + //search for and merge diagonally matching cables from the first direction component (north/south) + var/turf/T = get_step(src, direction&3)//go north/south + + for(var/obj/structure/cable/C in T) + + if(!C) + continue + + if(src == C) + continue + + if(C.d1 == (direction^3) || C.d2 == (direction^3)) //we've got a diagonally matching cable + if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) + var/datum/powernet/newPN = new() + newPN.add_cable(C) + + if(powernet) //if we already have a powernet, then merge the two powernets + merge_powernets(powernet,C.powernet) + else + C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet + + //the same from the second direction component (east/west) + T = get_step(src, direction&12)//go east/west + + for(var/obj/structure/cable/C in T) + + if(!C) + continue + + if(src == C) + continue + if(C.d1 == (direction^12) || C.d2 == (direction^12)) //we've got a diagonally matching cable + if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) + var/datum/powernet/newPN = new() + newPN.add_cable(C) + + if(powernet) //if we already have a powernet, then merge the two powernets + merge_powernets(powernet,C.powernet) + else + C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet + +// merge with the powernets of power objects in the given direction +/obj/structure/cable/proc/mergeConnectedNetworks(direction) + + var/fdir = (!direction)? 0 : turn(direction, 180) //flip the direction, to match with the source position on its turf + + if(!(d1 == direction || d2 == direction)) //if the cable is not pointed in this direction, do nothing + return + + var/turf/TB = get_step(src, direction) + + for(var/obj/structure/cable/C in TB) + + if(!C) + continue + + if(src == C) + continue + + if(C.d1 == fdir || C.d2 == fdir) //we've got a matching cable in the neighbor turf + if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables) + var/datum/powernet/newPN = new() + newPN.add_cable(C) + + if(powernet) //if we already have a powernet, then merge the two powernets + merge_powernets(powernet,C.powernet) + else + C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet + +// merge with the powernets of power objects in the source turf +/obj/structure/cable/proc/mergeConnectedNetworksOnTurf() + var/list/to_connect = list() + + if(!powernet) //if we somehow have no powernet, make one (should not happen for cables) + var/datum/powernet/newPN = new() + newPN.add_cable(src) + + //first let's add turf cables to our powernet + //then we'll connect machines on turf with a node cable is present + for(var/AM in loc) + if(istype(AM, /obj/structure/cable)) + var/obj/structure/cable/C = AM + if(C.d1 == d1 || C.d2 == d1 || C.d1 == d2 || C.d2 == d2) //only connected if they have a common direction + if(C.powernet == powernet) + continue + if(C.powernet) + merge_powernets(powernet, C.powernet) + else + powernet.add_cable(C) //the cable was powernetless, let's just add it to our powernet + + else if(istype(AM, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/N = AM + if(!N.terminal) + continue // APC are connected through their terminal + + if(N.terminal.powernet == powernet) + continue + + to_connect += N.terminal //we'll connect the machines after all cables are merged + + else if(istype(AM, /obj/machinery/power)) //other power machines + var/obj/machinery/power/M = AM + + if(M.powernet == powernet) + continue + + to_connect += M //we'll connect the machines after all cables are merged + + //now that cables are done, let's connect found machines + for(var/obj/machinery/power/PM in to_connect) + if(!PM.connect_to_network()) + PM.disconnect_from_network() //if we somehow can't connect the machine to the new powernet, remove it from the old nonetheless + +////////////////////////////////////////////// +// Powernets handling helpers +////////////////////////////////////////////// + +//if powernetless_only = 1, will only get connections without powernet +/obj/structure/cable/proc/get_connections(powernetless_only = 0) + . = list() // this will be a list of all connected power objects + var/turf/T + + //get matching cables from the first direction + if(d1) //if not a node cable + T = get_step(src, d1) + if(T) + . += power_list(T, src, turn(d1, 180), powernetless_only) //get adjacents matching cables + + if(d1&(d1-1)) //diagonal direction, must check the 4 possibles adjacents tiles + T = get_step(src,d1&3) // go north/south + if(T) + . += power_list(T, src, d1 ^ 3, powernetless_only) //get diagonally matching cables + T = get_step(src,d1&12) // go east/west + if(T) + . += power_list(T, src, d1 ^ 12, powernetless_only) //get diagonally matching cables + + . += power_list(loc, src, d1, powernetless_only) //get on turf matching cables + + //do the same on the second direction (which can't be 0) + T = get_step(src, d2) + if(T) + . += power_list(T, src, turn(d2, 180), powernetless_only) //get adjacents matching cables + + if(d2&(d2-1)) //diagonal direction, must check the 4 possibles adjacents tiles + T = get_step(src,d2&3) // go north/south + if(T) + . += power_list(T, src, d2 ^ 3, powernetless_only) //get diagonally matching cables + T = get_step(src,d2&12) // go east/west + if(T) + . += power_list(T, src, d2 ^ 12, powernetless_only) //get diagonally matching cables + . += power_list(loc, src, d2, powernetless_only) //get on turf matching cables + + return . + +//should be called after placing a cable which extends another cable, creating a "smooth" cable that no longer terminates in the centre of a turf. +//needed as this can, unlike other placements, disconnect cables +/obj/structure/cable/proc/denode() + var/turf/T1 = loc + if(!T1) + return + + var/list/powerlist = power_list(T1,src,0,0) //find the other cables that ended in the centre of the turf, with or without a powernet + if(powerlist.len>0) + var/datum/powernet/PN = new() + propagate_network(powerlist[1],PN) //propagates the new powernet beginning at the source cable + + if(PN.is_empty()) //can happen with machines made nodeless when smoothing cables + qdel(PN) + +/obj/structure/cable/proc/auto_propogate_cut_cable(obj/O) + if(O && !QDELETED(O)) + var/datum/powernet/newPN = new()// creates a new powernet... + propagate_network(O, newPN)//... and propagates it to the other side of the cable + +// cut the cable's powernet at this cable and updates the powergrid +/obj/structure/cable/proc/cut_cable_from_powernet(remove=TRUE) + var/turf/T1 = loc + var/list/P_list + if(!T1) + return + if(d1) + T1 = get_step(T1, d1) + P_list = power_list(T1, src, turn(d1,180),0,cable_only = 1) // what adjacently joins on to cut cable... + + P_list += power_list(loc, src, d1, 0, cable_only = 1)//... and on turf + + + if(P_list.len == 0)//if nothing in both list, then the cable was a lone cable, just delete it and its powernet + powernet.remove_cable(src) + + for(var/obj/machinery/power/P in T1)//check if it was powering a machine + if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to + P.disconnect_from_network() //remove from current network (and delete powernet) + return + + var/obj/O = P_list[1] + // remove the cut cable from its turf and powernet, so that it doesn't get count in propagate_network worklist + if(remove) + moveToNullspace() + powernet.remove_cable(src) //remove the cut cable from its powernet + + addtimer(CALLBACK(O, .proc/auto_propogate_cut_cable, O), 0) //so we don't rebuild the network X times when singulo/explosion destroys a line of X cables + + // Disconnect machines connected to nodes + if(d1 == 0) // if we cut a node (O-X) cable + for(var/obj/machinery/power/P in T1) + if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to + P.disconnect_from_network() //remove from current network + + +/////////////////////////////////////////////// +// The cable coil object, used for laying cable +/////////////////////////////////////////////// + +//////////////////////////////// +// Definitions +//////////////////////////////// + +GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe("cable restraints", /obj/item/restraints/handcuffs/cable, 15))) + +/obj/item/stack/cable_coil + name = "cable coil" + gender = NEUTER //That's a cable coil sounds better than that's some cable coils + icon = 'icons/obj/power.dmi' + icon_state = "coil" + item_state = "coil" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + max_amount = MAXCOIL + amount = MAXCOIL + merge_type = /obj/item/stack/cable_coil // This is here to let its children merge between themselves + item_color = "red" + desc = "A coil of insulated power cable." + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 5 + materials = list(MAT_METAL=10, MAT_GLASS=5) + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + attack_verb = list("whipped", "lashed", "disciplined", "flogged") + singular_name = "cable piece" + full_w_class = WEIGHT_CLASS_SMALL + grind_results = list(/datum/reagent/copper = 2) //2 copper per cable in the coil + usesound = 'sound/items/deconstruct.ogg' + +/obj/item/stack/cable_coil/cyborg + is_cyborg = 1 + materials = list() + cost = 1 + +/obj/item/stack/cable_coil/cyborg/attack_self(mob/user) + var/cable_color = input(user,"Pick a cable color.","Cable Color") in list("red","yellow","green","blue","pink","orange","cyan","white") + item_color = cable_color + update_icon() + +/obj/item/stack/cable_coil/suicide_act(mob/user) + if(locate(/obj/structure/chair/stool) in get_turf(user)) + user.visible_message("[user] is making a noose with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + else + 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/stack/cable_coil/Initialize(mapload, new_amount = null, param_color = null) + . = ..() + + var/list/cable_colors = GLOB.cable_colors + item_color = param_color || item_color || pick(cable_colors) + if(cable_colors[item_color]) + item_color = cable_colors[item_color] + + pixel_x = rand(-2,2) + pixel_y = rand(-2,2) + update_icon() + recipes = GLOB.cable_coil_recipes + +/////////////////////////////////// +// General procedures +/////////////////////////////////// + + +//you can use wires to heal robotics +/obj/item/stack/cable_coil/attack(mob/living/carbon/human/H, mob/user) + if(!istype(H)) + return ..() + + var/obj/item/bodypart/affecting = H.get_bodypart(check_zone(user.zone_selected)) + if(affecting && affecting.status == BODYPART_ROBOTIC) + if(user == H) + user.visible_message("[user] starts to fix some of the wires in [H]'s [affecting.name].", "You start fixing some of the wires in [H]'s [affecting.name].") + if(!do_mob(user, H, 50)) + return + if(item_heal_robotic(H, user, 0, 15)) + use(1) + return + else + return ..() + + +/obj/item/stack/cable_coil/update_icon() + icon_state = "[initial(item_state)][amount < 3 ? amount : ""]" + name = "cable [amount < 3 ? "piece" : "coil"]" + color = null + add_atom_colour(item_color, FIXED_COLOUR_PRIORITY) + +/obj/item/stack/cable_coil/attack_hand(mob/user) + . = ..() + if(.) + return + var/obj/item/stack/cable_coil/new_cable = ..() + if(istype(new_cable)) + new_cable.item_color = item_color + new_cable.update_icon() + +//add cables to the stack +/obj/item/stack/cable_coil/proc/give(extra) + if(amount + extra > max_amount) + amount = max_amount + else + amount += extra + update_icon() + + + +/////////////////////////////////////////////// +// Cable laying procedures +////////////////////////////////////////////// + +/obj/item/stack/cable_coil/proc/get_new_cable(location) + var/path = /obj/structure/cable + return new path(location, item_color) + +// called when cable_coil is clicked on a turf +/obj/item/stack/cable_coil/proc/place_turf(turf/T, mob/user, dirnew) + if(!isturf(user.loc)) + return + + if(!isturf(T) || T.intact || !T.can_have_cabling()) + to_chat(user, "You can only lay cables on catwalks and plating!") + return + + if(get_amount() < 1) // Out of cable + to_chat(user, "There is no cable left!") + return + + if(get_dist(T,user) > 1) // Too far + to_chat(user, "You can't lay cable at a place that far away!") + return + + var/dirn + if(!dirnew) //If we weren't given a direction, come up with one! (Called as null from catwalk.dm and floor.dm) + if(user.loc == T) + dirn = user.dir //If laying on the tile we're on, lay in the direction we're facing + else + dirn = get_dir(T, user) + else + dirn = dirnew + + for(var/obj/structure/cable/LC in T) + if(LC.d2 == dirn && LC.d1 == 0) + to_chat(user, "There's already a cable at that position!") + return + + var/obj/structure/cable/C = get_new_cable(T) + + //set up the new cable + C.d1 = 0 //it's a O-X node cable + C.d2 = dirn + C.add_fingerprint(user) + C.update_icon() + + //create a new powernet with the cable, if needed it will be merged later + var/datum/powernet/PN = new() + PN.add_cable(C) + + C.mergeConnectedNetworks(C.d2) //merge the powernet with adjacents powernets + C.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets + + if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions + C.mergeDiagonalsNetworks(C.d2) + + use(1) + + if(C.shock(user, 50)) + if(prob(50)) //fail + new /obj/item/stack/cable_coil(get_turf(C), 1, C.color) + C.deconstruct() + + return C + +// called when cable_coil is click on an installed obj/cable +// or click on a turf that already contains a "node" cable +/obj/item/stack/cable_coil/proc/cable_join(obj/structure/cable/C, mob/user, showerror = TRUE, forceddir) + var/turf/U = user.loc + if(!isturf(U)) + return + + var/turf/T = C.loc + + if(!isturf(T) || T.intact) // sanity checks, also stop use interacting with T-scanner revealed cable + return + + if(get_dist(C, user) > 1) // make sure it's close enough + to_chat(user, "You can't lay cable at a place that far away!") + return + + + if(U == T && !forceddir) //if clicked on the turf we're standing on and a direction wasn't supplied, try to put a cable in the direction we're facing + place_turf(T,user) + return + + var/dirn = get_dir(C, user) + + // one end of the clicked cable is pointing towards us and no direction was supplied + if((C.d1 == dirn || C.d2 == dirn) && !forceddir) + if(!U.can_have_cabling()) //checking if it's a plating or catwalk + if (showerror) + to_chat(user, "You can only lay cables on catwalks and plating!") + return + if(U.intact) //can't place a cable if it's a plating with a tile on it + to_chat(user, "You can't lay cable there unless the floor tiles are removed!") + return + else + // cable is pointing at us, we're standing on an open tile + // so create a stub pointing at the clicked cable on our tile + + var/fdirn = turn(dirn, 180) // the opposite direction + + for(var/obj/structure/cable/LC in U) // check to make sure there's not a cable there already + if(LC.d1 == fdirn || LC.d2 == fdirn) + if (showerror) + to_chat(user, "There's already a cable at that position!") + return + + var/obj/structure/cable/NC = get_new_cable (U) + + NC.d1 = 0 + NC.d2 = fdirn + NC.add_fingerprint(user) + NC.update_icon() + + //create a new powernet with the cable, if needed it will be merged later + var/datum/powernet/newPN = new() + newPN.add_cable(NC) + + NC.mergeConnectedNetworks(NC.d2) //merge the powernet with adjacents powernets + NC.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets + + if(NC.d2 & (NC.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions + NC.mergeDiagonalsNetworks(NC.d2) + + use(1) + + if (NC.shock(user, 50)) + if (prob(50)) //fail + NC.deconstruct() + + return + + // exisiting cable doesn't point at our position or we have a supplied direction, so see if it's a stub + else if(C.d1 == 0) + // if so, make it a full cable pointing from it's old direction to our dirn + var/nd1 = C.d2 // these will be the new directions + var/nd2 = dirn + + + if(nd1 > nd2) // swap directions to match icons/states + nd1 = dirn + nd2 = C.d2 + + + for(var/obj/structure/cable/LC in T) // check to make sure there's no matching cable + if(LC == C) // skip the cable we're interacting with + continue + if((LC.d1 == nd1 && LC.d2 == nd2) || (LC.d1 == nd2 && LC.d2 == nd1) ) // make sure no cable matches either direction + if (showerror) + to_chat(user, "There's already a cable at that position!") + + return + + + C.update_icon() + + C.d1 = nd1 + C.d2 = nd2 + + //updates the stored cable coil + C.update_stored(2, item_color) + + C.add_fingerprint(user) + C.update_icon() + + + C.mergeConnectedNetworks(C.d1) //merge the powernets... + C.mergeConnectedNetworks(C.d2) //...in the two new cable directions + C.mergeConnectedNetworksOnTurf() + + if(C.d1 & (C.d1 - 1))// if the cable is layed diagonally, check the others 2 possible directions + C.mergeDiagonalsNetworks(C.d1) + + if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions + C.mergeDiagonalsNetworks(C.d2) + + use(1) + + if (C.shock(user, 50)) + if (prob(50)) //fail + C.deconstruct() + return + + C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets. + return + +////////////////////////////// +// Misc. +///////////////////////////// + +/obj/item/stack/cable_coil/red + item_color = "red" + color = "#ff0000" + +/obj/item/stack/cable_coil/yellow + item_color = "yellow" + color = "#ffff00" + +/obj/item/stack/cable_coil/blue + item_color = "blue" + color = "#1919c8" + +/obj/item/stack/cable_coil/green + item_color = "green" + color = "#00aa00" + +/obj/item/stack/cable_coil/pink + item_color = "pink" + color = "#ff3ccd" + +/obj/item/stack/cable_coil/orange + item_color = "orange" + color = "#ff8000" + +/obj/item/stack/cable_coil/cyan + item_color = "cyan" + color = "#00ffff" + +/obj/item/stack/cable_coil/white + item_color = "white" + +/obj/item/stack/cable_coil/random + item_color = null + color = "#ffffff" + + +/obj/item/stack/cable_coil/random/five + amount = 5 + +/obj/item/stack/cable_coil/cut + amount = null + icon_state = "coil2" + +/obj/item/stack/cable_coil/cut/Initialize(mapload) + . = ..() + if(!amount) + amount = rand(1,2) + pixel_x = rand(-2,2) + pixel_y = rand(-2,2) + update_icon() + +/obj/item/stack/cable_coil/cut/red + item_color = "red" + color = "#ff0000" + +/obj/item/stack/cable_coil/cut/yellow + item_color = "yellow" + color = "#ffff00" + +/obj/item/stack/cable_coil/cut/blue + item_color = "blue" + color = "#1919c8" + +/obj/item/stack/cable_coil/cut/green + item_color = "green" + color = "#00aa00" + +/obj/item/stack/cable_coil/cut/pink + item_color = "pink" + color = "#ff3ccd" + +/obj/item/stack/cable_coil/cut/orange + item_color = "orange" + color = "#ff8000" + +/obj/item/stack/cable_coil/cut/cyan + item_color = "cyan" + color = "#00ffff" + +/obj/item/stack/cable_coil/cut/white + item_color = "white" + +/obj/item/stack/cable_coil/cut/random + item_color = null color = "#ffffff" \ No newline at end of file diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index 5f9e7038f3..d89eeb7e3d 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -1,366 +1,366 @@ -/obj/item/stock_parts/cell - name = "power cell" - desc = "A rechargeable electrochemical power cell." - icon = 'icons/obj/power.dmi' - icon_state = "cell" - item_state = "cell" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - force = 5 - throwforce = 5 - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - var/charge = 0 // note %age conveted to actual charge in New - var/maxcharge = 1000 - materials = list(MAT_METAL=700, MAT_GLASS=50) - grind_results = list(/datum/reagent/lithium = 15, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) - var/rigged = FALSE // true if rigged to explode - var/chargerate = 100 //how much power is given every tick in a recharger - var/self_recharge = 0 //does it self recharge, over time, or not? - var/ratingdesc = TRUE - var/grown_battery = FALSE // If it's a grown that acts as a battery, add a wire overlay to it. - rad_flags = RAD_NO_CONTAMINATE // Prevent the same cheese as with the stock parts - -/obj/item/stock_parts/cell/get_cell() - return src - -/obj/item/stock_parts/cell/Initialize(mapload, override_maxcharge) - . = ..() - if(self_recharge) - START_PROCESSING(SSobj, src) - create_reagents(5, INJECTABLE | DRAINABLE) - if (override_maxcharge) - maxcharge = override_maxcharge - charge = maxcharge - if(ratingdesc) - desc += " This one has a rating of [DisplayEnergy(maxcharge)], and you should not swallow it." - update_icon() - -/obj/item/stock_parts/cell/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/stock_parts/cell/vv_edit_var(var_name, var_value) - switch(var_name) - if("self_recharge") - if(var_value) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/stock_parts/cell/process() - if(self_recharge) - give(chargerate * 0.25) - else - return PROCESS_KILL - -/obj/item/stock_parts/cell/update_icon() - cut_overlays() - if(grown_battery) - add_overlay(image('icons/obj/power.dmi',"grown_wires")) - if(charge < 0.01) - return - else if(charge/maxcharge >=0.995) - add_overlay("cell-o2") - else - add_overlay("cell-o1") - -/obj/item/stock_parts/cell/proc/percent() // return % charge of cell - return 100*charge/maxcharge - -// use power from a cell -/obj/item/stock_parts/cell/use(amount, can_explode = TRUE) - if(rigged && amount > 0 && can_explode) - explode() - return 0 - if(charge < amount) - return 0 - charge = (charge - amount) - if(!istype(loc, /obj/machinery/power/apc)) - SSblackbox.record_feedback("tally", "cell_used", 1, type) - return 1 - -// recharge the cell -/obj/item/stock_parts/cell/proc/give(amount) - if(rigged && amount > 0) - explode() - return 0 - if(maxcharge < amount) - amount = maxcharge - var/power_used = min(maxcharge-charge,amount) - charge += power_used - return power_used - -/obj/item/stock_parts/cell/examine(mob/user) - . = ..() - if(rigged) - . += "This power cell seems to be faulty!" - else - . += "The charge meter reads [round(src.percent() )]%." - -/obj/item/stock_parts/cell/suicide_act(mob/user) - user.visible_message("[user] is licking the electrodes of [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (FIRELOSS) - -/obj/item/stock_parts/cell/on_reagent_change(changetype) - ..() - rigged = reagents?.has_reagent(/datum/reagent/toxin/plasma, 5) ? TRUE : FALSE //has_reagent returns the reagent datum - -/obj/item/stock_parts/cell/proc/explode() - var/turf/T = get_turf(src.loc) - if (charge==0) - return - var/devastation_range = -1 //round(charge/11000) - var/heavy_impact_range = round(sqrt(charge)/60) - var/light_impact_range = round(sqrt(charge)/30) - var/flash_range = light_impact_range - if (light_impact_range==0) - rigged = FALSE - corrupt() - return - //explosion(T, 0, 1, 2, 2) - explosion(T, devastation_range, heavy_impact_range, light_impact_range, flash_range) - qdel(src) - -/obj/item/stock_parts/cell/proc/corrupt() - charge /= 2 - maxcharge = max(maxcharge/2, chargerate) - if (prob(10)) - rigged = TRUE //broken batterys are dangerous - -/obj/item/stock_parts/cell/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - charge -= 1000 / severity - if (charge < 0) - charge = 0 - -/obj/item/stock_parts/cell/ex_act(severity, target) - ..() - if(!QDELETED(src)) - switch(severity) - if(2) - if(prob(50)) - corrupt() - if(3) - if(prob(25)) - corrupt() - - -/obj/item/stock_parts/cell/blob_act(obj/structure/blob/B) - ex_act(EXPLODE_DEVASTATE) - -/obj/item/stock_parts/cell/proc/get_electrocute_damage() - if(charge >= 1000) - return CLAMP(round(charge/10000), 10, 90) + rand(-5,5) - else - return 0 - -/obj/item/stock_parts/cell/get_part_rating() - return rating * maxcharge - -/* Cell variants*/ -/obj/item/stock_parts/cell/empty/Initialize() - . = ..() - charge = 0 - -/obj/item/stock_parts/cell/crap - name = "\improper Nanotrasen brand rechargeable AA battery" - desc = "You can't top the plasma top." //TOTALLY TRADEMARK INFRINGEMENT - maxcharge = 500 - materials = list(MAT_GLASS=40) - -/obj/item/stock_parts/cell/crap/empty/Initialize() - . = ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/upgraded - name = "upgraded power cell" - desc = "A power cell with a slightly higher capacity than normal!" - maxcharge = 2500 - materials = list(MAT_GLASS=50) - chargerate = 1000 - -/obj/item/stock_parts/cell/upgraded/plus - name = "upgraded power cell+" - desc = "A power cell with an even higher capacity than the base model!" - maxcharge = 5000 - -/obj/item/stock_parts/cell/secborg - name = "security borg rechargeable D battery" - maxcharge = 1250 //25/12/6 disabler/laser/taser shots. - materials = list(MAT_GLASS=40) - -/obj/item/stock_parts/cell/secborg/empty/Initialize() - . = ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/lascarbine - name = "laser carbine power supply" - maxcharge = 1500 //20 laser shots. - -/obj/item/stock_parts/cell/pulse //200 pulse shots - name = "pulse rifle power cell" - maxcharge = 40000 - chargerate = 1500 - -/obj/item/stock_parts/cell/pulse/carbine //25 pulse shots - name = "pulse carbine power cell" - maxcharge = 5000 - -/obj/item/stock_parts/cell/pulse/pistol //10 pulse shots - name = "pulse pistol power cell" - maxcharge = 2000 - -/obj/item/stock_parts/cell/high - name = "high-capacity power cell" - icon_state = "hcell" - maxcharge = 10000 - materials = list(MAT_GLASS=60) - chargerate = 1500 - -/obj/item/stock_parts/cell/high/plus - name = "high-capacity power cell+" - desc = "Where did these come from?" - icon_state = "h+cell" - maxcharge = 15000 - chargerate = 2250 - -/obj/item/stock_parts/cell/high/empty/Initialize() - . = ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/super - name = "super-capacity power cell" - icon_state = "scell" - maxcharge = 20000 - materials = list(MAT_GLASS=300) - chargerate = 2000 - -/obj/item/stock_parts/cell/super/empty/Initialize() - . = ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/hyper - name = "hyper-capacity power cell" - icon_state = "hpcell" - maxcharge = 30000 - materials = list(MAT_GLASS=400) - chargerate = 3000 - -/obj/item/stock_parts/cell/hyper/empty/Initialize() - . = ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/bluespace - name = "bluespace power cell" - desc = "A rechargeable transdimensional power cell." - icon_state = "bscell" - maxcharge = 40000 - materials = list(MAT_GLASS=600) - chargerate = 4000 - -/obj/item/stock_parts/cell/bluespace/empty/Initialize() - . = ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/infinite - name = "infinite-capacity power cell!" - icon_state = "icell" - maxcharge = 30000 - materials = list(MAT_GLASS=1000) - rating = 100 - chargerate = 30000 - -/obj/item/stock_parts/cell/infinite/use() - return 1 - -/obj/item/stock_parts/cell/infinite/abductor - name = "void core" - desc = "An alien power cell that produces energy seemingly out of nowhere." - icon = 'icons/obj/abductor.dmi' - icon_state = "cell" - maxcharge = 50000 - ratingdesc = FALSE - -/obj/item/stock_parts/cell/infinite/abductor/update_icon() - return - - -/obj/item/stock_parts/cell/potato - name = "potato battery" - desc = "A rechargeable starch based power cell." - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "potato" - charge = 100 - maxcharge = 300 - materials = list() - grown_battery = TRUE //it has the overlays for wires - -/obj/item/stock_parts/cell/high/slime - name = "charged slime core" - desc = "A yellow slime core infused with plasma, it crackles with power." - icon = 'icons/mob/slimes.dmi' - icon_state = "yellow slime extract" - materials = list() - rating = 5 //self-recharge makes these desirable - self_recharge = 1 // Infused slime cores self-recharge, over time - -/obj/item/stock_parts/cell/emproof - name = "\improper EMP-proof cell" - desc = "An EMP-proof cell." - maxcharge = 500 - rating = 3 - -/obj/item/stock_parts/cell/emproof/empty/Initialize() - . = ..() - charge = 0 - update_icon() - -/obj/item/stock_parts/cell/emproof/empty/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF) - -/obj/item/stock_parts/cell/emproof/corrupt() - return - -/obj/item/stock_parts/cell/beam_rifle - name = "beam rifle capacitor" - desc = "A high powered capacitor that can provide huge amounts of energy in an instant." - maxcharge = 50000 - chargerate = 5000 //Extremely energy intensive - -/obj/item/stock_parts/cell/beam_rifle/corrupt() - return - -/obj/item/stock_parts/cell/beam_rifle/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - charge = CLAMP((charge-(10000/severity)),0,maxcharge) - -/obj/item/stock_parts/cell/emergency_light - name = "miniature power cell" - desc = "A tiny power cell with a very low power capacity. Used in light fixtures to power them in the event of an outage." - maxcharge = 120 //Emergency lights use 0.2 W per tick, meaning ~10 minutes of emergency power from a cell - materials = list(MAT_GLASS = 20) - w_class = WEIGHT_CLASS_TINY - -/obj/item/stock_parts/cell/emergency_light/Initialize() - . = ..() - var/area/A = get_area(src) - if(!A.lightswitch || !A.light_power) - charge = 0 //For naturally depowered areas, we start with no power - -//found inside the inducers ordered from cargo. -/obj/item/stock_parts/cell/inducer_supply - maxcharge = 5000 +/obj/item/stock_parts/cell + name = "power cell" + desc = "A rechargeable electrochemical power cell." + icon = 'icons/obj/power.dmi' + icon_state = "cell" + item_state = "cell" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + force = 5 + throwforce = 5 + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + var/charge = 0 // note %age conveted to actual charge in New + var/maxcharge = 1000 + materials = list(MAT_METAL=700, MAT_GLASS=50) + grind_results = list(/datum/reagent/lithium = 15, /datum/reagent/iron = 5, /datum/reagent/silicon = 5) + var/rigged = FALSE // true if rigged to explode + var/chargerate = 100 //how much power is given every tick in a recharger + var/self_recharge = 0 //does it self recharge, over time, or not? + var/ratingdesc = TRUE + var/grown_battery = FALSE // If it's a grown that acts as a battery, add a wire overlay to it. + rad_flags = RAD_NO_CONTAMINATE // Prevent the same cheese as with the stock parts + +/obj/item/stock_parts/cell/get_cell() + return src + +/obj/item/stock_parts/cell/Initialize(mapload, override_maxcharge) + . = ..() + if(self_recharge) + START_PROCESSING(SSobj, src) + create_reagents(5, INJECTABLE | DRAINABLE) + if (override_maxcharge) + maxcharge = override_maxcharge + charge = maxcharge + if(ratingdesc) + desc += " This one has a rating of [DisplayEnergy(maxcharge)], and you should not swallow it." + update_icon() + +/obj/item/stock_parts/cell/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/stock_parts/cell/vv_edit_var(var_name, var_value) + switch(var_name) + if("self_recharge") + if(var_value) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/stock_parts/cell/process() + if(self_recharge) + give(chargerate * 0.25) + else + return PROCESS_KILL + +/obj/item/stock_parts/cell/update_icon() + cut_overlays() + if(grown_battery) + add_overlay(image('icons/obj/power.dmi',"grown_wires")) + if(charge < 0.01) + return + else if(charge/maxcharge >=0.995) + add_overlay("cell-o2") + else + add_overlay("cell-o1") + +/obj/item/stock_parts/cell/proc/percent() // return % charge of cell + return 100*charge/maxcharge + +// use power from a cell +/obj/item/stock_parts/cell/use(amount, can_explode = TRUE) + if(rigged && amount > 0 && can_explode) + explode() + return 0 + if(charge < amount) + return 0 + charge = (charge - amount) + if(!istype(loc, /obj/machinery/power/apc)) + SSblackbox.record_feedback("tally", "cell_used", 1, type) + return 1 + +// recharge the cell +/obj/item/stock_parts/cell/proc/give(amount) + if(rigged && amount > 0) + explode() + return 0 + if(maxcharge < amount) + amount = maxcharge + var/power_used = min(maxcharge-charge,amount) + charge += power_used + return power_used + +/obj/item/stock_parts/cell/examine(mob/user) + . = ..() + if(rigged) + . += "This power cell seems to be faulty!" + else + . += "The charge meter reads [round(src.percent() )]%." + +/obj/item/stock_parts/cell/suicide_act(mob/user) + user.visible_message("[user] is licking the electrodes of [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (FIRELOSS) + +/obj/item/stock_parts/cell/on_reagent_change(changetype) + ..() + rigged = reagents?.has_reagent(/datum/reagent/toxin/plasma, 5) ? TRUE : FALSE //has_reagent returns the reagent datum + +/obj/item/stock_parts/cell/proc/explode() + var/turf/T = get_turf(src.loc) + if (charge==0) + return + var/devastation_range = -1 //round(charge/11000) + var/heavy_impact_range = round(sqrt(charge)/60) + var/light_impact_range = round(sqrt(charge)/30) + var/flash_range = light_impact_range + if (light_impact_range==0) + rigged = FALSE + corrupt() + return + //explosion(T, 0, 1, 2, 2) + explosion(T, devastation_range, heavy_impact_range, light_impact_range, flash_range) + qdel(src) + +/obj/item/stock_parts/cell/proc/corrupt() + charge /= 2 + maxcharge = max(maxcharge/2, chargerate) + if (prob(10)) + rigged = TRUE //broken batterys are dangerous + +/obj/item/stock_parts/cell/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + charge -= 1000 / severity + if (charge < 0) + charge = 0 + +/obj/item/stock_parts/cell/ex_act(severity, target) + ..() + if(!QDELETED(src)) + switch(severity) + if(2) + if(prob(50)) + corrupt() + if(3) + if(prob(25)) + corrupt() + + +/obj/item/stock_parts/cell/blob_act(obj/structure/blob/B) + ex_act(EXPLODE_DEVASTATE) + +/obj/item/stock_parts/cell/proc/get_electrocute_damage() + if(charge >= 1000) + return CLAMP(round(charge/10000), 10, 90) + rand(-5,5) + else + return 0 + +/obj/item/stock_parts/cell/get_part_rating() + return rating * maxcharge + +/* Cell variants*/ +/obj/item/stock_parts/cell/empty/Initialize() + . = ..() + charge = 0 + +/obj/item/stock_parts/cell/crap + name = "\improper Nanotrasen brand rechargeable AA battery" + desc = "You can't top the plasma top." //TOTALLY TRADEMARK INFRINGEMENT + maxcharge = 500 + materials = list(MAT_GLASS=40) + +/obj/item/stock_parts/cell/crap/empty/Initialize() + . = ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/upgraded + name = "upgraded power cell" + desc = "A power cell with a slightly higher capacity than normal!" + maxcharge = 2500 + materials = list(MAT_GLASS=50) + chargerate = 1000 + +/obj/item/stock_parts/cell/upgraded/plus + name = "upgraded power cell+" + desc = "A power cell with an even higher capacity than the base model!" + maxcharge = 5000 + +/obj/item/stock_parts/cell/secborg + name = "security borg rechargeable D battery" + maxcharge = 1250 //25/12/6 disabler/laser/taser shots. + materials = list(MAT_GLASS=40) + +/obj/item/stock_parts/cell/secborg/empty/Initialize() + . = ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/lascarbine + name = "laser carbine power supply" + maxcharge = 1500 //20 laser shots. + +/obj/item/stock_parts/cell/pulse //200 pulse shots + name = "pulse rifle power cell" + maxcharge = 40000 + chargerate = 1500 + +/obj/item/stock_parts/cell/pulse/carbine //25 pulse shots + name = "pulse carbine power cell" + maxcharge = 5000 + +/obj/item/stock_parts/cell/pulse/pistol //10 pulse shots + name = "pulse pistol power cell" + maxcharge = 2000 + +/obj/item/stock_parts/cell/high + name = "high-capacity power cell" + icon_state = "hcell" + maxcharge = 10000 + materials = list(MAT_GLASS=60) + chargerate = 1500 + +/obj/item/stock_parts/cell/high/plus + name = "high-capacity power cell+" + desc = "Where did these come from?" + icon_state = "h+cell" + maxcharge = 15000 + chargerate = 2250 + +/obj/item/stock_parts/cell/high/empty/Initialize() + . = ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/super + name = "super-capacity power cell" + icon_state = "scell" + maxcharge = 20000 + materials = list(MAT_GLASS=300) + chargerate = 2000 + +/obj/item/stock_parts/cell/super/empty/Initialize() + . = ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/hyper + name = "hyper-capacity power cell" + icon_state = "hpcell" + maxcharge = 30000 + materials = list(MAT_GLASS=400) + chargerate = 3000 + +/obj/item/stock_parts/cell/hyper/empty/Initialize() + . = ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/bluespace + name = "bluespace power cell" + desc = "A rechargeable transdimensional power cell." + icon_state = "bscell" + maxcharge = 40000 + materials = list(MAT_GLASS=600) + chargerate = 4000 + +/obj/item/stock_parts/cell/bluespace/empty/Initialize() + . = ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/infinite + name = "infinite-capacity power cell!" + icon_state = "icell" + maxcharge = 30000 + materials = list(MAT_GLASS=1000) + rating = 100 + chargerate = 30000 + +/obj/item/stock_parts/cell/infinite/use() + return 1 + +/obj/item/stock_parts/cell/infinite/abductor + name = "void core" + desc = "An alien power cell that produces energy seemingly out of nowhere." + icon = 'icons/obj/abductor.dmi' + icon_state = "cell" + maxcharge = 50000 + ratingdesc = FALSE + +/obj/item/stock_parts/cell/infinite/abductor/update_icon() + return + + +/obj/item/stock_parts/cell/potato + name = "potato battery" + desc = "A rechargeable starch based power cell." + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "potato" + charge = 100 + maxcharge = 300 + materials = list() + grown_battery = TRUE //it has the overlays for wires + +/obj/item/stock_parts/cell/high/slime + name = "charged slime core" + desc = "A yellow slime core infused with plasma, it crackles with power." + icon = 'icons/mob/slimes.dmi' + icon_state = "yellow slime extract" + materials = list() + rating = 5 //self-recharge makes these desirable + self_recharge = 1 // Infused slime cores self-recharge, over time + +/obj/item/stock_parts/cell/emproof + name = "\improper EMP-proof cell" + desc = "An EMP-proof cell." + maxcharge = 500 + rating = 3 + +/obj/item/stock_parts/cell/emproof/empty/Initialize() + . = ..() + charge = 0 + update_icon() + +/obj/item/stock_parts/cell/emproof/empty/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF) + +/obj/item/stock_parts/cell/emproof/corrupt() + return + +/obj/item/stock_parts/cell/beam_rifle + name = "beam rifle capacitor" + desc = "A high powered capacitor that can provide huge amounts of energy in an instant." + maxcharge = 50000 + chargerate = 5000 //Extremely energy intensive + +/obj/item/stock_parts/cell/beam_rifle/corrupt() + return + +/obj/item/stock_parts/cell/beam_rifle/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + charge = CLAMP((charge-(10000/severity)),0,maxcharge) + +/obj/item/stock_parts/cell/emergency_light + name = "miniature power cell" + desc = "A tiny power cell with a very low power capacity. Used in light fixtures to power them in the event of an outage." + maxcharge = 120 //Emergency lights use 0.2 W per tick, meaning ~10 minutes of emergency power from a cell + materials = list(MAT_GLASS = 20) + w_class = WEIGHT_CLASS_TINY + +/obj/item/stock_parts/cell/emergency_light/Initialize() + . = ..() + var/area/A = get_area(src) + if(!A.lightswitch || !A.light_power) + charge = 0 //For naturally depowered areas, we start with no power + +//found inside the inducers ordered from cargo. +/obj/item/stock_parts/cell/inducer_supply + maxcharge = 5000 diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm index 8e121dc809..011a2e4fd2 100644 --- a/code/modules/power/generator.dm +++ b/code/modules/power/generator.dm @@ -1,234 +1,234 @@ -/obj/machinery/power/generator - name = "thermoelectric generator" - desc = "It's a high efficiency thermoelectric generator." - icon_state = "teg" - density = TRUE - use_power = NO_POWER_USE - - var/obj/machinery/atmospherics/components/binary/circulator/cold_circ - var/obj/machinery/atmospherics/components/binary/circulator/hot_circ - - var/lastgen = 0 - var/lastgenlev = -1 - var/lastcirc = "00" - - -/obj/machinery/power/generator/Initialize(mapload) - . = ..() - find_circs() - connect_to_network() - SSair.atmos_machinery += src - update_icon() - component_parts = list(new /obj/item/circuitboard/machine/generator) - -/obj/machinery/power/generator/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ) - -/obj/machinery/power/generator/Destroy() - kill_circs() - SSair.atmos_machinery -= src - return ..() - -/obj/machinery/power/generator/update_icon() - - if(stat & (NOPOWER|BROKEN)) - cut_overlays() - else - cut_overlays() - - var/L = min(round(lastgenlev/100000),11) - if(L != 0) - add_overlay(image('icons/obj/power.dmi', "teg-op[L]")) - - if(hot_circ && cold_circ) - add_overlay("teg-oc[lastcirc]") - - -#define GENRATE 800 // generator output coefficient from Q - -/obj/machinery/power/generator/process_atmos() - - if(!cold_circ || !hot_circ) - return - - if(powernet) - var/datum/gas_mixture/cold_air = cold_circ.return_transfer_air() - var/datum/gas_mixture/hot_air = hot_circ.return_transfer_air() - - if(cold_air && hot_air) - - var/cold_air_heat_capacity = cold_air.heat_capacity() - var/hot_air_heat_capacity = hot_air.heat_capacity() - - var/delta_temperature = hot_air.temperature - cold_air.temperature - - - if(delta_temperature > 0 && cold_air_heat_capacity > 0 && hot_air_heat_capacity > 0) - var/efficiency = 0.45 - - var/energy_transfer = delta_temperature*hot_air_heat_capacity*cold_air_heat_capacity/(hot_air_heat_capacity+cold_air_heat_capacity) - - var/heat = energy_transfer*(1-efficiency) - lastgen += energy_transfer*efficiency - - hot_air.temperature = hot_air.temperature - energy_transfer/hot_air_heat_capacity - cold_air.temperature = cold_air.temperature + heat/cold_air_heat_capacity - - //add_avail(lastgen) This is done in process now - // update icon overlays only if displayed level has changed - - if(hot_air) - var/datum/gas_mixture/hot_circ_air1 = hot_circ.airs[1] - hot_circ_air1.merge(hot_air) - - if(cold_air) - var/datum/gas_mixture/cold_circ_air1 = cold_circ.airs[1] - cold_circ_air1.merge(cold_air) - - update_icon() - - var/circ = "[cold_circ && cold_circ.last_pressure_delta > 0 ? "1" : "0"][hot_circ && hot_circ.last_pressure_delta > 0 ? "1" : "0"]" - if(circ != lastcirc) - lastcirc = circ - update_icon() - - src.updateDialog() - -/obj/machinery/power/generator/process() - //Setting this number higher just makes the change in power output slower, it doesnt actualy reduce power output cause **math** - var/power_output = round(lastgen / 10) - add_avail(power_output) - lastgenlev = power_output - lastgen -= power_output - ..() - -/obj/machinery/power/generator/proc/get_menu(include_link = TRUE) - var/t = "" - if(!powernet) - t += "Unable to connect to the power network!" - else if(cold_circ && hot_circ) - var/datum/gas_mixture/cold_circ_air1 = cold_circ.airs[1] - var/datum/gas_mixture/cold_circ_air2 = cold_circ.airs[2] - var/datum/gas_mixture/hot_circ_air1 = hot_circ.airs[1] - var/datum/gas_mixture/hot_circ_air2 = hot_circ.airs[2] - - t += "
                " - - t += "Output: [DisplayPower(lastgenlev)]" - - t += "
                " - - t += "Cold loop
                " - t += "Temperature Inlet: [round(cold_circ_air2.temperature, 0.1)] K / Outlet: [round(cold_circ_air1.temperature, 0.1)] K
                " - t += "Pressure Inlet: [round(cold_circ_air2.return_pressure(), 0.1)] kPa / Outlet: [round(cold_circ_air1.return_pressure(), 0.1)] kPa
                " - - t += "Hot loop
                " - t += "Temperature Inlet: [round(hot_circ_air2.temperature, 0.1)] K / Outlet: [round(hot_circ_air1.temperature, 0.1)] K
                " - t += "Pressure Inlet: [round(hot_circ_air2.return_pressure(), 0.1)] kPa / Outlet: [round(hot_circ_air1.return_pressure(), 0.1)] kPa
                " - - t += "
                " - else if(!hot_circ && cold_circ) - t += "Unable to locate hot circulator!" - else if(hot_circ && !cold_circ) - t += "Unable to locate cold circulator!" - else - t += "Unable to locate any parts!" - if(include_link) - t += "
                Close" - - return t - -/obj/machinery/power/generator/ui_interact(mob/user) - . = ..() - var/datum/browser/popup = new(user, "teg", "Thermo-Electric Generator", 460, 300) - popup.set_content(get_menu()) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/power/generator/Topic(href, href_list) - if(..()) - return - if( href_list["close"] ) - usr << browse(null, "window=teg") - usr.unset_machine() - return FALSE - return TRUE - - -/obj/machinery/power/generator/power_change() - ..() - update_icon() - -/obj/machinery/power/generator/proc/find_circs() - kill_circs() - var/list/circs = list() - var/obj/machinery/atmospherics/components/binary/circulator/C - var/circpath = /obj/machinery/atmospherics/components/binary/circulator - if(dir == NORTH || dir == SOUTH) - C = locate(circpath) in get_step(src, EAST) - if(C && C.dir == WEST) - circs += C - - C = locate(circpath) in get_step(src, WEST) - if(C && C.dir == EAST) - circs += C - - else - C = locate(circpath) in get_step(src, NORTH) - if(C && C.dir == SOUTH) - circs += C - - C = locate(circpath) in get_step(src, SOUTH) - if(C && C.dir == NORTH) - circs += C - - if(circs.len) - for(C in circs) - if(C.mode == CIRCULATOR_COLD && !cold_circ) - cold_circ = C - C.generator = src - else if(C.mode == CIRCULATOR_HOT && !hot_circ) - hot_circ = C - C.generator = src - -/obj/machinery/power/generator/wrench_act(mob/living/user, obj/item/I) - if(!panel_open) - return - anchored = !anchored - I.play_tool_sound(src) - if(!anchored) - kill_circs() - connect_to_network() - to_chat(user, "You [anchored?"secure":"unsecure"] [src].") - return TRUE - -/obj/machinery/power/generator/multitool_act(mob/living/user, obj/item/I) - if(!anchored) - return - find_circs() - to_chat(user, "You update [src]'s circulator links.") - return TRUE - -/obj/machinery/power/generator/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/power/generator/crowbar_act(mob/user, obj/item/I) - default_deconstruction_crowbar(I) - return TRUE - -/obj/machinery/power/generator/on_deconstruction() - kill_circs() - -/obj/machinery/power/generator/proc/kill_circs() - if(hot_circ) - hot_circ.generator = null - hot_circ = null - if(cold_circ) - cold_circ.generator = null - cold_circ = null +/obj/machinery/power/generator + name = "thermoelectric generator" + desc = "It's a high efficiency thermoelectric generator." + icon_state = "teg" + density = TRUE + use_power = NO_POWER_USE + + var/obj/machinery/atmospherics/components/binary/circulator/cold_circ + var/obj/machinery/atmospherics/components/binary/circulator/hot_circ + + var/lastgen = 0 + var/lastgenlev = -1 + var/lastcirc = "00" + + +/obj/machinery/power/generator/Initialize(mapload) + . = ..() + find_circs() + connect_to_network() + SSair.atmos_machinery += src + update_icon() + component_parts = list(new /obj/item/circuitboard/machine/generator) + +/obj/machinery/power/generator/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ) + +/obj/machinery/power/generator/Destroy() + kill_circs() + SSair.atmos_machinery -= src + return ..() + +/obj/machinery/power/generator/update_icon() + + if(stat & (NOPOWER|BROKEN)) + cut_overlays() + else + cut_overlays() + + var/L = min(round(lastgenlev/100000),11) + if(L != 0) + add_overlay(image('icons/obj/power.dmi', "teg-op[L]")) + + if(hot_circ && cold_circ) + add_overlay("teg-oc[lastcirc]") + + +#define GENRATE 800 // generator output coefficient from Q + +/obj/machinery/power/generator/process_atmos() + + if(!cold_circ || !hot_circ) + return + + if(powernet) + var/datum/gas_mixture/cold_air = cold_circ.return_transfer_air() + var/datum/gas_mixture/hot_air = hot_circ.return_transfer_air() + + if(cold_air && hot_air) + + var/cold_air_heat_capacity = cold_air.heat_capacity() + var/hot_air_heat_capacity = hot_air.heat_capacity() + + var/delta_temperature = hot_air.temperature - cold_air.temperature + + + if(delta_temperature > 0 && cold_air_heat_capacity > 0 && hot_air_heat_capacity > 0) + var/efficiency = 0.45 + + var/energy_transfer = delta_temperature*hot_air_heat_capacity*cold_air_heat_capacity/(hot_air_heat_capacity+cold_air_heat_capacity) + + var/heat = energy_transfer*(1-efficiency) + lastgen += energy_transfer*efficiency + + hot_air.temperature = hot_air.temperature - energy_transfer/hot_air_heat_capacity + cold_air.temperature = cold_air.temperature + heat/cold_air_heat_capacity + + //add_avail(lastgen) This is done in process now + // update icon overlays only if displayed level has changed + + if(hot_air) + var/datum/gas_mixture/hot_circ_air1 = hot_circ.airs[1] + hot_circ_air1.merge(hot_air) + + if(cold_air) + var/datum/gas_mixture/cold_circ_air1 = cold_circ.airs[1] + cold_circ_air1.merge(cold_air) + + update_icon() + + var/circ = "[cold_circ && cold_circ.last_pressure_delta > 0 ? "1" : "0"][hot_circ && hot_circ.last_pressure_delta > 0 ? "1" : "0"]" + if(circ != lastcirc) + lastcirc = circ + update_icon() + + src.updateDialog() + +/obj/machinery/power/generator/process() + //Setting this number higher just makes the change in power output slower, it doesnt actualy reduce power output cause **math** + var/power_output = round(lastgen / 10) + add_avail(power_output) + lastgenlev = power_output + lastgen -= power_output + ..() + +/obj/machinery/power/generator/proc/get_menu(include_link = TRUE) + var/t = "" + if(!powernet) + t += "Unable to connect to the power network!" + else if(cold_circ && hot_circ) + var/datum/gas_mixture/cold_circ_air1 = cold_circ.airs[1] + var/datum/gas_mixture/cold_circ_air2 = cold_circ.airs[2] + var/datum/gas_mixture/hot_circ_air1 = hot_circ.airs[1] + var/datum/gas_mixture/hot_circ_air2 = hot_circ.airs[2] + + t += "
                " + + t += "Output: [DisplayPower(lastgenlev)]" + + t += "
                " + + t += "Cold loop
                " + t += "Temperature Inlet: [round(cold_circ_air2.temperature, 0.1)] K / Outlet: [round(cold_circ_air1.temperature, 0.1)] K
                " + t += "Pressure Inlet: [round(cold_circ_air2.return_pressure(), 0.1)] kPa / Outlet: [round(cold_circ_air1.return_pressure(), 0.1)] kPa
                " + + t += "Hot loop
                " + t += "Temperature Inlet: [round(hot_circ_air2.temperature, 0.1)] K / Outlet: [round(hot_circ_air1.temperature, 0.1)] K
                " + t += "Pressure Inlet: [round(hot_circ_air2.return_pressure(), 0.1)] kPa / Outlet: [round(hot_circ_air1.return_pressure(), 0.1)] kPa
                " + + t += "
                " + else if(!hot_circ && cold_circ) + t += "Unable to locate hot circulator!" + else if(hot_circ && !cold_circ) + t += "Unable to locate cold circulator!" + else + t += "Unable to locate any parts!" + if(include_link) + t += "
                Close" + + return t + +/obj/machinery/power/generator/ui_interact(mob/user) + . = ..() + var/datum/browser/popup = new(user, "teg", "Thermo-Electric Generator", 460, 300) + popup.set_content(get_menu()) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/power/generator/Topic(href, href_list) + if(..()) + return + if( href_list["close"] ) + usr << browse(null, "window=teg") + usr.unset_machine() + return FALSE + return TRUE + + +/obj/machinery/power/generator/power_change() + ..() + update_icon() + +/obj/machinery/power/generator/proc/find_circs() + kill_circs() + var/list/circs = list() + var/obj/machinery/atmospherics/components/binary/circulator/C + var/circpath = /obj/machinery/atmospherics/components/binary/circulator + if(dir == NORTH || dir == SOUTH) + C = locate(circpath) in get_step(src, EAST) + if(C && C.dir == WEST) + circs += C + + C = locate(circpath) in get_step(src, WEST) + if(C && C.dir == EAST) + circs += C + + else + C = locate(circpath) in get_step(src, NORTH) + if(C && C.dir == SOUTH) + circs += C + + C = locate(circpath) in get_step(src, SOUTH) + if(C && C.dir == NORTH) + circs += C + + if(circs.len) + for(C in circs) + if(C.mode == CIRCULATOR_COLD && !cold_circ) + cold_circ = C + C.generator = src + else if(C.mode == CIRCULATOR_HOT && !hot_circ) + hot_circ = C + C.generator = src + +/obj/machinery/power/generator/wrench_act(mob/living/user, obj/item/I) + if(!panel_open) + return + anchored = !anchored + I.play_tool_sound(src) + if(!anchored) + kill_circs() + connect_to_network() + to_chat(user, "You [anchored?"secure":"unsecure"] [src].") + return TRUE + +/obj/machinery/power/generator/multitool_act(mob/living/user, obj/item/I) + if(!anchored) + return + find_circs() + to_chat(user, "You update [src]'s circulator links.") + return TRUE + +/obj/machinery/power/generator/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/power/generator/crowbar_act(mob/user, obj/item/I) + default_deconstruction_crowbar(I) + return TRUE + +/obj/machinery/power/generator/on_deconstruction() + kill_circs() + +/obj/machinery/power/generator/proc/kill_circs() + if(hot_circ) + hot_circ.generator = null + hot_circ = null + if(cold_circ) + cold_circ.generator = null + cold_circ = null diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm index f2d95c2bd0..74f78825a8 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -1,417 +1,417 @@ - -// -// Gravity Generator -// - -GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding new gravity generators to the list, and keying it with the z level. - -#define POWER_IDLE 0 -#define POWER_UP 1 -#define POWER_DOWN 2 - -#define GRAV_NEEDS_SCREWDRIVER 0 -#define GRAV_NEEDS_WELDING 1 -#define GRAV_NEEDS_PLASTEEL 2 -#define GRAV_NEEDS_WRENCH 3 - -// -// Abstract Generator -// - -/obj/machinery/gravity_generator - name = "gravitational generator" - desc = "A device which produces a graviton field when set up." - icon = 'icons/obj/machines/gravity_generator.dmi' - density = TRUE - use_power = NO_POWER_USE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/sprite_number = 0 - -/obj/machinery/gravity_generator/safe_throw_at() - return FALSE - -/obj/machinery/gravity_generator/ex_act(severity, target) - if(severity == 1) // Very sturdy. - set_broken() - -/obj/machinery/gravity_generator/blob_act(obj/structure/blob/B) - if(prob(20)) - set_broken() - -/obj/machinery/gravity_generator/tesla_act(power, tesla_flags) - ..() - if(tesla_flags & TESLA_MACHINE_EXPLOSIVE) - qdel(src)//like the singulo, tesla deletes it. stops it from exploding over and over - -/obj/machinery/gravity_generator/update_icon() - ..() - icon_state = "[get_status()]_[sprite_number]" - -/obj/machinery/gravity_generator/proc/get_status() - return "off" - -// You aren't allowed to move. -/obj/machinery/gravity_generator/Move() - . = ..() - qdel(src) - -/obj/machinery/gravity_generator/proc/set_broken() - stat |= BROKEN - -/obj/machinery/gravity_generator/proc/set_fix() - stat &= ~BROKEN - -/obj/machinery/gravity_generator/part/Destroy() - if(main_part) - qdel(main_part) - set_broken() - return ..() - -// -// Part generator which is mostly there for looks -// - -/obj/machinery/gravity_generator/part - var/obj/machinery/gravity_generator/main/main_part = null - -/obj/machinery/gravity_generator/part/attackby(obj/item/I, mob/user, params) - return main_part.attackby(I, user) - -/obj/machinery/gravity_generator/part/get_status() - return main_part.get_status() - -/obj/machinery/gravity_generator/part/attack_hand(mob/user) - return main_part.attack_hand(user) - -/obj/machinery/gravity_generator/part/set_broken() - ..() - if(main_part && !(main_part.stat & BROKEN)) - main_part.set_broken() - -// -// Generator which spawns with the station. -// - -/obj/machinery/gravity_generator/main/station/Initialize() - . = ..() - setup_parts() - middle.add_overlay("activated") - update_list() - -// -// Generator an admin can spawn -// -/obj/machinery/gravity_generator/main/station/admin - use_power = NO_POWER_USE - -// -// Main Generator with the main code -// - -/obj/machinery/gravity_generator/main - icon_state = "on_8" - idle_power_usage = 0 - active_power_usage = 3000 - power_channel = ENVIRON - sprite_number = 8 - use_power = IDLE_POWER_USE - interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OFFLINE - var/on = TRUE - var/breaker = TRUE - var/list/parts = list() - var/obj/middle = null - var/charging_state = POWER_IDLE - var/charge_count = 100 - var/current_overlay = null - var/broken_state = 0 - var/setting = 1 //Gravity value when on - -/obj/machinery/gravity_generator/main/Destroy() // If we somehow get deleted, remove all of our other parts. - investigate_log("was destroyed!", INVESTIGATE_GRAVITY) - on = FALSE - update_list() - for(var/obj/machinery/gravity_generator/part/O in parts) - O.main_part = null - if(!QDESTROYING(O)) - qdel(O) - return ..() - -/obj/machinery/gravity_generator/main/proc/setup_parts() - var/turf/our_turf = get_turf(src) - // 9x9 block obtained from the bottom middle of the block - var/list/spawn_turfs = block(locate(our_turf.x - 1, our_turf.y + 2, our_turf.z), locate(our_turf.x + 1, our_turf.y, our_turf.z)) - var/count = 10 - for(var/turf/T in spawn_turfs) - count-- - if(T == our_turf) // Skip our turf. - continue - var/obj/machinery/gravity_generator/part/part = new(T) - if(count == 5) // Middle - middle = part - if(count <= 3) // Their sprite is the top part of the generator - part.density = FALSE - part.layer = WALL_OBJ_LAYER - part.sprite_number = count - part.main_part = src - parts += part - part.update_icon() - -/obj/machinery/gravity_generator/main/proc/connected_parts() - return parts.len == 8 - -/obj/machinery/gravity_generator/main/set_broken() - ..() - for(var/obj/machinery/gravity_generator/M in parts) - if(!(M.stat & BROKEN)) - M.set_broken() - middle.cut_overlays() - charge_count = 0 - breaker = FALSE - set_power() - set_state(0) - investigate_log("has broken down.", INVESTIGATE_GRAVITY) - -/obj/machinery/gravity_generator/main/set_fix() - ..() - for(var/obj/machinery/gravity_generator/M in parts) - if(M.stat & BROKEN) - M.set_fix() - broken_state = FALSE - update_icon() - set_power() - -// Interaction - -// Fixing the gravity generator. -/obj/machinery/gravity_generator/main/attackby(obj/item/I, mob/user, params) - switch(broken_state) - if(GRAV_NEEDS_SCREWDRIVER) - if(istype(I, /obj/item/screwdriver)) - to_chat(user, "You secure the screws of the framework.") - I.play_tool_sound(src) - broken_state++ - update_icon() - return - if(GRAV_NEEDS_WELDING) - if(istype(I, /obj/item/weldingtool)) - if(I.use_tool(src, user, 0, volume=50, amount=1)) - to_chat(user, "You mend the damaged framework.") - broken_state++ - update_icon() - return - if(GRAV_NEEDS_PLASTEEL) - if(istype(I, /obj/item/stack/sheet/plasteel)) - var/obj/item/stack/sheet/plasteel/PS = I - if(PS.get_amount() >= 10) - PS.use(10) - to_chat(user, "You add the plating to the framework.") - playsound(src.loc, 'sound/machines/click.ogg', 75, 1) - broken_state++ - update_icon() - else - to_chat(user, "You need 10 sheets of plasteel!") - return - if(GRAV_NEEDS_WRENCH) - if(istype(I, /obj/item/wrench)) - to_chat(user, "You secure the plating to the framework.") - I.play_tool_sound(src) - set_fix() - return - return ..() - -/obj/machinery/gravity_generator/main/ui_interact(mob/user) - if(stat & BROKEN) - return - var/dat = "Gravity Generator Breaker: " - if(breaker) - dat += "ON OFF" - else - dat += "ON OFF " - - dat += "
                Generator Status:
                " - if(charging_state != POWER_IDLE) - dat += "WARNING Radiation Detected.
                [charging_state == POWER_UP ? "Charging..." : "Discharging..."]" - else if(on) - dat += "Powered." - else - dat += "Unpowered." - - dat += "
                Gravity Charge: [charge_count]%
                " - - var/datum/browser/popup = new(user, "gravgen", name) - popup.set_content(dat) - popup.open() - - -/obj/machinery/gravity_generator/main/Topic(href, href_list) - - if(..()) - return - - if(href_list["gentoggle"]) - breaker = !breaker - investigate_log("was toggled [breaker ? "ON" : "OFF"] by [key_name(usr)].", INVESTIGATE_GRAVITY) - set_power() - src.updateUsrDialog() - -// Power and Icon States - -/obj/machinery/gravity_generator/main/power_change() - ..() - investigate_log("has [stat & NOPOWER ? "lost" : "regained"] power.", INVESTIGATE_GRAVITY) - set_power() - -/obj/machinery/gravity_generator/main/get_status() - if(stat & BROKEN) - return "fix[min(broken_state, 3)]" - return on || charging_state != POWER_IDLE ? "on" : "off" - -/obj/machinery/gravity_generator/main/update_icon() - ..() - for(var/obj/O in parts) - O.update_icon() - -// Set the charging state based on power/breaker. -/obj/machinery/gravity_generator/main/proc/set_power() - var/new_state = FALSE - if(stat & (NOPOWER|BROKEN) || !breaker) - new_state = FALSE - else if(breaker) - new_state = TRUE - - charging_state = new_state ? POWER_UP : POWER_DOWN // Startup sequence animation. - investigate_log("is now [charging_state == POWER_UP ? "charging" : "discharging"].", INVESTIGATE_GRAVITY) - update_icon() - -// Set the state of the gravity. -/obj/machinery/gravity_generator/main/proc/set_state(new_state) - charging_state = POWER_IDLE - on = new_state - use_power = on ? ACTIVE_POWER_USE : IDLE_POWER_USE - // Sound the alert if gravity was just enabled or disabled. - var/alert = FALSE - if(SSticker.IsRoundInProgress()) - if(on) // If we turned on and the game is live. - if(gravity_in_level() == FALSE) - alert = TRUE - investigate_log("was brought online and is now producing gravity for this level.", INVESTIGATE_GRAVITY) - message_admins("The gravity generator was brought online [ADMIN_VERBOSEJMP(src)]") - else - if(gravity_in_level() == TRUE) - alert = TRUE - investigate_log("was brought offline and there is now no gravity for this level.", INVESTIGATE_GRAVITY) - message_admins("The gravity generator was brought offline with no backup generator. [ADMIN_VERBOSEJMP(src)]") - - update_icon() - update_list() - src.updateUsrDialog() - if(alert) - shake_everyone() - -// Charge/Discharge and turn on/off gravity when you reach 0/100 percent. -// Also emit radiation and handle the overlays. -/obj/machinery/gravity_generator/main/process() - if(stat & BROKEN) - return - if(charging_state != POWER_IDLE) - if(charging_state == POWER_UP && charge_count >= 100) - set_state(1) - else if(charging_state == POWER_DOWN && charge_count <= 0) - set_state(0) - else - if(charging_state == POWER_UP) - charge_count += 2 - else if(charging_state == POWER_DOWN) - charge_count -= 2 - - if(charge_count % 4 == 0 && prob(75)) // Let them know it is charging/discharging. - playsound(src.loc, 'sound/effects/empulse.ogg', 100, 1) - - updateDialog() - if(prob(25)) // To help stop "Your clothes feel warm." spam. - pulse_radiation() - - var/overlay_state = null - switch(charge_count) - if(0 to 20) - overlay_state = null - if(21 to 40) - overlay_state = "startup" - if(41 to 60) - overlay_state = "idle" - if(61 to 80) - overlay_state = "activating" - if(81 to 100) - overlay_state = "activated" - - if(overlay_state != current_overlay) - if(middle) - middle.cut_overlays() - if(overlay_state) - middle.add_overlay(overlay_state) - current_overlay = overlay_state - - -/obj/machinery/gravity_generator/main/proc/pulse_radiation() - radiation_pulse(src, 200) - -// Shake everyone on the z level to let them know that gravity was enagaged/disenagaged. -/obj/machinery/gravity_generator/main/proc/shake_everyone() - var/turf/T = get_turf(src) - var/sound/alert_sound = sound('sound/effects/alert.ogg') - for(var/i in GLOB.mob_list) - var/mob/M = i - if(M.z != z && !(SSmapping.level_trait(z, ZTRAITS_STATION) && SSmapping.level_trait(M.z, ZTRAITS_STATION))) - continue - M.update_gravity(M.mob_has_gravity()) - if(M.client) - shake_camera(M, 15, 1) - M.playsound_local(T, null, 100, 1, 0.5, S = alert_sound) - -/obj/machinery/gravity_generator/main/proc/gravity_in_level() - var/turf/T = get_turf(src) - if(!T) - return FALSE - if(GLOB.gravity_generators["[T.z]"]) - return length(GLOB.gravity_generators["[T.z]"]) - return FALSE - -/obj/machinery/gravity_generator/main/proc/update_list() - var/turf/T = get_turf(src.loc) - if(T) - var/list/z_list = list() - // Multi-Z, station gravity generator generates gravity on all ZTRAIT_STATION z-levels. - if(SSmapping.level_trait(T.z, ZTRAIT_STATION)) - for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION)) - z_list += z - else - z_list += T.z - for(var/z in z_list) - if(!GLOB.gravity_generators["[z]"]) - GLOB.gravity_generators["[z]"] = list() - if(on) - GLOB.gravity_generators["[z]"] |= src - else - GLOB.gravity_generators["[z]"] -= src - -/obj/machinery/gravity_generator/main/proc/change_setting(value) - if(value != setting) - setting = value - shake_everyone() - -// Misc - -/obj/item/paper/guides/jobs/engi/gravity_gen - name = "paper- 'Generate your own gravity!'" - info = {"

                Gravity Generator Instructions For Dummies

                -

                Surprisingly, gravity isn't that hard to make! All you have to do is inject deadly radioactive minerals into a ball of - energy and you have yourself gravity! You can turn the machine on or off when required but you must remember that the generator - will EMIT RADIATION when charging or discharging, you can tell it is charging or discharging by the noise it makes, so please WEAR PROTECTIVE CLOTHING.

                -
                -

                It blew up!

                -

                Don't panic! The gravity generator was designed to be easily repaired. If, somehow, the sturdy framework did not survive then - please proceed to panic; otherwise follow these steps.

                  -
                1. Secure the screws of the framework with a screwdriver.
                2. -
                3. Mend the damaged framework with a welding tool.
                4. -
                5. Add additional plasteel plating.
                6. -
                7. Secure the additional plating with a wrench.
                "} + +// +// Gravity Generator +// + +GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding new gravity generators to the list, and keying it with the z level. + +#define POWER_IDLE 0 +#define POWER_UP 1 +#define POWER_DOWN 2 + +#define GRAV_NEEDS_SCREWDRIVER 0 +#define GRAV_NEEDS_WELDING 1 +#define GRAV_NEEDS_PLASTEEL 2 +#define GRAV_NEEDS_WRENCH 3 + +// +// Abstract Generator +// + +/obj/machinery/gravity_generator + name = "gravitational generator" + desc = "A device which produces a graviton field when set up." + icon = 'icons/obj/machines/gravity_generator.dmi' + density = TRUE + use_power = NO_POWER_USE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/sprite_number = 0 + +/obj/machinery/gravity_generator/safe_throw_at() + return FALSE + +/obj/machinery/gravity_generator/ex_act(severity, target) + if(severity == 1) // Very sturdy. + set_broken() + +/obj/machinery/gravity_generator/blob_act(obj/structure/blob/B) + if(prob(20)) + set_broken() + +/obj/machinery/gravity_generator/tesla_act(power, tesla_flags) + ..() + if(tesla_flags & TESLA_MACHINE_EXPLOSIVE) + qdel(src)//like the singulo, tesla deletes it. stops it from exploding over and over + +/obj/machinery/gravity_generator/update_icon() + ..() + icon_state = "[get_status()]_[sprite_number]" + +/obj/machinery/gravity_generator/proc/get_status() + return "off" + +// You aren't allowed to move. +/obj/machinery/gravity_generator/Move() + . = ..() + qdel(src) + +/obj/machinery/gravity_generator/proc/set_broken() + stat |= BROKEN + +/obj/machinery/gravity_generator/proc/set_fix() + stat &= ~BROKEN + +/obj/machinery/gravity_generator/part/Destroy() + if(main_part) + qdel(main_part) + set_broken() + return ..() + +// +// Part generator which is mostly there for looks +// + +/obj/machinery/gravity_generator/part + var/obj/machinery/gravity_generator/main/main_part = null + +/obj/machinery/gravity_generator/part/attackby(obj/item/I, mob/user, params) + return main_part.attackby(I, user) + +/obj/machinery/gravity_generator/part/get_status() + return main_part.get_status() + +/obj/machinery/gravity_generator/part/attack_hand(mob/user) + return main_part.attack_hand(user) + +/obj/machinery/gravity_generator/part/set_broken() + ..() + if(main_part && !(main_part.stat & BROKEN)) + main_part.set_broken() + +// +// Generator which spawns with the station. +// + +/obj/machinery/gravity_generator/main/station/Initialize() + . = ..() + setup_parts() + middle.add_overlay("activated") + update_list() + +// +// Generator an admin can spawn +// +/obj/machinery/gravity_generator/main/station/admin + use_power = NO_POWER_USE + +// +// Main Generator with the main code +// + +/obj/machinery/gravity_generator/main + icon_state = "on_8" + idle_power_usage = 0 + active_power_usage = 3000 + power_channel = ENVIRON + sprite_number = 8 + use_power = IDLE_POWER_USE + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OFFLINE + var/on = TRUE + var/breaker = TRUE + var/list/parts = list() + var/obj/middle = null + var/charging_state = POWER_IDLE + var/charge_count = 100 + var/current_overlay = null + var/broken_state = 0 + var/setting = 1 //Gravity value when on + +/obj/machinery/gravity_generator/main/Destroy() // If we somehow get deleted, remove all of our other parts. + investigate_log("was destroyed!", INVESTIGATE_GRAVITY) + on = FALSE + update_list() + for(var/obj/machinery/gravity_generator/part/O in parts) + O.main_part = null + if(!QDESTROYING(O)) + qdel(O) + return ..() + +/obj/machinery/gravity_generator/main/proc/setup_parts() + var/turf/our_turf = get_turf(src) + // 9x9 block obtained from the bottom middle of the block + var/list/spawn_turfs = block(locate(our_turf.x - 1, our_turf.y + 2, our_turf.z), locate(our_turf.x + 1, our_turf.y, our_turf.z)) + var/count = 10 + for(var/turf/T in spawn_turfs) + count-- + if(T == our_turf) // Skip our turf. + continue + var/obj/machinery/gravity_generator/part/part = new(T) + if(count == 5) // Middle + middle = part + if(count <= 3) // Their sprite is the top part of the generator + part.density = FALSE + part.layer = WALL_OBJ_LAYER + part.sprite_number = count + part.main_part = src + parts += part + part.update_icon() + +/obj/machinery/gravity_generator/main/proc/connected_parts() + return parts.len == 8 + +/obj/machinery/gravity_generator/main/set_broken() + ..() + for(var/obj/machinery/gravity_generator/M in parts) + if(!(M.stat & BROKEN)) + M.set_broken() + middle.cut_overlays() + charge_count = 0 + breaker = FALSE + set_power() + set_state(0) + investigate_log("has broken down.", INVESTIGATE_GRAVITY) + +/obj/machinery/gravity_generator/main/set_fix() + ..() + for(var/obj/machinery/gravity_generator/M in parts) + if(M.stat & BROKEN) + M.set_fix() + broken_state = FALSE + update_icon() + set_power() + +// Interaction + +// Fixing the gravity generator. +/obj/machinery/gravity_generator/main/attackby(obj/item/I, mob/user, params) + switch(broken_state) + if(GRAV_NEEDS_SCREWDRIVER) + if(istype(I, /obj/item/screwdriver)) + to_chat(user, "You secure the screws of the framework.") + I.play_tool_sound(src) + broken_state++ + update_icon() + return + if(GRAV_NEEDS_WELDING) + if(istype(I, /obj/item/weldingtool)) + if(I.use_tool(src, user, 0, volume=50, amount=1)) + to_chat(user, "You mend the damaged framework.") + broken_state++ + update_icon() + return + if(GRAV_NEEDS_PLASTEEL) + if(istype(I, /obj/item/stack/sheet/plasteel)) + var/obj/item/stack/sheet/plasteel/PS = I + if(PS.get_amount() >= 10) + PS.use(10) + to_chat(user, "You add the plating to the framework.") + playsound(src.loc, 'sound/machines/click.ogg', 75, 1) + broken_state++ + update_icon() + else + to_chat(user, "You need 10 sheets of plasteel!") + return + if(GRAV_NEEDS_WRENCH) + if(istype(I, /obj/item/wrench)) + to_chat(user, "You secure the plating to the framework.") + I.play_tool_sound(src) + set_fix() + return + return ..() + +/obj/machinery/gravity_generator/main/ui_interact(mob/user) + if(stat & BROKEN) + return + var/dat = "Gravity Generator Breaker: " + if(breaker) + dat += "ON OFF" + else + dat += "ON OFF " + + dat += "
                Generator Status:
                " + if(charging_state != POWER_IDLE) + dat += "WARNING Radiation Detected.
                [charging_state == POWER_UP ? "Charging..." : "Discharging..."]" + else if(on) + dat += "Powered." + else + dat += "Unpowered." + + dat += "
                Gravity Charge: [charge_count]%
                " + + var/datum/browser/popup = new(user, "gravgen", name) + popup.set_content(dat) + popup.open() + + +/obj/machinery/gravity_generator/main/Topic(href, href_list) + + if(..()) + return + + if(href_list["gentoggle"]) + breaker = !breaker + investigate_log("was toggled [breaker ? "ON" : "OFF"] by [key_name(usr)].", INVESTIGATE_GRAVITY) + set_power() + src.updateUsrDialog() + +// Power and Icon States + +/obj/machinery/gravity_generator/main/power_change() + ..() + investigate_log("has [stat & NOPOWER ? "lost" : "regained"] power.", INVESTIGATE_GRAVITY) + set_power() + +/obj/machinery/gravity_generator/main/get_status() + if(stat & BROKEN) + return "fix[min(broken_state, 3)]" + return on || charging_state != POWER_IDLE ? "on" : "off" + +/obj/machinery/gravity_generator/main/update_icon() + ..() + for(var/obj/O in parts) + O.update_icon() + +// Set the charging state based on power/breaker. +/obj/machinery/gravity_generator/main/proc/set_power() + var/new_state = FALSE + if(stat & (NOPOWER|BROKEN) || !breaker) + new_state = FALSE + else if(breaker) + new_state = TRUE + + charging_state = new_state ? POWER_UP : POWER_DOWN // Startup sequence animation. + investigate_log("is now [charging_state == POWER_UP ? "charging" : "discharging"].", INVESTIGATE_GRAVITY) + update_icon() + +// Set the state of the gravity. +/obj/machinery/gravity_generator/main/proc/set_state(new_state) + charging_state = POWER_IDLE + on = new_state + use_power = on ? ACTIVE_POWER_USE : IDLE_POWER_USE + // Sound the alert if gravity was just enabled or disabled. + var/alert = FALSE + if(SSticker.IsRoundInProgress()) + if(on) // If we turned on and the game is live. + if(gravity_in_level() == FALSE) + alert = TRUE + investigate_log("was brought online and is now producing gravity for this level.", INVESTIGATE_GRAVITY) + message_admins("The gravity generator was brought online [ADMIN_VERBOSEJMP(src)]") + else + if(gravity_in_level() == TRUE) + alert = TRUE + investigate_log("was brought offline and there is now no gravity for this level.", INVESTIGATE_GRAVITY) + message_admins("The gravity generator was brought offline with no backup generator. [ADMIN_VERBOSEJMP(src)]") + + update_icon() + update_list() + src.updateUsrDialog() + if(alert) + shake_everyone() + +// Charge/Discharge and turn on/off gravity when you reach 0/100 percent. +// Also emit radiation and handle the overlays. +/obj/machinery/gravity_generator/main/process() + if(stat & BROKEN) + return + if(charging_state != POWER_IDLE) + if(charging_state == POWER_UP && charge_count >= 100) + set_state(1) + else if(charging_state == POWER_DOWN && charge_count <= 0) + set_state(0) + else + if(charging_state == POWER_UP) + charge_count += 2 + else if(charging_state == POWER_DOWN) + charge_count -= 2 + + if(charge_count % 4 == 0 && prob(75)) // Let them know it is charging/discharging. + playsound(src.loc, 'sound/effects/empulse.ogg', 100, 1) + + updateDialog() + if(prob(25)) // To help stop "Your clothes feel warm." spam. + pulse_radiation() + + var/overlay_state = null + switch(charge_count) + if(0 to 20) + overlay_state = null + if(21 to 40) + overlay_state = "startup" + if(41 to 60) + overlay_state = "idle" + if(61 to 80) + overlay_state = "activating" + if(81 to 100) + overlay_state = "activated" + + if(overlay_state != current_overlay) + if(middle) + middle.cut_overlays() + if(overlay_state) + middle.add_overlay(overlay_state) + current_overlay = overlay_state + + +/obj/machinery/gravity_generator/main/proc/pulse_radiation() + radiation_pulse(src, 200) + +// Shake everyone on the z level to let them know that gravity was enagaged/disenagaged. +/obj/machinery/gravity_generator/main/proc/shake_everyone() + var/turf/T = get_turf(src) + var/sound/alert_sound = sound('sound/effects/alert.ogg') + for(var/i in GLOB.mob_list) + var/mob/M = i + if(M.z != z && !(SSmapping.level_trait(z, ZTRAITS_STATION) && SSmapping.level_trait(M.z, ZTRAITS_STATION))) + continue + M.update_gravity(M.mob_has_gravity()) + if(M.client) + shake_camera(M, 15, 1) + M.playsound_local(T, null, 100, 1, 0.5, S = alert_sound) + +/obj/machinery/gravity_generator/main/proc/gravity_in_level() + var/turf/T = get_turf(src) + if(!T) + return FALSE + if(GLOB.gravity_generators["[T.z]"]) + return length(GLOB.gravity_generators["[T.z]"]) + return FALSE + +/obj/machinery/gravity_generator/main/proc/update_list() + var/turf/T = get_turf(src.loc) + if(T) + var/list/z_list = list() + // Multi-Z, station gravity generator generates gravity on all ZTRAIT_STATION z-levels. + if(SSmapping.level_trait(T.z, ZTRAIT_STATION)) + for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION)) + z_list += z + else + z_list += T.z + for(var/z in z_list) + if(!GLOB.gravity_generators["[z]"]) + GLOB.gravity_generators["[z]"] = list() + if(on) + GLOB.gravity_generators["[z]"] |= src + else + GLOB.gravity_generators["[z]"] -= src + +/obj/machinery/gravity_generator/main/proc/change_setting(value) + if(value != setting) + setting = value + shake_everyone() + +// Misc + +/obj/item/paper/guides/jobs/engi/gravity_gen + name = "paper- 'Generate your own gravity!'" + info = {"

                Gravity Generator Instructions For Dummies

                +

                Surprisingly, gravity isn't that hard to make! All you have to do is inject deadly radioactive minerals into a ball of + energy and you have yourself gravity! You can turn the machine on or off when required but you must remember that the generator + will EMIT RADIATION when charging or discharging, you can tell it is charging or discharging by the noise it makes, so please WEAR PROTECTIVE CLOTHING.

                +
                +

                It blew up!

                +

                Don't panic! The gravity generator was designed to be easily repaired. If, somehow, the sturdy framework did not survive then + please proceed to panic; otherwise follow these steps.

                  +
                1. Secure the screws of the framework with a screwdriver.
                2. +
                3. Mend the damaged framework with a welding tool.
                4. +
                5. Add additional plasteel plating.
                6. +
                7. Secure the additional plating with a wrench.
                "} diff --git a/code/modules/power/monitor.dm b/code/modules/power/monitor.dm index 28843e14e0..6b0ab6cbb2 100644 --- a/code/modules/power/monitor.dm +++ b/code/modules/power/monitor.dm @@ -1,121 +1,121 @@ -//modular computer program version is located in code\modules\modular_computers\file_system\programs\powermonitor.dm, /datum/computer_file/program/power_monitor - -/obj/machinery/computer/monitor - name = "power monitoring console" - desc = "It monitors power levels across the station." - icon_screen = "power" - icon_keyboard = "power_key" - light_color = LIGHT_COLOR_YELLOW - use_power = ACTIVE_POWER_USE - idle_power_usage = 20 - active_power_usage = 100 - circuit = /obj/item/circuitboard/computer/powermonitor - - var/obj/structure/cable/attached_wire - var/obj/machinery/power/apc/local_apc - - var/list/history = list() - var/record_size = 60 - var/record_interval = 50 - var/next_record = 0 - var/is_secret_monitor = FALSE - -/obj/machinery/computer/monitor/secret //Hides the power monitor (such as ones on ruins & CentCom) from PDA's to prevent metagaming. - name = "outdated power monitoring console" - desc = "It monitors power levels across the local powernet." - circuit = /obj/item/circuitboard/computer/powermonitor/secret - is_secret_monitor = TRUE - -/obj/machinery/computer/monitor/secret/examine(mob/user) - . = ..() - . += "It's operating system seems quite outdated... It doesn't seem like it'd be compatible with the latest remote NTOS monitoring systems." - -/obj/machinery/computer/monitor/Initialize() - . = ..() - search() - history["supply"] = list() - history["demand"] = list() - -/obj/machinery/computer/monitor/process() - if(!get_powernet()) - use_power = IDLE_POWER_USE - search() - else - use_power = ACTIVE_POWER_USE - record() - -/obj/machinery/computer/monitor/proc/search() //keep in sync with /datum/computer_file/program/power_monitor's version - var/turf/T = get_turf(src) - attached_wire = locate(/obj/structure/cable) in T - if(attached_wire) - return - var/area/A = get_area(src) //if the computer isn't directly connected to a wire, attempt to find the APC powering it to pull it's powernet instead - if(!A) - return - local_apc = A.get_apc() - if(!local_apc) - return - if(!local_apc.terminal) //this really shouldn't happen without badminnery. - local_apc = null - -/obj/machinery/computer/monitor/proc/get_powernet() //keep in sync with /datum/computer_file/program/power_monitor's version - if(attached_wire || (local_apc && local_apc.terminal)) - return attached_wire ? attached_wire.powernet : local_apc.terminal.powernet - return FALSE - -/obj/machinery/computer/monitor/proc/record() //keep in sync with /datum/computer_file/program/power_monitor's version - if(world.time >= next_record) - next_record = world.time + record_interval - - var/datum/powernet/connected_powernet = get_powernet() - - var/list/supply = history["supply"] - if(connected_powernet) - supply += connected_powernet.viewavail - if(supply.len > record_size) - supply.Cut(1, 2) - - var/list/demand = history["demand"] - if(connected_powernet) - demand += connected_powernet.viewload - if(demand.len > record_size) - demand.Cut(1, 2) - -/obj/machinery/computer/monitor/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, "power_monitor", name, 1200, 1000, master_ui, state) - ui.open() - -/obj/machinery/computer/monitor/ui_data() - var/datum/powernet/connected_powernet = get_powernet() - var/list/data = list() - data["stored"] = record_size - data["interval"] = record_interval / 10 - data["attached"] = connected_powernet ? TRUE : FALSE - data["history"] = history - data["areas"] = list() - - if(connected_powernet) - data["supply"] = DisplayPower(connected_powernet.viewavail) - data["demand"] = DisplayPower(connected_powernet.viewload) - for(var/obj/machinery/power/terminal/term in connected_powernet.nodes) - var/obj/machinery/power/apc/A = term.master - if(istype(A)) - var/cell_charge - if(!A.cell) - cell_charge = 0 - else - cell_charge = A.cell.percent() - data["areas"] += list(list( - "name" = A.area.name, - "charge" = cell_charge, - "load" = DisplayPower(A.lastused_total), - "charging" = A.charging, - "eqp" = A.equipment, - "lgt" = A.lighting, - "env" = A.environ - )) - - return data +//modular computer program version is located in code\modules\modular_computers\file_system\programs\powermonitor.dm, /datum/computer_file/program/power_monitor + +/obj/machinery/computer/monitor + name = "power monitoring console" + desc = "It monitors power levels across the station." + icon_screen = "power" + icon_keyboard = "power_key" + light_color = LIGHT_COLOR_YELLOW + use_power = ACTIVE_POWER_USE + idle_power_usage = 20 + active_power_usage = 100 + circuit = /obj/item/circuitboard/computer/powermonitor + + var/obj/structure/cable/attached_wire + var/obj/machinery/power/apc/local_apc + + var/list/history = list() + var/record_size = 60 + var/record_interval = 50 + var/next_record = 0 + var/is_secret_monitor = FALSE + +/obj/machinery/computer/monitor/secret //Hides the power monitor (such as ones on ruins & CentCom) from PDA's to prevent metagaming. + name = "outdated power monitoring console" + desc = "It monitors power levels across the local powernet." + circuit = /obj/item/circuitboard/computer/powermonitor/secret + is_secret_monitor = TRUE + +/obj/machinery/computer/monitor/secret/examine(mob/user) + . = ..() + . += "It's operating system seems quite outdated... It doesn't seem like it'd be compatible with the latest remote NTOS monitoring systems." + +/obj/machinery/computer/monitor/Initialize() + . = ..() + search() + history["supply"] = list() + history["demand"] = list() + +/obj/machinery/computer/monitor/process() + if(!get_powernet()) + use_power = IDLE_POWER_USE + search() + else + use_power = ACTIVE_POWER_USE + record() + +/obj/machinery/computer/monitor/proc/search() //keep in sync with /datum/computer_file/program/power_monitor's version + var/turf/T = get_turf(src) + attached_wire = locate(/obj/structure/cable) in T + if(attached_wire) + return + var/area/A = get_area(src) //if the computer isn't directly connected to a wire, attempt to find the APC powering it to pull it's powernet instead + if(!A) + return + local_apc = A.get_apc() + if(!local_apc) + return + if(!local_apc.terminal) //this really shouldn't happen without badminnery. + local_apc = null + +/obj/machinery/computer/monitor/proc/get_powernet() //keep in sync with /datum/computer_file/program/power_monitor's version + if(attached_wire || (local_apc && local_apc.terminal)) + return attached_wire ? attached_wire.powernet : local_apc.terminal.powernet + return FALSE + +/obj/machinery/computer/monitor/proc/record() //keep in sync with /datum/computer_file/program/power_monitor's version + if(world.time >= next_record) + next_record = world.time + record_interval + + var/datum/powernet/connected_powernet = get_powernet() + + var/list/supply = history["supply"] + if(connected_powernet) + supply += connected_powernet.viewavail + if(supply.len > record_size) + supply.Cut(1, 2) + + var/list/demand = history["demand"] + if(connected_powernet) + demand += connected_powernet.viewload + if(demand.len > record_size) + demand.Cut(1, 2) + +/obj/machinery/computer/monitor/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, "power_monitor", name, 1200, 1000, master_ui, state) + ui.open() + +/obj/machinery/computer/monitor/ui_data() + var/datum/powernet/connected_powernet = get_powernet() + var/list/data = list() + data["stored"] = record_size + data["interval"] = record_interval / 10 + data["attached"] = connected_powernet ? TRUE : FALSE + data["history"] = history + data["areas"] = list() + + if(connected_powernet) + data["supply"] = DisplayPower(connected_powernet.viewavail) + data["demand"] = DisplayPower(connected_powernet.viewload) + for(var/obj/machinery/power/terminal/term in connected_powernet.nodes) + var/obj/machinery/power/apc/A = term.master + if(istype(A)) + var/cell_charge + if(!A.cell) + cell_charge = 0 + else + cell_charge = A.cell.percent() + data["areas"] += list(list( + "name" = A.area.name, + "charge" = cell_charge, + "load" = DisplayPower(A.lastused_total), + "charging" = A.charging, + "eqp" = A.equipment, + "lgt" = A.lighting, + "env" = A.environ + )) + + return data diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm index a9cd97c331..9ae6c69847 100644 --- a/code/modules/power/port_gen.dm +++ b/code/modules/power/port_gen.dm @@ -1,287 +1,287 @@ - -//Baseline portable generator. Has all the default handling. Not intended to be used on it's own (since it generates unlimited power). -/obj/machinery/power/port_gen - name = "portable generator" - desc = "A portable generator for emergency backup power." - icon = 'icons/obj/power.dmi' - icon_state = "portgen0_0" - density = TRUE - anchored = FALSE - use_power = NO_POWER_USE - - var/active = 0 - var/power_gen = 5000 - var/recent_fault = 0 - var/power_output = 1 - var/consumption = 0 - var/base_icon = "portgen0" - var/datum/looping_sound/generator/soundloop - - interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT | INTERACT_ATOM_REQUIRES_ANCHORED - -/obj/machinery/power/port_gen/Initialize() - . = ..() - soundloop = new(list(src), active) - -/obj/machinery/power/port_gen/Destroy() - QDEL_NULL(soundloop) - return ..() - -/obj/machinery/power/port_gen/proc/HasFuel() //Placeholder for fuel check. - return 1 - -/obj/machinery/power/port_gen/proc/UseFuel() //Placeholder for fuel use. - return - -/obj/machinery/power/port_gen/proc/DropFuel() - return - -/obj/machinery/power/port_gen/proc/handleInactive() - return - -/obj/machinery/power/port_gen/update_icon() - icon_state = "[base_icon]_[active]" - -/obj/machinery/power/port_gen/process() - if(active && HasFuel() && !crit_fail && anchored && powernet) - add_avail(power_gen * power_output) - UseFuel() - src.updateDialog() - soundloop.start() - - else - active = 0 - handleInactive() - update_icon() - soundloop.stop() - -/obj/machinery/power/port_gen/examine(mob/user) - . = ..() - . += "It is[!active?"n't":""] running." - -/obj/machinery/power/port_gen/pacman - name = "\improper P.A.C.M.A.N.-type portable generator" - circuit = /obj/item/circuitboard/machine/pacman - var/sheets = 0 - var/max_sheets = 100 - var/sheet_name = "" - var/sheet_path = /obj/item/stack/sheet/mineral/plasma - var/sheet_left = 0 // How much is left of the sheet - var/time_per_sheet = 260 - var/current_heat = 0 - -/obj/machinery/power/port_gen/pacman/Initialize() - . = ..() - if(anchored) - connect_to_network() - -/obj/machinery/power/port_gen/pacman/Initialize() - . = ..() - - var/obj/sheet = new sheet_path(null) - sheet_name = sheet.name - -/obj/machinery/power/port_gen/pacman/Destroy() - DropFuel() - return ..() - -/obj/machinery/power/port_gen/pacman/RefreshParts() - var/temp_rating = 0 - var/consumption_coeff = 0 - for(var/obj/item/stock_parts/SP in component_parts) - if(istype(SP, /obj/item/stock_parts/matter_bin)) - max_sheets = SP.rating * SP.rating * 50 - else if(istype(SP, /obj/item/stock_parts/capacitor)) - temp_rating += SP.rating - else - consumption_coeff += SP.rating - power_gen = round(initial(power_gen) * temp_rating * 2) - consumption = consumption_coeff - -/obj/machinery/power/port_gen/pacman/examine(mob/user) - . = ..() - . += "The generator has [sheets] units of [sheet_name] fuel left, producing [power_gen] per cycle." - if(crit_fail) - . += "The generator seems to have broken down." - -/obj/machinery/power/port_gen/pacman/HasFuel() - if(sheets >= 1 / (time_per_sheet / power_output) - sheet_left) - return 1 - return 0 - -/obj/machinery/power/port_gen/pacman/DropFuel() - if(sheets) - new sheet_path(drop_location(), sheets) - sheets = 0 - -/obj/machinery/power/port_gen/pacman/UseFuel() - var/needed_sheets = 1 / (time_per_sheet * consumption / power_output) - var/temp = min(needed_sheets, sheet_left) - needed_sheets -= temp - sheet_left -= temp - sheets -= round(needed_sheets) - needed_sheets -= round(needed_sheets) - if (sheet_left <= 0 && sheets > 0) - sheet_left = 1 - needed_sheets - sheets-- - - var/lower_limit = 56 + power_output * 10 - var/upper_limit = 76 + power_output * 10 - var/bias = 0 - if (power_output > 4) - upper_limit = 400 - bias = power_output - consumption * (4 - consumption) - if (current_heat < lower_limit) - current_heat += 4 - consumption - else - current_heat += rand(-7 + bias, 7 + bias) - if (current_heat < lower_limit) - current_heat = lower_limit - if (current_heat > upper_limit) - current_heat = upper_limit - - if (current_heat > 300) - overheat() - qdel(src) - return - -/obj/machinery/power/port_gen/pacman/handleInactive() - - if (current_heat > 0) - current_heat = max(current_heat - 2, 0) - src.updateDialog() - -/obj/machinery/power/port_gen/pacman/proc/overheat() - explosion(src.loc, 2, 5, 2, -1) - -/obj/machinery/power/port_gen/pacman/attackby(obj/item/O, mob/user, params) - if(istype(O, sheet_path)) - var/obj/item/stack/addstack = O - var/amount = min((max_sheets - sheets), addstack.amount) - if(amount < 1) - to_chat(user, "The [src.name] is full!") - return - to_chat(user, "You add [amount] sheets to the [src.name].") - sheets += amount - addstack.use(amount) - updateUsrDialog() - return - else if(!active) - - if(istype(O, /obj/item/wrench)) - - if(!anchored && !isinspace()) - connect_to_network() - to_chat(user, "You secure the generator to the floor.") - anchored = TRUE - else if(anchored) - disconnect_from_network() - to_chat(user, "You unsecure the generator from the floor.") - anchored = FALSE - - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) - return - else if(istype(O, /obj/item/screwdriver)) - panel_open = !panel_open - O.play_tool_sound(src) - if(panel_open) - to_chat(user, "You open the access panel.") - else - to_chat(user, "You close the access panel.") - return - else if(default_deconstruction_crowbar(O)) - return - return ..() - -/obj/machinery/power/port_gen/pacman/emag_act(mob/user) - . = ..() - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - emp_act(EMP_HEAVY) - return TRUE - -/obj/machinery/power/port_gen/pacman/attack_ai(mob/user) - interact(user) - -/obj/machinery/power/port_gen/pacman/attack_paw(mob/user) - interact(user) - -/obj/machinery/power/port_gen/pacman/ui_interact(mob/user) - . = ..() - if (get_dist(src, user) > 1 ) - if(!isAI(user)) - user.unset_machine() - user << browse(null, "window=port_gen") - return - - var/dat = text("[name]
                ") - if (active) - dat += text("Generator: On
                ") - else - dat += text("Generator: Off
                ") - dat += text("[capitalize(sheet_name)]: [sheets] - Eject
                ") - var/stack_percent = round(sheet_left * 100, 1) - dat += text("Current stack: [stack_percent]%
                ") - dat += text("Power output: - [power_gen * power_output] +
                ") - dat += text("Power current: [(powernet == null ? "Unconnected" : "[DisplayPower(avail())]")]
                ") - dat += text("Heat: [current_heat]
                ") - dat += "
                Close" - user << browse(dat, "window=port_gen") - onclose(user, "port_gen") - -/obj/machinery/power/port_gen/pacman/Topic(href, href_list) - if(..()) - return - - src.add_fingerprint(usr) - if(href_list["action"]) - if(href_list["action"] == "enable") - if(!active && HasFuel() && !crit_fail) - active = 1 - src.updateUsrDialog() - update_icon() - if(href_list["action"] == "disable") - if (active) - active = 0 - src.updateUsrDialog() - update_icon() - if(href_list["action"] == "eject") - if(!active) - DropFuel() - src.updateUsrDialog() - if(href_list["action"] == "lower_power") - if (power_output > 1) - power_output-- - src.updateUsrDialog() - if (href_list["action"] == "higher_power") - if (power_output < 4 || (obj_flags & EMAGGED)) - power_output++ - src.updateUsrDialog() - if (href_list["action"] == "close") - usr << browse(null, "window=port_gen") - usr.unset_machine() - -/obj/machinery/power/port_gen/pacman/super - name = "\improper S.U.P.E.R.P.A.C.M.A.N.-type portable generator" - icon_state = "portgen1_0" - base_icon = "portgen1" - circuit = /obj/item/circuitboard/machine/pacman/super - sheet_path = /obj/item/stack/sheet/mineral/uranium - power_gen = 15000 - time_per_sheet = 85 - -/obj/machinery/power/port_gen/pacman/super/overheat() - explosion(src.loc, 3, 3, 3, -1) - -/obj/machinery/power/port_gen/pacman/mrs - name = "\improper M.R.S.P.A.C.M.A.N.-type portable generator" - base_icon = "portgen2" - icon_state = "portgen2_0" - circuit = /obj/item/circuitboard/machine/pacman/mrs - sheet_path = /obj/item/stack/sheet/mineral/diamond - power_gen = 40000 - time_per_sheet = 80 - -/obj/machinery/power/port_gen/pacman/mrs/overheat() - explosion(src.loc, 4, 4, 4, -1) + +//Baseline portable generator. Has all the default handling. Not intended to be used on it's own (since it generates unlimited power). +/obj/machinery/power/port_gen + name = "portable generator" + desc = "A portable generator for emergency backup power." + icon = 'icons/obj/power.dmi' + icon_state = "portgen0_0" + density = TRUE + anchored = FALSE + use_power = NO_POWER_USE + + var/active = 0 + var/power_gen = 5000 + var/recent_fault = 0 + var/power_output = 1 + var/consumption = 0 + var/base_icon = "portgen0" + var/datum/looping_sound/generator/soundloop + + interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT | INTERACT_ATOM_REQUIRES_ANCHORED + +/obj/machinery/power/port_gen/Initialize() + . = ..() + soundloop = new(list(src), active) + +/obj/machinery/power/port_gen/Destroy() + QDEL_NULL(soundloop) + return ..() + +/obj/machinery/power/port_gen/proc/HasFuel() //Placeholder for fuel check. + return 1 + +/obj/machinery/power/port_gen/proc/UseFuel() //Placeholder for fuel use. + return + +/obj/machinery/power/port_gen/proc/DropFuel() + return + +/obj/machinery/power/port_gen/proc/handleInactive() + return + +/obj/machinery/power/port_gen/update_icon() + icon_state = "[base_icon]_[active]" + +/obj/machinery/power/port_gen/process() + if(active && HasFuel() && !crit_fail && anchored && powernet) + add_avail(power_gen * power_output) + UseFuel() + src.updateDialog() + soundloop.start() + + else + active = 0 + handleInactive() + update_icon() + soundloop.stop() + +/obj/machinery/power/port_gen/examine(mob/user) + . = ..() + . += "It is[!active?"n't":""] running." + +/obj/machinery/power/port_gen/pacman + name = "\improper P.A.C.M.A.N.-type portable generator" + circuit = /obj/item/circuitboard/machine/pacman + var/sheets = 0 + var/max_sheets = 100 + var/sheet_name = "" + var/sheet_path = /obj/item/stack/sheet/mineral/plasma + var/sheet_left = 0 // How much is left of the sheet + var/time_per_sheet = 260 + var/current_heat = 0 + +/obj/machinery/power/port_gen/pacman/Initialize() + . = ..() + if(anchored) + connect_to_network() + +/obj/machinery/power/port_gen/pacman/Initialize() + . = ..() + + var/obj/sheet = new sheet_path(null) + sheet_name = sheet.name + +/obj/machinery/power/port_gen/pacman/Destroy() + DropFuel() + return ..() + +/obj/machinery/power/port_gen/pacman/RefreshParts() + var/temp_rating = 0 + var/consumption_coeff = 0 + for(var/obj/item/stock_parts/SP in component_parts) + if(istype(SP, /obj/item/stock_parts/matter_bin)) + max_sheets = SP.rating * SP.rating * 50 + else if(istype(SP, /obj/item/stock_parts/capacitor)) + temp_rating += SP.rating + else + consumption_coeff += SP.rating + power_gen = round(initial(power_gen) * temp_rating * 2) + consumption = consumption_coeff + +/obj/machinery/power/port_gen/pacman/examine(mob/user) + . = ..() + . += "The generator has [sheets] units of [sheet_name] fuel left, producing [power_gen] per cycle." + if(crit_fail) + . += "The generator seems to have broken down." + +/obj/machinery/power/port_gen/pacman/HasFuel() + if(sheets >= 1 / (time_per_sheet / power_output) - sheet_left) + return 1 + return 0 + +/obj/machinery/power/port_gen/pacman/DropFuel() + if(sheets) + new sheet_path(drop_location(), sheets) + sheets = 0 + +/obj/machinery/power/port_gen/pacman/UseFuel() + var/needed_sheets = 1 / (time_per_sheet * consumption / power_output) + var/temp = min(needed_sheets, sheet_left) + needed_sheets -= temp + sheet_left -= temp + sheets -= round(needed_sheets) + needed_sheets -= round(needed_sheets) + if (sheet_left <= 0 && sheets > 0) + sheet_left = 1 - needed_sheets + sheets-- + + var/lower_limit = 56 + power_output * 10 + var/upper_limit = 76 + power_output * 10 + var/bias = 0 + if (power_output > 4) + upper_limit = 400 + bias = power_output - consumption * (4 - consumption) + if (current_heat < lower_limit) + current_heat += 4 - consumption + else + current_heat += rand(-7 + bias, 7 + bias) + if (current_heat < lower_limit) + current_heat = lower_limit + if (current_heat > upper_limit) + current_heat = upper_limit + + if (current_heat > 300) + overheat() + qdel(src) + return + +/obj/machinery/power/port_gen/pacman/handleInactive() + + if (current_heat > 0) + current_heat = max(current_heat - 2, 0) + src.updateDialog() + +/obj/machinery/power/port_gen/pacman/proc/overheat() + explosion(src.loc, 2, 5, 2, -1) + +/obj/machinery/power/port_gen/pacman/attackby(obj/item/O, mob/user, params) + if(istype(O, sheet_path)) + var/obj/item/stack/addstack = O + var/amount = min((max_sheets - sheets), addstack.amount) + if(amount < 1) + to_chat(user, "The [src.name] is full!") + return + to_chat(user, "You add [amount] sheets to the [src.name].") + sheets += amount + addstack.use(amount) + updateUsrDialog() + return + else if(!active) + + if(istype(O, /obj/item/wrench)) + + if(!anchored && !isinspace()) + connect_to_network() + to_chat(user, "You secure the generator to the floor.") + anchored = TRUE + else if(anchored) + disconnect_from_network() + to_chat(user, "You unsecure the generator from the floor.") + anchored = FALSE + + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + return + else if(istype(O, /obj/item/screwdriver)) + panel_open = !panel_open + O.play_tool_sound(src) + if(panel_open) + to_chat(user, "You open the access panel.") + else + to_chat(user, "You close the access panel.") + return + else if(default_deconstruction_crowbar(O)) + return + return ..() + +/obj/machinery/power/port_gen/pacman/emag_act(mob/user) + . = ..() + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + emp_act(EMP_HEAVY) + return TRUE + +/obj/machinery/power/port_gen/pacman/attack_ai(mob/user) + interact(user) + +/obj/machinery/power/port_gen/pacman/attack_paw(mob/user) + interact(user) + +/obj/machinery/power/port_gen/pacman/ui_interact(mob/user) + . = ..() + if (get_dist(src, user) > 1 ) + if(!isAI(user)) + user.unset_machine() + user << browse(null, "window=port_gen") + return + + var/dat = text("[name]
                ") + if (active) + dat += text("Generator: On
                ") + else + dat += text("Generator: Off
                ") + dat += text("[capitalize(sheet_name)]: [sheets] - Eject
                ") + var/stack_percent = round(sheet_left * 100, 1) + dat += text("Current stack: [stack_percent]%
                ") + dat += text("Power output: - [power_gen * power_output] +
                ") + dat += text("Power current: [(powernet == null ? "Unconnected" : "[DisplayPower(avail())]")]
                ") + dat += text("Heat: [current_heat]
                ") + dat += "
                Close" + user << browse(dat, "window=port_gen") + onclose(user, "port_gen") + +/obj/machinery/power/port_gen/pacman/Topic(href, href_list) + if(..()) + return + + src.add_fingerprint(usr) + if(href_list["action"]) + if(href_list["action"] == "enable") + if(!active && HasFuel() && !crit_fail) + active = 1 + src.updateUsrDialog() + update_icon() + if(href_list["action"] == "disable") + if (active) + active = 0 + src.updateUsrDialog() + update_icon() + if(href_list["action"] == "eject") + if(!active) + DropFuel() + src.updateUsrDialog() + if(href_list["action"] == "lower_power") + if (power_output > 1) + power_output-- + src.updateUsrDialog() + if (href_list["action"] == "higher_power") + if (power_output < 4 || (obj_flags & EMAGGED)) + power_output++ + src.updateUsrDialog() + if (href_list["action"] == "close") + usr << browse(null, "window=port_gen") + usr.unset_machine() + +/obj/machinery/power/port_gen/pacman/super + name = "\improper S.U.P.E.R.P.A.C.M.A.N.-type portable generator" + icon_state = "portgen1_0" + base_icon = "portgen1" + circuit = /obj/item/circuitboard/machine/pacman/super + sheet_path = /obj/item/stack/sheet/mineral/uranium + power_gen = 15000 + time_per_sheet = 85 + +/obj/machinery/power/port_gen/pacman/super/overheat() + explosion(src.loc, 3, 3, 3, -1) + +/obj/machinery/power/port_gen/pacman/mrs + name = "\improper M.R.S.P.A.C.M.A.N.-type portable generator" + base_icon = "portgen2" + icon_state = "portgen2_0" + circuit = /obj/item/circuitboard/machine/pacman/mrs + sheet_path = /obj/item/stack/sheet/mineral/diamond + power_gen = 40000 + time_per_sheet = 80 + +/obj/machinery/power/port_gen/pacman/mrs/overheat() + explosion(src.loc, 4, 4, 4, -1) diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm index 91a2bc515f..0375a00c39 100644 --- a/code/modules/power/singularity/collector.dm +++ b/code/modules/power/singularity/collector.dm @@ -1,232 +1,232 @@ -// stored_power += (pulse_strength-RAD_COLLECTOR_EFFICIENCY)*RAD_COLLECTOR_COEFFICIENT -#define RAD_COLLECTOR_EFFICIENCY 80 // radiation needs to be over this amount to get power -#define RAD_COLLECTOR_COEFFICIENT 100 -#define RAD_COLLECTOR_STORED_OUT 0.04 // (this*100)% of stored power outputted per tick. Doesn't actualy change output total, lower numbers just means collectors output for longer in absence of a source -#define RAD_COLLECTOR_MINING_CONVERSION_RATE 0.00001 //This is gonna need a lot of tweaking to get right. This is the number used to calculate the conversion of watts to research points per process() -#define RAD_COLLECTOR_OUTPUT min(stored_power, (stored_power*RAD_COLLECTOR_STORED_OUT)+1000) //Produces at least 1000 watts if it has more than that stored - -/obj/machinery/power/rad_collector - name = "Radiation Collector Array" - desc = "A device which uses Hawking Radiation and plasma to produce power." - icon = 'icons/obj/singularity.dmi' - icon_state = "ca" - anchored = FALSE - density = TRUE - req_access = list(ACCESS_ENGINE_EQUIP) -// use_power = NO_POWER_USE - max_integrity = 350 - integrity_failure = 80 - circuit = /obj/item/circuitboard/machine/rad_collector - var/obj/item/tank/internals/plasma/loaded_tank = null - var/stored_power = 0 - var/last_push - var/active = 0 - var/locked = FALSE - var/drainratio = 1 - var/powerproduction_drain = 0.001 - - var/bitcoinproduction_drain = 0.15 - var/bitcoinmining = FALSE - rad_insulation = RAD_EXTREME_INSULATION - -/obj/machinery/power/rad_collector/anchored - anchored = TRUE - -/obj/machinery/power/rad_collector/Destroy() - return ..() - -/obj/machinery/power/rad_collector/process() - if(!loaded_tank) - return - if(!bitcoinmining) - if(!loaded_tank.air_contents.gases[/datum/gas/plasma]) - investigate_log("out of fuel.", INVESTIGATE_SINGULO) - playsound(src, 'sound/machines/ding.ogg', 50, 1) - eject() - else - var/gasdrained = min(powerproduction_drain*drainratio,loaded_tank.air_contents.gases[/datum/gas/plasma]) - loaded_tank.air_contents.gases[/datum/gas/plasma] -= 2.7 * gasdrained - loaded_tank.air_contents.gases[/datum/gas/tritium] += 2.7 * gasdrained - GAS_GARBAGE_COLLECT(loaded_tank.air_contents.gases) - - var/power_produced = RAD_COLLECTOR_OUTPUT - add_avail(power_produced) - stored_power-=power_produced - else if(is_station_level(z) && SSresearch.science_tech) - if(!loaded_tank.air_contents.gases[/datum/gas/tritium] || !loaded_tank.air_contents.gases[/datum/gas/oxygen]) - playsound(src, 'sound/machines/ding.ogg', 50, 1) - eject() - else - var/gasdrained = bitcoinproduction_drain*drainratio - loaded_tank.air_contents.gases[/datum/gas/tritium] -= gasdrained - loaded_tank.air_contents.gases[/datum/gas/oxygen] -= gasdrained - loaded_tank.air_contents.gases[/datum/gas/carbon_dioxide] += gasdrained*2 - GAS_GARBAGE_COLLECT(loaded_tank.air_contents.gases) - SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, stored_power*RAD_COLLECTOR_MINING_CONVERSION_RATE) - last_push = stored_power - stored_power = 0 - -/obj/machinery/power/rad_collector/interact(mob/user) - if(anchored) - if(!src.locked) - toggle_power() - user.visible_message("[user.name] turns the [src.name] [active? "on":"off"].", \ - "You turn the [src.name] [active? "on":"off"].") - var/fuel - if(loaded_tank) - fuel = loaded_tank.air_contents.gases[/datum/gas/plasma] - investigate_log("turned [active?"on":"off"] by [key_name(user)]. [loaded_tank?"Fuel: [round(fuel/0.29)]%":"It is empty"].", INVESTIGATE_SINGULO) - return - else - to_chat(user, "The controls are locked!") - return - -/obj/machinery/power/rad_collector/can_be_unfasten_wrench(mob/user, silent) - if(loaded_tank) - if(!silent) - to_chat(user, "Remove the plasma tank first!") - return FAILED_UNFASTEN - return ..() - -/obj/machinery/power/rad_collector/default_unfasten_wrench(mob/user, obj/item/I, time = 20) - . = ..() - if(. == SUCCESSFUL_UNFASTEN) - if(anchored) - connect_to_network() - else - disconnect_from_network() - -/obj/machinery/power/rad_collector/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/tank/internals/plasma)) - if(!anchored) - to_chat(user, "[src] needs to be secured to the floor first!") - return TRUE - if(loaded_tank) - to_chat(user, "There's already a plasma tank loaded!") - return TRUE - if(panel_open) - to_chat(user, "Close the maintenance panel first!") - return TRUE - if(!user.transferItemToLoc(W, src)) - return - loaded_tank = W - update_icon() - else if(W.GetID()) - if(allowed(user)) - if(active) - locked = !locked - to_chat(user, "You [locked ? "lock" : "unlock"] the controls.") - else - to_chat(user, "The controls can only be locked when \the [src] is active!") - else - to_chat(user, "Access denied.") - return TRUE - else - return ..() - -/obj/machinery/power/rad_collector/wrench_act(mob/living/user, obj/item/I) - default_unfasten_wrench(user, I) - return TRUE - -/obj/machinery/power/rad_collector/screwdriver_act(mob/living/user, obj/item/I) - if(..()) - return TRUE - if(loaded_tank) - to_chat(user, "Remove the plasma tank first!") - else - default_deconstruction_screwdriver(user, icon_state, icon_state, I) - return TRUE - -/obj/machinery/power/rad_collector/crowbar_act(mob/living/user, obj/item/I) - if(loaded_tank) - if(locked) - to_chat(user, "The controls are locked!") - return TRUE - eject() - return TRUE - if(default_deconstruction_crowbar(I)) - return TRUE - to_chat(user, "There isn't a tank loaded!") - return TRUE - -/obj/machinery/power/rad_collector/multitool_act(mob/living/user, obj/item/I) - if(!is_station_level(z) && !SSresearch.science_tech) - to_chat(user, "[src] isn't linked to a research system!") - return TRUE - if(locked) - to_chat(user, "[src] is locked!") - return TRUE - if(active) - to_chat(user, "[src] is currently active, producing [bitcoinmining ? "research points":"power"].") - return TRUE - bitcoinmining = !bitcoinmining - to_chat(user, "You [bitcoinmining ? "enable":"disable"] the research point production feature of [src].") - return TRUE - -/obj/machinery/power/rad_collector/analyzer_act(mob/living/user, obj/item/I) - if(loaded_tank) - loaded_tank.analyzer_act(user, I) - -/obj/machinery/power/rad_collector/examine(mob/user) - . = ..() - if(active) - if(!bitcoinmining) - . += "[src]'s display states that it has stored [DisplayPower(stored_power)], and is processing [DisplayPower((RAD_COLLECTOR_OUTPUT)*((60 SECONDS)/SSmachines.wait))] per minute.
                The plasma within it's tank is being irradiated into tritium.
                " - else - . += "[src]'s display states that it's producing a total of [(last_push*RAD_COLLECTOR_MINING_CONVERSION_RATE)*((60 SECONDS)/SSmachines.wait)] research points per minute.
                The tritium and oxygen within it's tank is being combusted into carbon dioxide.
                " - else - if(!bitcoinmining) - . += "[src]'s display displays the words: \"Power production mode. Please insert Plasma. Use a multitool to change production modes.\"" - else - . += "[src]'s display displays the words: \"Research point production mode. Please insert Tritium and Oxygen. Use a multitool to change production modes.\"" - -/obj/machinery/power/rad_collector/obj_break(damage_flag) - if(!(stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1)) - eject() - stat |= BROKEN - -/obj/machinery/power/rad_collector/proc/eject() - locked = FALSE - var/obj/item/tank/internals/plasma/Z = src.loaded_tank - if (!Z) - return - Z.forceMove(drop_location()) - Z.layer = initial(Z.layer) - Z.plane = initial(Z.plane) - src.loaded_tank = null - if(active) - toggle_power() - else - update_icon() - -/obj/machinery/power/rad_collector/rad_act(pulse_strength) - . = ..() - if(loaded_tank && active && pulse_strength > RAD_COLLECTOR_EFFICIENCY) - stored_power += (pulse_strength-RAD_COLLECTOR_EFFICIENCY)*RAD_COLLECTOR_COEFFICIENT - -/obj/machinery/power/rad_collector/update_icon() - cut_overlays() - if(loaded_tank) - add_overlay("ptank") - if(stat & (NOPOWER|BROKEN)) - return - if(active) - add_overlay("on") - - -/obj/machinery/power/rad_collector/proc/toggle_power() - active = !active - if(active) - icon_state = "ca_on" - flick("ca_active", src) - else - icon_state = "ca" - flick("ca_deactive", src) - update_icon() - return - -#undef RAD_COLLECTOR_EFFICIENCY -#undef RAD_COLLECTOR_COEFFICIENT -#undef RAD_COLLECTOR_STORED_OUT -#undef RAD_COLLECTOR_MINING_CONVERSION_RATE -#undef RAD_COLLECTOR_OUTPUT +// stored_power += (pulse_strength-RAD_COLLECTOR_EFFICIENCY)*RAD_COLLECTOR_COEFFICIENT +#define RAD_COLLECTOR_EFFICIENCY 80 // radiation needs to be over this amount to get power +#define RAD_COLLECTOR_COEFFICIENT 100 +#define RAD_COLLECTOR_STORED_OUT 0.04 // (this*100)% of stored power outputted per tick. Doesn't actualy change output total, lower numbers just means collectors output for longer in absence of a source +#define RAD_COLLECTOR_MINING_CONVERSION_RATE 0.00001 //This is gonna need a lot of tweaking to get right. This is the number used to calculate the conversion of watts to research points per process() +#define RAD_COLLECTOR_OUTPUT min(stored_power, (stored_power*RAD_COLLECTOR_STORED_OUT)+1000) //Produces at least 1000 watts if it has more than that stored + +/obj/machinery/power/rad_collector + name = "Radiation Collector Array" + desc = "A device which uses Hawking Radiation and plasma to produce power." + icon = 'icons/obj/singularity.dmi' + icon_state = "ca" + anchored = FALSE + density = TRUE + req_access = list(ACCESS_ENGINE_EQUIP) +// use_power = NO_POWER_USE + max_integrity = 350 + integrity_failure = 80 + circuit = /obj/item/circuitboard/machine/rad_collector + var/obj/item/tank/internals/plasma/loaded_tank = null + var/stored_power = 0 + var/last_push + var/active = 0 + var/locked = FALSE + var/drainratio = 1 + var/powerproduction_drain = 0.001 + + var/bitcoinproduction_drain = 0.15 + var/bitcoinmining = FALSE + rad_insulation = RAD_EXTREME_INSULATION + +/obj/machinery/power/rad_collector/anchored + anchored = TRUE + +/obj/machinery/power/rad_collector/Destroy() + return ..() + +/obj/machinery/power/rad_collector/process() + if(!loaded_tank) + return + if(!bitcoinmining) + if(!loaded_tank.air_contents.gases[/datum/gas/plasma]) + investigate_log("out of fuel.", INVESTIGATE_SINGULO) + playsound(src, 'sound/machines/ding.ogg', 50, 1) + eject() + else + var/gasdrained = min(powerproduction_drain*drainratio,loaded_tank.air_contents.gases[/datum/gas/plasma]) + loaded_tank.air_contents.gases[/datum/gas/plasma] -= 2.7 * gasdrained + loaded_tank.air_contents.gases[/datum/gas/tritium] += 2.7 * gasdrained + GAS_GARBAGE_COLLECT(loaded_tank.air_contents.gases) + + var/power_produced = RAD_COLLECTOR_OUTPUT + add_avail(power_produced) + stored_power-=power_produced + else if(is_station_level(z) && SSresearch.science_tech) + if(!loaded_tank.air_contents.gases[/datum/gas/tritium] || !loaded_tank.air_contents.gases[/datum/gas/oxygen]) + playsound(src, 'sound/machines/ding.ogg', 50, 1) + eject() + else + var/gasdrained = bitcoinproduction_drain*drainratio + loaded_tank.air_contents.gases[/datum/gas/tritium] -= gasdrained + loaded_tank.air_contents.gases[/datum/gas/oxygen] -= gasdrained + loaded_tank.air_contents.gases[/datum/gas/carbon_dioxide] += gasdrained*2 + GAS_GARBAGE_COLLECT(loaded_tank.air_contents.gases) + SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, stored_power*RAD_COLLECTOR_MINING_CONVERSION_RATE) + last_push = stored_power + stored_power = 0 + +/obj/machinery/power/rad_collector/interact(mob/user) + if(anchored) + if(!src.locked) + toggle_power() + user.visible_message("[user.name] turns the [src.name] [active? "on":"off"].", \ + "You turn the [src.name] [active? "on":"off"].") + var/fuel + if(loaded_tank) + fuel = loaded_tank.air_contents.gases[/datum/gas/plasma] + investigate_log("turned [active?"on":"off"] by [key_name(user)]. [loaded_tank?"Fuel: [round(fuel/0.29)]%":"It is empty"].", INVESTIGATE_SINGULO) + return + else + to_chat(user, "The controls are locked!") + return + +/obj/machinery/power/rad_collector/can_be_unfasten_wrench(mob/user, silent) + if(loaded_tank) + if(!silent) + to_chat(user, "Remove the plasma tank first!") + return FAILED_UNFASTEN + return ..() + +/obj/machinery/power/rad_collector/default_unfasten_wrench(mob/user, obj/item/I, time = 20) + . = ..() + if(. == SUCCESSFUL_UNFASTEN) + if(anchored) + connect_to_network() + else + disconnect_from_network() + +/obj/machinery/power/rad_collector/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/tank/internals/plasma)) + if(!anchored) + to_chat(user, "[src] needs to be secured to the floor first!") + return TRUE + if(loaded_tank) + to_chat(user, "There's already a plasma tank loaded!") + return TRUE + if(panel_open) + to_chat(user, "Close the maintenance panel first!") + return TRUE + if(!user.transferItemToLoc(W, src)) + return + loaded_tank = W + update_icon() + else if(W.GetID()) + if(allowed(user)) + if(active) + locked = !locked + to_chat(user, "You [locked ? "lock" : "unlock"] the controls.") + else + to_chat(user, "The controls can only be locked when \the [src] is active!") + else + to_chat(user, "Access denied.") + return TRUE + else + return ..() + +/obj/machinery/power/rad_collector/wrench_act(mob/living/user, obj/item/I) + default_unfasten_wrench(user, I) + return TRUE + +/obj/machinery/power/rad_collector/screwdriver_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + if(loaded_tank) + to_chat(user, "Remove the plasma tank first!") + else + default_deconstruction_screwdriver(user, icon_state, icon_state, I) + return TRUE + +/obj/machinery/power/rad_collector/crowbar_act(mob/living/user, obj/item/I) + if(loaded_tank) + if(locked) + to_chat(user, "The controls are locked!") + return TRUE + eject() + return TRUE + if(default_deconstruction_crowbar(I)) + return TRUE + to_chat(user, "There isn't a tank loaded!") + return TRUE + +/obj/machinery/power/rad_collector/multitool_act(mob/living/user, obj/item/I) + if(!is_station_level(z) && !SSresearch.science_tech) + to_chat(user, "[src] isn't linked to a research system!") + return TRUE + if(locked) + to_chat(user, "[src] is locked!") + return TRUE + if(active) + to_chat(user, "[src] is currently active, producing [bitcoinmining ? "research points":"power"].") + return TRUE + bitcoinmining = !bitcoinmining + to_chat(user, "You [bitcoinmining ? "enable":"disable"] the research point production feature of [src].") + return TRUE + +/obj/machinery/power/rad_collector/analyzer_act(mob/living/user, obj/item/I) + if(loaded_tank) + loaded_tank.analyzer_act(user, I) + +/obj/machinery/power/rad_collector/examine(mob/user) + . = ..() + if(active) + if(!bitcoinmining) + . += "[src]'s display states that it has stored [DisplayPower(stored_power)], and is processing [DisplayPower((RAD_COLLECTOR_OUTPUT)*((60 SECONDS)/SSmachines.wait))] per minute.
                The plasma within it's tank is being irradiated into tritium.
                " + else + . += "[src]'s display states that it's producing a total of [(last_push*RAD_COLLECTOR_MINING_CONVERSION_RATE)*((60 SECONDS)/SSmachines.wait)] research points per minute.
                The tritium and oxygen within it's tank is being combusted into carbon dioxide.
                " + else + if(!bitcoinmining) + . += "[src]'s display displays the words: \"Power production mode. Please insert Plasma. Use a multitool to change production modes.\"" + else + . += "[src]'s display displays the words: \"Research point production mode. Please insert Tritium and Oxygen. Use a multitool to change production modes.\"" + +/obj/machinery/power/rad_collector/obj_break(damage_flag) + if(!(stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1)) + eject() + stat |= BROKEN + +/obj/machinery/power/rad_collector/proc/eject() + locked = FALSE + var/obj/item/tank/internals/plasma/Z = src.loaded_tank + if (!Z) + return + Z.forceMove(drop_location()) + Z.layer = initial(Z.layer) + Z.plane = initial(Z.plane) + src.loaded_tank = null + if(active) + toggle_power() + else + update_icon() + +/obj/machinery/power/rad_collector/rad_act(pulse_strength) + . = ..() + if(loaded_tank && active && pulse_strength > RAD_COLLECTOR_EFFICIENCY) + stored_power += (pulse_strength-RAD_COLLECTOR_EFFICIENCY)*RAD_COLLECTOR_COEFFICIENT + +/obj/machinery/power/rad_collector/update_icon() + cut_overlays() + if(loaded_tank) + add_overlay("ptank") + if(stat & (NOPOWER|BROKEN)) + return + if(active) + add_overlay("on") + + +/obj/machinery/power/rad_collector/proc/toggle_power() + active = !active + if(active) + icon_state = "ca_on" + flick("ca_active", src) + else + icon_state = "ca" + flick("ca_deactive", src) + update_icon() + return + +#undef RAD_COLLECTOR_EFFICIENCY +#undef RAD_COLLECTOR_COEFFICIENT +#undef RAD_COLLECTOR_STORED_OUT +#undef RAD_COLLECTOR_MINING_CONVERSION_RATE +#undef RAD_COLLECTOR_OUTPUT diff --git a/code/modules/power/singularity/containment_field.dm b/code/modules/power/singularity/containment_field.dm index 3b0f7e7f6f..e007b72c88 100644 --- a/code/modules/power/singularity/containment_field.dm +++ b/code/modules/power/singularity/containment_field.dm @@ -1,134 +1,134 @@ - - -/obj/machinery/field/containment - name = "containment field" - desc = "An energy field." - icon = 'icons/obj/singularity.dmi' - icon_state = "Contain_F" - density = FALSE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - use_power = NO_POWER_USE - interaction_flags_atom = NONE - interaction_flags_machine = NONE - light_range = 4 - layer = ABOVE_OBJ_LAYER - var/obj/machinery/field/generator/FG1 = null - var/obj/machinery/field/generator/FG2 = null - -/obj/machinery/field/containment/Destroy() - FG1.fields -= src - FG2.fields -= src - return ..() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/machinery/field/containment/attack_hand(mob/user) - if(get_dist(src, user) > 1) - return FALSE - else - shock(user) - return TRUE - -/obj/machinery/field/containment/attackby(obj/item/W, mob/user, params) - shock(user) - return TRUE - -/obj/machinery/field/containment/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BURN) - playsound(loc, 'sound/effects/empulse.ogg', 75, 1) - if(BRUTE) - playsound(loc, 'sound/effects/empulse.ogg', 75, 1) - -/obj/machinery/field/containment/blob_act(obj/structure/blob/B) - return FALSE - -/obj/machinery/field/containment/ex_act(severity, target) - return FALSE - -/obj/machinery/field/containment/attack_animal(mob/living/simple_animal/M) - if(!FG1 || !FG2) - qdel(src) - return - if(ismegafauna(M)) - M.visible_message("[M] glows fiercely as the containment field flickers out!") - FG1.calc_power(INFINITY) //rip that 'containment' field - M.adjustHealth(-M.obj_damage) - else - ..() - -/obj/machinery/field/containment/Crossed(mob/mover) - if(isliving(mover)) - shock(mover) - - if(ismachinery(mover) || isstructure(mover) || ismecha(mover)) - bump_field(mover) - -/obj/machinery/field/containment/proc/set_master(master1,master2) - if(!master1 || !master2) - return FALSE - FG1 = master1 - FG2 = master2 - return TRUE - -/obj/machinery/field/containment/shock(mob/living/user) - if(!FG1 || !FG2) - qdel(src) - return FALSE - ..() - -/obj/machinery/field/containment/Move() - qdel(src) - return FALSE - - -// Abstract Field Class -// Used for overriding certain procs - -/obj/machinery/field - var/hasShocked = FALSE //Used to add a delay between shocks. In some cases this used to crash servers by spawning hundreds of sparks every second. - -/obj/machinery/field/Bumped(atom/movable/mover) - if(hasShocked) - return - if(isliving(mover)) - shock(mover) - return - if(ismachinery(mover) || isstructure(mover) || ismecha(mover)) - bump_field(mover) - return - - -/obj/machinery/field/CanPass(atom/movable/mover, turf/target) - if(hasShocked || isliving(mover) || ismachinery(mover) || isstructure(mover) || ismecha(mover)) - return FALSE - return ..() - -/obj/machinery/field/proc/shock(mob/living/user) - var/shock_damage = min(rand(30,40),rand(30,40)) - - if(iscarbon(user)) - user.Knockdown(300) - user.electrocute_act(shock_damage, src, 1) - - else if(issilicon(user)) - if(prob(20)) - user.Stun(40) - user.take_overall_damage(0, shock_damage) - user.visible_message("[user.name] was shocked by the [src.name]!", \ - "Energy pulse detected, system damaged!", \ - "You hear an electrical crack.") - - user.updatehealth() - bump_field(user) - -/obj/machinery/field/proc/clear_shock() - hasShocked = FALSE - -/obj/machinery/field/proc/bump_field(atom/movable/AM as mob|obj) - if(hasShocked) - return FALSE - hasShocked = TRUE - do_sparks(5, TRUE, AM.loc) - var/atom/target = get_edge_target_turf(AM, get_dir(src, get_step_away(AM, src))) - AM.throw_at(target, 200, 4) - addtimer(CALLBACK(src, .proc/clear_shock), 5) + + +/obj/machinery/field/containment + name = "containment field" + desc = "An energy field." + icon = 'icons/obj/singularity.dmi' + icon_state = "Contain_F" + density = FALSE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + use_power = NO_POWER_USE + interaction_flags_atom = NONE + interaction_flags_machine = NONE + light_range = 4 + layer = ABOVE_OBJ_LAYER + var/obj/machinery/field/generator/FG1 = null + var/obj/machinery/field/generator/FG2 = null + +/obj/machinery/field/containment/Destroy() + FG1.fields -= src + FG2.fields -= src + return ..() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/machinery/field/containment/attack_hand(mob/user) + if(get_dist(src, user) > 1) + return FALSE + else + shock(user) + return TRUE + +/obj/machinery/field/containment/attackby(obj/item/W, mob/user, params) + shock(user) + return TRUE + +/obj/machinery/field/containment/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BURN) + playsound(loc, 'sound/effects/empulse.ogg', 75, 1) + if(BRUTE) + playsound(loc, 'sound/effects/empulse.ogg', 75, 1) + +/obj/machinery/field/containment/blob_act(obj/structure/blob/B) + return FALSE + +/obj/machinery/field/containment/ex_act(severity, target) + return FALSE + +/obj/machinery/field/containment/attack_animal(mob/living/simple_animal/M) + if(!FG1 || !FG2) + qdel(src) + return + if(ismegafauna(M)) + M.visible_message("[M] glows fiercely as the containment field flickers out!") + FG1.calc_power(INFINITY) //rip that 'containment' field + M.adjustHealth(-M.obj_damage) + else + ..() + +/obj/machinery/field/containment/Crossed(mob/mover) + if(isliving(mover)) + shock(mover) + + if(ismachinery(mover) || isstructure(mover) || ismecha(mover)) + bump_field(mover) + +/obj/machinery/field/containment/proc/set_master(master1,master2) + if(!master1 || !master2) + return FALSE + FG1 = master1 + FG2 = master2 + return TRUE + +/obj/machinery/field/containment/shock(mob/living/user) + if(!FG1 || !FG2) + qdel(src) + return FALSE + ..() + +/obj/machinery/field/containment/Move() + qdel(src) + return FALSE + + +// Abstract Field Class +// Used for overriding certain procs + +/obj/machinery/field + var/hasShocked = FALSE //Used to add a delay between shocks. In some cases this used to crash servers by spawning hundreds of sparks every second. + +/obj/machinery/field/Bumped(atom/movable/mover) + if(hasShocked) + return + if(isliving(mover)) + shock(mover) + return + if(ismachinery(mover) || isstructure(mover) || ismecha(mover)) + bump_field(mover) + return + + +/obj/machinery/field/CanPass(atom/movable/mover, turf/target) + if(hasShocked || isliving(mover) || ismachinery(mover) || isstructure(mover) || ismecha(mover)) + return FALSE + return ..() + +/obj/machinery/field/proc/shock(mob/living/user) + var/shock_damage = min(rand(30,40),rand(30,40)) + + if(iscarbon(user)) + user.Knockdown(300) + user.electrocute_act(shock_damage, src, 1) + + else if(issilicon(user)) + if(prob(20)) + user.Stun(40) + user.take_overall_damage(0, shock_damage) + user.visible_message("[user.name] was shocked by the [src.name]!", \ + "Energy pulse detected, system damaged!", \ + "You hear an electrical crack.") + + user.updatehealth() + bump_field(user) + +/obj/machinery/field/proc/clear_shock() + hasShocked = FALSE + +/obj/machinery/field/proc/bump_field(atom/movable/AM as mob|obj) + if(hasShocked) + return FALSE + hasShocked = TRUE + do_sparks(5, TRUE, AM.loc) + var/atom/target = get_edge_target_turf(AM, get_dir(src, get_step_away(AM, src))) + AM.throw_at(target, 200, 4) + addtimer(CALLBACK(src, .proc/clear_shock), 5) diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index 71a8565d26..d6f51497cd 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -1,504 +1,504 @@ -//emitter construction defines -#define EMITTER_UNWRENCHED 0 -#define EMITTER_WRENCHED 1 -#define EMITTER_WELDED 2 - -/obj/machinery/power/emitter - name = "emitter" - desc = "A heavy-duty industrial laser, often used in containment fields and power generation." - icon = 'icons/obj/singularity.dmi' - icon_state = "emitter" - - anchored = FALSE - density = TRUE - req_access = list(ACCESS_ENGINE_EQUIP) - circuit = /obj/item/circuitboard/machine/emitter - - use_power = NO_POWER_USE - idle_power_usage = 10 - active_power_usage = 300 - - var/icon_state_on = "emitter_+a" - var/active = FALSE - var/powered = FALSE - var/fire_delay = 100 - var/maximum_fire_delay = 100 - var/minimum_fire_delay = 20 - var/last_shot = 0 - var/shot_number = 0 - var/state = EMITTER_UNWRENCHED - var/locked = FALSE - var/allow_switch_interact = TRUE - - var/projectile_type = /obj/item/projectile/beam/emitter - var/projectile_sound = 'sound/weapons/emitter.ogg' - var/datum/effect_system/spark_spread/sparks - - var/obj/item/gun/energy/gun - var/list/gun_properties - var/mode = 0 - - // The following 3 vars are mostly for the prototype - var/manual = FALSE - var/charge = 0 - var/last_projectile_params - - -/obj/machinery/power/emitter/anchored - anchored = TRUE - -/obj/machinery/power/emitter/ctf - name = "Energy Cannon" - active = TRUE - active_power_usage = FALSE - idle_power_usage = FALSE - locked = TRUE - req_access_txt = "100" - state = EMITTER_WELDED - use_power = FALSE - -/obj/machinery/power/emitter/Initialize() - . = ..() - RefreshParts() - wires = new /datum/wires/emitter(src) - if(state == EMITTER_WELDED && anchored) - connect_to_network() - - sparks = new - sparks.attach(src) - sparks.set_up(1, TRUE, src) - -/obj/machinery/power/emitter/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) - -/obj/machinery/power/emitter/RefreshParts() - var/max_firedelay = 120 - var/firedelay = 120 - var/min_firedelay = 24 - var/power_usage = 350 - for(var/obj/item/stock_parts/micro_laser/L in component_parts) - max_firedelay -= 20 * L.rating - min_firedelay -= 4 * L.rating - firedelay -= 20 * L.rating - maximum_fire_delay = max_firedelay - minimum_fire_delay = min_firedelay - fire_delay = firedelay - for(var/obj/item/stock_parts/manipulator/M in component_parts) - power_usage -= 50 * M.rating - active_power_usage = power_usage - -/obj/machinery/power/emitter/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated)) - -/obj/machinery/power/emitter/proc/can_be_rotated(mob/user,rotation_type) - if (anchored) - to_chat(user, "It is fastened to the floor!") - return FALSE - return TRUE - -/obj/machinery/power/emitter/Destroy() - if(SSticker.IsRoundInProgress()) - var/turf/T = get_turf(src) - message_admins("Emitter deleted at [ADMIN_VERBOSEJMP(T)]") - log_game("Emitter deleted at [AREACOORD(T)]") - investigate_log("deleted at [AREACOORD(T)]", INVESTIGATE_SINGULO) - QDEL_NULL(sparks) - return ..() - -/obj/machinery/power/emitter/update_icon() - if (active && powernet && avail(active_power_usage)) - icon_state = icon_state_on - else - icon_state = initial(icon_state) - - -/obj/machinery/power/emitter/interact(mob/user) - add_fingerprint(user) - if(state == EMITTER_WELDED) - if(!powernet) - to_chat(user, "\The [src] isn't connected to a wire!") - return TRUE - if(!locked && allow_switch_interact) - if(active == TRUE) - active = FALSE - to_chat(user, "You turn off [src].") - else - active = TRUE - to_chat(user, "You turn on [src].") - shot_number = 0 - fire_delay = maximum_fire_delay - - message_admins("Emitter turned [active ? "ON" : "OFF"] by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(src)]") - log_game("Emitter turned [active ? "ON" : "OFF"] by [key_name(user)] in [AREACOORD(src)]") - investigate_log("turned [active ? "ON" : "OFF"] by [key_name(user)] at [AREACOORD(src)]", INVESTIGATE_SINGULO) - - update_icon() - - else - to_chat(user, "The controls are locked!") - else - to_chat(user, "[src] needs to be firmly secured to the floor first!") - return TRUE - -/obj/machinery/power/emitter/attack_animal(mob/living/simple_animal/M) - if(ismegafauna(M) && anchored) - state = EMITTER_UNWRENCHED - anchored = FALSE - M.visible_message("[M] rips [src] free from its moorings!") - else - ..() - if(!anchored) - step(src, get_dir(M, src)) - -/obj/machinery/power/emitter/process() - if(stat & (BROKEN)) - return - if(state != EMITTER_WELDED || (!powernet && active_power_usage)) - active = FALSE - update_icon() - return - if(active == TRUE) - if(!active_power_usage || surplus() >= active_power_usage) - add_load(active_power_usage) - if(!powered) - powered = TRUE - update_icon() - investigate_log("regained power and turned ON at [AREACOORD(src)]", INVESTIGATE_SINGULO) - else - if(powered) - powered = FALSE - update_icon() - investigate_log("lost power and turned OFF at [AREACOORD(src)]", INVESTIGATE_SINGULO) - log_game("Emitter lost power in [AREACOORD(src)]") - return - if(charge <= 80) - charge += 5 - if(!check_delay() || manual == TRUE) - return FALSE - fire_beam() - -/obj/machinery/power/emitter/proc/check_delay() - if((src.last_shot + src.fire_delay) <= world.time) - return TRUE - return FALSE - -/obj/machinery/power/emitter/proc/fire_beam_pulse() - if(!check_delay()) - return FALSE - if(state != EMITTER_WELDED) - return FALSE - if(surplus() >= active_power_usage) - add_load(active_power_usage) - fire_beam() - -/obj/machinery/power/emitter/proc/fire_beam(mob/user) - var/obj/item/projectile/P = new projectile_type(get_turf(src)) - playsound(get_turf(src), projectile_sound, 50, TRUE) - if(prob(35)) - sparks.start() - P.firer = user ? user : src - P.fired_from = src - if(last_projectile_params) - P.p_x = last_projectile_params[2] - P.p_y = last_projectile_params[3] - P.fire(last_projectile_params[1]) - else - P.fire(dir2angle(dir)) - if(!manual) - last_shot = world.time - if(shot_number < 3) - fire_delay = 20 - shot_number ++ - else - fire_delay = rand(minimum_fire_delay,maximum_fire_delay) - shot_number = 0 - return P - -/obj/machinery/power/emitter/can_be_unfasten_wrench(mob/user, silent) - if(active) - if(!silent) - to_chat(user, "Turn \the [src] off first!") - return FAILED_UNFASTEN - - else if(state == EMITTER_WELDED) - if(!silent) - to_chat(user, "[src] is welded to the floor!") - return FAILED_UNFASTEN - - return ..() - -/obj/machinery/power/emitter/default_unfasten_wrench(mob/user, obj/item/I, time = 20) - . = ..() - if(. == SUCCESSFUL_UNFASTEN) - if(anchored) - state = EMITTER_WRENCHED - else - state = EMITTER_UNWRENCHED - -/obj/machinery/power/emitter/wrench_act(mob/living/user, obj/item/I) - default_unfasten_wrench(user, I) - return TRUE - -/obj/machinery/power/emitter/welder_act(mob/living/user, obj/item/I) - if(active) - to_chat(user, "Turn \the [src] off first.") - return TRUE - - switch(state) - if(EMITTER_UNWRENCHED) - to_chat(user, "The [src.name] needs to be wrenched to the floor!") - if(EMITTER_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, 20, volume=50)) - state = EMITTER_WELDED - to_chat(user, "You weld \the [src] to the floor.") - connect_to_network() - if(EMITTER_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, 20, volume=50)) - state = EMITTER_WRENCHED - to_chat(user, "You cut \the [src] free from the floor.") - disconnect_from_network() - - return TRUE - -/obj/machinery/power/emitter/crowbar_act(mob/living/user, obj/item/I) - if(panel_open && gun) - return remove_gun(user) - default_deconstruction_crowbar(I) - return TRUE - -/obj/machinery/power/emitter/screwdriver_act(mob/living/user, obj/item/I) - if(..()) - return TRUE - default_deconstruction_screwdriver(user, "emitter_open", "emitter", I) - return TRUE - - -/obj/machinery/power/emitter/attackby(obj/item/I, mob/user, params) - if(I.GetID()) - if(obj_flags & EMAGGED) - to_chat(user, "The lock seems to be broken!") - return - if(allowed(user)) - if(active) - locked = !locked - to_chat(user, "You [src.locked ? "lock" : "unlock"] the controls.") - else - to_chat(user, "The controls can only be locked when \the [src] is online!") - else - to_chat(user, "Access denied.") - return - - else if(is_wire_tool(I) && panel_open) - wires.interact(user) - return - else if(panel_open && !gun && istype(I,/obj/item/gun/energy)) - if(integrate(I,user)) - return - return ..() - -/obj/machinery/power/emitter/proc/integrate(obj/item/gun/energy/E,mob/user) - if(istype(E, /obj/item/gun/energy)) - if(!user.transferItemToLoc(E, src)) - return - gun = E - gun_properties = gun.get_turret_properties() - set_projectile() - return TRUE - -/obj/machinery/power/emitter/proc/remove_gun(mob/user) - if(!gun) - return - user.put_in_hands(gun) - gun = null - playsound(src, 'sound/items/deconstruct.ogg', 50, 1) - gun_properties = list() - set_projectile() - return TRUE - -/obj/machinery/power/emitter/proc/set_projectile() - if(LAZYLEN(gun_properties)) - if(mode || !gun_properties["lethal_projectile"]) - projectile_type = gun_properties["stun_projectile"] - projectile_sound = gun_properties["stun_projectile_sound"] - else - projectile_type = gun_properties["lethal_projectile"] - projectile_sound = gun_properties["lethal_projectile_sound"] - return - projectile_type = initial(projectile_type) - projectile_sound = initial(projectile_sound) - -/obj/machinery/power/emitter/emag_act(mob/user) - . = ..() - if(obj_flags & EMAGGED) - return - locked = FALSE - obj_flags |= EMAGGED - user?.visible_message("[user.name] emags [src].","You short out the lock.") - return TRUE - - -/obj/machinery/power/emitter/prototype - name = "Prototype Emitter" - icon = 'icons/obj/turrets.dmi' - icon_state = "protoemitter" - icon_state_on = "protoemitter_+a" - can_buckle = TRUE - buckle_lying = FALSE - var/view_range = 12 - var/datum/action/innate/protoemitter/firing/auto - -//BUCKLE HOOKS - -/obj/machinery/power/emitter/prototype/unbuckle_mob(mob/living/buckled_mob,force = 0) - playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) - manual = FALSE - for(var/obj/item/I in buckled_mob.held_items) - if(istype(I, /obj/item/turret_control)) - qdel(I) - if(istype(buckled_mob)) - buckled_mob.pixel_x = 0 - buckled_mob.pixel_y = 0 - if(buckled_mob.client) - buckled_mob.client.change_view(CONFIG_GET(string/default_view)) - auto.Remove(buckled_mob) - . = ..() - -/obj/machinery/power/emitter/prototype/user_buckle_mob(mob/living/M, mob/living/carbon/user) - if(user.incapacitated() || !istype(user)) - return - for(var/atom/movable/A in get_turf(src)) - if(A.density && (A != src && A != M)) - return - M.forceMove(get_turf(src)) - ..() - playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) - M.pixel_y = 14 - layer = 4.1 - if(M.client) - M.client.change_view(view_range) - if(!auto) - auto = new() - auto.Grant(M, src) - -/datum/action/innate/protoemitter - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS - var/obj/machinery/power/emitter/prototype/PE - var/mob/living/carbon/U - - -/datum/action/innate/protoemitter/Grant(mob/living/carbon/L, obj/machinery/power/emitter/prototype/proto) - PE = proto - U = L - . = ..() - -/datum/action/innate/protoemitter/firing - name = "Switch to Manual Firing" - desc = "The emitter will only fire on your command and at your designated target" - button_icon_state = "mech_zoom_on" - -/datum/action/innate/protoemitter/firing/Activate() - if(PE.manual) - playsound(PE,'sound/mecha/mechmove01.ogg', 50, TRUE) - PE.manual = FALSE - name = "Switch to Manual Firing" - desc = "The emitter will only fire on your command and at your designated target" - button_icon_state = "mech_zoom_on" - for(var/obj/item/I in U.held_items) - if(istype(I, /obj/item/turret_control)) - qdel(I) - UpdateButtonIcon() - return - else - playsound(PE,'sound/mecha/mechmove01.ogg', 50, TRUE) - name = "Switch to Automatic Firing" - desc = "Emitters will switch to periodic firing at your last target" - button_icon_state = "mech_zoom_off" - PE.manual = TRUE - for(var/V in U.held_items) - var/obj/item/I = V - if(istype(I)) - if(U.dropItemToGround(I)) - var/obj/item/turret_control/TC = new /obj/item/turret_control() - U.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/turret_control/TC = new /obj/item/turret_control() - U.put_in_hands(TC) - UpdateButtonIcon() - - -/obj/item/turret_control - name = "turret controls" - icon_state = "offhand" - w_class = WEIGHT_CLASS_HUGE - item_flags = ABSTRACT | NOBLUDGEON - resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/delay = 0 - -/obj/item/turret_control/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - -/obj/item/turret_control/afterattack(atom/targeted_atom, mob/user, proxflag, clickparams) - . = ..() - var/obj/machinery/power/emitter/E = user.buckled - E.setDir(get_dir(E,targeted_atom)) - user.setDir(E.dir) - switch(E.dir) - if(NORTH) - E.layer = 3.9 - user.pixel_x = 0 - user.pixel_y = -14 - if(NORTHEAST) - E.layer = 3.9 - user.pixel_x = -8 - user.pixel_y = -12 - if(EAST) - E.layer = 4.1 - user.pixel_x = -14 - user.pixel_y = 0 - if(SOUTHEAST) - E.layer = 3.9 - user.pixel_x = -8 - user.pixel_y = 12 - if(SOUTH) - E.layer = 4.1 - user.pixel_x = 0 - user.pixel_y = 14 - if(SOUTHWEST) - E.layer = 3.9 - user.pixel_x = 8 - user.pixel_y = 12 - if(WEST) - E.layer = 4.1 - user.pixel_x = 14 - user.pixel_y = 0 - if(NORTHWEST) - E.layer = 3.9 - user.pixel_x = 8 - user.pixel_y = -12 - - E.last_projectile_params = calculate_projectile_angle_and_pixel_offsets(user, clickparams) - - if(E.charge >= 10 && world.time > delay) - E.charge -= 10 - E.fire_beam(user) - delay = world.time + 10 - else if (E.charge < 10) - playsound(src,'sound/machines/buzz-sigh.ogg', 50, TRUE) - - -#undef EMITTER_UNWRENCHED -#undef EMITTER_WRENCHED -#undef EMITTER_WELDED +//emitter construction defines +#define EMITTER_UNWRENCHED 0 +#define EMITTER_WRENCHED 1 +#define EMITTER_WELDED 2 + +/obj/machinery/power/emitter + name = "emitter" + desc = "A heavy-duty industrial laser, often used in containment fields and power generation." + icon = 'icons/obj/singularity.dmi' + icon_state = "emitter" + + anchored = FALSE + density = TRUE + req_access = list(ACCESS_ENGINE_EQUIP) + circuit = /obj/item/circuitboard/machine/emitter + + use_power = NO_POWER_USE + idle_power_usage = 10 + active_power_usage = 300 + + var/icon_state_on = "emitter_+a" + var/active = FALSE + var/powered = FALSE + var/fire_delay = 100 + var/maximum_fire_delay = 100 + var/minimum_fire_delay = 20 + var/last_shot = 0 + var/shot_number = 0 + var/state = EMITTER_UNWRENCHED + var/locked = FALSE + var/allow_switch_interact = TRUE + + var/projectile_type = /obj/item/projectile/beam/emitter + var/projectile_sound = 'sound/weapons/emitter.ogg' + var/datum/effect_system/spark_spread/sparks + + var/obj/item/gun/energy/gun + var/list/gun_properties + var/mode = 0 + + // The following 3 vars are mostly for the prototype + var/manual = FALSE + var/charge = 0 + var/last_projectile_params + + +/obj/machinery/power/emitter/anchored + anchored = TRUE + +/obj/machinery/power/emitter/ctf + name = "Energy Cannon" + active = TRUE + active_power_usage = FALSE + idle_power_usage = FALSE + locked = TRUE + req_access_txt = "100" + state = EMITTER_WELDED + use_power = FALSE + +/obj/machinery/power/emitter/Initialize() + . = ..() + RefreshParts() + wires = new /datum/wires/emitter(src) + if(state == EMITTER_WELDED && anchored) + connect_to_network() + + sparks = new + sparks.attach(src) + sparks.set_up(1, TRUE, src) + +/obj/machinery/power/emitter/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) + +/obj/machinery/power/emitter/RefreshParts() + var/max_firedelay = 120 + var/firedelay = 120 + var/min_firedelay = 24 + var/power_usage = 350 + for(var/obj/item/stock_parts/micro_laser/L in component_parts) + max_firedelay -= 20 * L.rating + min_firedelay -= 4 * L.rating + firedelay -= 20 * L.rating + maximum_fire_delay = max_firedelay + minimum_fire_delay = min_firedelay + fire_delay = firedelay + for(var/obj/item/stock_parts/manipulator/M in component_parts) + power_usage -= 50 * M.rating + active_power_usage = power_usage + +/obj/machinery/power/emitter/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated)) + +/obj/machinery/power/emitter/proc/can_be_rotated(mob/user,rotation_type) + if (anchored) + to_chat(user, "It is fastened to the floor!") + return FALSE + return TRUE + +/obj/machinery/power/emitter/Destroy() + if(SSticker.IsRoundInProgress()) + var/turf/T = get_turf(src) + message_admins("Emitter deleted at [ADMIN_VERBOSEJMP(T)]") + log_game("Emitter deleted at [AREACOORD(T)]") + investigate_log("deleted at [AREACOORD(T)]", INVESTIGATE_SINGULO) + QDEL_NULL(sparks) + return ..() + +/obj/machinery/power/emitter/update_icon() + if (active && powernet && avail(active_power_usage)) + icon_state = icon_state_on + else + icon_state = initial(icon_state) + + +/obj/machinery/power/emitter/interact(mob/user) + add_fingerprint(user) + if(state == EMITTER_WELDED) + if(!powernet) + to_chat(user, "\The [src] isn't connected to a wire!") + return TRUE + if(!locked && allow_switch_interact) + if(active == TRUE) + active = FALSE + to_chat(user, "You turn off [src].") + else + active = TRUE + to_chat(user, "You turn on [src].") + shot_number = 0 + fire_delay = maximum_fire_delay + + message_admins("Emitter turned [active ? "ON" : "OFF"] by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(src)]") + log_game("Emitter turned [active ? "ON" : "OFF"] by [key_name(user)] in [AREACOORD(src)]") + investigate_log("turned [active ? "ON" : "OFF"] by [key_name(user)] at [AREACOORD(src)]", INVESTIGATE_SINGULO) + + update_icon() + + else + to_chat(user, "The controls are locked!") + else + to_chat(user, "[src] needs to be firmly secured to the floor first!") + return TRUE + +/obj/machinery/power/emitter/attack_animal(mob/living/simple_animal/M) + if(ismegafauna(M) && anchored) + state = EMITTER_UNWRENCHED + anchored = FALSE + M.visible_message("[M] rips [src] free from its moorings!") + else + ..() + if(!anchored) + step(src, get_dir(M, src)) + +/obj/machinery/power/emitter/process() + if(stat & (BROKEN)) + return + if(state != EMITTER_WELDED || (!powernet && active_power_usage)) + active = FALSE + update_icon() + return + if(active == TRUE) + if(!active_power_usage || surplus() >= active_power_usage) + add_load(active_power_usage) + if(!powered) + powered = TRUE + update_icon() + investigate_log("regained power and turned ON at [AREACOORD(src)]", INVESTIGATE_SINGULO) + else + if(powered) + powered = FALSE + update_icon() + investigate_log("lost power and turned OFF at [AREACOORD(src)]", INVESTIGATE_SINGULO) + log_game("Emitter lost power in [AREACOORD(src)]") + return + if(charge <= 80) + charge += 5 + if(!check_delay() || manual == TRUE) + return FALSE + fire_beam() + +/obj/machinery/power/emitter/proc/check_delay() + if((src.last_shot + src.fire_delay) <= world.time) + return TRUE + return FALSE + +/obj/machinery/power/emitter/proc/fire_beam_pulse() + if(!check_delay()) + return FALSE + if(state != EMITTER_WELDED) + return FALSE + if(surplus() >= active_power_usage) + add_load(active_power_usage) + fire_beam() + +/obj/machinery/power/emitter/proc/fire_beam(mob/user) + var/obj/item/projectile/P = new projectile_type(get_turf(src)) + playsound(get_turf(src), projectile_sound, 50, TRUE) + if(prob(35)) + sparks.start() + P.firer = user ? user : src + P.fired_from = src + if(last_projectile_params) + P.p_x = last_projectile_params[2] + P.p_y = last_projectile_params[3] + P.fire(last_projectile_params[1]) + else + P.fire(dir2angle(dir)) + if(!manual) + last_shot = world.time + if(shot_number < 3) + fire_delay = 20 + shot_number ++ + else + fire_delay = rand(minimum_fire_delay,maximum_fire_delay) + shot_number = 0 + return P + +/obj/machinery/power/emitter/can_be_unfasten_wrench(mob/user, silent) + if(active) + if(!silent) + to_chat(user, "Turn \the [src] off first!") + return FAILED_UNFASTEN + + else if(state == EMITTER_WELDED) + if(!silent) + to_chat(user, "[src] is welded to the floor!") + return FAILED_UNFASTEN + + return ..() + +/obj/machinery/power/emitter/default_unfasten_wrench(mob/user, obj/item/I, time = 20) + . = ..() + if(. == SUCCESSFUL_UNFASTEN) + if(anchored) + state = EMITTER_WRENCHED + else + state = EMITTER_UNWRENCHED + +/obj/machinery/power/emitter/wrench_act(mob/living/user, obj/item/I) + default_unfasten_wrench(user, I) + return TRUE + +/obj/machinery/power/emitter/welder_act(mob/living/user, obj/item/I) + if(active) + to_chat(user, "Turn \the [src] off first.") + return TRUE + + switch(state) + if(EMITTER_UNWRENCHED) + to_chat(user, "The [src.name] needs to be wrenched to the floor!") + if(EMITTER_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, 20, volume=50)) + state = EMITTER_WELDED + to_chat(user, "You weld \the [src] to the floor.") + connect_to_network() + if(EMITTER_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, 20, volume=50)) + state = EMITTER_WRENCHED + to_chat(user, "You cut \the [src] free from the floor.") + disconnect_from_network() + + return TRUE + +/obj/machinery/power/emitter/crowbar_act(mob/living/user, obj/item/I) + if(panel_open && gun) + return remove_gun(user) + default_deconstruction_crowbar(I) + return TRUE + +/obj/machinery/power/emitter/screwdriver_act(mob/living/user, obj/item/I) + if(..()) + return TRUE + default_deconstruction_screwdriver(user, "emitter_open", "emitter", I) + return TRUE + + +/obj/machinery/power/emitter/attackby(obj/item/I, mob/user, params) + if(I.GetID()) + if(obj_flags & EMAGGED) + to_chat(user, "The lock seems to be broken!") + return + if(allowed(user)) + if(active) + locked = !locked + to_chat(user, "You [src.locked ? "lock" : "unlock"] the controls.") + else + to_chat(user, "The controls can only be locked when \the [src] is online!") + else + to_chat(user, "Access denied.") + return + + else if(is_wire_tool(I) && panel_open) + wires.interact(user) + return + else if(panel_open && !gun && istype(I,/obj/item/gun/energy)) + if(integrate(I,user)) + return + return ..() + +/obj/machinery/power/emitter/proc/integrate(obj/item/gun/energy/E,mob/user) + if(istype(E, /obj/item/gun/energy)) + if(!user.transferItemToLoc(E, src)) + return + gun = E + gun_properties = gun.get_turret_properties() + set_projectile() + return TRUE + +/obj/machinery/power/emitter/proc/remove_gun(mob/user) + if(!gun) + return + user.put_in_hands(gun) + gun = null + playsound(src, 'sound/items/deconstruct.ogg', 50, 1) + gun_properties = list() + set_projectile() + return TRUE + +/obj/machinery/power/emitter/proc/set_projectile() + if(LAZYLEN(gun_properties)) + if(mode || !gun_properties["lethal_projectile"]) + projectile_type = gun_properties["stun_projectile"] + projectile_sound = gun_properties["stun_projectile_sound"] + else + projectile_type = gun_properties["lethal_projectile"] + projectile_sound = gun_properties["lethal_projectile_sound"] + return + projectile_type = initial(projectile_type) + projectile_sound = initial(projectile_sound) + +/obj/machinery/power/emitter/emag_act(mob/user) + . = ..() + if(obj_flags & EMAGGED) + return + locked = FALSE + obj_flags |= EMAGGED + user?.visible_message("[user.name] emags [src].","You short out the lock.") + return TRUE + + +/obj/machinery/power/emitter/prototype + name = "Prototype Emitter" + icon = 'icons/obj/turrets.dmi' + icon_state = "protoemitter" + icon_state_on = "protoemitter_+a" + can_buckle = TRUE + buckle_lying = FALSE + var/view_range = 12 + var/datum/action/innate/protoemitter/firing/auto + +//BUCKLE HOOKS + +/obj/machinery/power/emitter/prototype/unbuckle_mob(mob/living/buckled_mob,force = 0) + playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) + manual = FALSE + for(var/obj/item/I in buckled_mob.held_items) + if(istype(I, /obj/item/turret_control)) + qdel(I) + if(istype(buckled_mob)) + buckled_mob.pixel_x = 0 + buckled_mob.pixel_y = 0 + if(buckled_mob.client) + buckled_mob.client.change_view(CONFIG_GET(string/default_view)) + auto.Remove(buckled_mob) + . = ..() + +/obj/machinery/power/emitter/prototype/user_buckle_mob(mob/living/M, mob/living/carbon/user) + if(user.incapacitated() || !istype(user)) + return + for(var/atom/movable/A in get_turf(src)) + if(A.density && (A != src && A != M)) + return + M.forceMove(get_turf(src)) + ..() + playsound(src,'sound/mecha/mechmove01.ogg', 50, TRUE) + M.pixel_y = 14 + layer = 4.1 + if(M.client) + M.client.change_view(view_range) + if(!auto) + auto = new() + auto.Grant(M, src) + +/datum/action/innate/protoemitter + check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS + var/obj/machinery/power/emitter/prototype/PE + var/mob/living/carbon/U + + +/datum/action/innate/protoemitter/Grant(mob/living/carbon/L, obj/machinery/power/emitter/prototype/proto) + PE = proto + U = L + . = ..() + +/datum/action/innate/protoemitter/firing + name = "Switch to Manual Firing" + desc = "The emitter will only fire on your command and at your designated target" + button_icon_state = "mech_zoom_on" + +/datum/action/innate/protoemitter/firing/Activate() + if(PE.manual) + playsound(PE,'sound/mecha/mechmove01.ogg', 50, TRUE) + PE.manual = FALSE + name = "Switch to Manual Firing" + desc = "The emitter will only fire on your command and at your designated target" + button_icon_state = "mech_zoom_on" + for(var/obj/item/I in U.held_items) + if(istype(I, /obj/item/turret_control)) + qdel(I) + UpdateButtonIcon() + return + else + playsound(PE,'sound/mecha/mechmove01.ogg', 50, TRUE) + name = "Switch to Automatic Firing" + desc = "Emitters will switch to periodic firing at your last target" + button_icon_state = "mech_zoom_off" + PE.manual = TRUE + for(var/V in U.held_items) + var/obj/item/I = V + if(istype(I)) + if(U.dropItemToGround(I)) + var/obj/item/turret_control/TC = new /obj/item/turret_control() + U.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/turret_control/TC = new /obj/item/turret_control() + U.put_in_hands(TC) + UpdateButtonIcon() + + +/obj/item/turret_control + name = "turret controls" + icon_state = "offhand" + w_class = WEIGHT_CLASS_HUGE + item_flags = ABSTRACT | NOBLUDGEON + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/delay = 0 + +/obj/item/turret_control/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) + +/obj/item/turret_control/afterattack(atom/targeted_atom, mob/user, proxflag, clickparams) + . = ..() + var/obj/machinery/power/emitter/E = user.buckled + E.setDir(get_dir(E,targeted_atom)) + user.setDir(E.dir) + switch(E.dir) + if(NORTH) + E.layer = 3.9 + user.pixel_x = 0 + user.pixel_y = -14 + if(NORTHEAST) + E.layer = 3.9 + user.pixel_x = -8 + user.pixel_y = -12 + if(EAST) + E.layer = 4.1 + user.pixel_x = -14 + user.pixel_y = 0 + if(SOUTHEAST) + E.layer = 3.9 + user.pixel_x = -8 + user.pixel_y = 12 + if(SOUTH) + E.layer = 4.1 + user.pixel_x = 0 + user.pixel_y = 14 + if(SOUTHWEST) + E.layer = 3.9 + user.pixel_x = 8 + user.pixel_y = 12 + if(WEST) + E.layer = 4.1 + user.pixel_x = 14 + user.pixel_y = 0 + if(NORTHWEST) + E.layer = 3.9 + user.pixel_x = 8 + user.pixel_y = -12 + + E.last_projectile_params = calculate_projectile_angle_and_pixel_offsets(user, clickparams) + + if(E.charge >= 10 && world.time > delay) + E.charge -= 10 + E.fire_beam(user) + delay = world.time + 10 + else if (E.charge < 10) + playsound(src,'sound/machines/buzz-sigh.ogg', 50, TRUE) + + +#undef EMITTER_UNWRENCHED +#undef EMITTER_WRENCHED +#undef EMITTER_WELDED diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm index 6be89b43f4..595d87697d 100644 --- a/code/modules/power/singularity/field_generator.dm +++ b/code/modules/power/singularity/field_generator.dm @@ -1,353 +1,353 @@ - - - -/* -field_generator power level display - The icon used for the field_generator need to have 'num_power_levels' number of icon states - named 'Field_Gen +p[num]' where 'num' ranges from 1 to 'num_power_levels' - - The power level is displayed using overlays. The current displayed power level is stored in 'powerlevel'. - The overlay in use and the powerlevel variable must be kept in sync. A powerlevel equal to 0 means that - no power level overlay is currently in the overlays list. - -Aygar -*/ - -#define field_generator_max_power 250 - -#define FG_OFFLINE 0 -#define FG_CHARGING 1 -#define FG_ONLINE 2 - -//field generator construction defines -#define FG_UNSECURED 0 -#define FG_SECURED 1 -#define FG_WELDED 2 - -/obj/machinery/field/generator - name = "field generator" - desc = "A large thermal battery that projects a high amount of energy when powered." - icon = 'icons/obj/machines/field_generator.dmi' - icon_state = "Field_Gen" - anchored = FALSE - density = TRUE - use_power = NO_POWER_USE - max_integrity = 500 - //100% immune to lasers and energy projectiles since it absorbs their energy. - armor = list("melee" = 25, "bullet" = 10, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) - var/const/num_power_levels = 6 // Total number of power level icon has - var/power_level = 0 - var/active = FG_OFFLINE - var/power = 20 // Current amount of power - var/state = FG_UNSECURED - var/warming_up = 0 - var/list/obj/machinery/field/containment/fields - var/list/obj/machinery/field/generator/connected_gens - var/clean_up = 0 - -/obj/machinery/field/generator/update_icon() - cut_overlays() - if(warming_up) - add_overlay("+a[warming_up]") - if(fields.len) - add_overlay("+on") - if(power_level) - add_overlay("+p[power_level]") - - -/obj/machinery/field/generator/Initialize() - . = ..() - fields = list() - connected_gens = list() - -/obj/machinery/field/generator/ComponentInitialize() - . = ..() - AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) - -/obj/machinery/field/generator/process() - if(active == FG_ONLINE) - calc_power() - -/obj/machinery/field/generator/interact(mob/user) - if(state == FG_WELDED) - if(get_dist(src, user) <= 1)//Need to actually touch the thing to turn it on - if(active >= FG_CHARGING) - to_chat(user, "You are unable to turn off [src] once it is online!") - return 1 - else - user.visible_message("[user] turns on [src].", \ - "You turn on [src].", \ - "You hear heavy droning.") - turn_on() - investigate_log("activated by [key_name(user)].", INVESTIGATE_SINGULO) - - add_fingerprint(user) - else - to_chat(user, "[src] needs to be firmly secured to the floor first!") - -/obj/machinery/field/generator/can_be_unfasten_wrench(mob/user, silent) - if(active) - if(!silent) - to_chat(user, "Turn \the [src] off first!") - return FAILED_UNFASTEN - - else if(state == FG_WELDED) - if(!silent) - to_chat(user, "[src] is welded to the floor!") - return FAILED_UNFASTEN - - return ..() - -/obj/machinery/field/generator/default_unfasten_wrench(mob/user, obj/item/I, time = 20) - . = ..() - if(. == SUCCESSFUL_UNFASTEN) - if(anchored) - state = FG_SECURED - else - state = FG_UNSECURED - -/obj/machinery/field/generator/wrench_act(mob/living/user, obj/item/I) - default_unfasten_wrench(user, I) - return TRUE - -/obj/machinery/field/generator/welder_act(mob/living/user, obj/item/I) - if(active) - to_chat(user, "[src] needs to be off!") - return TRUE - - switch(state) - if(FG_UNSECURED) - to_chat(user, "[src] needs to be wrenched to the floor!") - - if(FG_SECURED) - if(!I.tool_start_check(user, amount=0)) - return TRUE - user.visible_message("[user] starts to weld [src] to the floor.", \ - "You start to weld \the [src] to the floor...", \ - "You hear welding.") - if(I.use_tool(src, user, 20, volume=50) && state == FG_SECURED) - state = FG_WELDED - to_chat(user, "You weld the field generator to the floor.") - - if(FG_WELDED) - if(!I.tool_start_check(user, amount=0)) - return TRUE - user.visible_message("[user] starts to cut [src] free from the floor.", \ - "You start to cut \the [src] free from the floor...", \ - "You hear welding.") - if(I.use_tool(src, user, 20, volume=50) && state == FG_WELDED) - state = FG_SECURED - to_chat(user, "You cut \the [src] free from the floor.") - - return TRUE - - -/obj/machinery/field/generator/attack_animal(mob/living/simple_animal/M) - if(M.environment_smash & ENVIRONMENT_SMASH_RWALLS && active == FG_OFFLINE && state != FG_UNSECURED) - state = FG_UNSECURED - anchored = FALSE - M.visible_message("[M] rips [src] free from its moorings!") - else - ..() - if(!anchored) - step(src, get_dir(M, src)) - -/obj/machinery/field/generator/blob_act(obj/structure/blob/B) - if(active) - return 0 - else - ..() - -/obj/machinery/field/generator/bullet_act(obj/item/projectile/Proj) - if(Proj.flag != "bullet") - power = min(power + Proj.damage, field_generator_max_power) - check_power_level() - ..() - - -/obj/machinery/field/generator/Destroy() - cleanup() - return ..() - - -/obj/machinery/field/generator/proc/check_power_level() - var/new_level = round(num_power_levels * power / field_generator_max_power) - if(new_level != power_level) - power_level = new_level - update_icon() - -/obj/machinery/field/generator/proc/turn_off() - active = FG_OFFLINE - spawn(1) - cleanup() - while (warming_up>0 && !active) - sleep(50) - warming_up-- - update_icon() - -/obj/machinery/field/generator/proc/turn_on() - active = FG_CHARGING - spawn(1) - while (warming_up<3 && active) - sleep(50) - warming_up++ - update_icon() - if(warming_up >= 3) - start_fields() - - -/obj/machinery/field/generator/proc/calc_power(set_power_draw) - var/power_draw = 2 + fields.len - if(set_power_draw) - power_draw = set_power_draw - - if(draw_power(round(power_draw/2,1))) - check_power_level() - return 1 - else - visible_message("The [name] shuts down!", "You hear something shutting down.") - turn_off() - investigate_log("ran out of power and deactivated", INVESTIGATE_SINGULO) - power = 0 - check_power_level() - return 0 - -//This could likely be better, it tends to start loopin if you have a complex generator loop setup. Still works well enough to run the engine fields will likely recode the field gens and fields sometime -Mport -/obj/machinery/field/generator/proc/draw_power(draw = 0, failsafe = FALSE, obj/machinery/field/generator/G = null, obj/machinery/field/generator/last = null) - if((G && (G == src)) || (failsafe >= 8))//Loopin, set fail - return 0 - else - failsafe++ - - if(power >= draw)//We have enough power - power -= draw - return 1 - - else//Need more power - draw -= power - power = 0 - for(var/CG in connected_gens) - var/obj/machinery/field/generator/FG = CG - if(FG == last)//We just asked you - continue - if(G)//Another gen is askin for power and we dont have it - if(FG.draw_power(draw,failsafe,G,src))//Can you take the load - return 1 - else - return 0 - else//We are askin another for power - if(FG.draw_power(draw,failsafe,src,src)) - return 1 - else - return 0 - - -/obj/machinery/field/generator/proc/start_fields() - if(state != FG_WELDED || !anchored) - turn_off() - return - spawn(1) - setup_field(1) - spawn(2) - setup_field(2) - spawn(3) - setup_field(4) - spawn(4) - setup_field(8) - spawn(5) - active = FG_ONLINE - - -/obj/machinery/field/generator/proc/setup_field(NSEW) - var/turf/T = loc - if(!istype(T)) - return 0 - - var/obj/machinery/field/generator/G = null - var/steps = 0 - if(!NSEW)//Make sure its ran right - return 0 - for(var/dist in 0 to 7) // checks out to 8 tiles away for another generator - T = get_step(T, NSEW) - if(T.density)//We cant shoot a field though this - return 0 - - G = locate(/obj/machinery/field/generator) in T - if(G) - steps -= 1 - if(!G.active) - return 0 - break - - for(var/TC in T.contents) - var/atom/A = TC - if(ismob(A)) - continue - if(A.density) - return 0 - - steps++ - - if(!G) - return 0 - - T = loc - for(var/dist in 0 to steps) // creates each field tile - var/field_dir = get_dir(T,get_step(G.loc, NSEW)) - T = get_step(T, NSEW) - if(!locate(/obj/machinery/field/containment) in T) - var/obj/machinery/field/containment/CF = new(T) - CF.set_master(src,G) - CF.setDir(field_dir) - fields += CF - G.fields += CF - for(var/mob/living/L in T) - CF.Crossed(L) - - connected_gens |= G - G.connected_gens |= src - update_icon() - - -/obj/machinery/field/generator/proc/cleanup() - clean_up = 1 - for (var/F in fields) - qdel(F) - - for(var/CG in connected_gens) - var/obj/machinery/field/generator/FG = CG - FG.connected_gens -= src - if(!FG.clean_up)//Makes the other gens clean up as well - FG.cleanup() - connected_gens -= FG - clean_up = 0 - update_icon() - - //This is here to help fight the "hurr durr, release singulo cos nobody will notice before the - //singulo eats the evidence". It's not fool-proof but better than nothing. - //I want to avoid using global variables. - spawn(1) - var/temp = 1 //stops spam - for(var/obj/singularity/O in GLOB.singularities) - if(O.last_warning && temp) - if((world.time - O.last_warning) > 50) //to stop message-spam - temp = 0 - var/turf/T = get_turf(src) - message_admins("A singulo exists and a containment field has failed at [ADMIN_VERBOSEJMP(T)].") - investigate_log("has failed whilst a singulo exists at [AREACOORD(T)].", INVESTIGATE_SINGULO) - O.last_warning = world.time - -/obj/machinery/field/generator/shock(mob/living/user) - if(fields.len) - ..() - -/obj/machinery/field/generator/bump_field(atom/movable/AM as mob|obj) - if(fields.len) - ..() - -#undef FG_UNSECURED -#undef FG_SECURED -#undef FG_WELDED - -#undef FG_OFFLINE -#undef FG_CHARGING -#undef FG_ONLINE + + + +/* +field_generator power level display + The icon used for the field_generator need to have 'num_power_levels' number of icon states + named 'Field_Gen +p[num]' where 'num' ranges from 1 to 'num_power_levels' + + The power level is displayed using overlays. The current displayed power level is stored in 'powerlevel'. + The overlay in use and the powerlevel variable must be kept in sync. A powerlevel equal to 0 means that + no power level overlay is currently in the overlays list. + -Aygar +*/ + +#define field_generator_max_power 250 + +#define FG_OFFLINE 0 +#define FG_CHARGING 1 +#define FG_ONLINE 2 + +//field generator construction defines +#define FG_UNSECURED 0 +#define FG_SECURED 1 +#define FG_WELDED 2 + +/obj/machinery/field/generator + name = "field generator" + desc = "A large thermal battery that projects a high amount of energy when powered." + icon = 'icons/obj/machines/field_generator.dmi' + icon_state = "Field_Gen" + anchored = FALSE + density = TRUE + use_power = NO_POWER_USE + max_integrity = 500 + //100% immune to lasers and energy projectiles since it absorbs their energy. + armor = list("melee" = 25, "bullet" = 10, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 70) + var/const/num_power_levels = 6 // Total number of power level icon has + var/power_level = 0 + var/active = FG_OFFLINE + var/power = 20 // Current amount of power + var/state = FG_UNSECURED + var/warming_up = 0 + var/list/obj/machinery/field/containment/fields + var/list/obj/machinery/field/generator/connected_gens + var/clean_up = 0 + +/obj/machinery/field/generator/update_icon() + cut_overlays() + if(warming_up) + add_overlay("+a[warming_up]") + if(fields.len) + add_overlay("+on") + if(power_level) + add_overlay("+p[power_level]") + + +/obj/machinery/field/generator/Initialize() + . = ..() + fields = list() + connected_gens = list() + +/obj/machinery/field/generator/ComponentInitialize() + . = ..() + AddComponent(/datum/component/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) + +/obj/machinery/field/generator/process() + if(active == FG_ONLINE) + calc_power() + +/obj/machinery/field/generator/interact(mob/user) + if(state == FG_WELDED) + if(get_dist(src, user) <= 1)//Need to actually touch the thing to turn it on + if(active >= FG_CHARGING) + to_chat(user, "You are unable to turn off [src] once it is online!") + return 1 + else + user.visible_message("[user] turns on [src].", \ + "You turn on [src].", \ + "You hear heavy droning.") + turn_on() + investigate_log("activated by [key_name(user)].", INVESTIGATE_SINGULO) + + add_fingerprint(user) + else + to_chat(user, "[src] needs to be firmly secured to the floor first!") + +/obj/machinery/field/generator/can_be_unfasten_wrench(mob/user, silent) + if(active) + if(!silent) + to_chat(user, "Turn \the [src] off first!") + return FAILED_UNFASTEN + + else if(state == FG_WELDED) + if(!silent) + to_chat(user, "[src] is welded to the floor!") + return FAILED_UNFASTEN + + return ..() + +/obj/machinery/field/generator/default_unfasten_wrench(mob/user, obj/item/I, time = 20) + . = ..() + if(. == SUCCESSFUL_UNFASTEN) + if(anchored) + state = FG_SECURED + else + state = FG_UNSECURED + +/obj/machinery/field/generator/wrench_act(mob/living/user, obj/item/I) + default_unfasten_wrench(user, I) + return TRUE + +/obj/machinery/field/generator/welder_act(mob/living/user, obj/item/I) + if(active) + to_chat(user, "[src] needs to be off!") + return TRUE + + switch(state) + if(FG_UNSECURED) + to_chat(user, "[src] needs to be wrenched to the floor!") + + if(FG_SECURED) + if(!I.tool_start_check(user, amount=0)) + return TRUE + user.visible_message("[user] starts to weld [src] to the floor.", \ + "You start to weld \the [src] to the floor...", \ + "You hear welding.") + if(I.use_tool(src, user, 20, volume=50) && state == FG_SECURED) + state = FG_WELDED + to_chat(user, "You weld the field generator to the floor.") + + if(FG_WELDED) + if(!I.tool_start_check(user, amount=0)) + return TRUE + user.visible_message("[user] starts to cut [src] free from the floor.", \ + "You start to cut \the [src] free from the floor...", \ + "You hear welding.") + if(I.use_tool(src, user, 20, volume=50) && state == FG_WELDED) + state = FG_SECURED + to_chat(user, "You cut \the [src] free from the floor.") + + return TRUE + + +/obj/machinery/field/generator/attack_animal(mob/living/simple_animal/M) + if(M.environment_smash & ENVIRONMENT_SMASH_RWALLS && active == FG_OFFLINE && state != FG_UNSECURED) + state = FG_UNSECURED + anchored = FALSE + M.visible_message("[M] rips [src] free from its moorings!") + else + ..() + if(!anchored) + step(src, get_dir(M, src)) + +/obj/machinery/field/generator/blob_act(obj/structure/blob/B) + if(active) + return 0 + else + ..() + +/obj/machinery/field/generator/bullet_act(obj/item/projectile/Proj) + if(Proj.flag != "bullet") + power = min(power + Proj.damage, field_generator_max_power) + check_power_level() + ..() + + +/obj/machinery/field/generator/Destroy() + cleanup() + return ..() + + +/obj/machinery/field/generator/proc/check_power_level() + var/new_level = round(num_power_levels * power / field_generator_max_power) + if(new_level != power_level) + power_level = new_level + update_icon() + +/obj/machinery/field/generator/proc/turn_off() + active = FG_OFFLINE + spawn(1) + cleanup() + while (warming_up>0 && !active) + sleep(50) + warming_up-- + update_icon() + +/obj/machinery/field/generator/proc/turn_on() + active = FG_CHARGING + spawn(1) + while (warming_up<3 && active) + sleep(50) + warming_up++ + update_icon() + if(warming_up >= 3) + start_fields() + + +/obj/machinery/field/generator/proc/calc_power(set_power_draw) + var/power_draw = 2 + fields.len + if(set_power_draw) + power_draw = set_power_draw + + if(draw_power(round(power_draw/2,1))) + check_power_level() + return 1 + else + visible_message("The [name] shuts down!", "You hear something shutting down.") + turn_off() + investigate_log("ran out of power and deactivated", INVESTIGATE_SINGULO) + power = 0 + check_power_level() + return 0 + +//This could likely be better, it tends to start loopin if you have a complex generator loop setup. Still works well enough to run the engine fields will likely recode the field gens and fields sometime -Mport +/obj/machinery/field/generator/proc/draw_power(draw = 0, failsafe = FALSE, obj/machinery/field/generator/G = null, obj/machinery/field/generator/last = null) + if((G && (G == src)) || (failsafe >= 8))//Loopin, set fail + return 0 + else + failsafe++ + + if(power >= draw)//We have enough power + power -= draw + return 1 + + else//Need more power + draw -= power + power = 0 + for(var/CG in connected_gens) + var/obj/machinery/field/generator/FG = CG + if(FG == last)//We just asked you + continue + if(G)//Another gen is askin for power and we dont have it + if(FG.draw_power(draw,failsafe,G,src))//Can you take the load + return 1 + else + return 0 + else//We are askin another for power + if(FG.draw_power(draw,failsafe,src,src)) + return 1 + else + return 0 + + +/obj/machinery/field/generator/proc/start_fields() + if(state != FG_WELDED || !anchored) + turn_off() + return + spawn(1) + setup_field(1) + spawn(2) + setup_field(2) + spawn(3) + setup_field(4) + spawn(4) + setup_field(8) + spawn(5) + active = FG_ONLINE + + +/obj/machinery/field/generator/proc/setup_field(NSEW) + var/turf/T = loc + if(!istype(T)) + return 0 + + var/obj/machinery/field/generator/G = null + var/steps = 0 + if(!NSEW)//Make sure its ran right + return 0 + for(var/dist in 0 to 7) // checks out to 8 tiles away for another generator + T = get_step(T, NSEW) + if(T.density)//We cant shoot a field though this + return 0 + + G = locate(/obj/machinery/field/generator) in T + if(G) + steps -= 1 + if(!G.active) + return 0 + break + + for(var/TC in T.contents) + var/atom/A = TC + if(ismob(A)) + continue + if(A.density) + return 0 + + steps++ + + if(!G) + return 0 + + T = loc + for(var/dist in 0 to steps) // creates each field tile + var/field_dir = get_dir(T,get_step(G.loc, NSEW)) + T = get_step(T, NSEW) + if(!locate(/obj/machinery/field/containment) in T) + var/obj/machinery/field/containment/CF = new(T) + CF.set_master(src,G) + CF.setDir(field_dir) + fields += CF + G.fields += CF + for(var/mob/living/L in T) + CF.Crossed(L) + + connected_gens |= G + G.connected_gens |= src + update_icon() + + +/obj/machinery/field/generator/proc/cleanup() + clean_up = 1 + for (var/F in fields) + qdel(F) + + for(var/CG in connected_gens) + var/obj/machinery/field/generator/FG = CG + FG.connected_gens -= src + if(!FG.clean_up)//Makes the other gens clean up as well + FG.cleanup() + connected_gens -= FG + clean_up = 0 + update_icon() + + //This is here to help fight the "hurr durr, release singulo cos nobody will notice before the + //singulo eats the evidence". It's not fool-proof but better than nothing. + //I want to avoid using global variables. + spawn(1) + var/temp = 1 //stops spam + for(var/obj/singularity/O in GLOB.singularities) + if(O.last_warning && temp) + if((world.time - O.last_warning) > 50) //to stop message-spam + temp = 0 + var/turf/T = get_turf(src) + message_admins("A singulo exists and a containment field has failed at [ADMIN_VERBOSEJMP(T)].") + investigate_log("has failed whilst a singulo exists at [AREACOORD(T)].", INVESTIGATE_SINGULO) + O.last_warning = world.time + +/obj/machinery/field/generator/shock(mob/living/user) + if(fields.len) + ..() + +/obj/machinery/field/generator/bump_field(atom/movable/AM as mob|obj) + if(fields.len) + ..() + +#undef FG_UNSECURED +#undef FG_SECURED +#undef FG_WELDED + +#undef FG_OFFLINE +#undef FG_CHARGING +#undef FG_ONLINE diff --git a/code/modules/power/singularity/generator.dm b/code/modules/power/singularity/generator.dm index a7df7198c0..98729de3cc 100644 --- a/code/modules/power/singularity/generator.dm +++ b/code/modules/power/singularity/generator.dm @@ -1,35 +1,35 @@ -/////SINGULARITY SPAWNER -/obj/machinery/the_singularitygen - name = "Gravitational Singularity Generator" - desc = "An odd device which produces a Gravitational Singularity when set up." - icon = 'icons/obj/singularity.dmi' - icon_state = "TheSingGen" - anchored = FALSE - density = TRUE - use_power = NO_POWER_USE - resistance_flags = FIRE_PROOF - - // You can buckle someone to the singularity generator, then start the engine. Fun! - can_buckle = TRUE - buckle_lying = FALSE - buckle_requires_restraints = TRUE - - var/energy = 0 - var/creation_type = /obj/singularity - -/obj/machinery/the_singularitygen/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/wrench)) - default_unfasten_wrench(user, W, 0) - else - return ..() - -/obj/machinery/the_singularitygen/process() - if(energy > 0) - if(energy >= 200) - var/turf/T = get_turf(src) - SSblackbox.record_feedback("tally", "engine_started", 1, type) - var/obj/singularity/S = new creation_type(T, 50) - transfer_fingerprints_to(S) - qdel(src) - else - energy -= 1 +/////SINGULARITY SPAWNER +/obj/machinery/the_singularitygen + name = "Gravitational Singularity Generator" + desc = "An odd device which produces a Gravitational Singularity when set up." + icon = 'icons/obj/singularity.dmi' + icon_state = "TheSingGen" + anchored = FALSE + density = TRUE + use_power = NO_POWER_USE + resistance_flags = FIRE_PROOF + + // You can buckle someone to the singularity generator, then start the engine. Fun! + can_buckle = TRUE + buckle_lying = FALSE + buckle_requires_restraints = TRUE + + var/energy = 0 + var/creation_type = /obj/singularity + +/obj/machinery/the_singularitygen/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/wrench)) + default_unfasten_wrench(user, W, 0) + else + return ..() + +/obj/machinery/the_singularitygen/process() + if(energy > 0) + if(energy >= 200) + var/turf/T = get_turf(src) + SSblackbox.record_feedback("tally", "engine_started", 1, type) + var/obj/singularity/S = new creation_type(T, 50) + transfer_fingerprints_to(S) + qdel(src) + else + energy -= 1 diff --git a/code/modules/power/singularity/investigate.dm b/code/modules/power/singularity/investigate.dm index 38ce154d01..3caf934b50 100644 --- a/code/modules/power/singularity/investigate.dm +++ b/code/modules/power/singularity/investigate.dm @@ -1,4 +1,4 @@ -/area/engine/engineering/poweralert(state, source) - if (state != poweralm) - investigate_log("has a power alarm!", INVESTIGATE_SINGULO) +/area/engine/engineering/poweralert(state, source) + if (state != poweralm) + investigate_log("has a power alarm!", INVESTIGATE_SINGULO) ..() \ No newline at end of file diff --git a/code/modules/power/singularity/particle_accelerator/particle.dm b/code/modules/power/singularity/particle_accelerator/particle.dm index 57f4a77fc2..142388c350 100644 --- a/code/modules/power/singularity/particle_accelerator/particle.dm +++ b/code/modules/power/singularity/particle_accelerator/particle.dm @@ -1,68 +1,68 @@ -/obj/effect/accelerated_particle - name = "Accelerated Particles" - desc = "Small things moving very fast." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "particle" - anchored = TRUE - density = FALSE - var/movement_range = 10 - var/energy = 10 - var/speed = 1 - -/obj/effect/accelerated_particle/weak - movement_range = 8 - energy = 5 - -/obj/effect/accelerated_particle/strong - movement_range = 15 - energy = 15 - -/obj/effect/accelerated_particle/powerful - movement_range = 20 - energy = 50 - - -/obj/effect/accelerated_particle/New(loc) - ..() - - addtimer(CALLBACK(src, .proc/move), 1) - - -/obj/effect/accelerated_particle/Bump(atom/A) - if(A) - if(isliving(A)) - toxmob(A) - else if(istype(A, /obj/machinery/the_singularitygen)) - var/obj/machinery/the_singularitygen/S = A - S.energy += energy - else if(istype(A, /obj/singularity)) - var/obj/singularity/S = A - S.energy += energy - else if(istype(A, /obj/structure/blob)) - var/obj/structure/blob/B = A - B.take_damage(energy*0.6) - movement_range = 0 - -/obj/effect/accelerated_particle/Crossed(atom/A) - if(isliving(A)) - toxmob(A) - - -/obj/effect/accelerated_particle/ex_act(severity, target) - qdel(src) - -/obj/effect/accelerated_particle/singularity_pull() - return - -/obj/effect/accelerated_particle/proc/toxmob(mob/living/M) - M.rad_act(energy*6) - -/obj/effect/accelerated_particle/proc/move() - if(!step(src,dir)) - forceMove(get_step(src,dir)) - movement_range-- - if(movement_range == 0) - qdel(src) - else - sleep(speed) - move() +/obj/effect/accelerated_particle + name = "Accelerated Particles" + desc = "Small things moving very fast." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "particle" + anchored = TRUE + density = FALSE + var/movement_range = 10 + var/energy = 10 + var/speed = 1 + +/obj/effect/accelerated_particle/weak + movement_range = 8 + energy = 5 + +/obj/effect/accelerated_particle/strong + movement_range = 15 + energy = 15 + +/obj/effect/accelerated_particle/powerful + movement_range = 20 + energy = 50 + + +/obj/effect/accelerated_particle/New(loc) + ..() + + addtimer(CALLBACK(src, .proc/move), 1) + + +/obj/effect/accelerated_particle/Bump(atom/A) + if(A) + if(isliving(A)) + toxmob(A) + else if(istype(A, /obj/machinery/the_singularitygen)) + var/obj/machinery/the_singularitygen/S = A + S.energy += energy + else if(istype(A, /obj/singularity)) + var/obj/singularity/S = A + S.energy += energy + else if(istype(A, /obj/structure/blob)) + var/obj/structure/blob/B = A + B.take_damage(energy*0.6) + movement_range = 0 + +/obj/effect/accelerated_particle/Crossed(atom/A) + if(isliving(A)) + toxmob(A) + + +/obj/effect/accelerated_particle/ex_act(severity, target) + qdel(src) + +/obj/effect/accelerated_particle/singularity_pull() + return + +/obj/effect/accelerated_particle/proc/toxmob(mob/living/M) + M.rad_act(energy*6) + +/obj/effect/accelerated_particle/proc/move() + if(!step(src,dir)) + forceMove(get_step(src,dir)) + movement_range-- + if(movement_range == 0) + qdel(src) + else + sleep(speed) + move() diff --git a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm index 82d33e59ff..09fb3ad4e9 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm @@ -1,172 +1,172 @@ -/*Composed of 7 parts : - - 3 Particle Emitters - 1 Power Box - 1 Fuel Chamber - 1 End Cap - 1 Control computer - - Setup map - - |EC| - CC|FC| - |PB| - PE|PE|PE - -*/ -#define PA_CONSTRUCTION_UNSECURED 0 -#define PA_CONSTRUCTION_UNWIRED 1 -#define PA_CONSTRUCTION_PANEL_OPEN 2 -#define PA_CONSTRUCTION_COMPLETE 3 - -/obj/structure/particle_accelerator - name = "Particle Accelerator" - desc = "Part of a Particle Accelerator." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "none" - anchored = FALSE - density = TRUE - max_integrity = 500 - armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 80) - - var/obj/machinery/particle_accelerator/control_box/master = null - var/construction_state = PA_CONSTRUCTION_UNSECURED - var/reference = null - var/powered = 0 - var/strength = null - -/obj/structure/particle_accelerator/examine(mob/user) - . = ..() - switch(construction_state) - if(PA_CONSTRUCTION_UNSECURED) - . += "Looks like it's not attached to the flooring." - if(PA_CONSTRUCTION_UNWIRED) - . += "It is missing some cables." - if(PA_CONSTRUCTION_PANEL_OPEN) - . += "The panel is open." - -/obj/structure/particle_accelerator/Destroy() - construction_state = PA_CONSTRUCTION_UNSECURED - if(master) - master.connected_parts -= src - master.assembled = 0 - master = null - return ..() - -/obj/structure/particle_accelerator/ComponentInitialize() - . = ..() - AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ) - - -/obj/structure/particle_accelerator/attackby(obj/item/W, mob/user, params) - var/did_something = FALSE - - switch(construction_state) - if(PA_CONSTRUCTION_UNSECURED) - if(istype(W, /obj/item/wrench) && !isinspace()) - W.play_tool_sound(src, 75) - anchored = TRUE - user.visible_message("[user.name] secures the [name] to the floor.", \ - "You secure the external bolts.") - construction_state = PA_CONSTRUCTION_UNWIRED - did_something = TRUE - if(PA_CONSTRUCTION_UNWIRED) - if(istype(W, /obj/item/wrench)) - W.play_tool_sound(src, 75) - anchored = FALSE - user.visible_message("[user.name] detaches the [name] from the floor.", \ - "You remove the external bolts.") - construction_state = PA_CONSTRUCTION_UNSECURED - did_something = TRUE - else if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/CC = W - if(CC.use(1)) - user.visible_message("[user.name] adds wires to the [name].", \ - "You add some wires.") - construction_state = PA_CONSTRUCTION_PANEL_OPEN - did_something = TRUE - if(PA_CONSTRUCTION_PANEL_OPEN) - if(istype(W, /obj/item/wirecutters))//TODO:Shock user if its on? - user.visible_message("[user.name] removes some wires from the [name].", \ - "You remove some wires.") - construction_state = PA_CONSTRUCTION_UNWIRED - did_something = TRUE - else if(istype(W, /obj/item/screwdriver)) - user.visible_message("[user.name] closes the [name]'s access panel.", \ - "You close the access panel.") - construction_state = PA_CONSTRUCTION_COMPLETE - did_something = TRUE - if(PA_CONSTRUCTION_COMPLETE) - if(istype(W, /obj/item/screwdriver)) - user.visible_message("[user.name] opens the [name]'s access panel.", \ - "You open the access panel.") - construction_state = PA_CONSTRUCTION_PANEL_OPEN - did_something = TRUE - - if(did_something) - user.changeNext_move(CLICK_CD_MELEE) - update_state() - update_icon() - return - - return ..() - - -/obj/structure/particle_accelerator/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal (loc, 5) - qdel(src) - -/obj/structure/particle_accelerator/Move() - . = ..() - if(master && master.active) - master.toggle_power() - investigate_log("was moved whilst active; it powered down.", INVESTIGATE_SINGULO) - - -/obj/structure/particle_accelerator/update_icon() - switch(construction_state) - if(PA_CONSTRUCTION_UNSECURED,PA_CONSTRUCTION_UNWIRED) - icon_state="[reference]" - if(PA_CONSTRUCTION_PANEL_OPEN) - icon_state="[reference]w" - if(PA_CONSTRUCTION_COMPLETE) - if(powered) - icon_state="[reference]p[strength]" - else - icon_state="[reference]c" - -/obj/structure/particle_accelerator/proc/update_state() - if(master) - master.update_state() - -/obj/structure/particle_accelerator/proc/connect_master(obj/O) - if(O.dir == dir) - master = O - return 1 - return 0 - -/////////// -// PARTS // -/////////// - - -/obj/structure/particle_accelerator/end_cap - name = "Alpha Particle Generation Array" - desc = "This is where Alpha particles are generated from \[REDACTED\]." - icon_state = "end_cap" - reference = "end_cap" - -/obj/structure/particle_accelerator/power_box - name = "Particle Focusing EM Lens" - desc = "This uses electromagnetic waves to focus the Alpha particles." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "power_box" - reference = "power_box" - -/obj/structure/particle_accelerator/fuel_chamber - name = "EM Acceleration Chamber" - desc = "This is where the Alpha particles are accelerated to radical speeds." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "fuel_chamber" - reference = "fuel_chamber" +/*Composed of 7 parts : + + 3 Particle Emitters + 1 Power Box + 1 Fuel Chamber + 1 End Cap + 1 Control computer + + Setup map + + |EC| + CC|FC| + |PB| + PE|PE|PE + +*/ +#define PA_CONSTRUCTION_UNSECURED 0 +#define PA_CONSTRUCTION_UNWIRED 1 +#define PA_CONSTRUCTION_PANEL_OPEN 2 +#define PA_CONSTRUCTION_COMPLETE 3 + +/obj/structure/particle_accelerator + name = "Particle Accelerator" + desc = "Part of a Particle Accelerator." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "none" + anchored = FALSE + density = TRUE + max_integrity = 500 + armor = list("melee" = 30, "bullet" = 20, "laser" = 20, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 80) + + var/obj/machinery/particle_accelerator/control_box/master = null + var/construction_state = PA_CONSTRUCTION_UNSECURED + var/reference = null + var/powered = 0 + var/strength = null + +/obj/structure/particle_accelerator/examine(mob/user) + . = ..() + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED) + . += "Looks like it's not attached to the flooring." + if(PA_CONSTRUCTION_UNWIRED) + . += "It is missing some cables." + if(PA_CONSTRUCTION_PANEL_OPEN) + . += "The panel is open." + +/obj/structure/particle_accelerator/Destroy() + construction_state = PA_CONSTRUCTION_UNSECURED + if(master) + master.connected_parts -= src + master.assembled = 0 + master = null + return ..() + +/obj/structure/particle_accelerator/ComponentInitialize() + . = ..() + AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS ) + + +/obj/structure/particle_accelerator/attackby(obj/item/W, mob/user, params) + var/did_something = FALSE + + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED) + if(istype(W, /obj/item/wrench) && !isinspace()) + W.play_tool_sound(src, 75) + anchored = TRUE + user.visible_message("[user.name] secures the [name] to the floor.", \ + "You secure the external bolts.") + construction_state = PA_CONSTRUCTION_UNWIRED + did_something = TRUE + if(PA_CONSTRUCTION_UNWIRED) + if(istype(W, /obj/item/wrench)) + W.play_tool_sound(src, 75) + anchored = FALSE + user.visible_message("[user.name] detaches the [name] from the floor.", \ + "You remove the external bolts.") + construction_state = PA_CONSTRUCTION_UNSECURED + did_something = TRUE + else if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/CC = W + if(CC.use(1)) + user.visible_message("[user.name] adds wires to the [name].", \ + "You add some wires.") + construction_state = PA_CONSTRUCTION_PANEL_OPEN + did_something = TRUE + if(PA_CONSTRUCTION_PANEL_OPEN) + if(istype(W, /obj/item/wirecutters))//TODO:Shock user if its on? + user.visible_message("[user.name] removes some wires from the [name].", \ + "You remove some wires.") + construction_state = PA_CONSTRUCTION_UNWIRED + did_something = TRUE + else if(istype(W, /obj/item/screwdriver)) + user.visible_message("[user.name] closes the [name]'s access panel.", \ + "You close the access panel.") + construction_state = PA_CONSTRUCTION_COMPLETE + did_something = TRUE + if(PA_CONSTRUCTION_COMPLETE) + if(istype(W, /obj/item/screwdriver)) + user.visible_message("[user.name] opens the [name]'s access panel.", \ + "You open the access panel.") + construction_state = PA_CONSTRUCTION_PANEL_OPEN + did_something = TRUE + + if(did_something) + user.changeNext_move(CLICK_CD_MELEE) + update_state() + update_icon() + return + + return ..() + + +/obj/structure/particle_accelerator/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal (loc, 5) + qdel(src) + +/obj/structure/particle_accelerator/Move() + . = ..() + if(master && master.active) + master.toggle_power() + investigate_log("was moved whilst active; it powered down.", INVESTIGATE_SINGULO) + + +/obj/structure/particle_accelerator/update_icon() + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED,PA_CONSTRUCTION_UNWIRED) + icon_state="[reference]" + if(PA_CONSTRUCTION_PANEL_OPEN) + icon_state="[reference]w" + if(PA_CONSTRUCTION_COMPLETE) + if(powered) + icon_state="[reference]p[strength]" + else + icon_state="[reference]c" + +/obj/structure/particle_accelerator/proc/update_state() + if(master) + master.update_state() + +/obj/structure/particle_accelerator/proc/connect_master(obj/O) + if(O.dir == dir) + master = O + return 1 + return 0 + +/////////// +// PARTS // +/////////// + + +/obj/structure/particle_accelerator/end_cap + name = "Alpha Particle Generation Array" + desc = "This is where Alpha particles are generated from \[REDACTED\]." + icon_state = "end_cap" + reference = "end_cap" + +/obj/structure/particle_accelerator/power_box + name = "Particle Focusing EM Lens" + desc = "This uses electromagnetic waves to focus the Alpha particles." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "power_box" + reference = "power_box" + +/obj/structure/particle_accelerator/fuel_chamber + name = "EM Acceleration Chamber" + desc = "This is where the Alpha particles are accelerated to radical speeds." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "fuel_chamber" + reference = "fuel_chamber" diff --git a/code/modules/power/singularity/particle_accelerator/particle_control.dm b/code/modules/power/singularity/particle_accelerator/particle_control.dm index 685030d4e6..f583fc138d 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_control.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_control.dm @@ -1,330 +1,330 @@ -/obj/machinery/particle_accelerator/control_box - name = "Particle Accelerator Control Console" - desc = "This controls the density of the particles." - icon = 'icons/obj/machines/particle_accelerator.dmi' - icon_state = "control_box" - anchored = FALSE - density = TRUE - use_power = NO_POWER_USE - idle_power_usage = 500 - active_power_usage = 10000 - dir = NORTH - var/strength_upper_limit = 2 - var/interface_control = 1 - var/list/obj/structure/particle_accelerator/connected_parts - var/assembled = 0 - var/construction_state = PA_CONSTRUCTION_UNSECURED - var/active = 0 - var/strength = 0 - var/powered = 0 - mouse_opacity = MOUSE_OPACITY_OPAQUE - -/obj/machinery/particle_accelerator/control_box/Initialize() - . = ..() - wires = new /datum/wires/particle_accelerator/control_box(src) - connected_parts = list() - -/obj/machinery/particle_accelerator/control_box/Destroy() - if(active) - toggle_power() - for(var/CP in connected_parts) - var/obj/structure/particle_accelerator/part = CP - part.master = null - connected_parts.Cut() - QDEL_NULL(wires) - return ..() - -/obj/machinery/particle_accelerator/control_box/attack_hand(mob/user) - . = ..() - if(.) - return - if(construction_state == PA_CONSTRUCTION_COMPLETE) - interact(user) - else if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) - wires.interact(user) - -/obj/machinery/particle_accelerator/control_box/proc/update_state() - if(construction_state < PA_CONSTRUCTION_COMPLETE) - use_power = NO_POWER_USE - assembled = 0 - active = 0 - for(var/CP in connected_parts) - var/obj/structure/particle_accelerator/part = CP - part.strength = null - part.powered = 0 - part.update_icon() - connected_parts.Cut() - return - if(!part_scan()) - use_power = IDLE_POWER_USE - active = 0 - connected_parts.Cut() - -/obj/machinery/particle_accelerator/control_box/update_icon() - if(active) - icon_state = "control_boxp1" - else - if(use_power) - if(assembled) - icon_state = "control_boxp" - else - icon_state = "ucontrol_boxp" - else - switch(construction_state) - if(PA_CONSTRUCTION_UNSECURED, PA_CONSTRUCTION_UNWIRED) - icon_state = "control_box" - if(PA_CONSTRUCTION_PANEL_OPEN) - icon_state = "control_boxw" - else - icon_state = "control_boxc" - -/obj/machinery/particle_accelerator/control_box/Topic(href, href_list) - if(..()) - return - - if(!interface_control) - to_chat(usr, "ERROR: Request timed out. Check wire contacts.") - return - - if(href_list["close"]) - usr << browse(null, "window=pacontrol") - usr.unset_machine() - return - if(href_list["togglep"]) - if(!wires.is_cut(WIRE_POWER)) - toggle_power() - - else if(href_list["scan"]) - part_scan() - - else if(href_list["strengthup"]) - if(!wires.is_cut(WIRE_STRENGTH)) - add_strength() - - else if(href_list["strengthdown"]) - if(!wires.is_cut(WIRE_STRENGTH)) - remove_strength() - - updateDialog() - update_icon() - -/obj/machinery/particle_accelerator/control_box/proc/strength_change() - for(var/CP in connected_parts) - var/obj/structure/particle_accelerator/part = CP - part.strength = strength - part.update_icon() - -/obj/machinery/particle_accelerator/control_box/proc/add_strength(s) - if(assembled && (strength < strength_upper_limit)) - strength++ - strength_change() - - message_admins("PA Control Computer increased to [strength] by [ADMIN_LOOKUPFLW(usr)] in [ADMIN_VERBOSEJMP(src)]") - log_game("PA Control Computer increased to [strength] by [key_name(usr)] in [AREACOORD(src)]") - investigate_log("increased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_SINGULO) - - -/obj/machinery/particle_accelerator/control_box/proc/remove_strength(s) - if(assembled && (strength > 0)) - strength-- - strength_change() - - message_admins("PA Control Computer decreased to [strength] by [ADMIN_LOOKUPFLW(usr)] in [ADMIN_VERBOSEJMP(src)]") - log_game("PA Control Computer decreased to [strength] by [key_name(usr)] in [AREACOORD(src)]") - investigate_log("decreased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_SINGULO) - - -/obj/machinery/particle_accelerator/control_box/power_change() - ..() - if(stat & NOPOWER) - active = 0 - use_power = NO_POWER_USE - else if(!stat && construction_state == PA_CONSTRUCTION_COMPLETE) - use_power = IDLE_POWER_USE - -/obj/machinery/particle_accelerator/control_box/process() - if(active) - //a part is missing! - if(connected_parts.len < 6) - investigate_log("lost a connected part; It powered down.", INVESTIGATE_SINGULO) - toggle_power() - update_icon() - return - //emit some particles - for(var/obj/structure/particle_accelerator/particle_emitter/PE in connected_parts) - PE.emit_particle(strength) - -/obj/machinery/particle_accelerator/control_box/proc/part_scan() - var/ldir = turn(dir,-90) - var/rdir = turn(dir,90) - var/odir = turn(dir,180) - var/turf/T = loc - - assembled = 0 - critical_machine = FALSE - - var/obj/structure/particle_accelerator/fuel_chamber/F = locate() in orange(1,src) - if(!F) - return 0 - - setDir(F.dir) - connected_parts.Cut() - - T = get_step(T,rdir) - if(!check_part(T, /obj/structure/particle_accelerator/fuel_chamber)) - return 0 - T = get_step(T,odir) - if(!check_part(T, /obj/structure/particle_accelerator/end_cap)) - return 0 - T = get_step(T,dir) - T = get_step(T,dir) - if(!check_part(T, /obj/structure/particle_accelerator/power_box)) - return 0 - T = get_step(T,dir) - if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/center)) - return 0 - T = get_step(T,ldir) - if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/left)) - return 0 - T = get_step(T,rdir) - T = get_step(T,rdir) - if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/right)) - return 0 - - assembled = 1 - critical_machine = TRUE //Only counts if the PA is actually assembled. - return 1 - -/obj/machinery/particle_accelerator/control_box/proc/check_part(turf/T, type) - var/obj/structure/particle_accelerator/PA = locate(/obj/structure/particle_accelerator) in T - if(istype(PA, type) && (PA.construction_state == PA_CONSTRUCTION_COMPLETE)) - if(PA.connect_master(src)) - connected_parts.Add(PA) - return 1 - return 0 - - -/obj/machinery/particle_accelerator/control_box/proc/toggle_power() - active = !active - investigate_log("turned [active?"ON":"OFF"] by [usr ? key_name(usr) : "outside forces"] at [AREACOORD(src)]", INVESTIGATE_SINGULO) - message_admins("PA Control Computer turned [active ?"ON":"OFF"] by [usr ? ADMIN_LOOKUPFLW(usr) : "outside forces"] in [ADMIN_VERBOSEJMP(src)]") - log_game("PA Control Computer turned [active ?"ON":"OFF"] by [usr ? "[key_name(usr)]" : "outside forces"] at [AREACOORD(src)]") - if(active) - use_power = ACTIVE_POWER_USE - for(var/CP in connected_parts) - var/obj/structure/particle_accelerator/part = CP - part.strength = strength - part.powered = 1 - part.update_icon() - else - use_power = IDLE_POWER_USE - for(var/CP in connected_parts) - var/obj/structure/particle_accelerator/part = CP - part.strength = null - part.powered = 0 - part.update_icon() - return 1 - - -/obj/machinery/particle_accelerator/control_box/ui_interact(mob/user) - . = ..() - if((get_dist(src, user) > 1) || (stat & (BROKEN|NOPOWER))) - if(!issilicon(user)) - user.unset_machine() - user << browse(null, "window=pacontrol") - return - - var/dat = "" - dat += "Close

                " - dat += "

                Status

                " - if(!assembled) - dat += "Unable to detect all parts!
                " - dat += "Run Scan

                " - else - dat += "All parts in place.

                " - dat += "Power:" - if(active) - dat += "On
                " - else - dat += "Off
                " - dat += "Toggle Power

                " - dat += "Particle Strength: [strength] " - dat += "--|++

                " - - var/datum/browser/popup = new(user, "pacontrol", name, 420, 300) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() - -/obj/machinery/particle_accelerator/control_box/examine(mob/user) - . = ..() - switch(construction_state) - if(PA_CONSTRUCTION_UNSECURED) - . += "Looks like it's not attached to the flooring." - if(PA_CONSTRUCTION_UNWIRED) - . += "It is missing some cables." - if(PA_CONSTRUCTION_PANEL_OPEN) - . += "The panel is open." - - -/obj/machinery/particle_accelerator/control_box/attackby(obj/item/W, mob/user, params) - var/did_something = FALSE - - switch(construction_state) - if(PA_CONSTRUCTION_UNSECURED) - if(istype(W, /obj/item/wrench) && !isinspace()) - W.play_tool_sound(src, 75) - anchored = TRUE - user.visible_message("[user.name] secures the [name] to the floor.", \ - "You secure the external bolts.") - construction_state = PA_CONSTRUCTION_UNWIRED - did_something = TRUE - if(PA_CONSTRUCTION_UNWIRED) - if(istype(W, /obj/item/wrench)) - W.play_tool_sound(src, 75) - anchored = FALSE - user.visible_message("[user.name] detaches the [name] from the floor.", \ - "You remove the external bolts.") - construction_state = PA_CONSTRUCTION_UNSECURED - did_something = TRUE - else if(istype(W, /obj/item/stack/cable_coil)) - var/obj/item/stack/cable_coil/CC = W - if(CC.use(1)) - user.visible_message("[user.name] adds wires to the [name].", \ - "You add some wires.") - construction_state = PA_CONSTRUCTION_PANEL_OPEN - did_something = TRUE - if(PA_CONSTRUCTION_PANEL_OPEN) - if(istype(W, /obj/item/wirecutters))//TODO:Shock user if its on? - user.visible_message("[user.name] removes some wires from the [name].", \ - "You remove some wires.") - construction_state = PA_CONSTRUCTION_UNWIRED - did_something = TRUE - else if(istype(W, /obj/item/screwdriver)) - user.visible_message("[user.name] closes the [name]'s access panel.", \ - "You close the access panel.") - construction_state = PA_CONSTRUCTION_COMPLETE - did_something = TRUE - if(PA_CONSTRUCTION_COMPLETE) - if(istype(W, /obj/item/screwdriver)) - user.visible_message("[user.name] opens the [name]'s access panel.", \ - "You open the access panel.") - construction_state = PA_CONSTRUCTION_PANEL_OPEN - did_something = TRUE - - if(did_something) - user.changeNext_move(CLICK_CD_MELEE) - update_state() - update_icon() - return - - ..() - -/obj/machinery/particle_accelerator/control_box/blob_act(obj/structure/blob/B) - if(prob(50)) - qdel(src) - -#undef PA_CONSTRUCTION_UNSECURED -#undef PA_CONSTRUCTION_UNWIRED -#undef PA_CONSTRUCTION_PANEL_OPEN -#undef PA_CONSTRUCTION_COMPLETE +/obj/machinery/particle_accelerator/control_box + name = "Particle Accelerator Control Console" + desc = "This controls the density of the particles." + icon = 'icons/obj/machines/particle_accelerator.dmi' + icon_state = "control_box" + anchored = FALSE + density = TRUE + use_power = NO_POWER_USE + idle_power_usage = 500 + active_power_usage = 10000 + dir = NORTH + var/strength_upper_limit = 2 + var/interface_control = 1 + var/list/obj/structure/particle_accelerator/connected_parts + var/assembled = 0 + var/construction_state = PA_CONSTRUCTION_UNSECURED + var/active = 0 + var/strength = 0 + var/powered = 0 + mouse_opacity = MOUSE_OPACITY_OPAQUE + +/obj/machinery/particle_accelerator/control_box/Initialize() + . = ..() + wires = new /datum/wires/particle_accelerator/control_box(src) + connected_parts = list() + +/obj/machinery/particle_accelerator/control_box/Destroy() + if(active) + toggle_power() + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.master = null + connected_parts.Cut() + QDEL_NULL(wires) + return ..() + +/obj/machinery/particle_accelerator/control_box/attack_hand(mob/user) + . = ..() + if(.) + return + if(construction_state == PA_CONSTRUCTION_COMPLETE) + interact(user) + else if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) + wires.interact(user) + +/obj/machinery/particle_accelerator/control_box/proc/update_state() + if(construction_state < PA_CONSTRUCTION_COMPLETE) + use_power = NO_POWER_USE + assembled = 0 + active = 0 + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.strength = null + part.powered = 0 + part.update_icon() + connected_parts.Cut() + return + if(!part_scan()) + use_power = IDLE_POWER_USE + active = 0 + connected_parts.Cut() + +/obj/machinery/particle_accelerator/control_box/update_icon() + if(active) + icon_state = "control_boxp1" + else + if(use_power) + if(assembled) + icon_state = "control_boxp" + else + icon_state = "ucontrol_boxp" + else + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED, PA_CONSTRUCTION_UNWIRED) + icon_state = "control_box" + if(PA_CONSTRUCTION_PANEL_OPEN) + icon_state = "control_boxw" + else + icon_state = "control_boxc" + +/obj/machinery/particle_accelerator/control_box/Topic(href, href_list) + if(..()) + return + + if(!interface_control) + to_chat(usr, "ERROR: Request timed out. Check wire contacts.") + return + + if(href_list["close"]) + usr << browse(null, "window=pacontrol") + usr.unset_machine() + return + if(href_list["togglep"]) + if(!wires.is_cut(WIRE_POWER)) + toggle_power() + + else if(href_list["scan"]) + part_scan() + + else if(href_list["strengthup"]) + if(!wires.is_cut(WIRE_STRENGTH)) + add_strength() + + else if(href_list["strengthdown"]) + if(!wires.is_cut(WIRE_STRENGTH)) + remove_strength() + + updateDialog() + update_icon() + +/obj/machinery/particle_accelerator/control_box/proc/strength_change() + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.strength = strength + part.update_icon() + +/obj/machinery/particle_accelerator/control_box/proc/add_strength(s) + if(assembled && (strength < strength_upper_limit)) + strength++ + strength_change() + + message_admins("PA Control Computer increased to [strength] by [ADMIN_LOOKUPFLW(usr)] in [ADMIN_VERBOSEJMP(src)]") + log_game("PA Control Computer increased to [strength] by [key_name(usr)] in [AREACOORD(src)]") + investigate_log("increased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_SINGULO) + + +/obj/machinery/particle_accelerator/control_box/proc/remove_strength(s) + if(assembled && (strength > 0)) + strength-- + strength_change() + + message_admins("PA Control Computer decreased to [strength] by [ADMIN_LOOKUPFLW(usr)] in [ADMIN_VERBOSEJMP(src)]") + log_game("PA Control Computer decreased to [strength] by [key_name(usr)] in [AREACOORD(src)]") + investigate_log("decreased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_SINGULO) + + +/obj/machinery/particle_accelerator/control_box/power_change() + ..() + if(stat & NOPOWER) + active = 0 + use_power = NO_POWER_USE + else if(!stat && construction_state == PA_CONSTRUCTION_COMPLETE) + use_power = IDLE_POWER_USE + +/obj/machinery/particle_accelerator/control_box/process() + if(active) + //a part is missing! + if(connected_parts.len < 6) + investigate_log("lost a connected part; It powered down.", INVESTIGATE_SINGULO) + toggle_power() + update_icon() + return + //emit some particles + for(var/obj/structure/particle_accelerator/particle_emitter/PE in connected_parts) + PE.emit_particle(strength) + +/obj/machinery/particle_accelerator/control_box/proc/part_scan() + var/ldir = turn(dir,-90) + var/rdir = turn(dir,90) + var/odir = turn(dir,180) + var/turf/T = loc + + assembled = 0 + critical_machine = FALSE + + var/obj/structure/particle_accelerator/fuel_chamber/F = locate() in orange(1,src) + if(!F) + return 0 + + setDir(F.dir) + connected_parts.Cut() + + T = get_step(T,rdir) + if(!check_part(T, /obj/structure/particle_accelerator/fuel_chamber)) + return 0 + T = get_step(T,odir) + if(!check_part(T, /obj/structure/particle_accelerator/end_cap)) + return 0 + T = get_step(T,dir) + T = get_step(T,dir) + if(!check_part(T, /obj/structure/particle_accelerator/power_box)) + return 0 + T = get_step(T,dir) + if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/center)) + return 0 + T = get_step(T,ldir) + if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/left)) + return 0 + T = get_step(T,rdir) + T = get_step(T,rdir) + if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/right)) + return 0 + + assembled = 1 + critical_machine = TRUE //Only counts if the PA is actually assembled. + return 1 + +/obj/machinery/particle_accelerator/control_box/proc/check_part(turf/T, type) + var/obj/structure/particle_accelerator/PA = locate(/obj/structure/particle_accelerator) in T + if(istype(PA, type) && (PA.construction_state == PA_CONSTRUCTION_COMPLETE)) + if(PA.connect_master(src)) + connected_parts.Add(PA) + return 1 + return 0 + + +/obj/machinery/particle_accelerator/control_box/proc/toggle_power() + active = !active + investigate_log("turned [active?"ON":"OFF"] by [usr ? key_name(usr) : "outside forces"] at [AREACOORD(src)]", INVESTIGATE_SINGULO) + message_admins("PA Control Computer turned [active ?"ON":"OFF"] by [usr ? ADMIN_LOOKUPFLW(usr) : "outside forces"] in [ADMIN_VERBOSEJMP(src)]") + log_game("PA Control Computer turned [active ?"ON":"OFF"] by [usr ? "[key_name(usr)]" : "outside forces"] at [AREACOORD(src)]") + if(active) + use_power = ACTIVE_POWER_USE + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.strength = strength + part.powered = 1 + part.update_icon() + else + use_power = IDLE_POWER_USE + for(var/CP in connected_parts) + var/obj/structure/particle_accelerator/part = CP + part.strength = null + part.powered = 0 + part.update_icon() + return 1 + + +/obj/machinery/particle_accelerator/control_box/ui_interact(mob/user) + . = ..() + if((get_dist(src, user) > 1) || (stat & (BROKEN|NOPOWER))) + if(!issilicon(user)) + user.unset_machine() + user << browse(null, "window=pacontrol") + return + + var/dat = "" + dat += "Close

                " + dat += "

                Status

                " + if(!assembled) + dat += "Unable to detect all parts!
                " + dat += "Run Scan

                " + else + dat += "All parts in place.

                " + dat += "Power:" + if(active) + dat += "On
                " + else + dat += "Off
                " + dat += "Toggle Power

                " + dat += "Particle Strength: [strength] " + dat += "--|++

                " + + var/datum/browser/popup = new(user, "pacontrol", name, 420, 300) + popup.set_content(dat) + popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) + popup.open() + +/obj/machinery/particle_accelerator/control_box/examine(mob/user) + . = ..() + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED) + . += "Looks like it's not attached to the flooring." + if(PA_CONSTRUCTION_UNWIRED) + . += "It is missing some cables." + if(PA_CONSTRUCTION_PANEL_OPEN) + . += "The panel is open." + + +/obj/machinery/particle_accelerator/control_box/attackby(obj/item/W, mob/user, params) + var/did_something = FALSE + + switch(construction_state) + if(PA_CONSTRUCTION_UNSECURED) + if(istype(W, /obj/item/wrench) && !isinspace()) + W.play_tool_sound(src, 75) + anchored = TRUE + user.visible_message("[user.name] secures the [name] to the floor.", \ + "You secure the external bolts.") + construction_state = PA_CONSTRUCTION_UNWIRED + did_something = TRUE + if(PA_CONSTRUCTION_UNWIRED) + if(istype(W, /obj/item/wrench)) + W.play_tool_sound(src, 75) + anchored = FALSE + user.visible_message("[user.name] detaches the [name] from the floor.", \ + "You remove the external bolts.") + construction_state = PA_CONSTRUCTION_UNSECURED + did_something = TRUE + else if(istype(W, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/CC = W + if(CC.use(1)) + user.visible_message("[user.name] adds wires to the [name].", \ + "You add some wires.") + construction_state = PA_CONSTRUCTION_PANEL_OPEN + did_something = TRUE + if(PA_CONSTRUCTION_PANEL_OPEN) + if(istype(W, /obj/item/wirecutters))//TODO:Shock user if its on? + user.visible_message("[user.name] removes some wires from the [name].", \ + "You remove some wires.") + construction_state = PA_CONSTRUCTION_UNWIRED + did_something = TRUE + else if(istype(W, /obj/item/screwdriver)) + user.visible_message("[user.name] closes the [name]'s access panel.", \ + "You close the access panel.") + construction_state = PA_CONSTRUCTION_COMPLETE + did_something = TRUE + if(PA_CONSTRUCTION_COMPLETE) + if(istype(W, /obj/item/screwdriver)) + user.visible_message("[user.name] opens the [name]'s access panel.", \ + "You open the access panel.") + construction_state = PA_CONSTRUCTION_PANEL_OPEN + did_something = TRUE + + if(did_something) + user.changeNext_move(CLICK_CD_MELEE) + update_state() + update_icon() + return + + ..() + +/obj/machinery/particle_accelerator/control_box/blob_act(obj/structure/blob/B) + if(prob(50)) + qdel(src) + +#undef PA_CONSTRUCTION_UNSECURED +#undef PA_CONSTRUCTION_UNWIRED +#undef PA_CONSTRUCTION_PANEL_OPEN +#undef PA_CONSTRUCTION_COMPLETE diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm index cc5b1d6322..5075e7656d 100644 --- a/code/modules/power/tracker.dm +++ b/code/modules/power/tracker.dm @@ -1,93 +1,93 @@ -//Solar tracker - -//Machine that tracks the sun and reports it's direction to the solar controllers -//As long as this is working, solar panels on same powernet will track automatically - -/obj/machinery/power/tracker - name = "solar tracker" - desc = "A solar directional tracker." - icon = 'goon/icons/obj/power.dmi' - icon_state = "tracker" - density = TRUE - use_power = NO_POWER_USE - max_integrity = 250 - integrity_failure = 50 - - var/id = 0 - var/sun_angle = 0 // sun angle as set by sun datum - var/obj/machinery/power/solar_control/control = null - -/obj/machinery/power/tracker/Initialize(mapload, obj/item/solar_assembly/S) - . = ..() - Make(S) - connect_to_network() - -/obj/machinery/power/tracker/Destroy() - unset_control() //remove from control computer - return ..() - -//set the control of the tracker to a given computer if closer than SOLAR_MAX_DIST -/obj/machinery/power/tracker/proc/set_control(obj/machinery/power/solar_control/SC) - if(!SC || (get_dist(src, SC) > SOLAR_MAX_DIST)) - return 0 - control = SC - SC.connected_tracker = src - return 1 - -//set the control of the tracker to null and removes it from the previous control computer if needed -/obj/machinery/power/tracker/proc/unset_control() - if(control) - control.connected_tracker = null - control = null - -/obj/machinery/power/tracker/proc/Make(obj/item/solar_assembly/S) - if(!S) - S = new /obj/item/solar_assembly(src) - S.glass_type = /obj/item/stack/sheet/glass - S.tracker = 1 - S.anchored = TRUE - S.forceMove(src) - update_icon() - -//updates the tracker icon and the facing angle for the control computer -/obj/machinery/power/tracker/proc/set_angle(angle) - sun_angle = angle - - //set icon dir to show sun illumination - setDir(turn(NORTH, -angle - 22.5) )// 22.5 deg bias ensures, e.g. 67.5-112.5 is EAST - - if(powernet && (powernet == control.powernet)) //update if we're still in the same powernet - control.currentdir = angle - -/obj/machinery/power/tracker/crowbar_act(mob/user, obj/item/I) - playsound(src.loc, 'sound/machines/click.ogg', 50, 1) - user.visible_message("[user] begins to take the glass off [src].", "You begin to take the glass off [src]...") - if(I.use_tool(src, user, 50)) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) - user.visible_message("[user] takes the glass off [src].", "You take the glass off [src].") - deconstruct(TRUE) - return TRUE - -/obj/machinery/power/tracker/obj_break(damage_flag) - if(!(stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1)) - playsound(loc, 'sound/effects/glassbr3.ogg', 100, 1) - stat |= BROKEN - unset_control() - -/obj/machinery/power/solar/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(disassembled) - var/obj/item/solar_assembly/S = locate() in src - if(S) - S.forceMove(loc) - S.give_glass(stat & BROKEN) - else - playsound(src, "shatter", 70, 1) - new /obj/item/shard(src.loc) - new /obj/item/shard(src.loc) - qdel(src) - -// Tracker Electronic - -/obj/item/electronics/tracker - name = "tracker electronics" +//Solar tracker + +//Machine that tracks the sun and reports it's direction to the solar controllers +//As long as this is working, solar panels on same powernet will track automatically + +/obj/machinery/power/tracker + name = "solar tracker" + desc = "A solar directional tracker." + icon = 'goon/icons/obj/power.dmi' + icon_state = "tracker" + density = TRUE + use_power = NO_POWER_USE + max_integrity = 250 + integrity_failure = 50 + + var/id = 0 + var/sun_angle = 0 // sun angle as set by sun datum + var/obj/machinery/power/solar_control/control = null + +/obj/machinery/power/tracker/Initialize(mapload, obj/item/solar_assembly/S) + . = ..() + Make(S) + connect_to_network() + +/obj/machinery/power/tracker/Destroy() + unset_control() //remove from control computer + return ..() + +//set the control of the tracker to a given computer if closer than SOLAR_MAX_DIST +/obj/machinery/power/tracker/proc/set_control(obj/machinery/power/solar_control/SC) + if(!SC || (get_dist(src, SC) > SOLAR_MAX_DIST)) + return 0 + control = SC + SC.connected_tracker = src + return 1 + +//set the control of the tracker to null and removes it from the previous control computer if needed +/obj/machinery/power/tracker/proc/unset_control() + if(control) + control.connected_tracker = null + control = null + +/obj/machinery/power/tracker/proc/Make(obj/item/solar_assembly/S) + if(!S) + S = new /obj/item/solar_assembly(src) + S.glass_type = /obj/item/stack/sheet/glass + S.tracker = 1 + S.anchored = TRUE + S.forceMove(src) + update_icon() + +//updates the tracker icon and the facing angle for the control computer +/obj/machinery/power/tracker/proc/set_angle(angle) + sun_angle = angle + + //set icon dir to show sun illumination + setDir(turn(NORTH, -angle - 22.5) )// 22.5 deg bias ensures, e.g. 67.5-112.5 is EAST + + if(powernet && (powernet == control.powernet)) //update if we're still in the same powernet + control.currentdir = angle + +/obj/machinery/power/tracker/crowbar_act(mob/user, obj/item/I) + playsound(src.loc, 'sound/machines/click.ogg', 50, 1) + user.visible_message("[user] begins to take the glass off [src].", "You begin to take the glass off [src]...") + if(I.use_tool(src, user, 50)) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + user.visible_message("[user] takes the glass off [src].", "You take the glass off [src].") + deconstruct(TRUE) + return TRUE + +/obj/machinery/power/tracker/obj_break(damage_flag) + if(!(stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1)) + playsound(loc, 'sound/effects/glassbr3.ogg', 100, 1) + stat |= BROKEN + unset_control() + +/obj/machinery/power/solar/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(disassembled) + var/obj/item/solar_assembly/S = locate() in src + if(S) + S.forceMove(loc) + S.give_glass(stat & BROKEN) + else + playsound(src, "shatter", 70, 1) + new /obj/item/shard(src.loc) + new /obj/item/shard(src.loc) + qdel(src) + +// Tracker Electronic + +/obj/item/electronics/tracker + name = "tracker electronics" diff --git a/code/modules/procedural_mapping/mapGenerators/lavaland.dm b/code/modules/procedural_mapping/mapGenerators/lavaland.dm index e6d7b00e8d..6ad21eb959 100644 --- a/code/modules/procedural_mapping/mapGenerators/lavaland.dm +++ b/code/modules/procedural_mapping/mapGenerators/lavaland.dm @@ -1,33 +1,33 @@ - -/datum/mapGeneratorModule/bottomLayer/lavaland_default - spawnableTurfs = list(/turf/open/floor/plating/asteroid/basalt/lava_land_surface = 100) - -/datum/mapGeneratorModule/bottomLayer/lavaland_mineral - spawnableTurfs = list(/turf/closed/mineral/random/volcanic = 100) - -/datum/mapGeneratorModule/bottomLayer/lavaland_mineral/dense - spawnableTurfs = list(/turf/closed/mineral/random/high_chance/volcanic = 100) - -/datum/mapGeneratorModule/splatterLayer/lavalandMonsters - spawnableTurfs = list() - spawnableAtoms = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast = 10, - /mob/living/simple_animal/hostile/asteroid/hivelord/legion = 10, - /mob/living/simple_animal/hostile/asteroid/basilisk/watcher = 10) - -/datum/mapGeneratorModule/splatterLayer/lavalandTendrils - spawnableTurfs = list() - spawnableAtoms = list(/mob/living/simple_animal/hostile/spawner/lavaland = 5, - /mob/living/simple_animal/hostile/spawner/lavaland/legion = 5, - /mob/living/simple_animal/hostile/spawner/lavaland/goliath = 5) - -/datum/mapGenerator/lavaland/ground_only - modules = list(/datum/mapGeneratorModule/bottomLayer/lavaland_default) - buildmode_name = "Block: Lavaland Floor" - -/datum/mapGenerator/lavaland/dense_ores - modules = list(/datum/mapGeneratorModule/bottomLayer/lavaland_mineral/dense) - buildmode_name = "Block: Lavaland Ores: Dense" - -/datum/mapGenerator/lavaland/normal_ores - modules = list(/datum/mapGeneratorModule/bottomLayer/lavaland_mineral) - buildmode_name = "Block: Lavaland Ores" + +/datum/mapGeneratorModule/bottomLayer/lavaland_default + spawnableTurfs = list(/turf/open/floor/plating/asteroid/basalt/lava_land_surface = 100) + +/datum/mapGeneratorModule/bottomLayer/lavaland_mineral + spawnableTurfs = list(/turf/closed/mineral/random/volcanic = 100) + +/datum/mapGeneratorModule/bottomLayer/lavaland_mineral/dense + spawnableTurfs = list(/turf/closed/mineral/random/high_chance/volcanic = 100) + +/datum/mapGeneratorModule/splatterLayer/lavalandMonsters + spawnableTurfs = list() + spawnableAtoms = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast = 10, + /mob/living/simple_animal/hostile/asteroid/hivelord/legion = 10, + /mob/living/simple_animal/hostile/asteroid/basilisk/watcher = 10) + +/datum/mapGeneratorModule/splatterLayer/lavalandTendrils + spawnableTurfs = list() + spawnableAtoms = list(/mob/living/simple_animal/hostile/spawner/lavaland = 5, + /mob/living/simple_animal/hostile/spawner/lavaland/legion = 5, + /mob/living/simple_animal/hostile/spawner/lavaland/goliath = 5) + +/datum/mapGenerator/lavaland/ground_only + modules = list(/datum/mapGeneratorModule/bottomLayer/lavaland_default) + buildmode_name = "Block: Lavaland Floor" + +/datum/mapGenerator/lavaland/dense_ores + modules = list(/datum/mapGeneratorModule/bottomLayer/lavaland_mineral/dense) + buildmode_name = "Block: Lavaland Ores: Dense" + +/datum/mapGenerator/lavaland/normal_ores + modules = list(/datum/mapGeneratorModule/bottomLayer/lavaland_mineral) + buildmode_name = "Block: Lavaland Ores" diff --git a/code/modules/procedural_mapping/mapGenerators/repair.dm b/code/modules/procedural_mapping/mapGenerators/repair.dm index bc8e9f74f1..20ba10b0e1 100644 --- a/code/modules/procedural_mapping/mapGenerators/repair.dm +++ b/code/modules/procedural_mapping/mapGenerators/repair.dm @@ -1,111 +1,111 @@ -/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel - spawnableTurfs = list(/turf/open/floor/plasteel = 100) - var/ignore_wall = FALSE - allowAtomsOnSpace = TRUE - -/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel/place(turf/T) - if(isclosedturf(T) && !ignore_wall) - return FALSE - return ..() - -/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel/flatten - ignore_wall = TRUE - -/datum/mapGeneratorModule/border/normalWalls - spawnableAtoms = list() - spawnableTurfs = list(/turf/closed/wall = 100) - allowAtomsOnSpace = TRUE - -/datum/mapGeneratorModule/reload_station_map/generate() - if(!istype(mother, /datum/mapGenerator/repair/reload_station_map)) - return - var/datum/mapGenerator/repair/reload_station_map/mother1 = mother - GLOB.reloading_map = TRUE - // This is kind of finicky on multi-Z maps but the reader would need to be - // changed to allow Z cropping and that's a mess - var/z_offset = SSmapping.station_start - var/list/bounds - for (var/path in SSmapping.config.GetFullMapPaths()) - var/datum/parsed_map/parsed = load_map(file(path), 1, 1, z_offset, measureOnly = FALSE, no_changeturf = FALSE, cropMap=TRUE, x_lower = mother1.x_low, y_lower = mother1.y_low, x_upper = mother1.x_high, y_upper = mother1.y_high) - bounds = parsed?.bounds - z_offset += bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1 - - var/list/obj/machinery/atmospherics/atmos_machines = list() - var/list/obj/structure/cable/cables = list() - var/list/atom/atoms = list() - - repopulate_sorted_areas() - - for(var/L in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], SSmapping.station_start), - locate(bounds[MAP_MAXX], bounds[MAP_MAXY], z_offset - 1))) - set waitfor = FALSE - var/turf/B = L - atoms += B - for(var/A in B) - atoms += A - if(istype(A,/obj/structure/cable)) - cables += A - continue - if(istype(A,/obj/machinery/atmospherics)) - atmos_machines += A - - SSatoms.InitializeAtoms(atoms) - SSmachines.setup_template_powernets(cables) - SSair.setup_template_machinery(atmos_machines) - GLOB.reloading_map = FALSE - -/datum/mapGenerator/repair - modules = list(/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel, - /datum/mapGeneratorModule/bottomLayer/repressurize) - buildmode_name = "Repair: Floor" - -/datum/mapGenerator/repair/delete_walls - modules = list(/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel/flatten, - /datum/mapGeneratorModule/bottomLayer/repressurize) - buildmode_name = "Repair: Floor: Flatten Walls" - -/datum/mapGenerator/repair/enclose_room - modules = list(/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel/flatten, - /datum/mapGeneratorModule/border/normalWalls, - /datum/mapGeneratorModule/bottomLayer/repressurize) - buildmode_name = "Repair: Generate Aired Room" - -/datum/mapGenerator/repair/reload_station_map - modules = list(/datum/mapGeneratorModule/bottomLayer/massdelete/no_delete_mobs) - var/x_low = 0 - var/x_high = 0 - var/y_low = 0 - var/y_high = 0 - var/z = 0 - var/cleanload = FALSE - var/datum/mapGeneratorModule/reload_station_map/loader - buildmode_name = "Repair: Reload Block \[DO NOT USE\]" - -/datum/mapGenerator/repair/reload_station_map/clean - buildmode_name = "Repair: Reload Block - Mass Delete" - cleanload = TRUE - -/datum/mapGenerator/repair/reload_station_map/clean/in_place - modules = list(/datum/mapGeneratorModule/bottomLayer/massdelete/regeneration_delete) - buildmode_name = "Repair: Reload Block - Mass Delete - In Place" - -/datum/mapGenerator/repair/reload_station_map/defineRegion(turf/start, turf/end) - . = ..() - if(!is_station_level(start.z) || !is_station_level(end.z)) - return - x_low = min(start.x, end.x) - y_low = min(start.y, end.y) - x_high = max(start.x, end.x) - y_high = max(start.y, end.y) - z = SSmapping.station_start - -GLOBAL_VAR_INIT(reloading_map, FALSE) - -/datum/mapGenerator/repair/reload_station_map/generate(clean = cleanload) - if(!loader) - loader = new - if(cleanload) - ..() //Trigger mass deletion. - modules |= loader - syncModules() - loader.generate() +/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel + spawnableTurfs = list(/turf/open/floor/plasteel = 100) + var/ignore_wall = FALSE + allowAtomsOnSpace = TRUE + +/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel/place(turf/T) + if(isclosedturf(T) && !ignore_wall) + return FALSE + return ..() + +/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel/flatten + ignore_wall = TRUE + +/datum/mapGeneratorModule/border/normalWalls + spawnableAtoms = list() + spawnableTurfs = list(/turf/closed/wall = 100) + allowAtomsOnSpace = TRUE + +/datum/mapGeneratorModule/reload_station_map/generate() + if(!istype(mother, /datum/mapGenerator/repair/reload_station_map)) + return + var/datum/mapGenerator/repair/reload_station_map/mother1 = mother + GLOB.reloading_map = TRUE + // This is kind of finicky on multi-Z maps but the reader would need to be + // changed to allow Z cropping and that's a mess + var/z_offset = SSmapping.station_start + var/list/bounds + for (var/path in SSmapping.config.GetFullMapPaths()) + var/datum/parsed_map/parsed = load_map(file(path), 1, 1, z_offset, measureOnly = FALSE, no_changeturf = FALSE, cropMap=TRUE, x_lower = mother1.x_low, y_lower = mother1.y_low, x_upper = mother1.x_high, y_upper = mother1.y_high) + bounds = parsed?.bounds + z_offset += bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1 + + var/list/obj/machinery/atmospherics/atmos_machines = list() + var/list/obj/structure/cable/cables = list() + var/list/atom/atoms = list() + + repopulate_sorted_areas() + + for(var/L in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], SSmapping.station_start), + locate(bounds[MAP_MAXX], bounds[MAP_MAXY], z_offset - 1))) + set waitfor = FALSE + var/turf/B = L + atoms += B + for(var/A in B) + atoms += A + if(istype(A,/obj/structure/cable)) + cables += A + continue + if(istype(A,/obj/machinery/atmospherics)) + atmos_machines += A + + SSatoms.InitializeAtoms(atoms) + SSmachines.setup_template_powernets(cables) + SSair.setup_template_machinery(atmos_machines) + GLOB.reloading_map = FALSE + +/datum/mapGenerator/repair + modules = list(/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel, + /datum/mapGeneratorModule/bottomLayer/repressurize) + buildmode_name = "Repair: Floor" + +/datum/mapGenerator/repair/delete_walls + modules = list(/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel/flatten, + /datum/mapGeneratorModule/bottomLayer/repressurize) + buildmode_name = "Repair: Floor: Flatten Walls" + +/datum/mapGenerator/repair/enclose_room + modules = list(/datum/mapGeneratorModule/bottomLayer/repairFloorPlasteel/flatten, + /datum/mapGeneratorModule/border/normalWalls, + /datum/mapGeneratorModule/bottomLayer/repressurize) + buildmode_name = "Repair: Generate Aired Room" + +/datum/mapGenerator/repair/reload_station_map + modules = list(/datum/mapGeneratorModule/bottomLayer/massdelete/no_delete_mobs) + var/x_low = 0 + var/x_high = 0 + var/y_low = 0 + var/y_high = 0 + var/z = 0 + var/cleanload = FALSE + var/datum/mapGeneratorModule/reload_station_map/loader + buildmode_name = "Repair: Reload Block \[DO NOT USE\]" + +/datum/mapGenerator/repair/reload_station_map/clean + buildmode_name = "Repair: Reload Block - Mass Delete" + cleanload = TRUE + +/datum/mapGenerator/repair/reload_station_map/clean/in_place + modules = list(/datum/mapGeneratorModule/bottomLayer/massdelete/regeneration_delete) + buildmode_name = "Repair: Reload Block - Mass Delete - In Place" + +/datum/mapGenerator/repair/reload_station_map/defineRegion(turf/start, turf/end) + . = ..() + if(!is_station_level(start.z) || !is_station_level(end.z)) + return + x_low = min(start.x, end.x) + y_low = min(start.y, end.y) + x_high = max(start.x, end.x) + y_high = max(start.y, end.y) + z = SSmapping.station_start + +GLOBAL_VAR_INIT(reloading_map, FALSE) + +/datum/mapGenerator/repair/reload_station_map/generate(clean = cleanload) + if(!loader) + loader = new + if(cleanload) + ..() //Trigger mass deletion. + modules |= loader + syncModules() + loader.generate() diff --git a/code/modules/procedural_mapping/mapGenerators/syndicate.dm b/code/modules/procedural_mapping/mapGenerators/syndicate.dm index 1f592b64ee..758df6e0a0 100644 --- a/code/modules/procedural_mapping/mapGenerators/syndicate.dm +++ b/code/modules/procedural_mapping/mapGenerators/syndicate.dm @@ -1,57 +1,57 @@ - -// Modules - -/turf/open/floor/plasteel/shuttle/red/syndicate - name = "floor" //Not Brig Floor - -/datum/mapGeneratorModule/bottomLayer/syndieFloor - spawnableTurfs = list(/turf/open/floor/plasteel/shuttle/red/syndicate = 100) - -/datum/mapGeneratorModule/border/syndieWalls - spawnableAtoms = list() - spawnableTurfs = list(/turf/closed/wall/r_wall = 100) - - -/datum/mapGeneratorModule/syndieFurniture - clusterCheckFlags = CLUSTER_CHECK_ALL - spawnableTurfs = list() - spawnableAtoms = list(/obj/structure/table = 20,/obj/structure/chair = 15,/obj/structure/chair/stool = 10, \ - /obj/structure/frame/computer = 15, /obj/item/storage/toolbox/syndicate = 15 ,\ - /obj/structure/closet/syndicate = 25, /obj/machinery/suit_storage_unit/syndicate = 15) - -/datum/mapGeneratorModule/splatterLayer/syndieMobs - spawnableAtoms = list(/mob/living/simple_animal/hostile/syndicate = 30, \ - /mob/living/simple_animal/hostile/syndicate/melee = 20, \ - /mob/living/simple_animal/hostile/syndicate/ranged = 20, \ - /mob/living/simple_animal/hostile/viscerator = 30) - spawnableTurfs = list() - -// Generators - -/datum/mapGenerator/syndicate/empty //walls and floor only - modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \ - /datum/mapGeneratorModule/border/syndieWalls,\ - /datum/mapGeneratorModule/bottomLayer/repressurize) - buildmode_name = "Pattern: Shuttle Room: Syndicate" - -/datum/mapGenerator/syndicate/mobsonly - modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \ - /datum/mapGeneratorModule/border/syndieWalls,\ - /datum/mapGeneratorModule/splatterLayer/syndieMobs, \ - /datum/mapGeneratorModule/bottomLayer/repressurize) - buildmode_name = "Pattern: Shuttle Room: Syndicate: Mobs" - -/datum/mapGenerator/syndicate/furniture - modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \ - /datum/mapGeneratorModule/border/syndieWalls,\ - /datum/mapGeneratorModule/syndieFurniture, \ - /datum/mapGeneratorModule/bottomLayer/repressurize) - buildmode_name = "Pattern: Shuttle Room: Syndicate: Furniture" - -/datum/mapGenerator/syndicate/full - modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \ - /datum/mapGeneratorModule/border/syndieWalls,\ - /datum/mapGeneratorModule/syndieFurniture, \ - /datum/mapGeneratorModule/splatterLayer/syndieMobs, \ - /datum/mapGeneratorModule/bottomLayer/repressurize) - buildmode_name = "Pattern: Shuttle Room: Syndicate: All" + +// Modules + +/turf/open/floor/plasteel/shuttle/red/syndicate + name = "floor" //Not Brig Floor + +/datum/mapGeneratorModule/bottomLayer/syndieFloor + spawnableTurfs = list(/turf/open/floor/plasteel/shuttle/red/syndicate = 100) + +/datum/mapGeneratorModule/border/syndieWalls + spawnableAtoms = list() + spawnableTurfs = list(/turf/closed/wall/r_wall = 100) + + +/datum/mapGeneratorModule/syndieFurniture + clusterCheckFlags = CLUSTER_CHECK_ALL + spawnableTurfs = list() + spawnableAtoms = list(/obj/structure/table = 20,/obj/structure/chair = 15,/obj/structure/chair/stool = 10, \ + /obj/structure/frame/computer = 15, /obj/item/storage/toolbox/syndicate = 15 ,\ + /obj/structure/closet/syndicate = 25, /obj/machinery/suit_storage_unit/syndicate = 15) + +/datum/mapGeneratorModule/splatterLayer/syndieMobs + spawnableAtoms = list(/mob/living/simple_animal/hostile/syndicate = 30, \ + /mob/living/simple_animal/hostile/syndicate/melee = 20, \ + /mob/living/simple_animal/hostile/syndicate/ranged = 20, \ + /mob/living/simple_animal/hostile/viscerator = 30) + spawnableTurfs = list() + +// Generators + +/datum/mapGenerator/syndicate/empty //walls and floor only + modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \ + /datum/mapGeneratorModule/border/syndieWalls,\ + /datum/mapGeneratorModule/bottomLayer/repressurize) + buildmode_name = "Pattern: Shuttle Room: Syndicate" + +/datum/mapGenerator/syndicate/mobsonly + modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \ + /datum/mapGeneratorModule/border/syndieWalls,\ + /datum/mapGeneratorModule/splatterLayer/syndieMobs, \ + /datum/mapGeneratorModule/bottomLayer/repressurize) + buildmode_name = "Pattern: Shuttle Room: Syndicate: Mobs" + +/datum/mapGenerator/syndicate/furniture + modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \ + /datum/mapGeneratorModule/border/syndieWalls,\ + /datum/mapGeneratorModule/syndieFurniture, \ + /datum/mapGeneratorModule/bottomLayer/repressurize) + buildmode_name = "Pattern: Shuttle Room: Syndicate: Furniture" + +/datum/mapGenerator/syndicate/full + modules = list(/datum/mapGeneratorModule/bottomLayer/syndieFloor, \ + /datum/mapGeneratorModule/border/syndieWalls,\ + /datum/mapGeneratorModule/syndieFurniture, \ + /datum/mapGeneratorModule/splatterLayer/syndieMobs, \ + /datum/mapGeneratorModule/bottomLayer/repressurize) + buildmode_name = "Pattern: Shuttle Room: Syndicate: All" diff --git a/code/modules/projectiles/ammunition/_ammunition.dm b/code/modules/projectiles/ammunition/_ammunition.dm index 87fdbc65b0..ccd43e734b 100644 --- a/code/modules/projectiles/ammunition/_ammunition.dm +++ b/code/modules/projectiles/ammunition/_ammunition.dm @@ -1,85 +1,85 @@ -/obj/item/ammo_casing - name = "bullet casing" - desc = "A bullet casing." - icon = 'icons/obj/ammo.dmi' - icon_state = "s-casing" - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - materials = list(MAT_METAL = 500) - var/fire_sound = null //What sound should play when this ammo is fired - var/caliber = null //Which kind of guns it can be loaded into - var/projectile_type = null //The bullet type to create when New() is called - var/obj/item/projectile/BB = null //The loaded bullet - var/pellets = 1 //Pellets for spreadshot - var/variance = 0 //Variance for inaccuracy fundamental to the casing - var/randomspread = 0 //Randomspread for automatics - var/delay = 0 //Delay for energy weapons - var/click_cooldown_override = 0 //Override this to make your gun have a faster fire rate, in tenths of a second. 4 is the default gun cooldown. - var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect //the visual effect appearing when the ammo is fired. - var/heavy_metal = TRUE - var/harmful = TRUE //pacifism check for boolet, set to FALSE if bullet is non-lethal - -/obj/item/ammo_casing/spent - name = "spent bullet casing" - BB = null - -/obj/item/ammo_casing/Initialize() - . = ..() - if(projectile_type) - BB = new projectile_type(src) - pixel_x = rand(-10, 10) - pixel_y = rand(-10, 10) - setDir(pick(GLOB.alldirs)) - update_icon() - -/obj/item/ammo_casing/update_icon() - ..() - icon_state = "[initial(icon_state)][BB ? "-live" : ""]" - desc = "[initial(desc)][BB ? "" : " This one is spent."]" - -//proc to magically refill a casing with a new projectile -/obj/item/ammo_casing/proc/newshot() //For energy weapons, syringe gun, shotgun shells and wands (!). - if(!BB) - BB = new projectile_type(src, src) - -/obj/item/ammo_casing/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/ammo_box)) - var/obj/item/ammo_box/box = I - if(isturf(loc)) - var/boolets = 0 - for(var/obj/item/ammo_casing/bullet in loc) - if (box.stored_ammo.len >= box.max_ammo) - break - if (bullet.BB) - if (box.give_round(bullet, 0)) - boolets++ - else - continue - if (boolets > 0) - box.update_icon() - to_chat(user, "You collect [boolets] shell\s. [box] now contains [box.stored_ammo.len] shell\s.") - else - to_chat(user, "You fail to collect anything!") - else - return ..() - -/obj/item/ammo_casing/throw_impact(atom/A) - if(heavy_metal) - bounce_away(FALSE, NONE) - . = ..() - -/obj/item/ammo_casing/proc/bounce_away(still_warm = FALSE, bounce_delay = 3) - update_icon() - SpinAnimation(10, 1) - var/matrix/M = matrix(transform) - M.Turn(rand(-170,170)) - transform = M - pixel_x = rand(-12, 12) - pixel_y = rand(-12, 12) - var/turf/T = get_turf(src) - if(still_warm && T && T.bullet_sizzle) - addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, 'sound/items/welder.ogg', 20, 1), bounce_delay) //If the turf is made of water and the shell casing is still hot, make a sizzling sound when it's ejected. - else if(T && T.bullet_bounce_sound) - addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, T.bullet_bounce_sound, 60, 1), bounce_delay) //Soft / non-solid turfs that shouldn't make a sound when a shell casing is ejected over them. +/obj/item/ammo_casing + name = "bullet casing" + desc = "A bullet casing." + icon = 'icons/obj/ammo.dmi' + icon_state = "s-casing" + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + materials = list(MAT_METAL = 500) + var/fire_sound = null //What sound should play when this ammo is fired + var/caliber = null //Which kind of guns it can be loaded into + var/projectile_type = null //The bullet type to create when New() is called + var/obj/item/projectile/BB = null //The loaded bullet + var/pellets = 1 //Pellets for spreadshot + var/variance = 0 //Variance for inaccuracy fundamental to the casing + var/randomspread = 0 //Randomspread for automatics + var/delay = 0 //Delay for energy weapons + var/click_cooldown_override = 0 //Override this to make your gun have a faster fire rate, in tenths of a second. 4 is the default gun cooldown. + var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect //the visual effect appearing when the ammo is fired. + var/heavy_metal = TRUE + var/harmful = TRUE //pacifism check for boolet, set to FALSE if bullet is non-lethal + +/obj/item/ammo_casing/spent + name = "spent bullet casing" + BB = null + +/obj/item/ammo_casing/Initialize() + . = ..() + if(projectile_type) + BB = new projectile_type(src) + pixel_x = rand(-10, 10) + pixel_y = rand(-10, 10) + setDir(pick(GLOB.alldirs)) + update_icon() + +/obj/item/ammo_casing/update_icon() + ..() + icon_state = "[initial(icon_state)][BB ? "-live" : ""]" + desc = "[initial(desc)][BB ? "" : " This one is spent."]" + +//proc to magically refill a casing with a new projectile +/obj/item/ammo_casing/proc/newshot() //For energy weapons, syringe gun, shotgun shells and wands (!). + if(!BB) + BB = new projectile_type(src, src) + +/obj/item/ammo_casing/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/ammo_box)) + var/obj/item/ammo_box/box = I + if(isturf(loc)) + var/boolets = 0 + for(var/obj/item/ammo_casing/bullet in loc) + if (box.stored_ammo.len >= box.max_ammo) + break + if (bullet.BB) + if (box.give_round(bullet, 0)) + boolets++ + else + continue + if (boolets > 0) + box.update_icon() + to_chat(user, "You collect [boolets] shell\s. [box] now contains [box.stored_ammo.len] shell\s.") + else + to_chat(user, "You fail to collect anything!") + else + return ..() + +/obj/item/ammo_casing/throw_impact(atom/A) + if(heavy_metal) + bounce_away(FALSE, NONE) + . = ..() + +/obj/item/ammo_casing/proc/bounce_away(still_warm = FALSE, bounce_delay = 3) + update_icon() + SpinAnimation(10, 1) + var/matrix/M = matrix(transform) + M.Turn(rand(-170,170)) + transform = M + pixel_x = rand(-12, 12) + pixel_y = rand(-12, 12) + var/turf/T = get_turf(src) + if(still_warm && T && T.bullet_sizzle) + addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, 'sound/items/welder.ogg', 20, 1), bounce_delay) //If the turf is made of water and the shell casing is still hot, make a sizzling sound when it's ejected. + else if(T && T.bullet_bounce_sound) + addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, T.bullet_bounce_sound, 60, 1), bounce_delay) //Soft / non-solid turfs that shouldn't make a sound when a shell casing is ejected over them. diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm index a83042c90a..848163d658 100644 --- a/code/modules/projectiles/ammunition/_firing.dm +++ b/code/modules/projectiles/ammunition/_firing.dm @@ -1,63 +1,63 @@ -/obj/item/ammo_casing/proc/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from) - distro += variance - for (var/i = max(1, pellets), i > 0, i--) - var/targloc = get_turf(target) - ready_proj(target, user, quiet, zone_override, fired_from) - if(distro) //We have to spread a pixel-precision bullet. throw_proj was called before so angles should exist by now... - if(randomspread) - spread = round((rand() - 0.5) * distro) - else //Smart spread - spread = round((i / pellets - 0.5) * distro) - if(!throw_proj(target, targloc, user, params, spread)) - return 0 - if(i > 1) - newshot() - if(click_cooldown_override) - user.changeNext_move(click_cooldown_override) - else - user.changeNext_move(CLICK_CD_RANGE) - user.newtonian_move(get_dir(target, user)) - update_icon() - return 1 - -/obj/item/ammo_casing/proc/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", fired_from) - if (!BB) - return - BB.original = target - BB.firer = user - BB.fired_from = fired_from - if (zone_override) - BB.def_zone = zone_override - else - BB.def_zone = user.zone_selected - BB.suppressed = quiet - - if(reagents && BB.reagents) - reagents.trans_to(BB, reagents.total_volume) //For chemical darts/bullets - qdel(reagents) - -/obj/item/ammo_casing/proc/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread) - var/turf/curloc = get_turf(user) - if (!istype(targloc) || !istype(curloc) || !BB) - return 0 - - var/firing_dir - if(BB.firer) - firing_dir = BB.firer.dir - if(!BB.suppressed && firing_effect_type) - new firing_effect_type(get_turf(src), firing_dir) - - var/direct_target - if(targloc == curloc) - if(target) //if the target is right on our location we'll skip the travelling code in the proj's fire() - direct_target = target - if(!direct_target) - BB.preparePixelProjectile(target, user, params, spread) - BB.fire(null, direct_target) - BB = null - return 1 - -/obj/item/ammo_casing/proc/spread(turf/target, turf/current, distro) - var/dx = abs(target.x - current.x) - var/dy = abs(target.y - current.y) - return locate(target.x + round(gaussian(0, distro) * (dy+2)/8, 1), target.y + round(gaussian(0, distro) * (dx+2)/8, 1), target.z) +/obj/item/ammo_casing/proc/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from) + distro += variance + for (var/i = max(1, pellets), i > 0, i--) + var/targloc = get_turf(target) + ready_proj(target, user, quiet, zone_override, fired_from) + if(distro) //We have to spread a pixel-precision bullet. throw_proj was called before so angles should exist by now... + if(randomspread) + spread = round((rand() - 0.5) * distro) + else //Smart spread + spread = round((i / pellets - 0.5) * distro) + if(!throw_proj(target, targloc, user, params, spread)) + return 0 + if(i > 1) + newshot() + if(click_cooldown_override) + user.changeNext_move(click_cooldown_override) + else + user.changeNext_move(CLICK_CD_RANGE) + user.newtonian_move(get_dir(target, user)) + update_icon() + return 1 + +/obj/item/ammo_casing/proc/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", fired_from) + if (!BB) + return + BB.original = target + BB.firer = user + BB.fired_from = fired_from + if (zone_override) + BB.def_zone = zone_override + else + BB.def_zone = user.zone_selected + BB.suppressed = quiet + + if(reagents && BB.reagents) + reagents.trans_to(BB, reagents.total_volume) //For chemical darts/bullets + qdel(reagents) + +/obj/item/ammo_casing/proc/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread) + var/turf/curloc = get_turf(user) + if (!istype(targloc) || !istype(curloc) || !BB) + return 0 + + var/firing_dir + if(BB.firer) + firing_dir = BB.firer.dir + if(!BB.suppressed && firing_effect_type) + new firing_effect_type(get_turf(src), firing_dir) + + var/direct_target + if(targloc == curloc) + if(target) //if the target is right on our location we'll skip the travelling code in the proj's fire() + direct_target = target + if(!direct_target) + BB.preparePixelProjectile(target, user, params, spread) + BB.fire(null, direct_target) + BB = null + return 1 + +/obj/item/ammo_casing/proc/spread(turf/target, turf/current, distro) + var/dx = abs(target.x - current.x) + var/dy = abs(target.y - current.y) + return locate(target.x + round(gaussian(0, distro) * (dy+2)/8, 1), target.y + round(gaussian(0, distro) * (dx+2)/8, 1), target.z) diff --git a/code/modules/projectiles/ammunition/ballistic/lmg.dm b/code/modules/projectiles/ammunition/ballistic/lmg.dm index 1ce2e0065e..04cc4df349 100644 --- a/code/modules/projectiles/ammunition/ballistic/lmg.dm +++ b/code/modules/projectiles/ammunition/ballistic/lmg.dm @@ -1,23 +1,23 @@ -// 1.95x129mm (SAW) - -/obj/item/ammo_casing/mm195x129 - name = "1.95x129mm bullet casing" - desc = "A 1.95x129mm bullet casing." - icon_state = "762-casing" - caliber = "mm195129" - projectile_type = /obj/item/projectile/bullet/mm195x129 - -/obj/item/ammo_casing/mm195x129/ap - name = "1.95x129mm armor-piercing bullet casing" - desc = "A 1.95x129mm bullet casing designed with a hardened-tipped core to help penetrate armored targets." - projectile_type = /obj/item/projectile/bullet/mm195x129_ap - -/obj/item/ammo_casing/mm195x129/hollow - name = "1.95x129mm hollow-point bullet casing" - desc = "A 1.95x129mm bullet casing designed to cause more damage to unarmored targets." - projectile_type = /obj/item/projectile/bullet/mm195x129_hp - -/obj/item/ammo_casing/mm195x129/incen - name = "1.95x129mm incendiary bullet casing" - desc = "A 1.95x129mm bullet casing designed with a chemical-filled capsule on the tip that when bursted, reacts with the atmosphere to produce a fireball, engulfing the target in flames." - projectile_type = /obj/item/projectile/bullet/incendiary/mm195x129 +// 1.95x129mm (SAW) + +/obj/item/ammo_casing/mm195x129 + name = "1.95x129mm bullet casing" + desc = "A 1.95x129mm bullet casing." + icon_state = "762-casing" + caliber = "mm195129" + projectile_type = /obj/item/projectile/bullet/mm195x129 + +/obj/item/ammo_casing/mm195x129/ap + name = "1.95x129mm armor-piercing bullet casing" + desc = "A 1.95x129mm bullet casing designed with a hardened-tipped core to help penetrate armored targets." + projectile_type = /obj/item/projectile/bullet/mm195x129_ap + +/obj/item/ammo_casing/mm195x129/hollow + name = "1.95x129mm hollow-point bullet casing" + desc = "A 1.95x129mm bullet casing designed to cause more damage to unarmored targets." + projectile_type = /obj/item/projectile/bullet/mm195x129_hp + +/obj/item/ammo_casing/mm195x129/incen + name = "1.95x129mm incendiary bullet casing" + desc = "A 1.95x129mm bullet casing designed with a chemical-filled capsule on the tip that when bursted, reacts with the atmosphere to produce a fireball, engulfing the target in flames." + projectile_type = /obj/item/projectile/bullet/incendiary/mm195x129 diff --git a/code/modules/projectiles/ammunition/ballistic/pistol.dm b/code/modules/projectiles/ammunition/ballistic/pistol.dm index 02134e95e1..13a284e970 100644 --- a/code/modules/projectiles/ammunition/ballistic/pistol.dm +++ b/code/modules/projectiles/ammunition/ballistic/pistol.dm @@ -1,50 +1,50 @@ -// 10mm (Stechkin) - -/obj/item/ammo_casing/c10mm - name = ".10mm bullet casing" - desc = "A 10mm bullet casing." - caliber = "10mm" - projectile_type = /obj/item/projectile/bullet/c10mm - -/obj/item/ammo_casing/c10mm/ap - name = ".10mm armor-piercing bullet casing" - desc = "A 10mm armor-piercing bullet casing." - projectile_type = /obj/item/projectile/bullet/c10mm_ap - -/obj/item/ammo_casing/c10mm/hp - name = ".10mm hollow-point bullet casing" - desc = "A 10mm hollow-point bullet casing." - projectile_type = /obj/item/projectile/bullet/c10mm_hp - -/obj/item/ammo_casing/c10mm/fire - name = ".10mm incendiary bullet casing" - desc = "A 10mm incendiary bullet casing." - projectile_type = /obj/item/projectile/bullet/incendiary/c10mm - -// 9mm (Stechkin APS) - -/obj/item/ammo_casing/c9mm - name = "9mm bullet casing" - desc = "A 9mm bullet casing." - caliber = "9mm" - projectile_type = /obj/item/projectile/bullet/c9mm - -/obj/item/ammo_casing/c9mm/ap - name = "9mm armor-piercing bullet casing" - desc = "A 9mm armor-piercing bullet casing." - projectile_type =/obj/item/projectile/bullet/c9mm_ap - -/obj/item/ammo_casing/c9mm/inc - name = "9mm incendiary bullet casing" - desc = "A 9mm incendiary bullet casing." - projectile_type = /obj/item/projectile/bullet/incendiary/c9mm - - -// .50AE (Desert Eagle) - -/obj/item/ammo_casing/a50AE - name = ".50AE bullet casing" - desc = "A .50AE bullet casing." - caliber = ".50" - projectile_type = /obj/item/projectile/bullet/a50AE - +// 10mm (Stechkin) + +/obj/item/ammo_casing/c10mm + name = ".10mm bullet casing" + desc = "A 10mm bullet casing." + caliber = "10mm" + projectile_type = /obj/item/projectile/bullet/c10mm + +/obj/item/ammo_casing/c10mm/ap + name = ".10mm armor-piercing bullet casing" + desc = "A 10mm armor-piercing bullet casing." + projectile_type = /obj/item/projectile/bullet/c10mm_ap + +/obj/item/ammo_casing/c10mm/hp + name = ".10mm hollow-point bullet casing" + desc = "A 10mm hollow-point bullet casing." + projectile_type = /obj/item/projectile/bullet/c10mm_hp + +/obj/item/ammo_casing/c10mm/fire + name = ".10mm incendiary bullet casing" + desc = "A 10mm incendiary bullet casing." + projectile_type = /obj/item/projectile/bullet/incendiary/c10mm + +// 9mm (Stechkin APS) + +/obj/item/ammo_casing/c9mm + name = "9mm bullet casing" + desc = "A 9mm bullet casing." + caliber = "9mm" + projectile_type = /obj/item/projectile/bullet/c9mm + +/obj/item/ammo_casing/c9mm/ap + name = "9mm armor-piercing bullet casing" + desc = "A 9mm armor-piercing bullet casing." + projectile_type =/obj/item/projectile/bullet/c9mm_ap + +/obj/item/ammo_casing/c9mm/inc + name = "9mm incendiary bullet casing" + desc = "A 9mm incendiary bullet casing." + projectile_type = /obj/item/projectile/bullet/incendiary/c9mm + + +// .50AE (Desert Eagle) + +/obj/item/ammo_casing/a50AE + name = ".50AE bullet casing" + desc = "A .50AE bullet casing." + caliber = ".50" + projectile_type = /obj/item/projectile/bullet/a50AE + diff --git a/code/modules/projectiles/ammunition/ballistic/revolver.dm b/code/modules/projectiles/ammunition/ballistic/revolver.dm index 6232ca4d69..d5a9fab4b4 100644 --- a/code/modules/projectiles/ammunition/ballistic/revolver.dm +++ b/code/modules/projectiles/ammunition/ballistic/revolver.dm @@ -1,50 +1,50 @@ -// .357 (Syndie Revolver) - -/obj/item/ammo_casing/a357 - name = ".357 bullet casing" - desc = "A .357 bullet casing." - caliber = "357" - projectile_type = /obj/item/projectile/bullet/a357 - -/obj/item/ammo_casing/a357/ap - name = ".357 armor-piercing bullet casing" - desc = "A .357 armor-piercing bullet casing." - projectile_type = /obj/item/projectile/bullet/a357/ap - -// 7.62x38mmR (Nagant Revolver) - -/obj/item/ammo_casing/n762 - name = "7.62x38mmR bullet casing" - desc = "A 7.62x38mmR bullet casing." - caliber = "n762" - projectile_type = /obj/item/projectile/bullet/n762 - -// .38 (Detective's Gun) - -/obj/item/ammo_casing/c38 - name = ".38 rubber bullet casing" - desc = "A .38 rubber bullet casing." - caliber = "38" - projectile_type = /obj/item/projectile/bullet/c38/rubber - -/obj/item/ammo_casing/c38/lethal - name = ".38 bullet casing" - desc = "A .38 bullet casing" - projectile_type = /obj/item/projectile/bullet/c38 - -/obj/item/ammo_casing/c38/trac - name = ".38 TRAC bullet casing" - desc = "A .38 \"TRAC\" bullet casing." - projectile_type = /obj/item/projectile/bullet/c38/trac - -/obj/item/ammo_casing/c38/hotshot - name = ".38 Hot Shot bullet casing" - desc = "A .38 Hot Shot bullet casing." - caliber = "38" - projectile_type = /obj/item/projectile/bullet/c38/hotshot - -/obj/item/ammo_casing/c38/iceblox - name = ".38 Iceblox bullet casing" - desc = "A .38 Iceblox bullet casing." - caliber = "38" +// .357 (Syndie Revolver) + +/obj/item/ammo_casing/a357 + name = ".357 bullet casing" + desc = "A .357 bullet casing." + caliber = "357" + projectile_type = /obj/item/projectile/bullet/a357 + +/obj/item/ammo_casing/a357/ap + name = ".357 armor-piercing bullet casing" + desc = "A .357 armor-piercing bullet casing." + projectile_type = /obj/item/projectile/bullet/a357/ap + +// 7.62x38mmR (Nagant Revolver) + +/obj/item/ammo_casing/n762 + name = "7.62x38mmR bullet casing" + desc = "A 7.62x38mmR bullet casing." + caliber = "n762" + projectile_type = /obj/item/projectile/bullet/n762 + +// .38 (Detective's Gun) + +/obj/item/ammo_casing/c38 + name = ".38 rubber bullet casing" + desc = "A .38 rubber bullet casing." + caliber = "38" + projectile_type = /obj/item/projectile/bullet/c38/rubber + +/obj/item/ammo_casing/c38/lethal + name = ".38 bullet casing" + desc = "A .38 bullet casing" + projectile_type = /obj/item/projectile/bullet/c38 + +/obj/item/ammo_casing/c38/trac + name = ".38 TRAC bullet casing" + desc = "A .38 \"TRAC\" bullet casing." + projectile_type = /obj/item/projectile/bullet/c38/trac + +/obj/item/ammo_casing/c38/hotshot + name = ".38 Hot Shot bullet casing" + desc = "A .38 Hot Shot bullet casing." + caliber = "38" + projectile_type = /obj/item/projectile/bullet/c38/hotshot + +/obj/item/ammo_casing/c38/iceblox + name = ".38 Iceblox bullet casing" + desc = "A .38 Iceblox bullet casing." + caliber = "38" projectile_type = /obj/item/projectile/bullet/c38/iceblox \ No newline at end of file diff --git a/code/modules/projectiles/ammunition/ballistic/rifle.dm b/code/modules/projectiles/ammunition/ballistic/rifle.dm index a35cfcba1c..47c5c6d602 100644 --- a/code/modules/projectiles/ammunition/ballistic/rifle.dm +++ b/code/modules/projectiles/ammunition/ballistic/rifle.dm @@ -1,28 +1,28 @@ -// 7.62 (Nagant Rifle) - -/obj/item/ammo_casing/a762 - name = "7.62 bullet casing" - desc = "A 7.62 bullet casing." - icon_state = "762-casing" - caliber = "a762" - projectile_type = /obj/item/projectile/bullet/a762 - -/obj/item/ammo_casing/a762/enchanted - projectile_type = /obj/item/projectile/bullet/a762_enchanted - -// 5.56mm (M-90gl Carbine) - -/obj/item/ammo_casing/a556 - name = "5.56mm bullet casing" - desc = "A 5.56mm bullet casing." - caliber = "a556" - projectile_type = /obj/item/projectile/bullet/a556 - -// 40mm (Grenade Launcher) - -/obj/item/ammo_casing/a40mm - name = "40mm HE shell" - desc = "A cased high explosive grenade that can only be activated once fired out of a grenade launcher." - caliber = "40mm" - icon_state = "40mmHE" - projectile_type = /obj/item/projectile/bullet/a40mm +// 7.62 (Nagant Rifle) + +/obj/item/ammo_casing/a762 + name = "7.62 bullet casing" + desc = "A 7.62 bullet casing." + icon_state = "762-casing" + caliber = "a762" + projectile_type = /obj/item/projectile/bullet/a762 + +/obj/item/ammo_casing/a762/enchanted + projectile_type = /obj/item/projectile/bullet/a762_enchanted + +// 5.56mm (M-90gl Carbine) + +/obj/item/ammo_casing/a556 + name = "5.56mm bullet casing" + desc = "A 5.56mm bullet casing." + caliber = "a556" + projectile_type = /obj/item/projectile/bullet/a556 + +// 40mm (Grenade Launcher) + +/obj/item/ammo_casing/a40mm + name = "40mm HE shell" + desc = "A cased high explosive grenade that can only be activated once fired out of a grenade launcher." + caliber = "40mm" + icon_state = "40mmHE" + projectile_type = /obj/item/projectile/bullet/a40mm diff --git a/code/modules/projectiles/ammunition/ballistic/shotgun.dm b/code/modules/projectiles/ammunition/ballistic/shotgun.dm index a8d3934208..9aabd0e6b1 100644 --- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm +++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm @@ -1,141 +1,141 @@ -// Shotgun - -/obj/item/ammo_casing/shotgun - name = "shotgun slug" - desc = "A 12 gauge lead slug." - icon_state = "blshell" - caliber = "shotgun" - projectile_type = /obj/item/projectile/bullet/shotgun_slug - materials = list(MAT_METAL=4000) - -/obj/item/ammo_casing/shotgun/beanbag - name = "beanbag slug" - desc = "A weak beanbag slug for riot control." - icon_state = "bshell" - projectile_type = /obj/item/projectile/bullet/shotgun_beanbag - materials = list(MAT_METAL=250) - -/obj/item/ammo_casing/shotgun/incendiary - name = "incendiary slug" - desc = "An incendiary-coated shotgun slug." - icon_state = "ishell" - projectile_type = /obj/item/projectile/bullet/incendiary/shotgun - -/obj/item/ammo_casing/shotgun/dragonsbreath - name = "dragonsbreath shell" - desc = "A shotgun shell which fires a spread of incendiary pellets." - icon_state = "ishell2" - projectile_type = /obj/item/projectile/bullet/incendiary/shotgun/dragonsbreath - pellets = 4 - variance = 35 - -/obj/item/ammo_casing/shotgun/stunslug - name = "taser slug" - desc = "A stunning taser slug." - icon_state = "stunshell" - projectile_type = /obj/item/projectile/bullet/shotgun_stunslug - materials = list(MAT_METAL=250) - -/obj/item/ammo_casing/shotgun/meteorslug - name = "meteorslug shell" - desc = "A shotgun shell rigged with CMC technology, which launches a massive slug when fired." - icon_state = "mshell" - projectile_type = /obj/item/projectile/bullet/shotgun_meteorslug - -/obj/item/ammo_casing/shotgun/pulseslug - name = "pulse slug" - desc = "A delicate device which can be loaded into a shotgun. The primer acts as a button which triggers the gain medium and fires a powerful \ - energy blast. While the heat and power drain limit it to one use, it can still allow an operator to engage targets that ballistic ammunition \ - would have difficulty with." - icon_state = "pshell" - projectile_type = /obj/item/projectile/beam/pulse/shotgun - -/obj/item/ammo_casing/shotgun/frag12 - name = "FRAG-12 slug" - desc = "A high explosive breaching round for a 12 gauge shotgun." - icon_state = "heshell" - projectile_type = /obj/item/projectile/bullet/shotgun_frag12 - -/obj/item/ammo_casing/shotgun/buckshot - name = "buckshot shell" - desc = "A 12 gauge buckshot shell." - icon_state = "gshell" - projectile_type = /obj/item/projectile/bullet/pellet/shotgun_buckshot - pellets = 6 - variance = 25 - -/obj/item/ammo_casing/shotgun/rubbershot - name = "rubber shot" - desc = "A shotgun casing filled with densely-packed rubber balls, used to incapacitate crowds from a distance." - icon_state = "bshell" - projectile_type = /obj/item/projectile/bullet/pellet/shotgun_rubbershot - pellets = 6 - variance = 25 - materials = list(MAT_METAL=4000) - -/obj/item/ammo_casing/shotgun/improvised - name = "improvised shell" - desc = "An extremely weak shotgun shell with multiple small pellets made out of metal shards." - icon_state = "improvshell" - projectile_type = /obj/item/projectile/bullet/pellet/shotgun_improvised - materials = list(MAT_METAL=250) - pellets = 10 - variance = 25 - -/obj/item/ammo_casing/shotgun/ion - name = "ion shell" - desc = "An advanced shotgun shell which uses a subspace ansible crystal to produce an effect similar to a standard ion rifle. \ - The unique properties of the crystal split the pulse into a spread of individually weaker bolts." - icon_state = "ionshell" - projectile_type = /obj/item/projectile/ion/weak - pellets = 4 - variance = 35 - -/obj/item/ammo_casing/shotgun/laserslug - name = "scatter laser shell" - desc = "An advanced shotgun shell that uses a micro laser to replicate the effects of a scatter laser weapon in a ballistic package." - icon_state = "lshell" - projectile_type = /obj/item/projectile/beam/weak - pellets = 6 - variance = 35 - -/obj/item/ammo_casing/shotgun/techshell - name = "unloaded technological shell" - desc = "A high-tech shotgun shell which can be loaded with materials to produce unique effects." - icon_state = "cshell" - projectile_type = null - -/obj/item/ammo_casing/shotgun/dart - name = "shotgun dart" - desc = "A dart for use in shotguns. Can be injected with up to 30 units of any chemical." - icon_state = "cshell" - projectile_type = /obj/item/projectile/bullet/dart - var/reagent_amount = 30 - -/obj/item/ammo_casing/shotgun/dart/Initialize() - . = ..() - create_reagents(reagent_amount, OPENCONTAINER) - -/obj/item/ammo_casing/shotgun/dart/attackby() - return - -/obj/item/ammo_casing/shotgun/dart/noreact - name = "cryostasis shotgun dart" - desc = "A dart for use in shotguns. Uses technology similar to cryostasis beakers to keep internal reagents from reacting. Can be injected with up to 10 units of any chemical." - icon_state = "cnrshell" - reagent_amount = 10 - -/obj/item/ammo_casing/shotgun/dart/noreact/Initialize() - . = ..() - ENABLE_BITFIELD(reagents.reagents_holder_flags, NO_REACT) - -/obj/item/ammo_casing/shotgun/dart/bioterror - desc = "A shotgun dart filled with deadly toxins." - -/obj/item/ammo_casing/shotgun/dart/bioterror/Initialize() - . = ..() - reagents.add_reagent(/datum/reagent/toxin/fentanyl, 6) - reagents.add_reagent(/datum/reagent/toxin/spore, 6) - reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 6) //;HELP OPS IN MAINT - reagents.add_reagent(/datum/reagent/toxin/coniine, 6) - reagents.add_reagent(/datum/reagent/toxin/sodium_thiopental, 6) +// Shotgun + +/obj/item/ammo_casing/shotgun + name = "shotgun slug" + desc = "A 12 gauge lead slug." + icon_state = "blshell" + caliber = "shotgun" + projectile_type = /obj/item/projectile/bullet/shotgun_slug + materials = list(MAT_METAL=4000) + +/obj/item/ammo_casing/shotgun/beanbag + name = "beanbag slug" + desc = "A weak beanbag slug for riot control." + icon_state = "bshell" + projectile_type = /obj/item/projectile/bullet/shotgun_beanbag + materials = list(MAT_METAL=250) + +/obj/item/ammo_casing/shotgun/incendiary + name = "incendiary slug" + desc = "An incendiary-coated shotgun slug." + icon_state = "ishell" + projectile_type = /obj/item/projectile/bullet/incendiary/shotgun + +/obj/item/ammo_casing/shotgun/dragonsbreath + name = "dragonsbreath shell" + desc = "A shotgun shell which fires a spread of incendiary pellets." + icon_state = "ishell2" + projectile_type = /obj/item/projectile/bullet/incendiary/shotgun/dragonsbreath + pellets = 4 + variance = 35 + +/obj/item/ammo_casing/shotgun/stunslug + name = "taser slug" + desc = "A stunning taser slug." + icon_state = "stunshell" + projectile_type = /obj/item/projectile/bullet/shotgun_stunslug + materials = list(MAT_METAL=250) + +/obj/item/ammo_casing/shotgun/meteorslug + name = "meteorslug shell" + desc = "A shotgun shell rigged with CMC technology, which launches a massive slug when fired." + icon_state = "mshell" + projectile_type = /obj/item/projectile/bullet/shotgun_meteorslug + +/obj/item/ammo_casing/shotgun/pulseslug + name = "pulse slug" + desc = "A delicate device which can be loaded into a shotgun. The primer acts as a button which triggers the gain medium and fires a powerful \ + energy blast. While the heat and power drain limit it to one use, it can still allow an operator to engage targets that ballistic ammunition \ + would have difficulty with." + icon_state = "pshell" + projectile_type = /obj/item/projectile/beam/pulse/shotgun + +/obj/item/ammo_casing/shotgun/frag12 + name = "FRAG-12 slug" + desc = "A high explosive breaching round for a 12 gauge shotgun." + icon_state = "heshell" + projectile_type = /obj/item/projectile/bullet/shotgun_frag12 + +/obj/item/ammo_casing/shotgun/buckshot + name = "buckshot shell" + desc = "A 12 gauge buckshot shell." + icon_state = "gshell" + projectile_type = /obj/item/projectile/bullet/pellet/shotgun_buckshot + pellets = 6 + variance = 25 + +/obj/item/ammo_casing/shotgun/rubbershot + name = "rubber shot" + desc = "A shotgun casing filled with densely-packed rubber balls, used to incapacitate crowds from a distance." + icon_state = "bshell" + projectile_type = /obj/item/projectile/bullet/pellet/shotgun_rubbershot + pellets = 6 + variance = 25 + materials = list(MAT_METAL=4000) + +/obj/item/ammo_casing/shotgun/improvised + name = "improvised shell" + desc = "An extremely weak shotgun shell with multiple small pellets made out of metal shards." + icon_state = "improvshell" + projectile_type = /obj/item/projectile/bullet/pellet/shotgun_improvised + materials = list(MAT_METAL=250) + pellets = 10 + variance = 25 + +/obj/item/ammo_casing/shotgun/ion + name = "ion shell" + desc = "An advanced shotgun shell which uses a subspace ansible crystal to produce an effect similar to a standard ion rifle. \ + The unique properties of the crystal split the pulse into a spread of individually weaker bolts." + icon_state = "ionshell" + projectile_type = /obj/item/projectile/ion/weak + pellets = 4 + variance = 35 + +/obj/item/ammo_casing/shotgun/laserslug + name = "scatter laser shell" + desc = "An advanced shotgun shell that uses a micro laser to replicate the effects of a scatter laser weapon in a ballistic package." + icon_state = "lshell" + projectile_type = /obj/item/projectile/beam/weak + pellets = 6 + variance = 35 + +/obj/item/ammo_casing/shotgun/techshell + name = "unloaded technological shell" + desc = "A high-tech shotgun shell which can be loaded with materials to produce unique effects." + icon_state = "cshell" + projectile_type = null + +/obj/item/ammo_casing/shotgun/dart + name = "shotgun dart" + desc = "A dart for use in shotguns. Can be injected with up to 30 units of any chemical." + icon_state = "cshell" + projectile_type = /obj/item/projectile/bullet/dart + var/reagent_amount = 30 + +/obj/item/ammo_casing/shotgun/dart/Initialize() + . = ..() + create_reagents(reagent_amount, OPENCONTAINER) + +/obj/item/ammo_casing/shotgun/dart/attackby() + return + +/obj/item/ammo_casing/shotgun/dart/noreact + name = "cryostasis shotgun dart" + desc = "A dart for use in shotguns. Uses technology similar to cryostasis beakers to keep internal reagents from reacting. Can be injected with up to 10 units of any chemical." + icon_state = "cnrshell" + reagent_amount = 10 + +/obj/item/ammo_casing/shotgun/dart/noreact/Initialize() + . = ..() + ENABLE_BITFIELD(reagents.reagents_holder_flags, NO_REACT) + +/obj/item/ammo_casing/shotgun/dart/bioterror + desc = "A shotgun dart filled with deadly toxins." + +/obj/item/ammo_casing/shotgun/dart/bioterror/Initialize() + . = ..() + reagents.add_reagent(/datum/reagent/toxin/fentanyl, 6) + reagents.add_reagent(/datum/reagent/toxin/spore, 6) + reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 6) //;HELP OPS IN MAINT + reagents.add_reagent(/datum/reagent/toxin/coniine, 6) + reagents.add_reagent(/datum/reagent/toxin/sodium_thiopental, 6) diff --git a/code/modules/projectiles/ammunition/ballistic/smg.dm b/code/modules/projectiles/ammunition/ballistic/smg.dm index 758be46344..49ec78f9dd 100644 --- a/code/modules/projectiles/ammunition/ballistic/smg.dm +++ b/code/modules/projectiles/ammunition/ballistic/smg.dm @@ -1,32 +1,32 @@ -// 4.6x30mm (Autorifles) - -/obj/item/ammo_casing/c46x30mm - name = "4.6x30mm bullet casing" - desc = "A 4.6x30mm bullet casing." - caliber = "4.6x30mm" - projectile_type = /obj/item/projectile/bullet/c46x30mm - -/obj/item/ammo_casing/c46x30mm/ap - name = "4.6x30mm armor-piercing bullet casing" - desc = "A 4.6x30mm armor-piercing bullet casing." - projectile_type = /obj/item/projectile/bullet/c46x30mm_ap - -/obj/item/ammo_casing/c46x30mm/inc - name = "4.6x30mm incendiary bullet casing" - desc = "A 4.6x30mm incendiary bullet casing." - projectile_type = /obj/item/projectile/bullet/incendiary/c46x30mm - -// .45 (M1911 + C20r) - -/obj/item/ammo_casing/c45 - name = ".45 bullet casing" - desc = "A .45 bullet casing." - caliber = ".45" - projectile_type = /obj/item/projectile/bullet/c45 - -/obj/item/ammo_casing/c45/nostamina - projectile_type = /obj/item/projectile/bullet/c45_nostamina - -/obj/item/ammo_casing/c45/kitchengun - desc = "A .45 bullet casing. It has a small sponge attached to it." - projectile_type = /obj/item/projectile/bullet/c45_cleaning +// 4.6x30mm (Autorifles) + +/obj/item/ammo_casing/c46x30mm + name = "4.6x30mm bullet casing" + desc = "A 4.6x30mm bullet casing." + caliber = "4.6x30mm" + projectile_type = /obj/item/projectile/bullet/c46x30mm + +/obj/item/ammo_casing/c46x30mm/ap + name = "4.6x30mm armor-piercing bullet casing" + desc = "A 4.6x30mm armor-piercing bullet casing." + projectile_type = /obj/item/projectile/bullet/c46x30mm_ap + +/obj/item/ammo_casing/c46x30mm/inc + name = "4.6x30mm incendiary bullet casing" + desc = "A 4.6x30mm incendiary bullet casing." + projectile_type = /obj/item/projectile/bullet/incendiary/c46x30mm + +// .45 (M1911 + C20r) + +/obj/item/ammo_casing/c45 + name = ".45 bullet casing" + desc = "A .45 bullet casing." + caliber = ".45" + projectile_type = /obj/item/projectile/bullet/c45 + +/obj/item/ammo_casing/c45/nostamina + projectile_type = /obj/item/projectile/bullet/c45_nostamina + +/obj/item/ammo_casing/c45/kitchengun + desc = "A .45 bullet casing. It has a small sponge attached to it." + projectile_type = /obj/item/projectile/bullet/c45_cleaning diff --git a/code/modules/projectiles/ammunition/ballistic/sniper.dm b/code/modules/projectiles/ammunition/ballistic/sniper.dm index 30cddedfd8..1944a13180 100644 --- a/code/modules/projectiles/ammunition/ballistic/sniper.dm +++ b/code/modules/projectiles/ammunition/ballistic/sniper.dm @@ -1,20 +1,20 @@ -// .50 (Sniper) - -/obj/item/ammo_casing/p50 - name = ".50 bullet casing" - desc = "A .50 bullet casing." - caliber = ".50" - projectile_type = /obj/item/projectile/bullet/p50 - icon_state = ".50" - -/obj/item/ammo_casing/p50/soporific - name = ".50 soporific bullet casing" - desc = "A .50 bullet casing, specialised in sending the target to sleep, instead of hell." - projectile_type = /obj/item/projectile/bullet/p50/soporific - icon_state = "sleeper" - harmful = FALSE - -/obj/item/ammo_casing/p50/penetrator - name = ".50 penetrator round bullet casing" - desc = "A .50 caliber penetrator round casing." - projectile_type = /obj/item/projectile/bullet/p50/penetrator +// .50 (Sniper) + +/obj/item/ammo_casing/p50 + name = ".50 bullet casing" + desc = "A .50 bullet casing." + caliber = ".50" + projectile_type = /obj/item/projectile/bullet/p50 + icon_state = ".50" + +/obj/item/ammo_casing/p50/soporific + name = ".50 soporific bullet casing" + desc = "A .50 bullet casing, specialised in sending the target to sleep, instead of hell." + projectile_type = /obj/item/projectile/bullet/p50/soporific + icon_state = "sleeper" + harmful = FALSE + +/obj/item/ammo_casing/p50/penetrator + name = ".50 penetrator round bullet casing" + desc = "A .50 caliber penetrator round casing." + projectile_type = /obj/item/projectile/bullet/p50/penetrator diff --git a/code/modules/projectiles/ammunition/caseless/_caseless.dm b/code/modules/projectiles/ammunition/caseless/_caseless.dm index 11f7b8670d..db1aa6562c 100644 --- a/code/modules/projectiles/ammunition/caseless/_caseless.dm +++ b/code/modules/projectiles/ammunition/caseless/_caseless.dm @@ -1,16 +1,16 @@ -/obj/item/ammo_casing/caseless - desc = "A caseless bullet casing." - firing_effect_type = null - heavy_metal = FALSE - -/obj/item/ammo_casing/caseless/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from) - if (..()) //successfully firing - moveToNullspace() - QDEL_NULL(src) - return TRUE - else - return FALSE - -/obj/item/ammo_casing/caseless/update_icon() - ..() - icon_state = "[initial(icon_state)]" +/obj/item/ammo_casing/caseless + desc = "A caseless bullet casing." + firing_effect_type = null + heavy_metal = FALSE + +/obj/item/ammo_casing/caseless/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from) + if (..()) //successfully firing + moveToNullspace() + QDEL_NULL(src) + return TRUE + else + return FALSE + +/obj/item/ammo_casing/caseless/update_icon() + ..() + icon_state = "[initial(icon_state)]" diff --git a/code/modules/projectiles/ammunition/caseless/foam.dm b/code/modules/projectiles/ammunition/caseless/foam.dm index 9457026261..1040c08d87 100644 --- a/code/modules/projectiles/ammunition/caseless/foam.dm +++ b/code/modules/projectiles/ammunition/caseless/foam.dm @@ -1,65 +1,65 @@ -/obj/item/ammo_casing/caseless/foam_dart - name = "foam dart" - desc = "It's nerf or nothing! Ages 8 and up." - projectile_type = /obj/item/projectile/bullet/reusable/foam_dart - caliber = "foam_force" - icon = 'icons/obj/guns/toy.dmi' - icon_state = "foamdart" - materials = list(MAT_METAL = 11.25) - harmful = FALSE - var/modified = FALSE - -/obj/item/ammo_casing/caseless/foam_dart/update_icon() - ..() - if (modified) - icon_state = "foamdart_empty" - desc = "It's nerf or nothing! ... Although, this one doesn't look too safe." - if(BB) - BB.icon_state = "foamdart_empty" - else - icon_state = initial(icon_state) - desc = "It's nerf or nothing! Ages 8 and up." - if(BB) - BB.icon_state = initial(BB.icon_state) - - -/obj/item/ammo_casing/caseless/foam_dart/attackby(obj/item/A, mob/user, params) - var/obj/item/projectile/bullet/reusable/foam_dart/FD = BB - if (istype(A, /obj/item/screwdriver) && !modified) - modified = TRUE - FD.modified = TRUE - FD.damage_type = BRUTE - to_chat(user, "You pop the safety cap off [src].") - update_icon() - else if (istype(A, /obj/item/pen)) - if(modified) - if(!FD.pen) - harmful = TRUE - if(!user.transferItemToLoc(A, FD)) - return - FD.pen = A - FD.damage = 5 - FD.nodamage = FALSE - to_chat(user, "You insert [A] into [src].") - else - to_chat(user, "There's already something in [src].") - else - to_chat(user, "The safety cap prevents you from inserting [A] into [src].") - else - return ..() - -/obj/item/ammo_casing/caseless/foam_dart/attack_self(mob/living/user) - var/obj/item/projectile/bullet/reusable/foam_dart/FD = BB - if(FD.pen) - FD.damage = initial(FD.damage) - FD.nodamage = initial(FD.nodamage) - user.put_in_hands(FD.pen) - to_chat(user, "You remove [FD.pen] from [src].") - FD.pen = null - -/obj/item/ammo_casing/caseless/foam_dart/riot - name = "riot foam dart" - desc = "Whose smart idea was it to use toys as crowd control? Ages 18 and up." - projectile_type = /obj/item/projectile/bullet/reusable/foam_dart/riot - icon_state = "foamdart_riot" - materials = list(MAT_METAL = 1125) +/obj/item/ammo_casing/caseless/foam_dart + name = "foam dart" + desc = "It's nerf or nothing! Ages 8 and up." + projectile_type = /obj/item/projectile/bullet/reusable/foam_dart + caliber = "foam_force" + icon = 'icons/obj/guns/toy.dmi' + icon_state = "foamdart" + materials = list(MAT_METAL = 11.25) + harmful = FALSE + var/modified = FALSE + +/obj/item/ammo_casing/caseless/foam_dart/update_icon() + ..() + if (modified) + icon_state = "foamdart_empty" + desc = "It's nerf or nothing! ... Although, this one doesn't look too safe." + if(BB) + BB.icon_state = "foamdart_empty" + else + icon_state = initial(icon_state) + desc = "It's nerf or nothing! Ages 8 and up." + if(BB) + BB.icon_state = initial(BB.icon_state) + + +/obj/item/ammo_casing/caseless/foam_dart/attackby(obj/item/A, mob/user, params) + var/obj/item/projectile/bullet/reusable/foam_dart/FD = BB + if (istype(A, /obj/item/screwdriver) && !modified) + modified = TRUE + FD.modified = TRUE + FD.damage_type = BRUTE + to_chat(user, "You pop the safety cap off [src].") + update_icon() + else if (istype(A, /obj/item/pen)) + if(modified) + if(!FD.pen) + harmful = TRUE + if(!user.transferItemToLoc(A, FD)) + return + FD.pen = A + FD.damage = 5 + FD.nodamage = FALSE + to_chat(user, "You insert [A] into [src].") + else + to_chat(user, "There's already something in [src].") + else + to_chat(user, "The safety cap prevents you from inserting [A] into [src].") + else + return ..() + +/obj/item/ammo_casing/caseless/foam_dart/attack_self(mob/living/user) + var/obj/item/projectile/bullet/reusable/foam_dart/FD = BB + if(FD.pen) + FD.damage = initial(FD.damage) + FD.nodamage = initial(FD.nodamage) + user.put_in_hands(FD.pen) + to_chat(user, "You remove [FD.pen] from [src].") + FD.pen = null + +/obj/item/ammo_casing/caseless/foam_dart/riot + name = "riot foam dart" + desc = "Whose smart idea was it to use toys as crowd control? Ages 18 and up." + projectile_type = /obj/item/projectile/bullet/reusable/foam_dart/riot + icon_state = "foamdart_riot" + materials = list(MAT_METAL = 1125) diff --git a/code/modules/projectiles/ammunition/caseless/misc.dm b/code/modules/projectiles/ammunition/caseless/misc.dm index 24f59ea658..8b783ff6aa 100644 --- a/code/modules/projectiles/ammunition/caseless/misc.dm +++ b/code/modules/projectiles/ammunition/caseless/misc.dm @@ -1,22 +1,22 @@ -/obj/item/ammo_casing/caseless/magspear - name = "magnetic spear" - desc = "A reusable spear that is typically loaded into kinetic spearguns." - projectile_type = /obj/item/projectile/bullet/reusable/magspear - caliber = "speargun" - icon_state = "magspear" - throwforce = 15 //still deadly when thrown - throw_speed = 3 - -/obj/item/ammo_casing/caseless/laser - name = "laser casing" - desc = "You shouldn't be seeing this." - caliber = "laser" - icon_state = "s-casing-live" - projectile_type = /obj/item/projectile/beam - fire_sound = 'sound/weapons/laser.ogg' - firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy - -/obj/item/ammo_casing/caseless/laser/gatling - projectile_type = /obj/item/projectile/beam/weak/penetrator - variance = 0.8 - click_cooldown_override = 1 +/obj/item/ammo_casing/caseless/magspear + name = "magnetic spear" + desc = "A reusable spear that is typically loaded into kinetic spearguns." + projectile_type = /obj/item/projectile/bullet/reusable/magspear + caliber = "speargun" + icon_state = "magspear" + throwforce = 15 //still deadly when thrown + throw_speed = 3 + +/obj/item/ammo_casing/caseless/laser + name = "laser casing" + desc = "You shouldn't be seeing this." + caliber = "laser" + icon_state = "s-casing-live" + projectile_type = /obj/item/projectile/beam + fire_sound = 'sound/weapons/laser.ogg' + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy + +/obj/item/ammo_casing/caseless/laser/gatling + projectile_type = /obj/item/projectile/beam/weak/penetrator + variance = 0.8 + click_cooldown_override = 1 diff --git a/code/modules/projectiles/ammunition/caseless/rocket.dm b/code/modules/projectiles/ammunition/caseless/rocket.dm index bc693d96bc..387be922f0 100644 --- a/code/modules/projectiles/ammunition/caseless/rocket.dm +++ b/code/modules/projectiles/ammunition/caseless/rocket.dm @@ -1,19 +1,19 @@ -/obj/item/ammo_casing/caseless/rocket - name = "\improper PM-9HE" - desc = "An 84mm High Explosive rocket. Fire at people and pray." - caliber = "84mm" - icon_state = "srm-8" - projectile_type = /obj/item/projectile/bullet/a84mm_he - -/obj/item/ammo_casing/caseless/rocket/hedp - name = "\improper PM-9HEDP" - desc = "An 84mm High Explosive Dual Purpose rocket. Pointy end toward mechs." - caliber = "84mm" - icon_state = "84mm-hedp" - projectile_type = /obj/item/projectile/bullet/a84mm - -/obj/item/ammo_casing/caseless/a75 - desc = "A .75 bullet casing." - caliber = "75" - icon_state = "s-casing-live" - projectile_type = /obj/item/projectile/bullet/gyro +/obj/item/ammo_casing/caseless/rocket + name = "\improper PM-9HE" + desc = "An 84mm High Explosive rocket. Fire at people and pray." + caliber = "84mm" + icon_state = "srm-8" + projectile_type = /obj/item/projectile/bullet/a84mm_he + +/obj/item/ammo_casing/caseless/rocket/hedp + name = "\improper PM-9HEDP" + desc = "An 84mm High Explosive Dual Purpose rocket. Pointy end toward mechs." + caliber = "84mm" + icon_state = "84mm-hedp" + projectile_type = /obj/item/projectile/bullet/a84mm + +/obj/item/ammo_casing/caseless/a75 + desc = "A .75 bullet casing." + caliber = "75" + icon_state = "s-casing-live" + projectile_type = /obj/item/projectile/bullet/gyro diff --git a/code/modules/projectiles/ammunition/energy/_energy.dm b/code/modules/projectiles/ammunition/energy/_energy.dm index 3a4e457c3d..10de173ecc 100644 --- a/code/modules/projectiles/ammunition/energy/_energy.dm +++ b/code/modules/projectiles/ammunition/energy/_energy.dm @@ -1,10 +1,10 @@ -/obj/item/ammo_casing/energy - name = "energy weapon lens" - desc = "The part of the gun that makes the laser go pew." - caliber = "energy" - projectile_type = /obj/item/projectile/energy - var/e_cost = 100 //The amount of energy a cell needs to expend to create this shot. - var/select_name = "energy" - fire_sound = 'sound/weapons/laser.ogg' - firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy - heavy_metal = FALSE +/obj/item/ammo_casing/energy + name = "energy weapon lens" + desc = "The part of the gun that makes the laser go pew." + caliber = "energy" + projectile_type = /obj/item/projectile/energy + var/e_cost = 100 //The amount of energy a cell needs to expend to create this shot. + var/select_name = "energy" + fire_sound = 'sound/weapons/laser.ogg' + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy + heavy_metal = FALSE diff --git a/code/modules/projectiles/ammunition/energy/ebow.dm b/code/modules/projectiles/ammunition/energy/ebow.dm index 8d9c72d1ba..98a7cbd051 100644 --- a/code/modules/projectiles/ammunition/energy/ebow.dm +++ b/code/modules/projectiles/ammunition/energy/ebow.dm @@ -1,12 +1,12 @@ -/obj/item/ammo_casing/energy/bolt - projectile_type = /obj/item/projectile/energy/bolt - select_name = "bolt" - e_cost = 500 - fire_sound = 'sound/weapons/genhit.ogg' - -/obj/item/ammo_casing/energy/bolt/halloween - projectile_type = /obj/item/projectile/energy/bolt/halloween - -/obj/item/ammo_casing/energy/bolt/large - projectile_type = /obj/item/projectile/energy/bolt/large - select_name = "heavy bolt" +/obj/item/ammo_casing/energy/bolt + projectile_type = /obj/item/projectile/energy/bolt + select_name = "bolt" + e_cost = 500 + fire_sound = 'sound/weapons/genhit.ogg' + +/obj/item/ammo_casing/energy/bolt/halloween + projectile_type = /obj/item/projectile/energy/bolt/halloween + +/obj/item/ammo_casing/energy/bolt/large + projectile_type = /obj/item/projectile/energy/bolt/large + select_name = "heavy bolt" diff --git a/code/modules/projectiles/ammunition/energy/gravity.dm b/code/modules/projectiles/ammunition/energy/gravity.dm index d8a6a1244d..eabb4f66d7 100644 --- a/code/modules/projectiles/ammunition/energy/gravity.dm +++ b/code/modules/projectiles/ammunition/energy/gravity.dm @@ -1,29 +1,29 @@ -/obj/item/ammo_casing/energy/gravity - e_cost = 0 - fire_sound = 'sound/weapons/wave.ogg' - select_name = "gravity" - delay = 50 - var/obj/item/gun/energy/gravity_gun/gun - -/obj/item/ammo_casing/energy/gravity/Initialize(mapload) - if(istype(loc,/obj/item/gun/energy/gravity_gun)) - gun = loc - . = ..() - -/obj/item/ammo_casing/energy/gravity/Destroy() - gun = null - . = ..() - -/obj/item/ammo_casing/energy/gravity/repulse - projectile_type = /obj/item/projectile/gravityrepulse - select_name = "repulse" - -/obj/item/ammo_casing/energy/gravity/attract - projectile_type = /obj/item/projectile/gravityattract - select_name = "attract" - -/obj/item/ammo_casing/energy/gravity/chaos - projectile_type = /obj/item/projectile/gravitychaos - select_name = "chaos" - - +/obj/item/ammo_casing/energy/gravity + e_cost = 0 + fire_sound = 'sound/weapons/wave.ogg' + select_name = "gravity" + delay = 50 + var/obj/item/gun/energy/gravity_gun/gun + +/obj/item/ammo_casing/energy/gravity/Initialize(mapload) + if(istype(loc,/obj/item/gun/energy/gravity_gun)) + gun = loc + . = ..() + +/obj/item/ammo_casing/energy/gravity/Destroy() + gun = null + . = ..() + +/obj/item/ammo_casing/energy/gravity/repulse + projectile_type = /obj/item/projectile/gravityrepulse + select_name = "repulse" + +/obj/item/ammo_casing/energy/gravity/attract + projectile_type = /obj/item/projectile/gravityattract + select_name = "attract" + +/obj/item/ammo_casing/energy/gravity/chaos + projectile_type = /obj/item/projectile/gravitychaos + select_name = "chaos" + + diff --git a/code/modules/projectiles/ammunition/energy/laser.dm b/code/modules/projectiles/ammunition/energy/laser.dm index 0940144721..f0aa9eae1f 100644 --- a/code/modules/projectiles/ammunition/energy/laser.dm +++ b/code/modules/projectiles/ammunition/energy/laser.dm @@ -1,73 +1,73 @@ -/obj/item/ammo_casing/energy/laser - projectile_type = /obj/item/projectile/beam/laser - select_name = "kill" - -/obj/item/ammo_casing/energy/lasergun - projectile_type = /obj/item/projectile/beam/laser - e_cost = 83 - select_name = "kill" - -/obj/item/ammo_casing/energy/lasergun/old - projectile_type = /obj/item/projectile/beam/laser - e_cost = 200 - select_name = "kill" - -/obj/item/ammo_casing/energy/laser/hos - e_cost = 100 - -/obj/item/ammo_casing/energy/laser/practice - projectile_type = /obj/item/projectile/beam/practice - select_name = "practice" - harmful = FALSE - -/obj/item/ammo_casing/energy/laser/scatter - projectile_type = /obj/item/projectile/beam/scatter - pellets = 5 - variance = 25 - select_name = "scatter" - -/obj/item/ammo_casing/energy/laser/scatter/disabler - projectile_type = /obj/item/projectile/beam/disabler - pellets = 3 - variance = 15 - harmful = FALSE - -/obj/item/ammo_casing/energy/laser/heavy - projectile_type = /obj/item/projectile/beam/laser/heavylaser - select_name = "anti-vehicle" - fire_sound = 'sound/weapons/lasercannonfire.ogg' - -/obj/item/ammo_casing/energy/laser/pulse - projectile_type = /obj/item/projectile/beam/pulse - e_cost = 200 - select_name = "DESTROY" - fire_sound = 'sound/weapons/pulse.ogg' - -/obj/item/ammo_casing/energy/laser/bluetag - projectile_type = /obj/item/projectile/beam/lasertag/bluetag - select_name = "bluetag" - harmful = FALSE - -/obj/item/ammo_casing/energy/laser/bluetag/hitscan - projectile_type = /obj/item/projectile/beam/lasertag/bluetag/hitscan - -/obj/item/ammo_casing/energy/laser/redtag - projectile_type = /obj/item/projectile/beam/lasertag/redtag - select_name = "redtag" - harmful = FALSE - -/obj/item/ammo_casing/energy/laser/redtag/hitscan - projectile_type = /obj/item/projectile/beam/lasertag/redtag/hitscan - -/obj/item/ammo_casing/energy/laser/redtag/hitscan/holy - projectile_type = /obj/item/projectile/beam/lasertag/redtag/hitscan/holy - -/obj/item/ammo_casing/energy/xray - projectile_type = /obj/item/projectile/beam/xray - e_cost = 50 - fire_sound = 'sound/weapons/laser3.ogg' - -/obj/item/ammo_casing/energy/mindflayer - projectile_type = /obj/item/projectile/beam/mindflayer - select_name = "MINDFUCK" - fire_sound = 'sound/weapons/laser.ogg' +/obj/item/ammo_casing/energy/laser + projectile_type = /obj/item/projectile/beam/laser + select_name = "kill" + +/obj/item/ammo_casing/energy/lasergun + projectile_type = /obj/item/projectile/beam/laser + e_cost = 83 + select_name = "kill" + +/obj/item/ammo_casing/energy/lasergun/old + projectile_type = /obj/item/projectile/beam/laser + e_cost = 200 + select_name = "kill" + +/obj/item/ammo_casing/energy/laser/hos + e_cost = 100 + +/obj/item/ammo_casing/energy/laser/practice + projectile_type = /obj/item/projectile/beam/practice + select_name = "practice" + harmful = FALSE + +/obj/item/ammo_casing/energy/laser/scatter + projectile_type = /obj/item/projectile/beam/scatter + pellets = 5 + variance = 25 + select_name = "scatter" + +/obj/item/ammo_casing/energy/laser/scatter/disabler + projectile_type = /obj/item/projectile/beam/disabler + pellets = 3 + variance = 15 + harmful = FALSE + +/obj/item/ammo_casing/energy/laser/heavy + projectile_type = /obj/item/projectile/beam/laser/heavylaser + select_name = "anti-vehicle" + fire_sound = 'sound/weapons/lasercannonfire.ogg' + +/obj/item/ammo_casing/energy/laser/pulse + projectile_type = /obj/item/projectile/beam/pulse + e_cost = 200 + select_name = "DESTROY" + fire_sound = 'sound/weapons/pulse.ogg' + +/obj/item/ammo_casing/energy/laser/bluetag + projectile_type = /obj/item/projectile/beam/lasertag/bluetag + select_name = "bluetag" + harmful = FALSE + +/obj/item/ammo_casing/energy/laser/bluetag/hitscan + projectile_type = /obj/item/projectile/beam/lasertag/bluetag/hitscan + +/obj/item/ammo_casing/energy/laser/redtag + projectile_type = /obj/item/projectile/beam/lasertag/redtag + select_name = "redtag" + harmful = FALSE + +/obj/item/ammo_casing/energy/laser/redtag/hitscan + projectile_type = /obj/item/projectile/beam/lasertag/redtag/hitscan + +/obj/item/ammo_casing/energy/laser/redtag/hitscan/holy + projectile_type = /obj/item/projectile/beam/lasertag/redtag/hitscan/holy + +/obj/item/ammo_casing/energy/xray + projectile_type = /obj/item/projectile/beam/xray + e_cost = 50 + fire_sound = 'sound/weapons/laser3.ogg' + +/obj/item/ammo_casing/energy/mindflayer + projectile_type = /obj/item/projectile/beam/mindflayer + select_name = "MINDFUCK" + fire_sound = 'sound/weapons/laser.ogg' diff --git a/code/modules/projectiles/ammunition/energy/lmg.dm b/code/modules/projectiles/ammunition/energy/lmg.dm index 5ebe83f792..c2cfd076b0 100644 --- a/code/modules/projectiles/ammunition/energy/lmg.dm +++ b/code/modules/projectiles/ammunition/energy/lmg.dm @@ -1,6 +1,6 @@ -/obj/item/ammo_casing/energy/c3dbullet - projectile_type = /obj/item/projectile/bullet/c3d - select_name = "spraydown" - fire_sound = 'sound/weapons/gunshot_smg.ogg' - e_cost = 20 - firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect +/obj/item/ammo_casing/energy/c3dbullet + projectile_type = /obj/item/projectile/bullet/c3d + select_name = "spraydown" + fire_sound = 'sound/weapons/gunshot_smg.ogg' + e_cost = 20 + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect diff --git a/code/modules/projectiles/ammunition/energy/plasma.dm b/code/modules/projectiles/ammunition/energy/plasma.dm index 3a71254508..42b03e4245 100644 --- a/code/modules/projectiles/ammunition/energy/plasma.dm +++ b/code/modules/projectiles/ammunition/energy/plasma.dm @@ -1,15 +1,15 @@ -/obj/item/ammo_casing/energy/plasma - projectile_type = /obj/item/projectile/plasma - select_name = "plasma burst" - fire_sound = 'sound/weapons/plasma_cutter.ogg' - delay = 15 - e_cost = 25 - -/obj/item/ammo_casing/energy/plasma/adv - projectile_type = /obj/item/projectile/plasma/adv - delay = 10 - e_cost = 10 - -/obj/item/ammo_casing/energy/plasma/weak - projectile_type = /obj/item/projectile/plasma/weak +/obj/item/ammo_casing/energy/plasma + projectile_type = /obj/item/projectile/plasma + select_name = "plasma burst" + fire_sound = 'sound/weapons/plasma_cutter.ogg' + delay = 15 + e_cost = 25 + +/obj/item/ammo_casing/energy/plasma/adv + projectile_type = /obj/item/projectile/plasma/adv + delay = 10 + e_cost = 10 + +/obj/item/ammo_casing/energy/plasma/weak + projectile_type = /obj/item/projectile/plasma/weak e_cost = 100 \ No newline at end of file diff --git a/code/modules/projectiles/ammunition/energy/portal.dm b/code/modules/projectiles/ammunition/energy/portal.dm index c4998a8843..7bb10da0cc 100644 --- a/code/modules/projectiles/ammunition/energy/portal.dm +++ b/code/modules/projectiles/ammunition/energy/portal.dm @@ -1,20 +1,20 @@ -/obj/item/ammo_casing/energy/wormhole - projectile_type = /obj/item/projectile/beam/wormhole - e_cost = 0 - fire_sound = 'sound/weapons/pulse3.ogg' - var/obj/item/gun/energy/wormhole_projector/gun = null - select_name = "blue" - -/obj/item/ammo_casing/energy/wormhole/orange - projectile_type = /obj/item/projectile/beam/wormhole/orange - select_name = "orange" - -/obj/item/ammo_casing/energy/wormhole/Initialize(mapload, obj/item/gun/energy/wormhole_projector/wh) - . = ..() - gun = wh - -/obj/item/ammo_casing/energy/wormhole/throw_proj() - . = ..() - if(istype(BB, /obj/item/projectile/beam/wormhole)) - var/obj/item/projectile/beam/wormhole/WH = BB - WH.gun = gun +/obj/item/ammo_casing/energy/wormhole + projectile_type = /obj/item/projectile/beam/wormhole + e_cost = 0 + fire_sound = 'sound/weapons/pulse3.ogg' + var/obj/item/gun/energy/wormhole_projector/gun = null + select_name = "blue" + +/obj/item/ammo_casing/energy/wormhole/orange + projectile_type = /obj/item/projectile/beam/wormhole/orange + select_name = "orange" + +/obj/item/ammo_casing/energy/wormhole/Initialize(mapload, obj/item/gun/energy/wormhole_projector/wh) + . = ..() + gun = wh + +/obj/item/ammo_casing/energy/wormhole/throw_proj() + . = ..() + if(istype(BB, /obj/item/projectile/beam/wormhole)) + var/obj/item/projectile/beam/wormhole/WH = BB + WH.gun = gun diff --git a/code/modules/projectiles/ammunition/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm index 2f87872710..39e3ca06ff 100644 --- a/code/modules/projectiles/ammunition/energy/special.dm +++ b/code/modules/projectiles/ammunition/energy/special.dm @@ -1,74 +1,74 @@ -/obj/item/ammo_casing/energy/ion - projectile_type = /obj/item/projectile/ion - select_name = "ion" - fire_sound = 'sound/weapons/ionrifle.ogg' - -/obj/item/ammo_casing/energy/declone - projectile_type = /obj/item/projectile/energy/declone - select_name = "declone" - fire_sound = 'sound/weapons/pulse3.ogg' - -/obj/item/ammo_casing/energy/flora - fire_sound = 'sound/effects/stealthoff.ogg' - harmful = FALSE - -/obj/item/ammo_casing/energy/flora/yield - projectile_type = /obj/item/projectile/energy/florayield - select_name = "yield" - -/obj/item/ammo_casing/energy/flora/mut - projectile_type = /obj/item/projectile/energy/floramut - select_name = "mutation" - -/obj/item/ammo_casing/energy/temp - projectile_type = /obj/item/projectile/temp - select_name = "freeze" - e_cost = 250 - fire_sound = 'sound/weapons/pulse3.ogg' - -/obj/item/ammo_casing/energy/temp/hot - projectile_type = /obj/item/projectile/temp/hot - select_name = "bake" - -/obj/item/ammo_casing/energy/meteor - projectile_type = /obj/item/projectile/meteor - select_name = "goddamn meteor" - -/obj/item/ammo_casing/energy/net - projectile_type = /obj/item/projectile/energy/net - select_name = "netting" - pellets = 6 - variance = 40 - harmful = FALSE - -/obj/item/ammo_casing/energy/trap - projectile_type = /obj/item/projectile/energy/trap - select_name = "snare" - harmful = FALSE - -/obj/item/ammo_casing/energy/instakill - projectile_type = /obj/item/projectile/beam/instakill - e_cost = 0 - select_name = "DESTROY" - -/obj/item/ammo_casing/energy/instakill/blue - projectile_type = /obj/item/projectile/beam/instakill/blue - -/obj/item/ammo_casing/energy/instakill/red - projectile_type = /obj/item/projectile/beam/instakill/red - -/obj/item/ammo_casing/energy/tesla_revolver - fire_sound = 'sound/magic/lightningbolt.ogg' - e_cost = 200 - select_name = "stun" - projectile_type = /obj/item/projectile/energy/tesla/revolver - -/obj/item/ammo_casing/energy/emitter - fire_sound = 'sound/weapons/emitter.ogg' - e_cost = 2000 //20,000 is in the cell making this 10 shots before reload - projectile_type = /obj/item/projectile/beam/emitter - -/obj/item/ammo_casing/energy/shrink - projectile_type = /obj/item/projectile/beam/shrink - select_name = "shrink ray" +/obj/item/ammo_casing/energy/ion + projectile_type = /obj/item/projectile/ion + select_name = "ion" + fire_sound = 'sound/weapons/ionrifle.ogg' + +/obj/item/ammo_casing/energy/declone + projectile_type = /obj/item/projectile/energy/declone + select_name = "declone" + fire_sound = 'sound/weapons/pulse3.ogg' + +/obj/item/ammo_casing/energy/flora + fire_sound = 'sound/effects/stealthoff.ogg' + harmful = FALSE + +/obj/item/ammo_casing/energy/flora/yield + projectile_type = /obj/item/projectile/energy/florayield + select_name = "yield" + +/obj/item/ammo_casing/energy/flora/mut + projectile_type = /obj/item/projectile/energy/floramut + select_name = "mutation" + +/obj/item/ammo_casing/energy/temp + projectile_type = /obj/item/projectile/temp + select_name = "freeze" + e_cost = 250 + fire_sound = 'sound/weapons/pulse3.ogg' + +/obj/item/ammo_casing/energy/temp/hot + projectile_type = /obj/item/projectile/temp/hot + select_name = "bake" + +/obj/item/ammo_casing/energy/meteor + projectile_type = /obj/item/projectile/meteor + select_name = "goddamn meteor" + +/obj/item/ammo_casing/energy/net + projectile_type = /obj/item/projectile/energy/net + select_name = "netting" + pellets = 6 + variance = 40 + harmful = FALSE + +/obj/item/ammo_casing/energy/trap + projectile_type = /obj/item/projectile/energy/trap + select_name = "snare" + harmful = FALSE + +/obj/item/ammo_casing/energy/instakill + projectile_type = /obj/item/projectile/beam/instakill + e_cost = 0 + select_name = "DESTROY" + +/obj/item/ammo_casing/energy/instakill/blue + projectile_type = /obj/item/projectile/beam/instakill/blue + +/obj/item/ammo_casing/energy/instakill/red + projectile_type = /obj/item/projectile/beam/instakill/red + +/obj/item/ammo_casing/energy/tesla_revolver + fire_sound = 'sound/magic/lightningbolt.ogg' + e_cost = 200 + select_name = "stun" + projectile_type = /obj/item/projectile/energy/tesla/revolver + +/obj/item/ammo_casing/energy/emitter + fire_sound = 'sound/weapons/emitter.ogg' + e_cost = 2000 //20,000 is in the cell making this 10 shots before reload + projectile_type = /obj/item/projectile/beam/emitter + +/obj/item/ammo_casing/energy/shrink + projectile_type = /obj/item/projectile/beam/shrink + select_name = "shrink ray" e_cost = 200 \ No newline at end of file diff --git a/code/modules/projectiles/ammunition/energy/stun.dm b/code/modules/projectiles/ammunition/energy/stun.dm index 36d6e16496..7c2b62c02c 100644 --- a/code/modules/projectiles/ammunition/energy/stun.dm +++ b/code/modules/projectiles/ammunition/energy/stun.dm @@ -1,30 +1,30 @@ -/obj/item/ammo_casing/energy/electrode - projectile_type = /obj/item/projectile/energy/electrode - select_name = "stun" - fire_sound = 'sound/weapons/taser.ogg' - e_cost = 200 - harmful = FALSE - -/obj/item/ammo_casing/energy/electrode/spec - e_cost = 100 - -/obj/item/ammo_casing/energy/electrode/gun - fire_sound = 'sound/weapons/gunshot.ogg' - e_cost = 100 - -/obj/item/ammo_casing/energy/electrode/hos - e_cost = 200 - -/obj/item/ammo_casing/energy/electrode/old - e_cost = 1000 - -/obj/item/ammo_casing/energy/disabler - projectile_type = /obj/item/projectile/beam/disabler - select_name = "disable" - e_cost = 40 - fire_sound = 'sound/weapons/taser2.ogg' - harmful = FALSE - click_cooldown_override = 3.5 - -/obj/item/ammo_casing/energy/disabler/secborg +/obj/item/ammo_casing/energy/electrode + projectile_type = /obj/item/projectile/energy/electrode + select_name = "stun" + fire_sound = 'sound/weapons/taser.ogg' + e_cost = 200 + harmful = FALSE + +/obj/item/ammo_casing/energy/electrode/spec + e_cost = 100 + +/obj/item/ammo_casing/energy/electrode/gun + fire_sound = 'sound/weapons/gunshot.ogg' + e_cost = 100 + +/obj/item/ammo_casing/energy/electrode/hos + e_cost = 200 + +/obj/item/ammo_casing/energy/electrode/old + e_cost = 1000 + +/obj/item/ammo_casing/energy/disabler + projectile_type = /obj/item/projectile/beam/disabler + select_name = "disable" + e_cost = 40 + fire_sound = 'sound/weapons/taser2.ogg' + harmful = FALSE + click_cooldown_override = 3.5 + +/obj/item/ammo_casing/energy/disabler/secborg e_cost = 50 \ No newline at end of file diff --git a/code/modules/projectiles/ammunition/special/magic.dm b/code/modules/projectiles/ammunition/special/magic.dm index 51b39e0410..cc039c5f2b 100644 --- a/code/modules/projectiles/ammunition/special/magic.dm +++ b/code/modules/projectiles/ammunition/special/magic.dm @@ -1,45 +1,45 @@ -/obj/item/ammo_casing/magic - name = "magic casing" - desc = "I didn't even know magic needed ammo..." - projectile_type = /obj/item/projectile/magic - firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/magic - heavy_metal = FALSE - -/obj/item/ammo_casing/magic/change - projectile_type = /obj/item/projectile/magic/change - -/obj/item/ammo_casing/magic/animate - projectile_type = /obj/item/projectile/magic/animate - -/obj/item/ammo_casing/magic/heal - projectile_type = /obj/item/projectile/magic/resurrection - harmful = FALSE - -/obj/item/ammo_casing/magic/death - projectile_type = /obj/item/projectile/magic/death - -/obj/item/ammo_casing/magic/teleport - projectile_type = /obj/item/projectile/magic/teleport - harmful = FALSE - -/obj/item/ammo_casing/magic/door - projectile_type = /obj/item/projectile/magic/door - harmful = FALSE - -/obj/item/ammo_casing/magic/fireball - projectile_type = /obj/item/projectile/magic/aoe/fireball - -/obj/item/ammo_casing/magic/chaos - projectile_type = /obj/item/projectile/magic - -/obj/item/ammo_casing/magic/spellblade - projectile_type = /obj/item/projectile/magic/spellblade - -/obj/item/ammo_casing/magic/arcane_barrage - projectile_type = /obj/item/projectile/magic/arcane_barrage - -/obj/item/ammo_casing/magic/honk - projectile_type = /obj/item/projectile/bullet/honker - -/obj/item/ammo_casing/magic/locker - projectile_type = /obj/item/projectile/magic/locker +/obj/item/ammo_casing/magic + name = "magic casing" + desc = "I didn't even know magic needed ammo..." + projectile_type = /obj/item/projectile/magic + firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/magic + heavy_metal = FALSE + +/obj/item/ammo_casing/magic/change + projectile_type = /obj/item/projectile/magic/change + +/obj/item/ammo_casing/magic/animate + projectile_type = /obj/item/projectile/magic/animate + +/obj/item/ammo_casing/magic/heal + projectile_type = /obj/item/projectile/magic/resurrection + harmful = FALSE + +/obj/item/ammo_casing/magic/death + projectile_type = /obj/item/projectile/magic/death + +/obj/item/ammo_casing/magic/teleport + projectile_type = /obj/item/projectile/magic/teleport + harmful = FALSE + +/obj/item/ammo_casing/magic/door + projectile_type = /obj/item/projectile/magic/door + harmful = FALSE + +/obj/item/ammo_casing/magic/fireball + projectile_type = /obj/item/projectile/magic/aoe/fireball + +/obj/item/ammo_casing/magic/chaos + projectile_type = /obj/item/projectile/magic + +/obj/item/ammo_casing/magic/spellblade + projectile_type = /obj/item/projectile/magic/spellblade + +/obj/item/ammo_casing/magic/arcane_barrage + projectile_type = /obj/item/projectile/magic/arcane_barrage + +/obj/item/ammo_casing/magic/honk + projectile_type = /obj/item/projectile/bullet/honker + +/obj/item/ammo_casing/magic/locker + projectile_type = /obj/item/projectile/magic/locker diff --git a/code/modules/projectiles/ammunition/special/syringe.dm b/code/modules/projectiles/ammunition/special/syringe.dm index d5cba6936f..10e4402856 100644 --- a/code/modules/projectiles/ammunition/special/syringe.dm +++ b/code/modules/projectiles/ammunition/special/syringe.dm @@ -1,78 +1,78 @@ -/obj/item/ammo_casing/syringegun - name = "syringe gun spring" - desc = "A high-power spring that throws syringes." - projectile_type = /obj/item/projectile/bullet/dart/syringe - firing_effect_type = null - -/obj/item/ammo_casing/syringegun/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") - if(!BB) - return - if(istype(loc, /obj/item/gun/syringe)) - var/obj/item/gun/syringe/SG = loc - if(!SG.syringes.len) - return - - var/obj/item/reagent_containers/syringe/S = SG.syringes[1] - - S.reagents.trans_to(BB, S.reagents.total_volume) - BB.name = S.name - var/obj/item/projectile/bullet/dart/D = BB - D.piercing = S.proj_piercing - SG.syringes.Remove(S) - qdel(S) - ..() - -/obj/item/ammo_casing/chemgun - name = "dart synthesiser" - desc = "A high-power spring, linked to an energy-based dart synthesiser." - projectile_type = /obj/item/projectile/bullet/dart - firing_effect_type = null - -/obj/item/ammo_casing/chemgun/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") - if(!BB) - return - if(istype(loc, /obj/item/gun/chem)) - var/obj/item/gun/chem/CG = loc - if(CG.syringes_left <= 0) - return - CG.reagents.trans_to(BB, 15) - BB.name = "chemical dart" - CG.syringes_left-- - ..() - -/obj/item/ammo_casing/dnainjector - name = "rigged syringe gun spring" - desc = "A high-power spring that throws DNA injectors." - projectile_type = /obj/item/projectile/bullet/dnainjector - firing_effect_type = null - -/obj/item/ammo_casing/dnainjector/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") - if(!BB) - return - if(istype(loc, /obj/item/gun/syringe/dna)) - var/obj/item/gun/syringe/dna/SG = loc - if(!SG.syringes.len) - return - - var/obj/item/dnainjector/S = popleft(SG.syringes) - var/obj/item/projectile/bullet/dnainjector/D = BB - S.forceMove(D) - D.injector = S - ..() - -/obj/item/ammo_casing/syringegun/dart - name = "used air canister" - desc = "A small canister of compressed gas." - projectile_type = /obj/item/projectile/bullet/dart/syringe/dart - firing_effect_type = null - harmful = FALSE - -/obj/item/ammo_casing/syringegun/dart/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") - ..() - var/obj/item/gun/syringe/SG = loc - if(!SG.syringes.len) - return - var/obj/item/reagent_containers/syringe/dart/S = SG.syringes[1] - if(S.emptrig == TRUE) - var/obj/item/projectile/bullet/dart/syringe/dart/D = BB - D.emptrig = TRUE +/obj/item/ammo_casing/syringegun + name = "syringe gun spring" + desc = "A high-power spring that throws syringes." + projectile_type = /obj/item/projectile/bullet/dart/syringe + firing_effect_type = null + +/obj/item/ammo_casing/syringegun/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") + if(!BB) + return + if(istype(loc, /obj/item/gun/syringe)) + var/obj/item/gun/syringe/SG = loc + if(!SG.syringes.len) + return + + var/obj/item/reagent_containers/syringe/S = SG.syringes[1] + + S.reagents.trans_to(BB, S.reagents.total_volume) + BB.name = S.name + var/obj/item/projectile/bullet/dart/D = BB + D.piercing = S.proj_piercing + SG.syringes.Remove(S) + qdel(S) + ..() + +/obj/item/ammo_casing/chemgun + name = "dart synthesiser" + desc = "A high-power spring, linked to an energy-based dart synthesiser." + projectile_type = /obj/item/projectile/bullet/dart + firing_effect_type = null + +/obj/item/ammo_casing/chemgun/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") + if(!BB) + return + if(istype(loc, /obj/item/gun/chem)) + var/obj/item/gun/chem/CG = loc + if(CG.syringes_left <= 0) + return + CG.reagents.trans_to(BB, 15) + BB.name = "chemical dart" + CG.syringes_left-- + ..() + +/obj/item/ammo_casing/dnainjector + name = "rigged syringe gun spring" + desc = "A high-power spring that throws DNA injectors." + projectile_type = /obj/item/projectile/bullet/dnainjector + firing_effect_type = null + +/obj/item/ammo_casing/dnainjector/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") + if(!BB) + return + if(istype(loc, /obj/item/gun/syringe/dna)) + var/obj/item/gun/syringe/dna/SG = loc + if(!SG.syringes.len) + return + + var/obj/item/dnainjector/S = popleft(SG.syringes) + var/obj/item/projectile/bullet/dnainjector/D = BB + S.forceMove(D) + D.injector = S + ..() + +/obj/item/ammo_casing/syringegun/dart + name = "used air canister" + desc = "A small canister of compressed gas." + projectile_type = /obj/item/projectile/bullet/dart/syringe/dart + firing_effect_type = null + harmful = FALSE + +/obj/item/ammo_casing/syringegun/dart/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") + ..() + var/obj/item/gun/syringe/SG = loc + if(!SG.syringes.len) + return + var/obj/item/reagent_containers/syringe/dart/S = SG.syringes[1] + if(S.emptrig == TRUE) + var/obj/item/projectile/bullet/dart/syringe/dart/D = BB + D.emptrig = TRUE diff --git a/code/modules/projectiles/boxes_magazines/_box_magazine.dm b/code/modules/projectiles/boxes_magazines/_box_magazine.dm index 476240a852..3cdb5ca06d 100644 --- a/code/modules/projectiles/boxes_magazines/_box_magazine.dm +++ b/code/modules/projectiles/boxes_magazines/_box_magazine.dm @@ -1,138 +1,138 @@ -//Boxes of ammo -/obj/item/ammo_box - name = "ammo box (null_reference_exception)" - desc = "A box of ammo." - icon = 'icons/obj/ammo.dmi' - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - materials = list(MAT_METAL = 30000) - throwforce = 2 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - var/list/stored_ammo = list() - var/ammo_type = /obj/item/ammo_casing - var/max_ammo = 7 - var/multiple_sprites = 0 - var/caliber - var/multiload = 1 - var/start_empty = 0 - var/list/bullet_cost - var/list/base_cost// override this one as well if you override bullet_cost - -/obj/item/ammo_box/Initialize() - . = ..() - if (!bullet_cost) - for (var/material in materials) - var/material_amount = materials[material] - LAZYSET(base_cost, material, (material_amount * 0.10)) - - material_amount *= 0.90 // 10% for the container - material_amount /= max_ammo - LAZYSET(bullet_cost, material, material_amount) - if(!start_empty) - for(var/i = 1, i <= max_ammo, i++) - stored_ammo += new ammo_type(src) - update_icon() - -/obj/item/ammo_box/proc/get_round(keep = 0) - if (!stored_ammo.len) - return null - else - var/b = stored_ammo[stored_ammo.len] - stored_ammo -= b - if (keep) - stored_ammo.Insert(1,b) - return b - -/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = 0) - // Boxes don't have a caliber type, magazines do. Not sure if it's intended or not, but if we fail to find a caliber, then we fall back to ammo_type. - if(!R || (caliber && R.caliber != caliber) || (!caliber && R.type != ammo_type)) - return 0 - - if (stored_ammo.len < max_ammo) - stored_ammo += R - R.forceMove(src) - return 1 - - //for accessibles magazines (e.g internal ones) when full, start replacing spent ammo - else if(replace_spent) - for(var/obj/item/ammo_casing/AC in stored_ammo) - if(!AC.BB)//found a spent ammo - stored_ammo -= AC - AC.forceMove(get_turf(src.loc)) - - stored_ammo += R - R.forceMove(src) - return 1 - - return 0 - -/obj/item/ammo_box/proc/can_load(mob/user) - return 1 - -/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = FALSE, replace_spent = 0) - var/num_loaded = 0 - if(!can_load(user)) - return - if(istype(A, /obj/item/ammo_box)) - var/obj/item/ammo_box/AM = A - for(var/obj/item/ammo_casing/AC in AM.stored_ammo) - var/did_load = give_round(AC, replace_spent) - if(did_load) - AM.stored_ammo -= AC - num_loaded++ - if(!did_load || !multiload) - break - if(istype(A, /obj/item/ammo_casing)) - var/obj/item/ammo_casing/AC = A - if(give_round(AC, replace_spent)) - user.transferItemToLoc(AC, src, TRUE) - num_loaded++ - - if(num_loaded) - if(!silent) - to_chat(user, "You load [num_loaded] shell\s into \the [src]!") - playsound(src, 'sound/weapons/bulletinsert.ogg', 60, 1) - A.update_icon() - update_icon() - - return num_loaded - -/obj/item/ammo_box/attack_self(mob/user) - var/obj/item/ammo_casing/A = get_round() - if(A) - if(!user.put_in_hands(A)) - A.bounce_away(FALSE, NONE) - playsound(src, 'sound/weapons/bulletinsert.ogg', 60, 1) - to_chat(user, "You remove a round from \the [src]!") - update_icon() - -/obj/item/ammo_box/update_icon() - switch(multiple_sprites) - if(1) - icon_state = "[initial(icon_state)]-[stored_ammo.len]" - if(2) - icon_state = "[initial(icon_state)]-[stored_ammo.len ? "[max_ammo]" : "0"]" - desc = "[initial(desc)] There are [stored_ammo.len] shell\s left!" - for (var/material in bullet_cost) - var/material_amount = bullet_cost[material] - material_amount = (material_amount*stored_ammo.len) + base_cost[material] - materials[material] = material_amount - -//Behavior for magazines -/obj/item/ammo_box/magazine/proc/ammo_count() - return stored_ammo.len - -/obj/item/ammo_box/magazine/proc/empty_magazine() - var/turf_mag = get_turf(src) - for(var/obj/item/ammo in stored_ammo) - ammo.forceMove(turf_mag) - stored_ammo -= ammo - -/obj/item/ammo_box/magazine/handle_atom_del(atom/A) - stored_ammo -= A - update_icon() +//Boxes of ammo +/obj/item/ammo_box + name = "ammo box (null_reference_exception)" + desc = "A box of ammo." + icon = 'icons/obj/ammo.dmi' + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + materials = list(MAT_METAL = 30000) + throwforce = 2 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + var/list/stored_ammo = list() + var/ammo_type = /obj/item/ammo_casing + var/max_ammo = 7 + var/multiple_sprites = 0 + var/caliber + var/multiload = 1 + var/start_empty = 0 + var/list/bullet_cost + var/list/base_cost// override this one as well if you override bullet_cost + +/obj/item/ammo_box/Initialize() + . = ..() + if (!bullet_cost) + for (var/material in materials) + var/material_amount = materials[material] + LAZYSET(base_cost, material, (material_amount * 0.10)) + + material_amount *= 0.90 // 10% for the container + material_amount /= max_ammo + LAZYSET(bullet_cost, material, material_amount) + if(!start_empty) + for(var/i = 1, i <= max_ammo, i++) + stored_ammo += new ammo_type(src) + update_icon() + +/obj/item/ammo_box/proc/get_round(keep = 0) + if (!stored_ammo.len) + return null + else + var/b = stored_ammo[stored_ammo.len] + stored_ammo -= b + if (keep) + stored_ammo.Insert(1,b) + return b + +/obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = 0) + // Boxes don't have a caliber type, magazines do. Not sure if it's intended or not, but if we fail to find a caliber, then we fall back to ammo_type. + if(!R || (caliber && R.caliber != caliber) || (!caliber && R.type != ammo_type)) + return 0 + + if (stored_ammo.len < max_ammo) + stored_ammo += R + R.forceMove(src) + return 1 + + //for accessibles magazines (e.g internal ones) when full, start replacing spent ammo + else if(replace_spent) + for(var/obj/item/ammo_casing/AC in stored_ammo) + if(!AC.BB)//found a spent ammo + stored_ammo -= AC + AC.forceMove(get_turf(src.loc)) + + stored_ammo += R + R.forceMove(src) + return 1 + + return 0 + +/obj/item/ammo_box/proc/can_load(mob/user) + return 1 + +/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = FALSE, replace_spent = 0) + var/num_loaded = 0 + if(!can_load(user)) + return + if(istype(A, /obj/item/ammo_box)) + var/obj/item/ammo_box/AM = A + for(var/obj/item/ammo_casing/AC in AM.stored_ammo) + var/did_load = give_round(AC, replace_spent) + if(did_load) + AM.stored_ammo -= AC + num_loaded++ + if(!did_load || !multiload) + break + if(istype(A, /obj/item/ammo_casing)) + var/obj/item/ammo_casing/AC = A + if(give_round(AC, replace_spent)) + user.transferItemToLoc(AC, src, TRUE) + num_loaded++ + + if(num_loaded) + if(!silent) + to_chat(user, "You load [num_loaded] shell\s into \the [src]!") + playsound(src, 'sound/weapons/bulletinsert.ogg', 60, 1) + A.update_icon() + update_icon() + + return num_loaded + +/obj/item/ammo_box/attack_self(mob/user) + var/obj/item/ammo_casing/A = get_round() + if(A) + if(!user.put_in_hands(A)) + A.bounce_away(FALSE, NONE) + playsound(src, 'sound/weapons/bulletinsert.ogg', 60, 1) + to_chat(user, "You remove a round from \the [src]!") + update_icon() + +/obj/item/ammo_box/update_icon() + switch(multiple_sprites) + if(1) + icon_state = "[initial(icon_state)]-[stored_ammo.len]" + if(2) + icon_state = "[initial(icon_state)]-[stored_ammo.len ? "[max_ammo]" : "0"]" + desc = "[initial(desc)] There are [stored_ammo.len] shell\s left!" + for (var/material in bullet_cost) + var/material_amount = bullet_cost[material] + material_amount = (material_amount*stored_ammo.len) + base_cost[material] + materials[material] = material_amount + +//Behavior for magazines +/obj/item/ammo_box/magazine/proc/ammo_count() + return stored_ammo.len + +/obj/item/ammo_box/magazine/proc/empty_magazine() + var/turf_mag = get_turf(src) + for(var/obj/item/ammo in stored_ammo) + ammo.forceMove(turf_mag) + stored_ammo -= ammo + +/obj/item/ammo_box/magazine/handle_atom_del(atom/A) + stored_ammo -= A + update_icon() diff --git a/code/modules/projectiles/boxes_magazines/external/grenade.dm b/code/modules/projectiles/boxes_magazines/external/grenade.dm index 2b3c31f81c..6a068c3e75 100644 --- a/code/modules/projectiles/boxes_magazines/external/grenade.dm +++ b/code/modules/projectiles/boxes_magazines/external/grenade.dm @@ -1,8 +1,8 @@ -/obj/item/ammo_box/magazine/m75 - name = "specialized magazine (.75)" - icon_state = "75" - ammo_type = /obj/item/ammo_casing/caseless/a75 - caliber = "75" - multiple_sprites = 2 - max_ammo = 8 - +/obj/item/ammo_box/magazine/m75 + name = "specialized magazine (.75)" + icon_state = "75" + ammo_type = /obj/item/ammo_casing/caseless/a75 + caliber = "75" + multiple_sprites = 2 + max_ammo = 8 + diff --git a/code/modules/projectiles/boxes_magazines/external/lmg.dm b/code/modules/projectiles/boxes_magazines/external/lmg.dm index cb42989022..e42c6a4719 100644 --- a/code/modules/projectiles/boxes_magazines/external/lmg.dm +++ b/code/modules/projectiles/boxes_magazines/external/lmg.dm @@ -1,22 +1,22 @@ -/obj/item/ammo_box/magazine/mm195x129 - name = "box magazine (1.95x129mm)" - icon_state = "a762-50" - ammo_type = /obj/item/ammo_casing/mm195x129 - caliber = "mm195129" - max_ammo = 50 - -/obj/item/ammo_box/magazine/mm195x129/hollow - name = "box magazine (Hollow-Point 1.95x129mm)" - ammo_type = /obj/item/ammo_casing/mm195x129/hollow - -/obj/item/ammo_box/magazine/mm195x129/ap - name = "box magazine (Armor Penetrating 1.95x129mm)" - ammo_type = /obj/item/ammo_casing/mm195x129/ap - -/obj/item/ammo_box/magazine/mm195x129/incen - name = "box magazine (Incendiary 1.95x129mm)" - ammo_type = /obj/item/ammo_casing/mm195x129/incen - -/obj/item/ammo_box/magazine/mm195x129/update_icon() - ..() - icon_state = "a762-[round(ammo_count(),10)]" +/obj/item/ammo_box/magazine/mm195x129 + name = "box magazine (1.95x129mm)" + icon_state = "a762-50" + ammo_type = /obj/item/ammo_casing/mm195x129 + caliber = "mm195129" + max_ammo = 50 + +/obj/item/ammo_box/magazine/mm195x129/hollow + name = "box magazine (Hollow-Point 1.95x129mm)" + ammo_type = /obj/item/ammo_casing/mm195x129/hollow + +/obj/item/ammo_box/magazine/mm195x129/ap + name = "box magazine (Armor Penetrating 1.95x129mm)" + ammo_type = /obj/item/ammo_casing/mm195x129/ap + +/obj/item/ammo_box/magazine/mm195x129/incen + name = "box magazine (Incendiary 1.95x129mm)" + ammo_type = /obj/item/ammo_casing/mm195x129/incen + +/obj/item/ammo_box/magazine/mm195x129/update_icon() + ..() + icon_state = "a762-[round(ammo_count(),10)]" diff --git a/code/modules/projectiles/boxes_magazines/external/pistol.dm b/code/modules/projectiles/boxes_magazines/external/pistol.dm index fb3771a5c4..dbde572db1 100644 --- a/code/modules/projectiles/boxes_magazines/external/pistol.dm +++ b/code/modules/projectiles/boxes_magazines/external/pistol.dm @@ -1,61 +1,61 @@ -/obj/item/ammo_box/magazine/m10mm - name = "pistol magazine (10mm)" - desc = "A gun magazine." - icon_state = "9x19p" - ammo_type = /obj/item/ammo_casing/c10mm - caliber = "10mm" - max_ammo = 8 - multiple_sprites = 2 - -/obj/item/ammo_box/magazine/m10mm/fire - name = "pistol magazine (10mm incendiary)" - icon_state = "9x19pI" - desc = "A gun magazine. Loaded with rounds which ignite the target." - ammo_type = /obj/item/ammo_casing/c10mm/fire - -/obj/item/ammo_box/magazine/m10mm/hp - name = "pistol magazine (10mm HP)" - icon_state = "9x19pH" - desc= "A gun magazine. Loaded with hollow-point rounds, extremely effective against unarmored targets, but nearly useless against protective clothing." - ammo_type = /obj/item/ammo_casing/c10mm/hp - -/obj/item/ammo_box/magazine/m10mm/ap - name = "pistol magazine (10mm AP)" - icon_state = "9x19pA" - desc= "A gun magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets." - ammo_type = /obj/item/ammo_casing/c10mm/ap - -/obj/item/ammo_box/magazine/m45 - name = "handgun magazine (.45)" - icon_state = "45-8" - ammo_type = /obj/item/ammo_casing/c45 - caliber = ".45" - max_ammo = 8 - -/obj/item/ammo_box/magazine/m45/update_icon() - ..() - icon_state = "45-[ammo_count() ? "8" : "0"]" - -/obj/item/ammo_box/magazine/m45/kitchengun - name = "handgun magazine (.45 cleaning)" - desc = "BANG! BANG! BANG!" - ammo_type = /obj/item/ammo_casing/c45/kitchengun - -/obj/item/ammo_box/magazine/pistolm9mm - name = "pistol magazine (9mm)" - icon_state = "9x19p-8" - ammo_type = /obj/item/ammo_casing/c9mm - caliber = "9mm" - max_ammo = 15 - -/obj/item/ammo_box/magazine/pistolm9mm/update_icon() - ..() - icon_state = "9x19p-[ammo_count() ? "8" : "0"]" - -/obj/item/ammo_box/magazine/m50 - name = "handgun magazine (.50ae)" - icon_state = "50ae" - ammo_type = /obj/item/ammo_casing/a50AE - caliber = ".50" - max_ammo = 7 - multiple_sprites = 1 +/obj/item/ammo_box/magazine/m10mm + name = "pistol magazine (10mm)" + desc = "A gun magazine." + icon_state = "9x19p" + ammo_type = /obj/item/ammo_casing/c10mm + caliber = "10mm" + max_ammo = 8 + multiple_sprites = 2 + +/obj/item/ammo_box/magazine/m10mm/fire + name = "pistol magazine (10mm incendiary)" + icon_state = "9x19pI" + desc = "A gun magazine. Loaded with rounds which ignite the target." + ammo_type = /obj/item/ammo_casing/c10mm/fire + +/obj/item/ammo_box/magazine/m10mm/hp + name = "pistol magazine (10mm HP)" + icon_state = "9x19pH" + desc= "A gun magazine. Loaded with hollow-point rounds, extremely effective against unarmored targets, but nearly useless against protective clothing." + ammo_type = /obj/item/ammo_casing/c10mm/hp + +/obj/item/ammo_box/magazine/m10mm/ap + name = "pistol magazine (10mm AP)" + icon_state = "9x19pA" + desc= "A gun magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets." + ammo_type = /obj/item/ammo_casing/c10mm/ap + +/obj/item/ammo_box/magazine/m45 + name = "handgun magazine (.45)" + icon_state = "45-8" + ammo_type = /obj/item/ammo_casing/c45 + caliber = ".45" + max_ammo = 8 + +/obj/item/ammo_box/magazine/m45/update_icon() + ..() + icon_state = "45-[ammo_count() ? "8" : "0"]" + +/obj/item/ammo_box/magazine/m45/kitchengun + name = "handgun magazine (.45 cleaning)" + desc = "BANG! BANG! BANG!" + ammo_type = /obj/item/ammo_casing/c45/kitchengun + +/obj/item/ammo_box/magazine/pistolm9mm + name = "pistol magazine (9mm)" + icon_state = "9x19p-8" + ammo_type = /obj/item/ammo_casing/c9mm + caliber = "9mm" + max_ammo = 15 + +/obj/item/ammo_box/magazine/pistolm9mm/update_icon() + ..() + icon_state = "9x19p-[ammo_count() ? "8" : "0"]" + +/obj/item/ammo_box/magazine/m50 + name = "handgun magazine (.50ae)" + icon_state = "50ae" + ammo_type = /obj/item/ammo_casing/a50AE + caliber = ".50" + max_ammo = 7 + multiple_sprites = 1 diff --git a/code/modules/projectiles/boxes_magazines/external/rechargable.dm b/code/modules/projectiles/boxes_magazines/external/rechargable.dm index fada5c8659..7ed0cde50a 100644 --- a/code/modules/projectiles/boxes_magazines/external/rechargable.dm +++ b/code/modules/projectiles/boxes_magazines/external/rechargable.dm @@ -1,14 +1,14 @@ -/obj/item/ammo_box/magazine/recharge - name = "power pack" - desc = "A rechargeable, detachable battery that serves as a magazine for laser rifles." - icon_state = "oldrifle-20" - ammo_type = /obj/item/ammo_casing/caseless/laser - caliber = "laser" - max_ammo = 20 - -/obj/item/ammo_box/magazine/recharge/update_icon() - desc = "[initial(desc)] It has [stored_ammo.len] shot\s left." - icon_state = "oldrifle-[round(ammo_count(),4)]" - -/obj/item/ammo_box/magazine/recharge/attack_self() //No popping out the "bullets" - return +/obj/item/ammo_box/magazine/recharge + name = "power pack" + desc = "A rechargeable, detachable battery that serves as a magazine for laser rifles." + icon_state = "oldrifle-20" + ammo_type = /obj/item/ammo_casing/caseless/laser + caliber = "laser" + max_ammo = 20 + +/obj/item/ammo_box/magazine/recharge/update_icon() + desc = "[initial(desc)] It has [stored_ammo.len] shot\s left." + icon_state = "oldrifle-[round(ammo_count(),4)]" + +/obj/item/ammo_box/magazine/recharge/attack_self() //No popping out the "bullets" + return diff --git a/code/modules/projectiles/boxes_magazines/external/rifle.dm b/code/modules/projectiles/boxes_magazines/external/rifle.dm index 96e7d377ea..4512ba5851 100644 --- a/code/modules/projectiles/boxes_magazines/external/rifle.dm +++ b/code/modules/projectiles/boxes_magazines/external/rifle.dm @@ -1,21 +1,21 @@ -/obj/item/ammo_box/magazine/m10mm/rifle - name = "rifle magazine (10mm)" - desc = "A well-worn magazine fitted for the surplus rifle." - icon_state = "75-8" - ammo_type = /obj/item/ammo_casing/c10mm - caliber = "10mm" - max_ammo = 10 - -/obj/item/ammo_box/magazine/m10mm/rifle/update_icon() - if(ammo_count()) - icon_state = "75-8" - else - icon_state = "75-0" - -/obj/item/ammo_box/magazine/m556 - name = "toploader magazine (5.56mm)" - icon_state = "5.56m" - ammo_type = /obj/item/ammo_casing/a556 - caliber = "a556" - max_ammo = 30 - multiple_sprites = 2 +/obj/item/ammo_box/magazine/m10mm/rifle + name = "rifle magazine (10mm)" + desc = "A well-worn magazine fitted for the surplus rifle." + icon_state = "75-8" + ammo_type = /obj/item/ammo_casing/c10mm + caliber = "10mm" + max_ammo = 10 + +/obj/item/ammo_box/magazine/m10mm/rifle/update_icon() + if(ammo_count()) + icon_state = "75-8" + else + icon_state = "75-0" + +/obj/item/ammo_box/magazine/m556 + name = "toploader magazine (5.56mm)" + icon_state = "5.56m" + ammo_type = /obj/item/ammo_casing/a556 + caliber = "a556" + max_ammo = 30 + multiple_sprites = 2 diff --git a/code/modules/projectiles/boxes_magazines/external/shotgun.dm b/code/modules/projectiles/boxes_magazines/external/shotgun.dm index 1fa3db28b5..1001937678 100644 --- a/code/modules/projectiles/boxes_magazines/external/shotgun.dm +++ b/code/modules/projectiles/boxes_magazines/external/shotgun.dm @@ -1,41 +1,41 @@ -/obj/item/ammo_box/magazine/m12g - name = "shotgun magazine (12g buckshot slugs)" - desc = "A drum magazine." - icon_state = "m12gb" - ammo_type = /obj/item/ammo_casing/shotgun/buckshot - caliber = "shotgun" - max_ammo = 8 - -/obj/item/ammo_box/magazine/m12g/update_icon() - ..() - icon_state = "[initial(icon_state)]-[CEILING(ammo_count(0)/8, 1)*8]" - -/obj/item/ammo_box/magazine/m12g/stun - name = "shotgun magazine (12g taser slugs)" - icon_state = "m12gs" - ammo_type = /obj/item/ammo_casing/shotgun/stunslug - -/obj/item/ammo_box/magazine/m12g/slug - name = "shotgun magazine (12g slugs)" - icon_state = "m12gb" //this may need an unique sprite - ammo_type = /obj/item/ammo_casing/shotgun - -/obj/item/ammo_box/magazine/m12g/dragon - name = "shotgun magazine (12g dragon's breath)" - icon_state = "m12gf" - ammo_type = /obj/item/ammo_casing/shotgun/dragonsbreath - -/obj/item/ammo_box/magazine/m12g/bioterror - name = "shotgun magazine (12g bioterror)" - icon_state = "m12gt" - ammo_type = /obj/item/ammo_casing/shotgun/dart/bioterror - -/obj/item/ammo_box/magazine/m12g/meteor - name = "shotgun magazine (12g meteor slugs)" - icon_state = "m12gbc" - ammo_type = /obj/item/ammo_casing/shotgun/meteorslug - -/obj/item/ammo_box/magazine/m12g/scatter - name = "shotgun magazine (12g scatter laser shot slugs)" - icon_state = "m12gb" - ammo_type = /obj/item/ammo_casing/shotgun/laserslug +/obj/item/ammo_box/magazine/m12g + name = "shotgun magazine (12g buckshot slugs)" + desc = "A drum magazine." + icon_state = "m12gb" + ammo_type = /obj/item/ammo_casing/shotgun/buckshot + caliber = "shotgun" + max_ammo = 8 + +/obj/item/ammo_box/magazine/m12g/update_icon() + ..() + icon_state = "[initial(icon_state)]-[CEILING(ammo_count(0)/8, 1)*8]" + +/obj/item/ammo_box/magazine/m12g/stun + name = "shotgun magazine (12g taser slugs)" + icon_state = "m12gs" + ammo_type = /obj/item/ammo_casing/shotgun/stunslug + +/obj/item/ammo_box/magazine/m12g/slug + name = "shotgun magazine (12g slugs)" + icon_state = "m12gb" //this may need an unique sprite + ammo_type = /obj/item/ammo_casing/shotgun + +/obj/item/ammo_box/magazine/m12g/dragon + name = "shotgun magazine (12g dragon's breath)" + icon_state = "m12gf" + ammo_type = /obj/item/ammo_casing/shotgun/dragonsbreath + +/obj/item/ammo_box/magazine/m12g/bioterror + name = "shotgun magazine (12g bioterror)" + icon_state = "m12gt" + ammo_type = /obj/item/ammo_casing/shotgun/dart/bioterror + +/obj/item/ammo_box/magazine/m12g/meteor + name = "shotgun magazine (12g meteor slugs)" + icon_state = "m12gbc" + ammo_type = /obj/item/ammo_casing/shotgun/meteorslug + +/obj/item/ammo_box/magazine/m12g/scatter + name = "shotgun magazine (12g scatter laser shot slugs)" + icon_state = "m12gb" + ammo_type = /obj/item/ammo_casing/shotgun/laserslug diff --git a/code/modules/projectiles/boxes_magazines/external/smg.dm b/code/modules/projectiles/boxes_magazines/external/smg.dm index 783b8b895b..70637bb57a 100644 --- a/code/modules/projectiles/boxes_magazines/external/smg.dm +++ b/code/modules/projectiles/boxes_magazines/external/smg.dm @@ -1,76 +1,76 @@ -/obj/item/ammo_box/magazine/wt550m9 - name = "wt550 magazine (4.6x30mm)" - icon_state = "46x30mmt-20" - ammo_type = /obj/item/ammo_casing/c46x30mm - caliber = "4.6x30mm" - max_ammo = 32 - -/obj/item/ammo_box/magazine/wt550m9/update_icon() - ..() - icon_state = "46x30mmt-[round(20*(ammo_count()/max_ammo),4)]" - -/obj/item/ammo_box/magazine/wt550m9/wtap - name = "wt550 magazine (Armour Piercing 4.6x30mm)" - icon_state = "46x30mmtA-20" - ammo_type = /obj/item/ammo_casing/c46x30mm/ap - -/obj/item/ammo_box/magazine/wt550m9/wtap/update_icon() - ..() - icon_state = "46x30mmtA-[round(20*(ammo_count()/max_ammo),4)]" - -/obj/item/ammo_box/magazine/wt550m9/wtic - name = "wt550 magazine (Incendiary 4.6x30mm)" - icon_state = "46x30mmtI-20" - ammo_type = /obj/item/ammo_casing/c46x30mm/inc - -/obj/item/ammo_box/magazine/wt550m9/wtic/update_icon() - ..() - icon_state = "46x30mmtI-[round(20*(ammo_count()/max_ammo),4)]" - -/obj/item/ammo_box/magazine/uzim9mm - name = "uzi magazine (9mm)" - icon_state = "uzi9mm-32" - ammo_type = /obj/item/ammo_casing/c9mm - caliber = "9mm" - max_ammo = 32 - -/obj/item/ammo_box/magazine/uzim9mm/update_icon() - ..() - icon_state = "uzi9mm-[round(ammo_count(),4)]" - -/obj/item/ammo_box/magazine/smgm9mm - name = "SMG magazine (9mm)" - icon_state = "smg9mm-42" - ammo_type = /obj/item/ammo_casing/c9mm - caliber = "9mm" - max_ammo = 21 - -/obj/item/ammo_box/magazine/smgm9mm/update_icon() - ..() - icon_state = "smg9mm-[ammo_count() ? "42" : "0"]" - -/obj/item/ammo_box/magazine/smgm9mm/ap - name = "SMG magazine (Armour Piercing 9mm)" - ammo_type = /obj/item/ammo_casing/c9mm/ap - -/obj/item/ammo_box/magazine/smgm9mm/fire - name = "SMG Magazine (Incendiary 9mm)" - ammo_type = /obj/item/ammo_casing/c9mm/inc - -/obj/item/ammo_box/magazine/smgm45 - name = "SMG magazine (.45)" - icon_state = "c20r45-24" - ammo_type = /obj/item/ammo_casing/c45/nostamina - caliber = ".45" - max_ammo = 24 - -/obj/item/ammo_box/magazine/smgm45/update_icon() - ..() - icon_state = "c20r45-[round(ammo_count(),2)]" - -/obj/item/ammo_box/magazine/tommygunm45 - name = "drum magazine (.45)" - icon_state = "drum45" - ammo_type = /obj/item/ammo_casing/c45 - caliber = ".45" - max_ammo = 50 +/obj/item/ammo_box/magazine/wt550m9 + name = "wt550 magazine (4.6x30mm)" + icon_state = "46x30mmt-20" + ammo_type = /obj/item/ammo_casing/c46x30mm + caliber = "4.6x30mm" + max_ammo = 32 + +/obj/item/ammo_box/magazine/wt550m9/update_icon() + ..() + icon_state = "46x30mmt-[round(20*(ammo_count()/max_ammo),4)]" + +/obj/item/ammo_box/magazine/wt550m9/wtap + name = "wt550 magazine (Armour Piercing 4.6x30mm)" + icon_state = "46x30mmtA-20" + ammo_type = /obj/item/ammo_casing/c46x30mm/ap + +/obj/item/ammo_box/magazine/wt550m9/wtap/update_icon() + ..() + icon_state = "46x30mmtA-[round(20*(ammo_count()/max_ammo),4)]" + +/obj/item/ammo_box/magazine/wt550m9/wtic + name = "wt550 magazine (Incendiary 4.6x30mm)" + icon_state = "46x30mmtI-20" + ammo_type = /obj/item/ammo_casing/c46x30mm/inc + +/obj/item/ammo_box/magazine/wt550m9/wtic/update_icon() + ..() + icon_state = "46x30mmtI-[round(20*(ammo_count()/max_ammo),4)]" + +/obj/item/ammo_box/magazine/uzim9mm + name = "uzi magazine (9mm)" + icon_state = "uzi9mm-32" + ammo_type = /obj/item/ammo_casing/c9mm + caliber = "9mm" + max_ammo = 32 + +/obj/item/ammo_box/magazine/uzim9mm/update_icon() + ..() + icon_state = "uzi9mm-[round(ammo_count(),4)]" + +/obj/item/ammo_box/magazine/smgm9mm + name = "SMG magazine (9mm)" + icon_state = "smg9mm-42" + ammo_type = /obj/item/ammo_casing/c9mm + caliber = "9mm" + max_ammo = 21 + +/obj/item/ammo_box/magazine/smgm9mm/update_icon() + ..() + icon_state = "smg9mm-[ammo_count() ? "42" : "0"]" + +/obj/item/ammo_box/magazine/smgm9mm/ap + name = "SMG magazine (Armour Piercing 9mm)" + ammo_type = /obj/item/ammo_casing/c9mm/ap + +/obj/item/ammo_box/magazine/smgm9mm/fire + name = "SMG Magazine (Incendiary 9mm)" + ammo_type = /obj/item/ammo_casing/c9mm/inc + +/obj/item/ammo_box/magazine/smgm45 + name = "SMG magazine (.45)" + icon_state = "c20r45-24" + ammo_type = /obj/item/ammo_casing/c45/nostamina + caliber = ".45" + max_ammo = 24 + +/obj/item/ammo_box/magazine/smgm45/update_icon() + ..() + icon_state = "c20r45-[round(ammo_count(),2)]" + +/obj/item/ammo_box/magazine/tommygunm45 + name = "drum magazine (.45)" + icon_state = "drum45" + ammo_type = /obj/item/ammo_casing/c45 + caliber = ".45" + max_ammo = 50 diff --git a/code/modules/projectiles/boxes_magazines/external/sniper.dm b/code/modules/projectiles/boxes_magazines/external/sniper.dm index 67c6257bac..813e61cdc5 100644 --- a/code/modules/projectiles/boxes_magazines/external/sniper.dm +++ b/code/modules/projectiles/boxes_magazines/external/sniper.dm @@ -1,26 +1,26 @@ -/obj/item/ammo_box/magazine/sniper_rounds - name = "sniper rounds (.50)" - icon_state = ".50mag" - ammo_type = /obj/item/ammo_casing/p50 - max_ammo = 6 - caliber = ".50" - -/obj/item/ammo_box/magazine/sniper_rounds/update_icon() - if(ammo_count()) - icon_state = "[initial(icon_state)]-ammo" - else - icon_state = "[initial(icon_state)]" - -/obj/item/ammo_box/magazine/sniper_rounds/soporific - name = "sniper rounds (Zzzzz)" - desc = "Soporific sniper rounds, designed for happy days and dead quiet nights..." - icon_state = "soporific" - ammo_type = /obj/item/ammo_casing/p50/soporific - max_ammo = 3 - caliber = ".50" - -/obj/item/ammo_box/magazine/sniper_rounds/penetrator - name = "sniper rounds (penetrator)" - desc = "An extremely powerful round capable of passing straight through cover and anyone unfortunate enough to be behind it." - ammo_type = /obj/item/ammo_casing/p50/penetrator - max_ammo = 5 +/obj/item/ammo_box/magazine/sniper_rounds + name = "sniper rounds (.50)" + icon_state = ".50mag" + ammo_type = /obj/item/ammo_casing/p50 + max_ammo = 6 + caliber = ".50" + +/obj/item/ammo_box/magazine/sniper_rounds/update_icon() + if(ammo_count()) + icon_state = "[initial(icon_state)]-ammo" + else + icon_state = "[initial(icon_state)]" + +/obj/item/ammo_box/magazine/sniper_rounds/soporific + name = "sniper rounds (Zzzzz)" + desc = "Soporific sniper rounds, designed for happy days and dead quiet nights..." + icon_state = "soporific" + ammo_type = /obj/item/ammo_casing/p50/soporific + max_ammo = 3 + caliber = ".50" + +/obj/item/ammo_box/magazine/sniper_rounds/penetrator + name = "sniper rounds (penetrator)" + desc = "An extremely powerful round capable of passing straight through cover and anyone unfortunate enough to be behind it." + ammo_type = /obj/item/ammo_casing/p50/penetrator + max_ammo = 5 diff --git a/code/modules/projectiles/boxes_magazines/external/toy.dm b/code/modules/projectiles/boxes_magazines/external/toy.dm index 9a8d7e59d9..b9e1ec9615 100644 --- a/code/modules/projectiles/boxes_magazines/external/toy.dm +++ b/code/modules/projectiles/boxes_magazines/external/toy.dm @@ -1,59 +1,59 @@ -/obj/item/ammo_box/magazine/toy - name = "foam force META magazine" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart - caliber = "foam_force" - -/obj/item/ammo_box/magazine/toy/smg - name = "foam force SMG magazine" - icon_state = "smg9mm-42" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart - max_ammo = 20 - -/obj/item/ammo_box/magazine/toy/smg/update_icon() - ..() - if(ammo_count()) - icon_state = "smg9mm-42" - else - icon_state = "smg9mm-0" - -/obj/item/ammo_box/magazine/toy/smg/riot - ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot - -/obj/item/ammo_box/magazine/toy/pistol - name = "foam force pistol magazine" - icon_state = "9x19p" - max_ammo = 8 - multiple_sprites = 2 - -/obj/item/ammo_box/magazine/toy/pistol/riot - ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot - -/obj/item/ammo_box/magazine/toy/smgm45 - name = "donksoft SMG magazine" - icon_state = "c20r45-toy" - caliber = "foam_force" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart - max_ammo = 20 - -/obj/item/ammo_box/magazine/toy/smgm45/update_icon() - ..() - icon_state = "c20r45-[round(ammo_count(),2)]" - -/obj/item/ammo_box/magazine/toy/smgm45/riot - icon_state = "c20r45-riot" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot - -/obj/item/ammo_box/magazine/toy/m762 - name = "donksoft box magazine" - icon_state = "a762-toy" - caliber = "foam_force" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart - max_ammo = 50 - -/obj/item/ammo_box/magazine/toy/m762/update_icon() - ..() - icon_state = "a762-[round(ammo_count(),10)]" - -/obj/item/ammo_box/magazine/toy/m762/riot - icon_state = "a762-riot" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot +/obj/item/ammo_box/magazine/toy + name = "foam force META magazine" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart + caliber = "foam_force" + +/obj/item/ammo_box/magazine/toy/smg + name = "foam force SMG magazine" + icon_state = "smg9mm-42" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart + max_ammo = 20 + +/obj/item/ammo_box/magazine/toy/smg/update_icon() + ..() + if(ammo_count()) + icon_state = "smg9mm-42" + else + icon_state = "smg9mm-0" + +/obj/item/ammo_box/magazine/toy/smg/riot + ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot + +/obj/item/ammo_box/magazine/toy/pistol + name = "foam force pistol magazine" + icon_state = "9x19p" + max_ammo = 8 + multiple_sprites = 2 + +/obj/item/ammo_box/magazine/toy/pistol/riot + ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot + +/obj/item/ammo_box/magazine/toy/smgm45 + name = "donksoft SMG magazine" + icon_state = "c20r45-toy" + caliber = "foam_force" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart + max_ammo = 20 + +/obj/item/ammo_box/magazine/toy/smgm45/update_icon() + ..() + icon_state = "c20r45-[round(ammo_count(),2)]" + +/obj/item/ammo_box/magazine/toy/smgm45/riot + icon_state = "c20r45-riot" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot + +/obj/item/ammo_box/magazine/toy/m762 + name = "donksoft box magazine" + icon_state = "a762-toy" + caliber = "foam_force" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart + max_ammo = 50 + +/obj/item/ammo_box/magazine/toy/m762/update_icon() + ..() + icon_state = "a762-[round(ammo_count(),10)]" + +/obj/item/ammo_box/magazine/toy/m762/riot + icon_state = "a762-riot" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot diff --git a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm index bbfc79471c..538ac753e2 100644 --- a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm +++ b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm @@ -1,47 +1,47 @@ -/obj/item/ammo_box/magazine/internal/cylinder - name = "revolver cylinder" - ammo_type = /obj/item/ammo_casing/a357 - caliber = "357" - max_ammo = 7 - -/obj/item/ammo_box/magazine/internal/cylinder/ammo_count(countempties = 1) - var/boolets = 0 - for(var/obj/item/ammo_casing/bullet in stored_ammo) - if(bullet && (bullet.BB || countempties)) - boolets++ - - return boolets - -/obj/item/ammo_box/magazine/internal/cylinder/get_round(keep = 0) - rotate() - - var/b = stored_ammo[1] - if(!keep) - stored_ammo[1] = null - - return b - -/obj/item/ammo_box/magazine/internal/cylinder/proc/rotate() - var/b = stored_ammo[1] - stored_ammo.Cut(1,2) - stored_ammo.Insert(0, b) - -/obj/item/ammo_box/magazine/internal/cylinder/proc/spin() - for(var/i in 1 to rand(0, max_ammo*2)) - rotate() - -/obj/item/ammo_box/magazine/internal/cylinder/give_round(obj/item/ammo_casing/R, replace_spent = 0) - if(!R || (caliber && R.caliber != caliber) || (!caliber && R.type != ammo_type)) - return FALSE - - for(var/i in 1 to stored_ammo.len) - var/obj/item/ammo_casing/bullet = stored_ammo[i] - if(!bullet || !bullet.BB) // found a spent ammo - stored_ammo[i] = R - R.forceMove(src) - - if(bullet) - bullet.forceMove(drop_location()) - return TRUE - - return FALSE +/obj/item/ammo_box/magazine/internal/cylinder + name = "revolver cylinder" + ammo_type = /obj/item/ammo_casing/a357 + caliber = "357" + max_ammo = 7 + +/obj/item/ammo_box/magazine/internal/cylinder/ammo_count(countempties = 1) + var/boolets = 0 + for(var/obj/item/ammo_casing/bullet in stored_ammo) + if(bullet && (bullet.BB || countempties)) + boolets++ + + return boolets + +/obj/item/ammo_box/magazine/internal/cylinder/get_round(keep = 0) + rotate() + + var/b = stored_ammo[1] + if(!keep) + stored_ammo[1] = null + + return b + +/obj/item/ammo_box/magazine/internal/cylinder/proc/rotate() + var/b = stored_ammo[1] + stored_ammo.Cut(1,2) + stored_ammo.Insert(0, b) + +/obj/item/ammo_box/magazine/internal/cylinder/proc/spin() + for(var/i in 1 to rand(0, max_ammo*2)) + rotate() + +/obj/item/ammo_box/magazine/internal/cylinder/give_round(obj/item/ammo_casing/R, replace_spent = 0) + if(!R || (caliber && R.caliber != caliber) || (!caliber && R.type != ammo_type)) + return FALSE + + for(var/i in 1 to stored_ammo.len) + var/obj/item/ammo_casing/bullet = stored_ammo[i] + if(!bullet || !bullet.BB) // found a spent ammo + stored_ammo[i] = R + R.forceMove(src) + + if(bullet) + bullet.forceMove(drop_location()) + return TRUE + + return FALSE diff --git a/code/modules/projectiles/boxes_magazines/internal/_internal.dm b/code/modules/projectiles/boxes_magazines/internal/_internal.dm index c6ee7ec0cb..c14e66af82 100644 --- a/code/modules/projectiles/boxes_magazines/internal/_internal.dm +++ b/code/modules/projectiles/boxes_magazines/internal/_internal.dm @@ -1,8 +1,8 @@ -/obj/item/ammo_box/magazine/internal - desc = "Oh god, this shouldn't be here" - flags_1 = CONDUCT_1 - item_flags = ABSTRACT - -//internals magazines are accessible, so replace spent ammo if full when trying to put a live one in -/obj/item/ammo_box/magazine/internal/give_round(obj/item/ammo_casing/R) - return ..(R,1) +/obj/item/ammo_box/magazine/internal + desc = "Oh god, this shouldn't be here" + flags_1 = CONDUCT_1 + item_flags = ABSTRACT + +//internals magazines are accessible, so replace spent ammo if full when trying to put a live one in +/obj/item/ammo_box/magazine/internal/give_round(obj/item/ammo_casing/R) + return ..(R,1) diff --git a/code/modules/projectiles/boxes_magazines/internal/grenade.dm b/code/modules/projectiles/boxes_magazines/internal/grenade.dm index 79a005ee8a..8b2ec9fca3 100644 --- a/code/modules/projectiles/boxes_magazines/internal/grenade.dm +++ b/code/modules/projectiles/boxes_magazines/internal/grenade.dm @@ -1,17 +1,17 @@ -/obj/item/ammo_box/magazine/internal/cylinder/grenademulti - name = "grenade launcher internal magazine" - ammo_type = /obj/item/ammo_casing/a40mm - caliber = "40mm" - max_ammo = 6 - -/obj/item/ammo_box/magazine/internal/grenadelauncher - name = "grenade launcher internal magazine" - ammo_type = /obj/item/ammo_casing/a40mm - caliber = "40mm" - max_ammo = 1 - -/obj/item/ammo_box/magazine/internal/rocketlauncher - name = "rocket launcher internal magazine" - ammo_type = /obj/item/ammo_casing/caseless/rocket - caliber = "84mm" - max_ammo = 1 +/obj/item/ammo_box/magazine/internal/cylinder/grenademulti + name = "grenade launcher internal magazine" + ammo_type = /obj/item/ammo_casing/a40mm + caliber = "40mm" + max_ammo = 6 + +/obj/item/ammo_box/magazine/internal/grenadelauncher + name = "grenade launcher internal magazine" + ammo_type = /obj/item/ammo_casing/a40mm + caliber = "40mm" + max_ammo = 1 + +/obj/item/ammo_box/magazine/internal/rocketlauncher + name = "rocket launcher internal magazine" + ammo_type = /obj/item/ammo_casing/caseless/rocket + caliber = "84mm" + max_ammo = 1 diff --git a/code/modules/projectiles/boxes_magazines/internal/misc.dm b/code/modules/projectiles/boxes_magazines/internal/misc.dm index aab0643cbc..2b87557b39 100644 --- a/code/modules/projectiles/boxes_magazines/internal/misc.dm +++ b/code/modules/projectiles/boxes_magazines/internal/misc.dm @@ -1,11 +1,11 @@ -/obj/item/ammo_box/magazine/internal/speargun - name = "speargun internal magazine" - ammo_type = /obj/item/ammo_casing/caseless/magspear - caliber = "speargun" - max_ammo = 1 - -/obj/item/ammo_box/magazine/internal/minigun - name = "gatling gun fusion core" - ammo_type = /obj/item/ammo_casing/caseless/laser/gatling - caliber = "gatling" - max_ammo = 5000 +/obj/item/ammo_box/magazine/internal/speargun + name = "speargun internal magazine" + ammo_type = /obj/item/ammo_casing/caseless/magspear + caliber = "speargun" + max_ammo = 1 + +/obj/item/ammo_box/magazine/internal/minigun + name = "gatling gun fusion core" + ammo_type = /obj/item/ammo_casing/caseless/laser/gatling + caliber = "gatling" + max_ammo = 5000 diff --git a/code/modules/projectiles/boxes_magazines/internal/revolver.dm b/code/modules/projectiles/boxes_magazines/internal/revolver.dm index a98fd29e99..982d22493f 100644 --- a/code/modules/projectiles/boxes_magazines/internal/revolver.dm +++ b/code/modules/projectiles/boxes_magazines/internal/revolver.dm @@ -1,22 +1,22 @@ -/obj/item/ammo_box/magazine/internal/cylinder/rev38 - name = "detective revolver cylinder" - ammo_type = /obj/item/ammo_casing/c38 - caliber = "38" - max_ammo = 6 - -/obj/item/ammo_box/magazine/internal/cylinder/rev762 - name = "\improper Nagant revolver cylinder" - ammo_type = /obj/item/ammo_casing/n762 - caliber = "n762" - max_ammo = 7 - -/obj/item/ammo_box/magazine/internal/cylinder/rus357 - name = "\improper Russian revolver cylinder" - ammo_type = /obj/item/ammo_casing/a357 - caliber = "357" - max_ammo = 6 - multiload = 0 - -/obj/item/ammo_box/magazine/internal/rus357/Initialize() - stored_ammo += new ammo_type(src) - . = ..() +/obj/item/ammo_box/magazine/internal/cylinder/rev38 + name = "detective revolver cylinder" + ammo_type = /obj/item/ammo_casing/c38 + caliber = "38" + max_ammo = 6 + +/obj/item/ammo_box/magazine/internal/cylinder/rev762 + name = "\improper Nagant revolver cylinder" + ammo_type = /obj/item/ammo_casing/n762 + caliber = "n762" + max_ammo = 7 + +/obj/item/ammo_box/magazine/internal/cylinder/rus357 + name = "\improper Russian revolver cylinder" + ammo_type = /obj/item/ammo_casing/a357 + caliber = "357" + max_ammo = 6 + multiload = 0 + +/obj/item/ammo_box/magazine/internal/rus357/Initialize() + stored_ammo += new ammo_type(src) + . = ..() diff --git a/code/modules/projectiles/boxes_magazines/internal/rifle.dm b/code/modules/projectiles/boxes_magazines/internal/rifle.dm index ae49a8cadd..3d174cc07b 100644 --- a/code/modules/projectiles/boxes_magazines/internal/rifle.dm +++ b/code/modules/projectiles/boxes_magazines/internal/rifle.dm @@ -1,17 +1,17 @@ -/obj/item/ammo_box/magazine/internal/boltaction - name = "bolt action rifle internal magazine" - desc = "Oh god, this shouldn't be here" - ammo_type = /obj/item/ammo_casing/a762 - caliber = "a762" - max_ammo = 5 - multiload = 1 - -/obj/item/ammo_box/magazine/internal/boltaction/improvised - max_ammo = 1 - -/obj/item/ammo_box/magazine/internal/boltaction/enchanted - max_ammo = 1 - ammo_type = /obj/item/ammo_casing/a762/enchanted - -/obj/item/ammo_box/magazine/internal/boltaction/enchanted/arcane_barrage - ammo_type = /obj/item/ammo_casing/magic/arcane_barrage +/obj/item/ammo_box/magazine/internal/boltaction + name = "bolt action rifle internal magazine" + desc = "Oh god, this shouldn't be here" + ammo_type = /obj/item/ammo_casing/a762 + caliber = "a762" + max_ammo = 5 + multiload = 1 + +/obj/item/ammo_box/magazine/internal/boltaction/improvised + max_ammo = 1 + +/obj/item/ammo_box/magazine/internal/boltaction/enchanted + max_ammo = 1 + ammo_type = /obj/item/ammo_casing/a762/enchanted + +/obj/item/ammo_box/magazine/internal/boltaction/enchanted/arcane_barrage + ammo_type = /obj/item/ammo_casing/magic/arcane_barrage diff --git a/code/modules/projectiles/boxes_magazines/internal/shotgun.dm b/code/modules/projectiles/boxes_magazines/internal/shotgun.dm index 3bd277da31..09d056a5f2 100644 --- a/code/modules/projectiles/boxes_magazines/internal/shotgun.dm +++ b/code/modules/projectiles/boxes_magazines/internal/shotgun.dm @@ -1,48 +1,48 @@ -/obj/item/ammo_box/magazine/internal/shot - name = "shotgun internal magazine" - ammo_type = /obj/item/ammo_casing/shotgun/beanbag - caliber = "shotgun" - max_ammo = 4 - multiload = 0 - -/obj/item/ammo_box/magazine/internal/shot/ammo_count(countempties = 1) - if (!countempties) - var/boolets = 0 - for(var/obj/item/ammo_casing/bullet in stored_ammo) - if(bullet.BB) - boolets++ - return boolets - else - return ..() - -/obj/item/ammo_box/magazine/internal/shot/tube - name = "dual feed shotgun internal tube" - ammo_type = /obj/item/ammo_casing/shotgun/rubbershot - max_ammo = 4 - -/obj/item/ammo_box/magazine/internal/shot/lethal - ammo_type = /obj/item/ammo_casing/shotgun/buckshot - -/obj/item/ammo_box/magazine/internal/shot/com - name = "combat shotgun internal magazine" - ammo_type = /obj/item/ammo_casing/shotgun/buckshot - max_ammo = 6 - -/obj/item/ammo_box/magazine/internal/shot/com/compact - name = "compact combat shotgun internal magazine" - ammo_type = /obj/item/ammo_casing/shotgun/buckshot - max_ammo = 4 - -/obj/item/ammo_box/magazine/internal/shot/dual - name = "double-barrel shotgun internal magazine" - max_ammo = 2 - -/obj/item/ammo_box/magazine/internal/shot/improvised - name = "improvised shotgun internal magazine" - ammo_type = /obj/item/ammo_casing/shotgun/improvised - max_ammo = 1 - -/obj/item/ammo_box/magazine/internal/shot/riot - name = "riot shotgun internal magazine" - ammo_type = /obj/item/ammo_casing/shotgun/rubbershot - max_ammo = 6 +/obj/item/ammo_box/magazine/internal/shot + name = "shotgun internal magazine" + ammo_type = /obj/item/ammo_casing/shotgun/beanbag + caliber = "shotgun" + max_ammo = 4 + multiload = 0 + +/obj/item/ammo_box/magazine/internal/shot/ammo_count(countempties = 1) + if (!countempties) + var/boolets = 0 + for(var/obj/item/ammo_casing/bullet in stored_ammo) + if(bullet.BB) + boolets++ + return boolets + else + return ..() + +/obj/item/ammo_box/magazine/internal/shot/tube + name = "dual feed shotgun internal tube" + ammo_type = /obj/item/ammo_casing/shotgun/rubbershot + max_ammo = 4 + +/obj/item/ammo_box/magazine/internal/shot/lethal + ammo_type = /obj/item/ammo_casing/shotgun/buckshot + +/obj/item/ammo_box/magazine/internal/shot/com + name = "combat shotgun internal magazine" + ammo_type = /obj/item/ammo_casing/shotgun/buckshot + max_ammo = 6 + +/obj/item/ammo_box/magazine/internal/shot/com/compact + name = "compact combat shotgun internal magazine" + ammo_type = /obj/item/ammo_casing/shotgun/buckshot + max_ammo = 4 + +/obj/item/ammo_box/magazine/internal/shot/dual + name = "double-barrel shotgun internal magazine" + max_ammo = 2 + +/obj/item/ammo_box/magazine/internal/shot/improvised + name = "improvised shotgun internal magazine" + ammo_type = /obj/item/ammo_casing/shotgun/improvised + max_ammo = 1 + +/obj/item/ammo_box/magazine/internal/shot/riot + name = "riot shotgun internal magazine" + ammo_type = /obj/item/ammo_casing/shotgun/rubbershot + max_ammo = 6 diff --git a/code/modules/projectiles/boxes_magazines/internal/toy.dm b/code/modules/projectiles/boxes_magazines/internal/toy.dm index f2bb0dbf08..7a520c6a1f 100644 --- a/code/modules/projectiles/boxes_magazines/internal/toy.dm +++ b/code/modules/projectiles/boxes_magazines/internal/toy.dm @@ -1,7 +1,7 @@ -/obj/item/ammo_box/magazine/internal/shot/toy - ammo_type = /obj/item/ammo_casing/caseless/foam_dart - caliber = "foam_force" - max_ammo = 4 - -/obj/item/ammo_box/magazine/internal/shot/toy/crossbow - max_ammo = 5 +/obj/item/ammo_box/magazine/internal/shot/toy + ammo_type = /obj/item/ammo_casing/caseless/foam_dart + caliber = "foam_force" + max_ammo = 4 + +/obj/item/ammo_box/magazine/internal/shot/toy/crossbow + max_ammo = 5 diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm index 8a65a29826..1329e5418f 100644 --- a/code/modules/projectiles/guns/energy.dm +++ b/code/modules/projectiles/guns/energy.dm @@ -1,236 +1,236 @@ -/obj/item/gun/energy - icon_state = "energy" - name = "energy gun" - desc = "A basic energy-based gun." - icon = 'icons/obj/guns/energy.dmi' - - var/obj/item/stock_parts/cell/cell //What type of power cell this uses - var/cell_type = /obj/item/stock_parts/cell - var/modifystate = 0 - var/list/ammo_type = list(/obj/item/ammo_casing/energy) - var/select = 1 //The state of the select fire switch. Determines from the ammo_type list what kind of shot is fired next. - var/can_charge = 1 //Can it be charged in a recharger? - var/automatic_charge_overlays = TRUE //Do we handle overlays with base update_icon()? - var/charge_sections = 4 - ammo_x_offset = 2 - var/shaded_charge = FALSE //if this gun uses a stateful charge bar for more detail - var/old_ratio = 0 // stores the gun's previous ammo "ratio" to see if it needs an updated icon - var/selfcharge = EGUN_NO_SELFCHARGE // EGUN_SELFCHARGE if true, EGUN_SELFCHARGE_BORG drains the cyborg's cell to recharge its own - var/charge_tick = 0 - var/charge_delay = 4 - var/use_cyborg_cell = FALSE //whether the gun drains the cyborg user's cell instead, not to be confused with EGUN_SELFCHARGE_BORG - var/dead_cell = FALSE //set to true so the gun is given an empty cell - -/obj/item/gun/energy/emp_act(severity) - . = ..() - if(!(. & EMP_PROTECT_CONTENTS)) - cell.use(round(cell.charge / severity)) - chambered = null //we empty the chamber - recharge_newshot() //and try to charge a new shot - update_icon() - -/obj/item/gun/energy/get_cell() - return cell - -/obj/item/gun/energy/Initialize() - . = ..() - if(cell_type) - cell = new cell_type(src) - else - cell = new(src) - if(!dead_cell) - cell.give(cell.maxcharge) - update_ammo_types() - recharge_newshot(TRUE) - if(selfcharge) - START_PROCESSING(SSobj, src) - update_icon() - -/obj/item/gun/energy/proc/update_ammo_types() - var/obj/item/ammo_casing/energy/shot - for (var/i = 1, i <= ammo_type.len, i++) - var/shottype = ammo_type[i] - shot = new shottype(src) - ammo_type[i] = shot - shot = ammo_type[select] - fire_sound = shot.fire_sound - fire_delay = shot.delay - -/obj/item/gun/energy/Destroy() - QDEL_NULL(cell) - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/gun/energy/process() - if(selfcharge && cell?.charge < cell.maxcharge) - charge_tick++ - if(charge_tick < charge_delay) - return - charge_tick = 0 - if(selfcharge == EGUN_SELFCHARGE_BORG) - var/atom/owner = loc - if(istype(owner, /obj/item/robot_module)) - owner = owner.loc - if(!iscyborg(owner)) - return - var/mob/living/silicon/robot/R = owner - if(!R.cell?.use(100)) - return - cell.give(100) - if(!chambered) //if empty chamber we try to charge a new shot - recharge_newshot(TRUE) - update_icon() - -/obj/item/gun/energy/attack_self(mob/living/user as mob) - if(ammo_type.len > 1) - select_fire(user) - update_icon() - -/obj/item/gun/energy/can_shoot() - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - return !QDELETED(cell) ? (cell.charge >= shot.e_cost) : FALSE - -/obj/item/gun/energy/recharge_newshot(no_cyborg_drain) - if (!ammo_type || !cell) - return - if(use_cyborg_cell && !no_cyborg_drain) - if(iscyborg(loc)) - var/mob/living/silicon/robot/R = loc - if(R.cell) - var/obj/item/ammo_casing/energy/shot = ammo_type[select] //Necessary to find cost of shot - if(R.cell.use(shot.e_cost)) //Take power from the borg... - cell.give(shot.e_cost) //... to recharge the shot - if(!chambered) - var/obj/item/ammo_casing/energy/AC = ammo_type[select] - if(cell.charge >= AC.e_cost) //if there's enough power in the cell cell... - chambered = AC //...prepare a new shot based on the current ammo type selected - if(!chambered.BB) - chambered.newshot() - -/obj/item/gun/energy/process_chamber() - if(chambered && !chambered.BB) //if BB is null, i.e the shot has been fired... - var/obj/item/ammo_casing/energy/shot = chambered - cell.use(shot.e_cost)//... drain the cell cell - chambered = null //either way, released the prepared shot - recharge_newshot() //try to charge a new shot - -/obj/item/gun/energy/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) - if(!chambered && can_shoot()) - process_chamber() // If the gun was drained and then recharged, load a new shot. - return ..() - -/obj/item/gun/energy/process_burst(mob/living/user, atom/target, message = TRUE, params = null, zone_override="", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0) - if(!chambered && can_shoot()) - process_chamber() // Ditto. - return ..() - -/obj/item/gun/energy/proc/select_fire(mob/living/user) - select++ - if (select > ammo_type.len) - select = 1 - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - fire_sound = shot.fire_sound - fire_delay = shot.delay - if (shot.select_name) - to_chat(user, "[src] is now set to [shot.select_name].") - chambered = null - recharge_newshot(TRUE) - update_icon(TRUE) - return - -/obj/item/gun/energy/update_icon(force_update) - if(QDELETED(src)) - return - ..() - if(!automatic_charge_overlays) - return - var/ratio = can_shoot() ? CEILING(CLAMP(cell.charge / cell.maxcharge, 0, 1) * charge_sections, 1) : 0 - // Sets the ratio to 0 if the gun doesn't have enough charge to fire, or if it's power cell is removed. - // TG issues #5361 & #47908 - if(ratio == old_ratio && !force_update) - return - old_ratio = ratio - cut_overlays() - var/iconState = "[icon_state]_charge" - var/itemState = null - if(!initial(item_state)) - itemState = icon_state - if (modifystate) - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - add_overlay("[icon_state]_[shot.select_name]") - iconState += "_[shot.select_name]" - if(itemState) - itemState += "[shot.select_name]" - if(ratio == 0) - add_overlay("[icon_state]_empty") - else - if(!shaded_charge) - var/mutable_appearance/charge_overlay = mutable_appearance(icon, iconState) - for(var/i = ratio, i >= 1, i--) - charge_overlay.pixel_x = ammo_x_offset * (i - 1) - charge_overlay.pixel_y = ammo_y_offset * (i - 1) - add_overlay(charge_overlay) - else - add_overlay("[icon_state]_charge[ratio]") - if(itemState) - itemState += "[ratio]" - item_state = itemState - -/obj/item/gun/energy/suicide_act(mob/living/user) - if (istype(user) && can_shoot() && can_trigger_gun(user) && user.get_bodypart(BODY_ZONE_HEAD)) - user.visible_message("[user] is putting the barrel of [src] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide!") - sleep(25) - if(user.is_holding(src)) - user.visible_message("[user] melts [user.p_their()] face off with [src]!") - playsound(loc, fire_sound, 50, 1, -1) - playsound(src, 'sound/weapons/dink.ogg', 30, 1) - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - cell.use(shot.e_cost) - update_icon() - return(FIRELOSS) - else - user.visible_message("[user] panics and starts choking to death!") - return(OXYLOSS) - else - user.visible_message("[user] is pretending to melt [user.p_their()] face off with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(src, "gun_dry_fire", 30, 1) - return (OXYLOSS) - - -/obj/item/gun/energy/vv_edit_var(var_name, var_value) - switch(var_name) - if("selfcharge") - if(var_value) - START_PROCESSING(SSobj, src) - else - STOP_PROCESSING(SSobj, src) - . = ..() - - -/obj/item/gun/energy/ignition_effect(atom/A, mob/living/user) - if(!can_shoot() || !ammo_type[select]) - shoot_with_empty_chamber() - . = "" - else - var/obj/item/ammo_casing/energy/E = ammo_type[select] - var/obj/item/projectile/energy/BB = E.BB - if(!BB) - . = "" - else if(BB.nodamage || !BB.damage || BB.damage_type == STAMINA) - user.visible_message("[user] tries to light [user.p_their()] [A.name] with [src], but it doesn't do anything. Dumbass.") - playsound(user, E.fire_sound, 50, 1) - playsound(user, BB.hitsound, 50, 1) - cell.use(E.e_cost) - . = "" - else if(BB.damage_type != BURN) - user.visible_message("[user] tries to light [user.p_their()] [A.name] with [src], but only succeeds in utterly destroying it. Dumbass.") - playsound(user, E.fire_sound, 50, 1) - playsound(user, BB.hitsound, 50, 1) - cell.use(E.e_cost) - qdel(A) - . = "" - else - playsound(user, E.fire_sound, 50, 1) - playsound(user, BB.hitsound, 50, 1) - cell.use(E.e_cost) - . = "[user] casually lights their [A.name] with [src]. Damn." +/obj/item/gun/energy + icon_state = "energy" + name = "energy gun" + desc = "A basic energy-based gun." + icon = 'icons/obj/guns/energy.dmi' + + var/obj/item/stock_parts/cell/cell //What type of power cell this uses + var/cell_type = /obj/item/stock_parts/cell + var/modifystate = 0 + var/list/ammo_type = list(/obj/item/ammo_casing/energy) + var/select = 1 //The state of the select fire switch. Determines from the ammo_type list what kind of shot is fired next. + var/can_charge = 1 //Can it be charged in a recharger? + var/automatic_charge_overlays = TRUE //Do we handle overlays with base update_icon()? + var/charge_sections = 4 + ammo_x_offset = 2 + var/shaded_charge = FALSE //if this gun uses a stateful charge bar for more detail + var/old_ratio = 0 // stores the gun's previous ammo "ratio" to see if it needs an updated icon + var/selfcharge = EGUN_NO_SELFCHARGE // EGUN_SELFCHARGE if true, EGUN_SELFCHARGE_BORG drains the cyborg's cell to recharge its own + var/charge_tick = 0 + var/charge_delay = 4 + var/use_cyborg_cell = FALSE //whether the gun drains the cyborg user's cell instead, not to be confused with EGUN_SELFCHARGE_BORG + var/dead_cell = FALSE //set to true so the gun is given an empty cell + +/obj/item/gun/energy/emp_act(severity) + . = ..() + if(!(. & EMP_PROTECT_CONTENTS)) + cell.use(round(cell.charge / severity)) + chambered = null //we empty the chamber + recharge_newshot() //and try to charge a new shot + update_icon() + +/obj/item/gun/energy/get_cell() + return cell + +/obj/item/gun/energy/Initialize() + . = ..() + if(cell_type) + cell = new cell_type(src) + else + cell = new(src) + if(!dead_cell) + cell.give(cell.maxcharge) + update_ammo_types() + recharge_newshot(TRUE) + if(selfcharge) + START_PROCESSING(SSobj, src) + update_icon() + +/obj/item/gun/energy/proc/update_ammo_types() + var/obj/item/ammo_casing/energy/shot + for (var/i = 1, i <= ammo_type.len, i++) + var/shottype = ammo_type[i] + shot = new shottype(src) + ammo_type[i] = shot + shot = ammo_type[select] + fire_sound = shot.fire_sound + fire_delay = shot.delay + +/obj/item/gun/energy/Destroy() + QDEL_NULL(cell) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/gun/energy/process() + if(selfcharge && cell?.charge < cell.maxcharge) + charge_tick++ + if(charge_tick < charge_delay) + return + charge_tick = 0 + if(selfcharge == EGUN_SELFCHARGE_BORG) + var/atom/owner = loc + if(istype(owner, /obj/item/robot_module)) + owner = owner.loc + if(!iscyborg(owner)) + return + var/mob/living/silicon/robot/R = owner + if(!R.cell?.use(100)) + return + cell.give(100) + if(!chambered) //if empty chamber we try to charge a new shot + recharge_newshot(TRUE) + update_icon() + +/obj/item/gun/energy/attack_self(mob/living/user as mob) + if(ammo_type.len > 1) + select_fire(user) + update_icon() + +/obj/item/gun/energy/can_shoot() + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + return !QDELETED(cell) ? (cell.charge >= shot.e_cost) : FALSE + +/obj/item/gun/energy/recharge_newshot(no_cyborg_drain) + if (!ammo_type || !cell) + return + if(use_cyborg_cell && !no_cyborg_drain) + if(iscyborg(loc)) + var/mob/living/silicon/robot/R = loc + if(R.cell) + var/obj/item/ammo_casing/energy/shot = ammo_type[select] //Necessary to find cost of shot + if(R.cell.use(shot.e_cost)) //Take power from the borg... + cell.give(shot.e_cost) //... to recharge the shot + if(!chambered) + var/obj/item/ammo_casing/energy/AC = ammo_type[select] + if(cell.charge >= AC.e_cost) //if there's enough power in the cell cell... + chambered = AC //...prepare a new shot based on the current ammo type selected + if(!chambered.BB) + chambered.newshot() + +/obj/item/gun/energy/process_chamber() + if(chambered && !chambered.BB) //if BB is null, i.e the shot has been fired... + var/obj/item/ammo_casing/energy/shot = chambered + cell.use(shot.e_cost)//... drain the cell cell + chambered = null //either way, released the prepared shot + recharge_newshot() //try to charge a new shot + +/obj/item/gun/energy/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) + if(!chambered && can_shoot()) + process_chamber() // If the gun was drained and then recharged, load a new shot. + return ..() + +/obj/item/gun/energy/process_burst(mob/living/user, atom/target, message = TRUE, params = null, zone_override="", sprd = 0, randomized_gun_spread = 0, randomized_bonus_spread = 0, rand_spr = 0, iteration = 0) + if(!chambered && can_shoot()) + process_chamber() // Ditto. + return ..() + +/obj/item/gun/energy/proc/select_fire(mob/living/user) + select++ + if (select > ammo_type.len) + select = 1 + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + fire_sound = shot.fire_sound + fire_delay = shot.delay + if (shot.select_name) + to_chat(user, "[src] is now set to [shot.select_name].") + chambered = null + recharge_newshot(TRUE) + update_icon(TRUE) + return + +/obj/item/gun/energy/update_icon(force_update) + if(QDELETED(src)) + return + ..() + if(!automatic_charge_overlays) + return + var/ratio = can_shoot() ? CEILING(CLAMP(cell.charge / cell.maxcharge, 0, 1) * charge_sections, 1) : 0 + // Sets the ratio to 0 if the gun doesn't have enough charge to fire, or if it's power cell is removed. + // TG issues #5361 & #47908 + if(ratio == old_ratio && !force_update) + return + old_ratio = ratio + cut_overlays() + var/iconState = "[icon_state]_charge" + var/itemState = null + if(!initial(item_state)) + itemState = icon_state + if (modifystate) + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + add_overlay("[icon_state]_[shot.select_name]") + iconState += "_[shot.select_name]" + if(itemState) + itemState += "[shot.select_name]" + if(ratio == 0) + add_overlay("[icon_state]_empty") + else + if(!shaded_charge) + var/mutable_appearance/charge_overlay = mutable_appearance(icon, iconState) + for(var/i = ratio, i >= 1, i--) + charge_overlay.pixel_x = ammo_x_offset * (i - 1) + charge_overlay.pixel_y = ammo_y_offset * (i - 1) + add_overlay(charge_overlay) + else + add_overlay("[icon_state]_charge[ratio]") + if(itemState) + itemState += "[ratio]" + item_state = itemState + +/obj/item/gun/energy/suicide_act(mob/living/user) + if (istype(user) && can_shoot() && can_trigger_gun(user) && user.get_bodypart(BODY_ZONE_HEAD)) + user.visible_message("[user] is putting the barrel of [src] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide!") + sleep(25) + if(user.is_holding(src)) + user.visible_message("[user] melts [user.p_their()] face off with [src]!") + playsound(loc, fire_sound, 50, 1, -1) + playsound(src, 'sound/weapons/dink.ogg', 30, 1) + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + cell.use(shot.e_cost) + update_icon() + return(FIRELOSS) + else + user.visible_message("[user] panics and starts choking to death!") + return(OXYLOSS) + else + user.visible_message("[user] is pretending to melt [user.p_their()] face off with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(src, "gun_dry_fire", 30, 1) + return (OXYLOSS) + + +/obj/item/gun/energy/vv_edit_var(var_name, var_value) + switch(var_name) + if("selfcharge") + if(var_value) + START_PROCESSING(SSobj, src) + else + STOP_PROCESSING(SSobj, src) + . = ..() + + +/obj/item/gun/energy/ignition_effect(atom/A, mob/living/user) + if(!can_shoot() || !ammo_type[select]) + shoot_with_empty_chamber() + . = "" + else + var/obj/item/ammo_casing/energy/E = ammo_type[select] + var/obj/item/projectile/energy/BB = E.BB + if(!BB) + . = "" + else if(BB.nodamage || !BB.damage || BB.damage_type == STAMINA) + user.visible_message("[user] tries to light [user.p_their()] [A.name] with [src], but it doesn't do anything. Dumbass.") + playsound(user, E.fire_sound, 50, 1) + playsound(user, BB.hitsound, 50, 1) + cell.use(E.e_cost) + . = "" + else if(BB.damage_type != BURN) + user.visible_message("[user] tries to light [user.p_their()] [A.name] with [src], but only succeeds in utterly destroying it. Dumbass.") + playsound(user, E.fire_sound, 50, 1) + playsound(user, BB.hitsound, 50, 1) + cell.use(E.e_cost) + qdel(A) + . = "" + else + playsound(user, E.fire_sound, 50, 1) + playsound(user, BB.hitsound, 50, 1) + cell.use(E.e_cost) + . = "[user] casually lights their [A.name] with [src]. Damn." diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index 150ee74a65..1e1af62d5a 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -1,225 +1,225 @@ -/obj/item/gun/energy/laser - name = "laser gun" - desc = "A basic energy-based laser gun that fires concentrated beams of light which pass through glass and thin metal." - icon_state = "laser" - item_state = "laser" - w_class = WEIGHT_CLASS_NORMAL - materials = list(MAT_METAL=2000) - ammo_type = list(/obj/item/ammo_casing/energy/lasergun) - ammo_x_offset = 1 - shaded_charge = 1 - -/obj/item/gun/energy/laser/practice - name = "practice laser gun" - desc = "A modified version of the basic laser gun, this one fires less concentrated energy bolts designed for target practice." - ammo_type = list(/obj/item/ammo_casing/energy/laser/practice) - clumsy_check = 0 - item_flags = NONE - -/obj/item/gun/energy/laser/retro - name ="retro laser gun" - icon_state = "retro" - desc = "An older model of the basic lasergun, no longer used by Nanotrasen's private security or military forces. Nevertheless, it is still quite deadly and easy to maintain, making it a favorite amongst pirates and other outlaws." - ammo_x_offset = 3 - -/obj/item/gun/energy/laser/retro/old - name ="laser gun" - icon_state = "retro" - desc = "First generation lasergun, developed by Nanotrasen. Suffers from ammo issues but its unique ability to recharge its ammo without the need of a magazine helps compensate. You really hope someone has developed a better lasergun while you were in cryo." - ammo_type = list(/obj/item/ammo_casing/energy/lasergun/old) - ammo_x_offset = 3 - -/obj/item/gun/energy/laser/captain - name = "antique laser gun" - icon_state = "caplaser" - item_state = "caplaser" - desc = "This is an antique laser gun. All craftsmanship is of the highest quality. It is decorated with assistant leather and chrome. The object menaces with spikes of energy. On the item is an image of Space Station 13. The station is exploding." - force = 10 - ammo_x_offset = 3 - selfcharge = EGUN_SELFCHARGE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/gun/energy/laser/carbine - name = "laser carbine" - desc = "A ruggedized laser carbine featuring much higher capacity and improved handling when compared to a normal laser gun." - icon = 'icons/obj/guns/energy.dmi' - icon_state = "lasernew" - item_state = "laser" - force = 10 - throwforce = 10 - ammo_type = list(/obj/item/ammo_casing/energy/lasergun) - cell_type = /obj/item/stock_parts/cell/lascarbine - resistance_flags = FIRE_PROOF | ACID_PROOF - -/obj/item/gun/energy/laser/carbine/nopin - pin = null - -/obj/item/gun/energy/laser/captain/scattershot - name = "scatter shot laser rifle" - icon_state = "lasercannon" - item_state = "laser" - desc = "An industrial-grade heavy-duty laser rifle with a modified laser lens to scatter its shot into multiple smaller lasers. The inner-core can self-charge for theoretically infinite use." - ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter, /obj/item/ammo_casing/energy/laser) - -/obj/item/gun/energy/laser/cyborg - can_charge = FALSE - desc = "An energy-based laser gun that draws power from the cyborg's internal energy cell directly. So this is what freedom looks like?" - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "laser_cyborg" - selfcharge = EGUN_SELFCHARGE_BORG - cell_type = /obj/item/stock_parts/cell/secborg - charge_delay = 3 - -/obj/item/gun/energy/laser/cyborg/emp_act() - return - -/obj/item/gun/energy/laser/cyborg/mean - use_cyborg_cell = TRUE - selfcharge = EGUN_NO_SELFCHARGE - -/obj/item/gun/energy/laser/scatter - name = "scatter laser gun" - desc = "A laser gun equipped with a refraction kit that spreads bolts." - ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter, /obj/item/ammo_casing/energy/laser) - -/obj/item/gun/energy/laser/scatter/shotty - name = "energy shotgun" - icon = 'icons/obj/guns/projectile.dmi' - icon_state = "cshotgun" - item_state = "shotgun" - desc = "A combat shotgun gutted and refitted with an internal laser system. Can switch between taser and scattered disabler shots." - shaded_charge = 0 - pin = /obj/item/firing_pin/implant/mindshield - ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter/disabler, /obj/item/ammo_casing/energy/electrode) - -///Laser Cannon - -/obj/item/gun/energy/lasercannon - name = "accelerator laser cannon" - desc = "An advanced laser cannon that does more damage the farther away the target is." - icon_state = "lasercannon" - item_state = "laser" - w_class = WEIGHT_CLASS_BULKY - force = 10 - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BACK - ammo_type = list(/obj/item/ammo_casing/energy/laser/accelerator) - pin = null - ammo_x_offset = 3 - -/obj/item/ammo_casing/energy/laser/accelerator - projectile_type = /obj/item/projectile/beam/laser/accelerator - select_name = "accelerator" - fire_sound = 'sound/weapons/lasercannonfire.ogg' - -/obj/item/projectile/beam/laser/accelerator - name = "accelerator laser" - icon_state = "scatterlaser" - range = 255 - damage = 6 - -/obj/item/projectile/beam/laser/accelerator/Range() - ..() - damage += 7 - transform *= 1 + ((damage/7) * 0.2)//20% larger per tile - -/obj/item/gun/energy/xray - name = "\improper X-ray laser gun" - desc = "A high-power laser gun capable of expelling concentrated X-ray blasts that pass through multiple soft targets and heavier materials." - icon_state = "xray" - item_state = null - ammo_type = list(/obj/item/ammo_casing/energy/xray) - pin = null - ammo_x_offset = 3 - -////////Laser Tag//////////////////// - -/obj/item/gun/energy/laser/bluetag - name = "laser tag gun" - icon_state = "bluetag" - desc = "A retro laser gun modified to fire harmless blue beams of light. Sound effects included!" - ammo_type = list(/obj/item/ammo_casing/energy/laser/bluetag) - item_flags = NONE - clumsy_check = FALSE - pin = /obj/item/firing_pin/tag/blue - ammo_x_offset = 2 - selfcharge = EGUN_SELFCHARGE - -/obj/item/gun/energy/laser/bluetag/hitscan - ammo_type = list(/obj/item/ammo_casing/energy/laser/bluetag/hitscan) - -/obj/item/gun/energy/laser/redtag - name = "laser tag gun" - icon_state = "redtag" - desc = "A retro laser gun modified to fire harmless beams red of light. Sound effects included!" - ammo_type = list(/obj/item/ammo_casing/energy/laser/redtag) - item_flags = NONE - clumsy_check = FALSE - pin = /obj/item/firing_pin/tag/red - ammo_x_offset = 2 - selfcharge = EGUN_SELFCHARGE - -/obj/item/gun/energy/laser/redtag/hitscan - ammo_type = list(/obj/item/ammo_casing/energy/laser/redtag/hitscan) - -/obj/item/gun/energy/laser/redtag/hitscan/chaplain - name = "\improper holy lasrifle" - desc = "A lasrifle from the old Imperium. This one seems to be blessed by techpriests." - icon_state = "LaserAK" - item_state = null - force = 14 - pin = /obj/item/firing_pin/holy - icon = 'modular_citadel/icons/obj/guns/VGguns.dmi' - ammo_x_offset = 4 - ammo_type = list(/obj/item/ammo_casing/energy/laser/redtag/hitscan/holy) - lefthand_file = 'modular_citadel/icons/mob/citadel/guns_lefthand.dmi' - righthand_file = 'modular_citadel/icons/mob/citadel/guns_righthand.dmi' - var/chaplain_spawnable = TRUE - total_mass = TOTAL_MASS_MEDIEVAL_WEAPON - throw_speed = 3 - throw_range = 4 - throwforce = 10 - obj_flags = UNIQUE_RENAME - -/obj/item/gun/energy/laser/redtag/hitscan/chaplain/Initialize() - . = ..() - AddComponent(/datum/component/anti_magic, TRUE, TRUE, FALSE, null, null, FALSE) - -/obj/item/gun/energy/laser/redtag/hitscan/chaplain/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params, bypass_timer) - if(!ishuman(user) || !ishuman(target)) - return - - if(semicd) - return - - if(user == target) - target.visible_message("[user] sticks [src] in [user.p_their()] mouth, ready to pull the trigger...", \ - "You stick [src] in your mouth, ready to pull the trigger...") - else - target.visible_message("[user] points [src] at [target]'s head, ready to pull the trigger...", \ - "[user] points [src] at your head, ready to pull the trigger...") - - semicd = TRUE - - if(!bypass_timer && (!do_mob(user, target, 120) || user.zone_selected != BODY_ZONE_PRECISE_MOUTH)) - if(user) - if(user == target) - user.visible_message("[user] decided not to shoot.") - else if(target && target.Adjacent(user)) - target.visible_message("[user] has decided to spare [target]", "[user] has decided to spare your life!") - semicd = FALSE - return - - semicd = FALSE - - target.visible_message("[user] pulls the trigger!", "[user] pulls the trigger!") - - playsound('sound/weapons/dink.ogg', 30, 1) - - if((iscultist(target)) || (is_servant_of_ratvar(target))) - chambered.BB.damage *= 1500 - - else if(chambered && chambered.BB) - chambered.BB.damage *= 5 - - process_fire(target, user, TRUE, params) +/obj/item/gun/energy/laser + name = "laser gun" + desc = "A basic energy-based laser gun that fires concentrated beams of light which pass through glass and thin metal." + icon_state = "laser" + item_state = "laser" + w_class = WEIGHT_CLASS_NORMAL + materials = list(MAT_METAL=2000) + ammo_type = list(/obj/item/ammo_casing/energy/lasergun) + ammo_x_offset = 1 + shaded_charge = 1 + +/obj/item/gun/energy/laser/practice + name = "practice laser gun" + desc = "A modified version of the basic laser gun, this one fires less concentrated energy bolts designed for target practice." + ammo_type = list(/obj/item/ammo_casing/energy/laser/practice) + clumsy_check = 0 + item_flags = NONE + +/obj/item/gun/energy/laser/retro + name ="retro laser gun" + icon_state = "retro" + desc = "An older model of the basic lasergun, no longer used by Nanotrasen's private security or military forces. Nevertheless, it is still quite deadly and easy to maintain, making it a favorite amongst pirates and other outlaws." + ammo_x_offset = 3 + +/obj/item/gun/energy/laser/retro/old + name ="laser gun" + icon_state = "retro" + desc = "First generation lasergun, developed by Nanotrasen. Suffers from ammo issues but its unique ability to recharge its ammo without the need of a magazine helps compensate. You really hope someone has developed a better lasergun while you were in cryo." + ammo_type = list(/obj/item/ammo_casing/energy/lasergun/old) + ammo_x_offset = 3 + +/obj/item/gun/energy/laser/captain + name = "antique laser gun" + icon_state = "caplaser" + item_state = "caplaser" + desc = "This is an antique laser gun. All craftsmanship is of the highest quality. It is decorated with assistant leather and chrome. The object menaces with spikes of energy. On the item is an image of Space Station 13. The station is exploding." + force = 10 + ammo_x_offset = 3 + selfcharge = EGUN_SELFCHARGE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/gun/energy/laser/carbine + name = "laser carbine" + desc = "A ruggedized laser carbine featuring much higher capacity and improved handling when compared to a normal laser gun." + icon = 'icons/obj/guns/energy.dmi' + icon_state = "lasernew" + item_state = "laser" + force = 10 + throwforce = 10 + ammo_type = list(/obj/item/ammo_casing/energy/lasergun) + cell_type = /obj/item/stock_parts/cell/lascarbine + resistance_flags = FIRE_PROOF | ACID_PROOF + +/obj/item/gun/energy/laser/carbine/nopin + pin = null + +/obj/item/gun/energy/laser/captain/scattershot + name = "scatter shot laser rifle" + icon_state = "lasercannon" + item_state = "laser" + desc = "An industrial-grade heavy-duty laser rifle with a modified laser lens to scatter its shot into multiple smaller lasers. The inner-core can self-charge for theoretically infinite use." + ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter, /obj/item/ammo_casing/energy/laser) + +/obj/item/gun/energy/laser/cyborg + can_charge = FALSE + desc = "An energy-based laser gun that draws power from the cyborg's internal energy cell directly. So this is what freedom looks like?" + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "laser_cyborg" + selfcharge = EGUN_SELFCHARGE_BORG + cell_type = /obj/item/stock_parts/cell/secborg + charge_delay = 3 + +/obj/item/gun/energy/laser/cyborg/emp_act() + return + +/obj/item/gun/energy/laser/cyborg/mean + use_cyborg_cell = TRUE + selfcharge = EGUN_NO_SELFCHARGE + +/obj/item/gun/energy/laser/scatter + name = "scatter laser gun" + desc = "A laser gun equipped with a refraction kit that spreads bolts." + ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter, /obj/item/ammo_casing/energy/laser) + +/obj/item/gun/energy/laser/scatter/shotty + name = "energy shotgun" + icon = 'icons/obj/guns/projectile.dmi' + icon_state = "cshotgun" + item_state = "shotgun" + desc = "A combat shotgun gutted and refitted with an internal laser system. Can switch between taser and scattered disabler shots." + shaded_charge = 0 + pin = /obj/item/firing_pin/implant/mindshield + ammo_type = list(/obj/item/ammo_casing/energy/laser/scatter/disabler, /obj/item/ammo_casing/energy/electrode) + +///Laser Cannon + +/obj/item/gun/energy/lasercannon + name = "accelerator laser cannon" + desc = "An advanced laser cannon that does more damage the farther away the target is." + icon_state = "lasercannon" + item_state = "laser" + w_class = WEIGHT_CLASS_BULKY + force = 10 + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BACK + ammo_type = list(/obj/item/ammo_casing/energy/laser/accelerator) + pin = null + ammo_x_offset = 3 + +/obj/item/ammo_casing/energy/laser/accelerator + projectile_type = /obj/item/projectile/beam/laser/accelerator + select_name = "accelerator" + fire_sound = 'sound/weapons/lasercannonfire.ogg' + +/obj/item/projectile/beam/laser/accelerator + name = "accelerator laser" + icon_state = "scatterlaser" + range = 255 + damage = 6 + +/obj/item/projectile/beam/laser/accelerator/Range() + ..() + damage += 7 + transform *= 1 + ((damage/7) * 0.2)//20% larger per tile + +/obj/item/gun/energy/xray + name = "\improper X-ray laser gun" + desc = "A high-power laser gun capable of expelling concentrated X-ray blasts that pass through multiple soft targets and heavier materials." + icon_state = "xray" + item_state = null + ammo_type = list(/obj/item/ammo_casing/energy/xray) + pin = null + ammo_x_offset = 3 + +////////Laser Tag//////////////////// + +/obj/item/gun/energy/laser/bluetag + name = "laser tag gun" + icon_state = "bluetag" + desc = "A retro laser gun modified to fire harmless blue beams of light. Sound effects included!" + ammo_type = list(/obj/item/ammo_casing/energy/laser/bluetag) + item_flags = NONE + clumsy_check = FALSE + pin = /obj/item/firing_pin/tag/blue + ammo_x_offset = 2 + selfcharge = EGUN_SELFCHARGE + +/obj/item/gun/energy/laser/bluetag/hitscan + ammo_type = list(/obj/item/ammo_casing/energy/laser/bluetag/hitscan) + +/obj/item/gun/energy/laser/redtag + name = "laser tag gun" + icon_state = "redtag" + desc = "A retro laser gun modified to fire harmless beams red of light. Sound effects included!" + ammo_type = list(/obj/item/ammo_casing/energy/laser/redtag) + item_flags = NONE + clumsy_check = FALSE + pin = /obj/item/firing_pin/tag/red + ammo_x_offset = 2 + selfcharge = EGUN_SELFCHARGE + +/obj/item/gun/energy/laser/redtag/hitscan + ammo_type = list(/obj/item/ammo_casing/energy/laser/redtag/hitscan) + +/obj/item/gun/energy/laser/redtag/hitscan/chaplain + name = "\improper holy lasrifle" + desc = "A lasrifle from the old Imperium. This one seems to be blessed by techpriests." + icon_state = "LaserAK" + item_state = null + force = 14 + pin = /obj/item/firing_pin/holy + icon = 'modular_citadel/icons/obj/guns/VGguns.dmi' + ammo_x_offset = 4 + ammo_type = list(/obj/item/ammo_casing/energy/laser/redtag/hitscan/holy) + lefthand_file = 'modular_citadel/icons/mob/citadel/guns_lefthand.dmi' + righthand_file = 'modular_citadel/icons/mob/citadel/guns_righthand.dmi' + var/chaplain_spawnable = TRUE + total_mass = TOTAL_MASS_MEDIEVAL_WEAPON + throw_speed = 3 + throw_range = 4 + throwforce = 10 + obj_flags = UNIQUE_RENAME + +/obj/item/gun/energy/laser/redtag/hitscan/chaplain/Initialize() + . = ..() + AddComponent(/datum/component/anti_magic, TRUE, TRUE, FALSE, null, null, FALSE) + +/obj/item/gun/energy/laser/redtag/hitscan/chaplain/handle_suicide(mob/living/carbon/human/user, mob/living/carbon/human/target, params, bypass_timer) + if(!ishuman(user) || !ishuman(target)) + return + + if(semicd) + return + + if(user == target) + target.visible_message("[user] sticks [src] in [user.p_their()] mouth, ready to pull the trigger...", \ + "You stick [src] in your mouth, ready to pull the trigger...") + else + target.visible_message("[user] points [src] at [target]'s head, ready to pull the trigger...", \ + "[user] points [src] at your head, ready to pull the trigger...") + + semicd = TRUE + + if(!bypass_timer && (!do_mob(user, target, 120) || user.zone_selected != BODY_ZONE_PRECISE_MOUTH)) + if(user) + if(user == target) + user.visible_message("[user] decided not to shoot.") + else if(target && target.Adjacent(user)) + target.visible_message("[user] has decided to spare [target]", "[user] has decided to spare your life!") + semicd = FALSE + return + + semicd = FALSE + + target.visible_message("[user] pulls the trigger!", "[user] pulls the trigger!") + + playsound('sound/weapons/dink.ogg', 30, 1) + + if((iscultist(target)) || (is_servant_of_ratvar(target))) + chambered.BB.damage *= 1500 + + else if(chambered && chambered.BB) + chambered.BB.damage *= 5 + + process_fire(target, user, TRUE, params) diff --git a/code/modules/projectiles/guns/energy/mounted.dm b/code/modules/projectiles/guns/energy/mounted.dm index eed4e7316f..25a94895f8 100644 --- a/code/modules/projectiles/guns/energy/mounted.dm +++ b/code/modules/projectiles/guns/energy/mounted.dm @@ -1,20 +1,20 @@ -/obj/item/gun/energy/e_gun/advtaser/mounted - name = "mounted taser" - desc = "An arm mounted dual-mode weapon that fires electrodes and disabler shots." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "taser" - item_state = "armcannonstun4" - force = 5 - selfcharge = EGUN_SELFCHARGE - can_flashlight = 0 - trigger_guard = TRIGGER_GUARD_ALLOW_ALL // Has no trigger at all, uses neural signals instead - -/obj/item/gun/energy/laser/mounted - name = "mounted laser" - desc = "An arm mounted cannon that fires lethal lasers." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "laser" - item_state = "armcannonlase" - force = 5 - selfcharge = EGUN_SELFCHARGE - trigger_guard = TRIGGER_GUARD_ALLOW_ALL +/obj/item/gun/energy/e_gun/advtaser/mounted + name = "mounted taser" + desc = "An arm mounted dual-mode weapon that fires electrodes and disabler shots." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "taser" + item_state = "armcannonstun4" + force = 5 + selfcharge = EGUN_SELFCHARGE + can_flashlight = 0 + trigger_guard = TRIGGER_GUARD_ALLOW_ALL // Has no trigger at all, uses neural signals instead + +/obj/item/gun/energy/laser/mounted + name = "mounted laser" + desc = "An arm mounted cannon that fires lethal lasers." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "laser" + item_state = "armcannonlase" + force = 5 + selfcharge = EGUN_SELFCHARGE + trigger_guard = TRIGGER_GUARD_ALLOW_ALL diff --git a/code/modules/projectiles/guns/energy/pulse.dm b/code/modules/projectiles/guns/energy/pulse.dm index cf9b697d00..b05be99808 100644 --- a/code/modules/projectiles/guns/energy/pulse.dm +++ b/code/modules/projectiles/guns/energy/pulse.dm @@ -1,79 +1,79 @@ -/obj/item/gun/energy/pulse - name = "pulse rifle" - desc = "A heavy-duty, multifaceted energy rifle with three modes. Preferred by front-line combat personnel." - icon_state = "pulse" - item_state = null - w_class = WEIGHT_CLASS_BULKY - force = 10 - modifystate = TRUE - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BACK - ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse, /obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser) - cell_type = "/obj/item/stock_parts/cell/pulse" - -/obj/item/gun/energy/pulse/emp_act(severity) - return - -/obj/item/gun/energy/pulse/prize - pin = /obj/item/firing_pin - -/obj/item/gun/energy/pulse/prize/Initialize() - . = ..() - GLOB.poi_list += src - var/turf/T = get_turf(src) - var/msg = "A pulse rifle prize has been created at [ADMIN_VERBOSEJMP(T)]" - - message_admins(msg) - log_game(msg) - - notify_ghosts("Someone won a pulse rifle as a prize!", source = src, action = NOTIFY_ORBIT) - -/obj/item/gun/energy/pulse/prize/Destroy() - GLOB.poi_list -= src - . = ..() - -/obj/item/gun/energy/pulse/loyalpin - pin = /obj/item/firing_pin/implant/mindshield - -/obj/item/gun/energy/pulse/carbine - name = "pulse carbine" - desc = "A compact variant of the pulse rifle with less firepower but easier storage." - w_class = WEIGHT_CLASS_NORMAL - slot_flags = ITEM_SLOT_BELT - icon_state = "pulse_carbine" - item_state = null - cell_type = "/obj/item/stock_parts/cell/pulse/carbine" - can_flashlight = 1 - flight_x_offset = 18 - flight_y_offset = 12 - -/obj/item/gun/energy/pulse/carbine/loyalpin - pin = /obj/item/firing_pin/implant/mindshield - -/obj/item/gun/energy/pulse/pistol - name = "pulse pistol" - desc = "A pulse rifle in an easily concealed handgun package with low capacity." - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - icon_state = "pulse_pistol" - item_state = "gun" - cell_type = "/obj/item/stock_parts/cell/pulse/pistol" - -/obj/item/gun/energy/pulse/pistol/loyalpin - pin = /obj/item/firing_pin/implant/mindshield - -/obj/item/gun/energy/pulse/destroyer - name = "pulse destroyer" - desc = "A heavy-duty energy rifle built for pure destruction." - cell_type = "/obj/item/stock_parts/cell/infinite" - ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse) - -/obj/item/gun/energy/pulse/destroyer/attack_self(mob/living/user) - to_chat(user, "[src.name] has three settings, and they are all DESTROY.") - -/obj/item/gun/energy/pulse/pistol/m1911 - name = "\improper M1911-P" - desc = "A compact pulse core in a classic handgun frame for Nanotrasen officers. It's not the size of the gun, it's the size of the hole it puts through people." - icon_state = "m1911" - item_state = "gun" - cell_type = "/obj/item/stock_parts/cell/infinite" +/obj/item/gun/energy/pulse + name = "pulse rifle" + desc = "A heavy-duty, multifaceted energy rifle with three modes. Preferred by front-line combat personnel." + icon_state = "pulse" + item_state = null + w_class = WEIGHT_CLASS_BULKY + force = 10 + modifystate = TRUE + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BACK + ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse, /obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser) + cell_type = "/obj/item/stock_parts/cell/pulse" + +/obj/item/gun/energy/pulse/emp_act(severity) + return + +/obj/item/gun/energy/pulse/prize + pin = /obj/item/firing_pin + +/obj/item/gun/energy/pulse/prize/Initialize() + . = ..() + GLOB.poi_list += src + var/turf/T = get_turf(src) + var/msg = "A pulse rifle prize has been created at [ADMIN_VERBOSEJMP(T)]" + + message_admins(msg) + log_game(msg) + + notify_ghosts("Someone won a pulse rifle as a prize!", source = src, action = NOTIFY_ORBIT) + +/obj/item/gun/energy/pulse/prize/Destroy() + GLOB.poi_list -= src + . = ..() + +/obj/item/gun/energy/pulse/loyalpin + pin = /obj/item/firing_pin/implant/mindshield + +/obj/item/gun/energy/pulse/carbine + name = "pulse carbine" + desc = "A compact variant of the pulse rifle with less firepower but easier storage." + w_class = WEIGHT_CLASS_NORMAL + slot_flags = ITEM_SLOT_BELT + icon_state = "pulse_carbine" + item_state = null + cell_type = "/obj/item/stock_parts/cell/pulse/carbine" + can_flashlight = 1 + flight_x_offset = 18 + flight_y_offset = 12 + +/obj/item/gun/energy/pulse/carbine/loyalpin + pin = /obj/item/firing_pin/implant/mindshield + +/obj/item/gun/energy/pulse/pistol + name = "pulse pistol" + desc = "A pulse rifle in an easily concealed handgun package with low capacity." + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + icon_state = "pulse_pistol" + item_state = "gun" + cell_type = "/obj/item/stock_parts/cell/pulse/pistol" + +/obj/item/gun/energy/pulse/pistol/loyalpin + pin = /obj/item/firing_pin/implant/mindshield + +/obj/item/gun/energy/pulse/destroyer + name = "pulse destroyer" + desc = "A heavy-duty energy rifle built for pure destruction." + cell_type = "/obj/item/stock_parts/cell/infinite" + ammo_type = list(/obj/item/ammo_casing/energy/laser/pulse) + +/obj/item/gun/energy/pulse/destroyer/attack_self(mob/living/user) + to_chat(user, "[src.name] has three settings, and they are all DESTROY.") + +/obj/item/gun/energy/pulse/pistol/m1911 + name = "\improper M1911-P" + desc = "A compact pulse core in a classic handgun frame for Nanotrasen officers. It's not the size of the gun, it's the size of the hole it puts through people." + icon_state = "m1911" + item_state = "gun" + cell_type = "/obj/item/stock_parts/cell/infinite" diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index a61654142c..eeb272f8d9 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -1,325 +1,325 @@ -/obj/item/gun/energy/ionrifle - name = "ion rifle" - desc = "A man-portable anti-armor weapon designed to disable mechanical threats at range." - icon_state = "ionrifle" - item_state = null //so the human update icon uses the icon_state instead. - can_flashlight = 1 - w_class = WEIGHT_CLASS_HUGE - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BACK - ammo_type = list(/obj/item/ammo_casing/energy/ion) - ammo_x_offset = 3 - flight_x_offset = 17 - flight_y_offset = 9 - -/obj/item/gun/energy/ionrifle/emp_act(severity) - return - -/obj/item/gun/energy/ionrifle/carbine - name = "ion carbine" - desc = "The MK.II Prototype Ion Projector is a lightweight carbine version of the larger ion rifle, built to be ergonomic and efficient." - icon_state = "ioncarbine" - w_class = WEIGHT_CLASS_NORMAL - slot_flags = ITEM_SLOT_BELT - pin = null - ammo_x_offset = 2 - flight_x_offset = 18 - flight_y_offset = 11 - -/obj/item/gun/energy/decloner - name = "biological demolecularisor" - desc = "A gun that discharges high amounts of controlled radiation to slowly break a target into component elements." - icon_state = "decloner" - ammo_type = list(/obj/item/ammo_casing/energy/declone) - pin = null - ammo_x_offset = 1 - -/obj/item/gun/energy/decloner/update_icon() - ..() - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - if(!QDELETED(cell) && (cell.charge > shot.e_cost)) - add_overlay("decloner_spin") - -/obj/item/gun/energy/floragun - name = "floral somatoray" - desc = "A tool that discharges controlled radiation which induces mutation in plant cells." - icon_state = "flora" - item_state = "gun" - ammo_type = list(/obj/item/ammo_casing/energy/flora/yield, /obj/item/ammo_casing/energy/flora/mut) - modifystate = 1 - ammo_x_offset = 1 - selfcharge = EGUN_SELFCHARGE - -/obj/item/gun/energy/meteorgun - name = "meteor gun" - desc = "For the love of god, make sure you're aiming this the right way!" - icon_state = "meteor_gun" - item_state = "c20r" - w_class = WEIGHT_CLASS_BULKY - ammo_type = list(/obj/item/ammo_casing/energy/meteor) - cell_type = "/obj/item/stock_parts/cell/potato" - clumsy_check = 0 //Admin spawn only, might as well let clowns use it. - selfcharge = EGUN_SELFCHARGE - -/obj/item/gun/energy/meteorgun/pen - name = "meteor pen" - desc = "The pen is mightier than the sword." - icon = 'icons/obj/bureaucracy.dmi' - icon_state = "pen" - item_state = "pen" - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - w_class = WEIGHT_CLASS_TINY - -/obj/item/gun/energy/mindflayer - name = "\improper Mind Flayer" - desc = "A prototype weapon recovered from the ruins of Research-Station Epsilon." - icon_state = "xray" - item_state = null - ammo_type = list(/obj/item/ammo_casing/energy/mindflayer) - ammo_x_offset = 2 - -/obj/item/gun/energy/kinetic_accelerator/crossbow - name = "mini energy crossbow" - desc = "A weapon favored by syndicate stealth specialists." - icon_state = "crossbow" - item_state = "crossbow" - w_class = WEIGHT_CLASS_SMALL - materials = list(MAT_METAL=2000) - suppressed = TRUE - ammo_type = list(/obj/item/ammo_casing/energy/bolt) - weapon_weight = WEAPON_LIGHT - obj_flags = 0 - overheat_time = 20 - holds_charge = TRUE - unique_frequency = TRUE - can_flashlight = 0 - max_mod_capacity = 0 - -/obj/item/gun/energy/kinetic_accelerator/crossbow/halloween - name = "candy corn crossbow" - desc = "A weapon favored by Syndicate trick-or-treaters." - icon_state = "crossbow_halloween" - item_state = "crossbow" - ammo_type = list(/obj/item/ammo_casing/energy/bolt/halloween) - -/obj/item/gun/energy/kinetic_accelerator/crossbow/large - name = "energy crossbow" - desc = "A reverse engineered weapon using syndicate technology." - icon_state = "crossbowlarge" - w_class = WEIGHT_CLASS_NORMAL - materials = list(MAT_METAL=4000) - suppressed = null - ammo_type = list(/obj/item/ammo_casing/energy/bolt/large) - pin = null - -/obj/item/gun/energy/plasmacutter - name = "plasma cutter" - desc = "A mining tool capable of expelling concentrated plasma bursts. You could use it to cut limbs off xenos! Or, you know, mine stuff." - icon_state = "plasmacutter" - item_state = "plasmacutter" - ammo_type = list(/obj/item/ammo_casing/energy/plasma) - flags_1 = CONDUCT_1 - attack_verb = list("attacked", "slashed", "cut", "sliced") - force = 12 - sharpness = IS_SHARP - can_charge = 0 - - heat = 3800 - usesound = list('sound/items/welder.ogg', 'sound/items/welder2.ogg') - tool_behaviour = TOOL_WELDER - toolspeed = 0.7 //plasmacutters can be used as welders, and are faster than standard welders - -/obj/item/gun/energy/plasmacutter/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 25, 105, 0, 'sound/weapons/plasma_cutter.ogg') - -/obj/item/gun/energy/plasmacutter/examine(mob/user) - . = ..() - if(cell) - . += "[src] is [round(cell.percent())]% charged." - -/obj/item/gun/energy/plasmacutter/attackby(obj/item/I, mob/user) - if(istype(I, /obj/item/stack/sheet/mineral/plasma)) - I.use(1) - cell.give(1000) - to_chat(user, "You insert [I] in [src], recharging it.") - else if(istype(I, /obj/item/stack/ore/plasma)) - I.use(1) - cell.give(500) - to_chat(user, "You insert [I] in [src], recharging it.") - else - ..() - -// Tool procs, in case plasma cutter is used as welder -/obj/item/gun/energy/plasmacutter/tool_use_check(mob/living/user, amount) - if(!QDELETED(cell) && (cell.charge >= amount * 100)) - return TRUE - - to_chat(user, "You need more charge to complete this task!") - return FALSE - -/obj/item/gun/energy/plasmacutter/use(amount) - return cell.use(amount * 100) - -/obj/item/gun/energy/plasmacutter/update_icon() - return - -/obj/item/gun/energy/plasmacutter/adv - name = "advanced plasma cutter" - icon_state = "adv_plasmacutter" - force = 15 - ammo_type = list(/obj/item/ammo_casing/energy/plasma/adv) - -/obj/item/gun/energy/wormhole_projector - name = "bluespace wormhole projector" - desc = "A projector that emits high density quantum-coupled bluespace beams." - ammo_type = list(/obj/item/ammo_casing/energy/wormhole, /obj/item/ammo_casing/energy/wormhole/orange) - item_state = null - icon_state = "wormhole_projector" - pin = null - var/obj/effect/portal/p_blue - var/obj/effect/portal/p_orange - var/atmos_link = FALSE - -/obj/item/gun/energy/wormhole_projector/update_icon() - icon_state = "[initial(icon_state)][select]" - item_state = icon_state - -/obj/item/gun/energy/wormhole_projector/update_ammo_types() - . = ..() - for(var/i in 1 to ammo_type.len) - var/obj/item/ammo_casing/energy/wormhole/W = ammo_type[i] - if(istype(W)) - W.gun = src - var/obj/item/projectile/beam/wormhole/WH = W.BB - if(istype(WH)) - WH.gun = src - -/obj/item/gun/energy/wormhole_projector/process_chamber() - ..() - select_fire() - -/obj/item/gun/energy/wormhole_projector/proc/on_portal_destroy(obj/effect/portal/P) - if(P == p_blue) - p_blue = null - else if(P == p_orange) - p_orange = null - -/obj/item/gun/energy/wormhole_projector/proc/has_blue_portal() - if(istype(p_blue) && !QDELETED(p_blue)) - return TRUE - return FALSE - -/obj/item/gun/energy/wormhole_projector/proc/has_orange_portal() - if(istype(p_orange) && !QDELETED(p_orange)) - return TRUE - return FALSE - -/obj/item/gun/energy/wormhole_projector/proc/crosslink() - if(!has_blue_portal() && !has_orange_portal()) - return - if(!has_blue_portal() && has_orange_portal()) - p_orange.link_portal(null) - return - if(!has_orange_portal() && has_blue_portal()) - p_blue.link_portal(null) - return - p_orange.link_portal(p_blue) - p_blue.link_portal(p_orange) - -/obj/item/gun/energy/wormhole_projector/proc/create_portal(obj/item/projectile/beam/wormhole/W, turf/target) - var/obj/effect/portal/P = new /obj/effect/portal(target, src, 300, null, FALSE, null, atmos_link) - if(istype(W, /obj/item/projectile/beam/wormhole/orange)) - qdel(p_orange) - p_orange = P - P.icon_state = "portal1" - else - qdel(p_blue) - p_blue = P - crosslink() - -/* 3d printer 'pseudo guns' for borgs */ - -/obj/item/gun/energy/printer - name = "cyborg lmg" - desc = "A LMG that fires 3D-printed flechettes. They are slowly resupplied using the cyborg's internal power source." - icon_state = "l6closed0" - icon = 'icons/obj/guns/projectile.dmi' - cell_type = "/obj/item/stock_parts/cell/secborg" - ammo_type = list(/obj/item/ammo_casing/energy/c3dbullet) - can_charge = 0 - use_cyborg_cell = 1 - -/obj/item/gun/energy/printer/update_icon() - return - -/obj/item/gun/energy/printer/emp_act() - return - -/obj/item/gun/energy/temperature - name = "temperature gun" - icon_state = "freezegun" - desc = "A gun that changes temperatures." - ammo_type = list(/obj/item/ammo_casing/energy/temp, /obj/item/ammo_casing/energy/temp/hot) - cell_type = "/obj/item/stock_parts/cell/high" - pin = null - -/obj/item/gun/energy/temperature/security - name = "security temperature gun" - desc = "A weapon that can only be used to its full potential by the truly robust." - pin = /obj/item/firing_pin - -/obj/item/gun/energy/laser/instakill - name = "instakill rifle" - icon_state = "instagib" - item_state = "instagib" - desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit." - ammo_type = list(/obj/item/ammo_casing/energy/instakill) - force = 60 - -/obj/item/gun/energy/laser/instakill/red - desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit. This one has a red design." - icon_state = "instagibred" - item_state = "instagibred" - ammo_type = list(/obj/item/ammo_casing/energy/instakill/red) - -/obj/item/gun/energy/laser/instakill/blue - desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit. This one has a blue design." - icon_state = "instagibblue" - item_state = "instagibblue" - ammo_type = list(/obj/item/ammo_casing/energy/instakill/blue) - -/obj/item/gun/energy/laser/instakill/emp_act() //implying you could stop the instagib - return - -/obj/item/gun/energy/gravity_gun - name = "one-point bluespace-gravitational manipulator" - desc = "An experimental, multi-mode device that fires bolts of Zero-Point Energy, causing local distortions in gravity." - ammo_type = list(/obj/item/ammo_casing/energy/gravity/repulse, /obj/item/ammo_casing/energy/gravity/attract, /obj/item/ammo_casing/energy/gravity/chaos) - item_state = "gravity_gun" - icon_state = "gravity_gun" - pin = null - var/power = 4 - -/obj/item/gun/energy/gravity_gun/security - pin = /obj/item/firing_pin - -//Emitter Gun - -/obj/item/gun/energy/emitter - name = "Emitter Carbine" - desc = "A small emitter fitted into a handgun case, do to size constraints and safety it can only shoot about ten times when fully charged." - icon_state = "emitter_carbine" - force = 12 - w_class = WEIGHT_CLASS_SMALL - cell_type = /obj/item/stock_parts/cell/super - ammo_type = list(/obj/item/ammo_casing/energy/emitter) - -/obj/item/gun/energy/emitter/update_icon() - ..() - var/obj/item/ammo_casing/energy/shot = ammo_type[select] - if(!QDELETED(cell) && (cell.charge > shot.e_cost)) - add_overlay("emitter_carbine_empty") - else - add_overlay("emitter_carbine") +/obj/item/gun/energy/ionrifle + name = "ion rifle" + desc = "A man-portable anti-armor weapon designed to disable mechanical threats at range." + icon_state = "ionrifle" + item_state = null //so the human update icon uses the icon_state instead. + can_flashlight = 1 + w_class = WEIGHT_CLASS_HUGE + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BACK + ammo_type = list(/obj/item/ammo_casing/energy/ion) + ammo_x_offset = 3 + flight_x_offset = 17 + flight_y_offset = 9 + +/obj/item/gun/energy/ionrifle/emp_act(severity) + return + +/obj/item/gun/energy/ionrifle/carbine + name = "ion carbine" + desc = "The MK.II Prototype Ion Projector is a lightweight carbine version of the larger ion rifle, built to be ergonomic and efficient." + icon_state = "ioncarbine" + w_class = WEIGHT_CLASS_NORMAL + slot_flags = ITEM_SLOT_BELT + pin = null + ammo_x_offset = 2 + flight_x_offset = 18 + flight_y_offset = 11 + +/obj/item/gun/energy/decloner + name = "biological demolecularisor" + desc = "A gun that discharges high amounts of controlled radiation to slowly break a target into component elements." + icon_state = "decloner" + ammo_type = list(/obj/item/ammo_casing/energy/declone) + pin = null + ammo_x_offset = 1 + +/obj/item/gun/energy/decloner/update_icon() + ..() + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + if(!QDELETED(cell) && (cell.charge > shot.e_cost)) + add_overlay("decloner_spin") + +/obj/item/gun/energy/floragun + name = "floral somatoray" + desc = "A tool that discharges controlled radiation which induces mutation in plant cells." + icon_state = "flora" + item_state = "gun" + ammo_type = list(/obj/item/ammo_casing/energy/flora/yield, /obj/item/ammo_casing/energy/flora/mut) + modifystate = 1 + ammo_x_offset = 1 + selfcharge = EGUN_SELFCHARGE + +/obj/item/gun/energy/meteorgun + name = "meteor gun" + desc = "For the love of god, make sure you're aiming this the right way!" + icon_state = "meteor_gun" + item_state = "c20r" + w_class = WEIGHT_CLASS_BULKY + ammo_type = list(/obj/item/ammo_casing/energy/meteor) + cell_type = "/obj/item/stock_parts/cell/potato" + clumsy_check = 0 //Admin spawn only, might as well let clowns use it. + selfcharge = EGUN_SELFCHARGE + +/obj/item/gun/energy/meteorgun/pen + name = "meteor pen" + desc = "The pen is mightier than the sword." + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "pen" + item_state = "pen" + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + w_class = WEIGHT_CLASS_TINY + +/obj/item/gun/energy/mindflayer + name = "\improper Mind Flayer" + desc = "A prototype weapon recovered from the ruins of Research-Station Epsilon." + icon_state = "xray" + item_state = null + ammo_type = list(/obj/item/ammo_casing/energy/mindflayer) + ammo_x_offset = 2 + +/obj/item/gun/energy/kinetic_accelerator/crossbow + name = "mini energy crossbow" + desc = "A weapon favored by syndicate stealth specialists." + icon_state = "crossbow" + item_state = "crossbow" + w_class = WEIGHT_CLASS_SMALL + materials = list(MAT_METAL=2000) + suppressed = TRUE + ammo_type = list(/obj/item/ammo_casing/energy/bolt) + weapon_weight = WEAPON_LIGHT + obj_flags = 0 + overheat_time = 20 + holds_charge = TRUE + unique_frequency = TRUE + can_flashlight = 0 + max_mod_capacity = 0 + +/obj/item/gun/energy/kinetic_accelerator/crossbow/halloween + name = "candy corn crossbow" + desc = "A weapon favored by Syndicate trick-or-treaters." + icon_state = "crossbow_halloween" + item_state = "crossbow" + ammo_type = list(/obj/item/ammo_casing/energy/bolt/halloween) + +/obj/item/gun/energy/kinetic_accelerator/crossbow/large + name = "energy crossbow" + desc = "A reverse engineered weapon using syndicate technology." + icon_state = "crossbowlarge" + w_class = WEIGHT_CLASS_NORMAL + materials = list(MAT_METAL=4000) + suppressed = null + ammo_type = list(/obj/item/ammo_casing/energy/bolt/large) + pin = null + +/obj/item/gun/energy/plasmacutter + name = "plasma cutter" + desc = "A mining tool capable of expelling concentrated plasma bursts. You could use it to cut limbs off xenos! Or, you know, mine stuff." + icon_state = "plasmacutter" + item_state = "plasmacutter" + ammo_type = list(/obj/item/ammo_casing/energy/plasma) + flags_1 = CONDUCT_1 + attack_verb = list("attacked", "slashed", "cut", "sliced") + force = 12 + sharpness = IS_SHARP + can_charge = 0 + + heat = 3800 + usesound = list('sound/items/welder.ogg', 'sound/items/welder2.ogg') + tool_behaviour = TOOL_WELDER + toolspeed = 0.7 //plasmacutters can be used as welders, and are faster than standard welders + +/obj/item/gun/energy/plasmacutter/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 25, 105, 0, 'sound/weapons/plasma_cutter.ogg') + +/obj/item/gun/energy/plasmacutter/examine(mob/user) + . = ..() + if(cell) + . += "[src] is [round(cell.percent())]% charged." + +/obj/item/gun/energy/plasmacutter/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/stack/sheet/mineral/plasma)) + I.use(1) + cell.give(1000) + to_chat(user, "You insert [I] in [src], recharging it.") + else if(istype(I, /obj/item/stack/ore/plasma)) + I.use(1) + cell.give(500) + to_chat(user, "You insert [I] in [src], recharging it.") + else + ..() + +// Tool procs, in case plasma cutter is used as welder +/obj/item/gun/energy/plasmacutter/tool_use_check(mob/living/user, amount) + if(!QDELETED(cell) && (cell.charge >= amount * 100)) + return TRUE + + to_chat(user, "You need more charge to complete this task!") + return FALSE + +/obj/item/gun/energy/plasmacutter/use(amount) + return cell.use(amount * 100) + +/obj/item/gun/energy/plasmacutter/update_icon() + return + +/obj/item/gun/energy/plasmacutter/adv + name = "advanced plasma cutter" + icon_state = "adv_plasmacutter" + force = 15 + ammo_type = list(/obj/item/ammo_casing/energy/plasma/adv) + +/obj/item/gun/energy/wormhole_projector + name = "bluespace wormhole projector" + desc = "A projector that emits high density quantum-coupled bluespace beams." + ammo_type = list(/obj/item/ammo_casing/energy/wormhole, /obj/item/ammo_casing/energy/wormhole/orange) + item_state = null + icon_state = "wormhole_projector" + pin = null + var/obj/effect/portal/p_blue + var/obj/effect/portal/p_orange + var/atmos_link = FALSE + +/obj/item/gun/energy/wormhole_projector/update_icon() + icon_state = "[initial(icon_state)][select]" + item_state = icon_state + +/obj/item/gun/energy/wormhole_projector/update_ammo_types() + . = ..() + for(var/i in 1 to ammo_type.len) + var/obj/item/ammo_casing/energy/wormhole/W = ammo_type[i] + if(istype(W)) + W.gun = src + var/obj/item/projectile/beam/wormhole/WH = W.BB + if(istype(WH)) + WH.gun = src + +/obj/item/gun/energy/wormhole_projector/process_chamber() + ..() + select_fire() + +/obj/item/gun/energy/wormhole_projector/proc/on_portal_destroy(obj/effect/portal/P) + if(P == p_blue) + p_blue = null + else if(P == p_orange) + p_orange = null + +/obj/item/gun/energy/wormhole_projector/proc/has_blue_portal() + if(istype(p_blue) && !QDELETED(p_blue)) + return TRUE + return FALSE + +/obj/item/gun/energy/wormhole_projector/proc/has_orange_portal() + if(istype(p_orange) && !QDELETED(p_orange)) + return TRUE + return FALSE + +/obj/item/gun/energy/wormhole_projector/proc/crosslink() + if(!has_blue_portal() && !has_orange_portal()) + return + if(!has_blue_portal() && has_orange_portal()) + p_orange.link_portal(null) + return + if(!has_orange_portal() && has_blue_portal()) + p_blue.link_portal(null) + return + p_orange.link_portal(p_blue) + p_blue.link_portal(p_orange) + +/obj/item/gun/energy/wormhole_projector/proc/create_portal(obj/item/projectile/beam/wormhole/W, turf/target) + var/obj/effect/portal/P = new /obj/effect/portal(target, src, 300, null, FALSE, null, atmos_link) + if(istype(W, /obj/item/projectile/beam/wormhole/orange)) + qdel(p_orange) + p_orange = P + P.icon_state = "portal1" + else + qdel(p_blue) + p_blue = P + crosslink() + +/* 3d printer 'pseudo guns' for borgs */ + +/obj/item/gun/energy/printer + name = "cyborg lmg" + desc = "A LMG that fires 3D-printed flechettes. They are slowly resupplied using the cyborg's internal power source." + icon_state = "l6closed0" + icon = 'icons/obj/guns/projectile.dmi' + cell_type = "/obj/item/stock_parts/cell/secborg" + ammo_type = list(/obj/item/ammo_casing/energy/c3dbullet) + can_charge = 0 + use_cyborg_cell = 1 + +/obj/item/gun/energy/printer/update_icon() + return + +/obj/item/gun/energy/printer/emp_act() + return + +/obj/item/gun/energy/temperature + name = "temperature gun" + icon_state = "freezegun" + desc = "A gun that changes temperatures." + ammo_type = list(/obj/item/ammo_casing/energy/temp, /obj/item/ammo_casing/energy/temp/hot) + cell_type = "/obj/item/stock_parts/cell/high" + pin = null + +/obj/item/gun/energy/temperature/security + name = "security temperature gun" + desc = "A weapon that can only be used to its full potential by the truly robust." + pin = /obj/item/firing_pin + +/obj/item/gun/energy/laser/instakill + name = "instakill rifle" + icon_state = "instagib" + item_state = "instagib" + desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit." + ammo_type = list(/obj/item/ammo_casing/energy/instakill) + force = 60 + +/obj/item/gun/energy/laser/instakill/red + desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit. This one has a red design." + icon_state = "instagibred" + item_state = "instagibred" + ammo_type = list(/obj/item/ammo_casing/energy/instakill/red) + +/obj/item/gun/energy/laser/instakill/blue + desc = "A specialized ASMD laser-rifle, capable of flat-out disintegrating most targets in a single hit. This one has a blue design." + icon_state = "instagibblue" + item_state = "instagibblue" + ammo_type = list(/obj/item/ammo_casing/energy/instakill/blue) + +/obj/item/gun/energy/laser/instakill/emp_act() //implying you could stop the instagib + return + +/obj/item/gun/energy/gravity_gun + name = "one-point bluespace-gravitational manipulator" + desc = "An experimental, multi-mode device that fires bolts of Zero-Point Energy, causing local distortions in gravity." + ammo_type = list(/obj/item/ammo_casing/energy/gravity/repulse, /obj/item/ammo_casing/energy/gravity/attract, /obj/item/ammo_casing/energy/gravity/chaos) + item_state = "gravity_gun" + icon_state = "gravity_gun" + pin = null + var/power = 4 + +/obj/item/gun/energy/gravity_gun/security + pin = /obj/item/firing_pin + +//Emitter Gun + +/obj/item/gun/energy/emitter + name = "Emitter Carbine" + desc = "A small emitter fitted into a handgun case, do to size constraints and safety it can only shoot about ten times when fully charged." + icon_state = "emitter_carbine" + force = 12 + w_class = WEIGHT_CLASS_SMALL + cell_type = /obj/item/stock_parts/cell/super + ammo_type = list(/obj/item/ammo_casing/energy/emitter) + +/obj/item/gun/energy/emitter/update_icon() + ..() + var/obj/item/ammo_casing/energy/shot = ammo_type[select] + if(!QDELETED(cell) && (cell.charge > shot.e_cost)) + add_overlay("emitter_carbine_empty") + else + add_overlay("emitter_carbine") diff --git a/code/modules/projectiles/guns/energy/stun.dm b/code/modules/projectiles/guns/energy/stun.dm index aa00831e97..55d11c52d1 100644 --- a/code/modules/projectiles/guns/energy/stun.dm +++ b/code/modules/projectiles/guns/energy/stun.dm @@ -1,64 +1,64 @@ -/obj/item/gun/energy/taser - name = "taser gun" - desc = "A low-capacity, energy-based stun gun used by security teams to subdue targets at range." - icon_state = "taser" - item_state = null //so the human update icon uses the icon_state instead. - ammo_type = list(/obj/item/ammo_casing/energy/electrode) - ammo_x_offset = 3 - -/obj/item/gun/energy/tesla_revolver - name = "tesla gun" - desc = "An experimental gun based on an experimental engine, it's about as likely to kill its operator as it is the target." - icon_state = "tesla" - item_state = "tesla" - ammo_type = list(/obj/item/ammo_casing/energy/tesla_revolver) - can_flashlight = 0 - pin = null - shaded_charge = 1 - -/obj/item/gun/energy/e_gun/advtaser - name = "hybrid taser" - desc = "A dual-mode taser designed to fire both short-range high-power electrodes and long-range disabler beams." - icon_state = "advtaser" - ammo_type = list(/obj/item/ammo_casing/energy/disabler, /obj/item/ammo_casing/energy/electrode) - ammo_x_offset = 2 - -/obj/item/gun/energy/e_gun/advtaser/cyborg - name = "cyborg taser" - desc = "An integrated hybrid taser that draws directly from a cyborg's power cell. The one contains a limiter to prevent the cyborg's power cell from overheating." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "taser" - can_flashlight = FALSE - can_charge = FALSE - selfcharge = EGUN_SELFCHARGE_BORG - cell_type = /obj/item/stock_parts/cell/secborg - charge_delay = 5 - -/obj/item/gun/energy/e_gun/advtaser/cyborg/mean - desc = "An integrated hybrid taser that draws directly from a cyborg's power cell." - use_cyborg_cell = TRUE - selfcharge = EGUN_NO_SELFCHARGE - -/obj/item/gun/energy/disabler - name = "disabler" - desc = "A self-defense weapon that exhausts organic targets, weakening them until they collapse." - icon_state = "disabler" - item_state = null - ammo_type = list(/obj/item/ammo_casing/energy/disabler) - ammo_x_offset = 3 - -/obj/item/gun/energy/disabler/cyborg - name = "cyborg disabler" - desc = "An integrated disabler that draws from a cyborg's power cell. This one contains a limiter to prevent the cyborg's power cell from overheating." - icon = 'icons/obj/items_cyborg.dmi' - icon_state = "taser" - can_charge = FALSE - ammo_type = list(/obj/item/ammo_casing/energy/disabler/secborg) - selfcharge = EGUN_SELFCHARGE_BORG - cell_type = /obj/item/stock_parts/cell/secborg - charge_delay = 5 - -/obj/item/gun/energy/disabler/cyborg/mean - desc = "An integrated disabler that draws from a cyborg's power cell." - use_cyborg_cell = TRUE - selfcharge = EGUN_NO_SELFCHARGE +/obj/item/gun/energy/taser + name = "taser gun" + desc = "A low-capacity, energy-based stun gun used by security teams to subdue targets at range." + icon_state = "taser" + item_state = null //so the human update icon uses the icon_state instead. + ammo_type = list(/obj/item/ammo_casing/energy/electrode) + ammo_x_offset = 3 + +/obj/item/gun/energy/tesla_revolver + name = "tesla gun" + desc = "An experimental gun based on an experimental engine, it's about as likely to kill its operator as it is the target." + icon_state = "tesla" + item_state = "tesla" + ammo_type = list(/obj/item/ammo_casing/energy/tesla_revolver) + can_flashlight = 0 + pin = null + shaded_charge = 1 + +/obj/item/gun/energy/e_gun/advtaser + name = "hybrid taser" + desc = "A dual-mode taser designed to fire both short-range high-power electrodes and long-range disabler beams." + icon_state = "advtaser" + ammo_type = list(/obj/item/ammo_casing/energy/disabler, /obj/item/ammo_casing/energy/electrode) + ammo_x_offset = 2 + +/obj/item/gun/energy/e_gun/advtaser/cyborg + name = "cyborg taser" + desc = "An integrated hybrid taser that draws directly from a cyborg's power cell. The one contains a limiter to prevent the cyborg's power cell from overheating." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "taser" + can_flashlight = FALSE + can_charge = FALSE + selfcharge = EGUN_SELFCHARGE_BORG + cell_type = /obj/item/stock_parts/cell/secborg + charge_delay = 5 + +/obj/item/gun/energy/e_gun/advtaser/cyborg/mean + desc = "An integrated hybrid taser that draws directly from a cyborg's power cell." + use_cyborg_cell = TRUE + selfcharge = EGUN_NO_SELFCHARGE + +/obj/item/gun/energy/disabler + name = "disabler" + desc = "A self-defense weapon that exhausts organic targets, weakening them until they collapse." + icon_state = "disabler" + item_state = null + ammo_type = list(/obj/item/ammo_casing/energy/disabler) + ammo_x_offset = 3 + +/obj/item/gun/energy/disabler/cyborg + name = "cyborg disabler" + desc = "An integrated disabler that draws from a cyborg's power cell. This one contains a limiter to prevent the cyborg's power cell from overheating." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "taser" + can_charge = FALSE + ammo_type = list(/obj/item/ammo_casing/energy/disabler/secborg) + selfcharge = EGUN_SELFCHARGE_BORG + cell_type = /obj/item/stock_parts/cell/secborg + charge_delay = 5 + +/obj/item/gun/energy/disabler/cyborg/mean + desc = "An integrated disabler that draws from a cyborg's power cell." + use_cyborg_cell = TRUE + selfcharge = EGUN_NO_SELFCHARGE diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm index 55f21a5a2b..f724f982d0 100644 --- a/code/modules/projectiles/guns/magic.dm +++ b/code/modules/projectiles/guns/magic.dm @@ -1,91 +1,91 @@ -/obj/item/gun/magic - name = "staff of nothing" - desc = "This staff is boring to watch because even though it came first you've seen everything it can do in other staves for years." - icon = 'icons/obj/guns/magic.dmi' - icon_state = "staffofnothing" - item_state = "staff" - lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' - fire_sound = 'sound/weapons/emitter.ogg' - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_HUGE - var/checks_antimagic = FALSE - var/max_charges = 6 - var/charges = 0 - var/recharge_rate = 4 - var/charge_tick = 0 - var/can_charge = 1 - var/ammo_type - var/no_den_usage - clumsy_check = 0 - trigger_guard = TRIGGER_GUARD_ALLOW_ALL // Has no trigger at all, uses magic instead - pin = /obj/item/firing_pin/magic - - lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' //not really a gun and some toys use these inhands - righthand_file = 'icons/mob/inhands/items_righthand.dmi' - -/obj/item/gun/magic/afterattack(atom/target, mob/living/user, flag) - if(no_den_usage) - var/area/A = get_area(user) - if(istype(A, /area/wizard_station)) - to_chat(user, "You know better than to violate the security of The Den, best wait until you leave to use [src].") - return - else - no_den_usage = 0 - if(checks_antimagic && user.anti_magic_check(TRUE, FALSE, FALSE, 0, TRUE)) - to_chat(user, "Something is interfering with [src].") - return - . = ..() - -/obj/item/gun/magic/can_shoot() - return charges - -/obj/item/gun/magic/recharge_newshot() - if (charges && chambered && !chambered.BB) - chambered.newshot() - -/obj/item/gun/magic/process_chamber() - if(chambered && !chambered.BB) //if BB is null, i.e the shot has been fired... - charges--//... drain a charge - recharge_newshot() - -/obj/item/gun/magic/Initialize() - . = ..() - charges = max_charges - chambered = new ammo_type(src) - if(can_charge) - START_PROCESSING(SSobj, src) - - -/obj/item/gun/magic/Destroy() - if(can_charge) - STOP_PROCESSING(SSobj, src) - return ..() - - -/obj/item/gun/magic/process() - charge_tick++ - if(charge_tick < recharge_rate || charges >= max_charges) - return 0 - charge_tick = 0 - charges++ - if(charges == 1) - recharge_newshot() - return 1 - -/obj/item/gun/magic/update_icon() - return - -/obj/item/gun/magic/shoot_with_empty_chamber(mob/living/user as mob|obj) - to_chat(user, "The [name] whizzles quietly.") - -/obj/item/gun/magic/suicide_act(mob/user) - user.visible_message("[user] is twisting [src] above [user.p_their()] head, releasing a magical blast! It looks like [user.p_theyre()] trying to commit suicide!") - playsound(loc, fire_sound, 50, 1, -1) - return (FIRELOSS) - -/obj/item/gun/magic/vv_edit_var(var_name, var_value) - . = ..() - switch (var_name) - if ("charges") - recharge_newshot() +/obj/item/gun/magic + name = "staff of nothing" + desc = "This staff is boring to watch because even though it came first you've seen everything it can do in other staves for years." + icon = 'icons/obj/guns/magic.dmi' + icon_state = "staffofnothing" + item_state = "staff" + lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi' + fire_sound = 'sound/weapons/emitter.ogg' + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_HUGE + var/checks_antimagic = FALSE + var/max_charges = 6 + var/charges = 0 + var/recharge_rate = 4 + var/charge_tick = 0 + var/can_charge = 1 + var/ammo_type + var/no_den_usage + clumsy_check = 0 + trigger_guard = TRIGGER_GUARD_ALLOW_ALL // Has no trigger at all, uses magic instead + pin = /obj/item/firing_pin/magic + + lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' //not really a gun and some toys use these inhands + righthand_file = 'icons/mob/inhands/items_righthand.dmi' + +/obj/item/gun/magic/afterattack(atom/target, mob/living/user, flag) + if(no_den_usage) + var/area/A = get_area(user) + if(istype(A, /area/wizard_station)) + to_chat(user, "You know better than to violate the security of The Den, best wait until you leave to use [src].") + return + else + no_den_usage = 0 + if(checks_antimagic && user.anti_magic_check(TRUE, FALSE, FALSE, 0, TRUE)) + to_chat(user, "Something is interfering with [src].") + return + . = ..() + +/obj/item/gun/magic/can_shoot() + return charges + +/obj/item/gun/magic/recharge_newshot() + if (charges && chambered && !chambered.BB) + chambered.newshot() + +/obj/item/gun/magic/process_chamber() + if(chambered && !chambered.BB) //if BB is null, i.e the shot has been fired... + charges--//... drain a charge + recharge_newshot() + +/obj/item/gun/magic/Initialize() + . = ..() + charges = max_charges + chambered = new ammo_type(src) + if(can_charge) + START_PROCESSING(SSobj, src) + + +/obj/item/gun/magic/Destroy() + if(can_charge) + STOP_PROCESSING(SSobj, src) + return ..() + + +/obj/item/gun/magic/process() + charge_tick++ + if(charge_tick < recharge_rate || charges >= max_charges) + return 0 + charge_tick = 0 + charges++ + if(charges == 1) + recharge_newshot() + return 1 + +/obj/item/gun/magic/update_icon() + return + +/obj/item/gun/magic/shoot_with_empty_chamber(mob/living/user as mob|obj) + to_chat(user, "The [name] whizzles quietly.") + +/obj/item/gun/magic/suicide_act(mob/user) + user.visible_message("[user] is twisting [src] above [user.p_their()] head, releasing a magical blast! It looks like [user.p_theyre()] trying to commit suicide!") + playsound(loc, fire_sound, 50, 1, -1) + return (FIRELOSS) + +/obj/item/gun/magic/vv_edit_var(var_name, var_value) + . = ..() + switch (var_name) + if ("charges") + recharge_newshot() diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm index 95809f5044..52236ab695 100644 --- a/code/modules/projectiles/guns/magic/wand.dm +++ b/code/modules/projectiles/guns/magic/wand.dm @@ -1,177 +1,177 @@ -/obj/item/gun/magic/wand - name = "wand of nothing" - desc = "It's not just a stick, it's a MAGIC stick!" - ammo_type = /obj/item/ammo_casing/magic - icon_state = "nothingwand" - item_state = "wand" - w_class = WEIGHT_CLASS_SMALL - can_charge = 0 - max_charges = 100 //100, 50, 50, 34 (max charge distribution by 25%ths) - var/variable_charges = 1 - -/obj/item/gun/magic/wand/Initialize() - if(prob(75) && variable_charges) //25% chance of listed max charges, 50% chance of 1/2 max charges, 25% chance of 1/3 max charges - if(prob(33)) - max_charges = CEILING(max_charges / 3, 1) - else - max_charges = CEILING(max_charges / 2, 1) - return ..() - -/obj/item/gun/magic/wand/examine(mob/user) - . = ..() - . += "Has [charges] charge\s remaining." - -/obj/item/gun/magic/wand/update_icon() - icon_state = "[initial(icon_state)][charges ? "" : "-drained"]" - -/obj/item/gun/magic/wand/attack(atom/target, mob/living/user) - if(target == user) - return - ..() - -/obj/item/gun/magic/wand/afterattack(atom/target, mob/living/user) - if(!charges) - shoot_with_empty_chamber(user) - return - if(target == user) - if(no_den_usage) - var/area/A = get_area(user) - if(istype(A, /area/wizard_station)) - to_chat(user, "You know better than to violate the security of The Den, best wait until you leave to use [src].") - return - else - no_den_usage = 0 - zap_self(user) - else - . = ..() - update_icon() - - -/obj/item/gun/magic/wand/proc/zap_self(mob/living/user) - user.visible_message("[user] zaps [user.p_them()]self with [src].") - playsound(user, fire_sound, 50, 1) - user.log_message("zapped [user.p_them()]self with a [src]", LOG_ATTACK) - - -///////////////////////////////////// -//WAND OF DEATH -///////////////////////////////////// - -/obj/item/gun/magic/wand/death - name = "wand of death" - desc = "This deadly wand overwhelms the victim's body with pure energy, slaying them without fail." - fire_sound = 'sound/magic/wandodeath.ogg' - ammo_type = /obj/item/ammo_casing/magic/death - icon_state = "deathwand" - max_charges = 3 //3, 2, 2, 1 - -/obj/item/gun/magic/wand/death/zap_self(mob/living/user) - ..() - to_chat(user, "You irradiate yourself with pure energy! \ - [pick("Do not pass go. Do not collect 200 zorkmids.","You feel more confident in your spell casting skills.","You Die...","Do you want your possessions identified?")]\ - ") - user.adjustOxyLoss(500) - charges-- - - -///////////////////////////////////// -//WAND OF HEALING -///////////////////////////////////// - -/obj/item/gun/magic/wand/resurrection - name = "wand of healing" - desc = "This wand uses healing magics to heal and revive. They are rarely utilized within the Wizard Federation for some reason." - ammo_type = /obj/item/ammo_casing/magic/heal - fire_sound = 'sound/magic/staff_healing.ogg' - icon_state = "revivewand" - max_charges = 10 //10, 5, 5, 4 - -/obj/item/gun/magic/wand/resurrection/zap_self(mob/living/user) - ..() - charges-- - if(user.anti_magic_check()) - user.visible_message("[src] has no effect on [user]!") - return - user.revive(full_heal = 1) - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.regenerate_limbs() - C.regenerate_organs() - to_chat(user, "You feel great!") - -/obj/item/gun/magic/wand/resurrection/debug //for testing - name = "debug wand of healing" - max_charges = 500 - -///////////////////////////////////// -//WAND OF POLYMORPH -///////////////////////////////////// - -/obj/item/gun/magic/wand/polymorph - name = "wand of polymorph" - desc = "This wand is attuned to chaos and will radically alter the victim's form." - ammo_type = /obj/item/ammo_casing/magic/change - icon_state = "polywand" - fire_sound = 'sound/magic/staff_change.ogg' - max_charges = 10 //10, 5, 5, 4 - -/obj/item/gun/magic/wand/polymorph/zap_self(mob/living/user) - ..() //because the user mob ceases to exists by the time wabbajack fully resolves - wabbajack(user) - charges-- - -///////////////////////////////////// -//WAND OF TELEPORTATION -///////////////////////////////////// - -/obj/item/gun/magic/wand/teleport - name = "wand of teleportation" - desc = "This wand will wrench targets through space and time to move them somewhere else." - ammo_type = /obj/item/ammo_casing/magic/teleport - fire_sound = 'sound/magic/wand_teleport.ogg' - icon_state = "telewand" - max_charges = 10 //10, 5, 5, 4 - no_den_usage = 1 - -/obj/item/gun/magic/wand/teleport/zap_self(mob/living/user) - if(do_teleport(user, user, 10, channel = TELEPORT_CHANNEL_MAGIC)) - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(3, user.loc) - smoke.start() - charges-- - ..() - -///////////////////////////////////// -//WAND OF DOOR CREATION -///////////////////////////////////// - -/obj/item/gun/magic/wand/door - name = "wand of door creation" - desc = "This particular wand can create doors in any wall for the unscrupulous wizard who shuns teleportation magics." - ammo_type = /obj/item/ammo_casing/magic/door - icon_state = "doorwand" - fire_sound = 'sound/magic/staff_door.ogg' - max_charges = 20 //20, 10, 10, 7 - no_den_usage = 1 - -/obj/item/gun/magic/wand/door/zap_self(mob/living/user) - to_chat(user, "You feel vaguely more open with your feelings.") - charges-- - ..() - -///////////////////////////////////// -//WAND OF FIREBALL -///////////////////////////////////// - -/obj/item/gun/magic/wand/fireball - name = "wand of fireball" - desc = "This wand shoots scorching balls of fire that explode into destructive flames." - fire_sound = 'sound/magic/fireball.ogg' - ammo_type = /obj/item/ammo_casing/magic/fireball - icon_state = "firewand" - max_charges = 8 //8, 4, 4, 3 - -/obj/item/gun/magic/wand/fireball/zap_self(mob/living/user) - ..() - explosion(user.loc, -1, 0, 2, 3, 0, flame_range = 2) - charges-- +/obj/item/gun/magic/wand + name = "wand of nothing" + desc = "It's not just a stick, it's a MAGIC stick!" + ammo_type = /obj/item/ammo_casing/magic + icon_state = "nothingwand" + item_state = "wand" + w_class = WEIGHT_CLASS_SMALL + can_charge = 0 + max_charges = 100 //100, 50, 50, 34 (max charge distribution by 25%ths) + var/variable_charges = 1 + +/obj/item/gun/magic/wand/Initialize() + if(prob(75) && variable_charges) //25% chance of listed max charges, 50% chance of 1/2 max charges, 25% chance of 1/3 max charges + if(prob(33)) + max_charges = CEILING(max_charges / 3, 1) + else + max_charges = CEILING(max_charges / 2, 1) + return ..() + +/obj/item/gun/magic/wand/examine(mob/user) + . = ..() + . += "Has [charges] charge\s remaining." + +/obj/item/gun/magic/wand/update_icon() + icon_state = "[initial(icon_state)][charges ? "" : "-drained"]" + +/obj/item/gun/magic/wand/attack(atom/target, mob/living/user) + if(target == user) + return + ..() + +/obj/item/gun/magic/wand/afterattack(atom/target, mob/living/user) + if(!charges) + shoot_with_empty_chamber(user) + return + if(target == user) + if(no_den_usage) + var/area/A = get_area(user) + if(istype(A, /area/wizard_station)) + to_chat(user, "You know better than to violate the security of The Den, best wait until you leave to use [src].") + return + else + no_den_usage = 0 + zap_self(user) + else + . = ..() + update_icon() + + +/obj/item/gun/magic/wand/proc/zap_self(mob/living/user) + user.visible_message("[user] zaps [user.p_them()]self with [src].") + playsound(user, fire_sound, 50, 1) + user.log_message("zapped [user.p_them()]self with a [src]", LOG_ATTACK) + + +///////////////////////////////////// +//WAND OF DEATH +///////////////////////////////////// + +/obj/item/gun/magic/wand/death + name = "wand of death" + desc = "This deadly wand overwhelms the victim's body with pure energy, slaying them without fail." + fire_sound = 'sound/magic/wandodeath.ogg' + ammo_type = /obj/item/ammo_casing/magic/death + icon_state = "deathwand" + max_charges = 3 //3, 2, 2, 1 + +/obj/item/gun/magic/wand/death/zap_self(mob/living/user) + ..() + to_chat(user, "You irradiate yourself with pure energy! \ + [pick("Do not pass go. Do not collect 200 zorkmids.","You feel more confident in your spell casting skills.","You Die...","Do you want your possessions identified?")]\ + ") + user.adjustOxyLoss(500) + charges-- + + +///////////////////////////////////// +//WAND OF HEALING +///////////////////////////////////// + +/obj/item/gun/magic/wand/resurrection + name = "wand of healing" + desc = "This wand uses healing magics to heal and revive. They are rarely utilized within the Wizard Federation for some reason." + ammo_type = /obj/item/ammo_casing/magic/heal + fire_sound = 'sound/magic/staff_healing.ogg' + icon_state = "revivewand" + max_charges = 10 //10, 5, 5, 4 + +/obj/item/gun/magic/wand/resurrection/zap_self(mob/living/user) + ..() + charges-- + if(user.anti_magic_check()) + user.visible_message("[src] has no effect on [user]!") + return + user.revive(full_heal = 1) + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.regenerate_limbs() + C.regenerate_organs() + to_chat(user, "You feel great!") + +/obj/item/gun/magic/wand/resurrection/debug //for testing + name = "debug wand of healing" + max_charges = 500 + +///////////////////////////////////// +//WAND OF POLYMORPH +///////////////////////////////////// + +/obj/item/gun/magic/wand/polymorph + name = "wand of polymorph" + desc = "This wand is attuned to chaos and will radically alter the victim's form." + ammo_type = /obj/item/ammo_casing/magic/change + icon_state = "polywand" + fire_sound = 'sound/magic/staff_change.ogg' + max_charges = 10 //10, 5, 5, 4 + +/obj/item/gun/magic/wand/polymorph/zap_self(mob/living/user) + ..() //because the user mob ceases to exists by the time wabbajack fully resolves + wabbajack(user) + charges-- + +///////////////////////////////////// +//WAND OF TELEPORTATION +///////////////////////////////////// + +/obj/item/gun/magic/wand/teleport + name = "wand of teleportation" + desc = "This wand will wrench targets through space and time to move them somewhere else." + ammo_type = /obj/item/ammo_casing/magic/teleport + fire_sound = 'sound/magic/wand_teleport.ogg' + icon_state = "telewand" + max_charges = 10 //10, 5, 5, 4 + no_den_usage = 1 + +/obj/item/gun/magic/wand/teleport/zap_self(mob/living/user) + if(do_teleport(user, user, 10, channel = TELEPORT_CHANNEL_MAGIC)) + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(3, user.loc) + smoke.start() + charges-- + ..() + +///////////////////////////////////// +//WAND OF DOOR CREATION +///////////////////////////////////// + +/obj/item/gun/magic/wand/door + name = "wand of door creation" + desc = "This particular wand can create doors in any wall for the unscrupulous wizard who shuns teleportation magics." + ammo_type = /obj/item/ammo_casing/magic/door + icon_state = "doorwand" + fire_sound = 'sound/magic/staff_door.ogg' + max_charges = 20 //20, 10, 10, 7 + no_den_usage = 1 + +/obj/item/gun/magic/wand/door/zap_self(mob/living/user) + to_chat(user, "You feel vaguely more open with your feelings.") + charges-- + ..() + +///////////////////////////////////// +//WAND OF FIREBALL +///////////////////////////////////// + +/obj/item/gun/magic/wand/fireball + name = "wand of fireball" + desc = "This wand shoots scorching balls of fire that explode into destructive flames." + fire_sound = 'sound/magic/fireball.ogg' + ammo_type = /obj/item/ammo_casing/magic/fireball + icon_state = "firewand" + max_charges = 8 //8, 4, 4, 3 + +/obj/item/gun/magic/wand/fireball/zap_self(mob/living/user) + ..() + explosion(user.loc, -1, 0, 2, 3, 0, flame_range = 2) + charges-- diff --git a/code/modules/projectiles/guns/misc/beam_rifle.dm b/code/modules/projectiles/guns/misc/beam_rifle.dm index 92fe91c222..7aa85db246 100644 --- a/code/modules/projectiles/guns/misc/beam_rifle.dm +++ b/code/modules/projectiles/guns/misc/beam_rifle.dm @@ -1,572 +1,572 @@ - -#define ZOOM_LOCK_AUTOZOOM_FREEMOVE 0 -#define ZOOM_LOCK_AUTOZOOM_ANGLELOCK 1 -#define ZOOM_LOCK_CENTER_VIEW 2 -#define ZOOM_LOCK_OFF 3 - -#define AUTOZOOM_PIXEL_STEP_FACTOR 48 - -#define AIMING_BEAM_ANGLE_CHANGE_THRESHOLD 0.1 - -/obj/item/gun/energy/beam_rifle - name = "particle acceleration rifle" - desc = "An energy-based anti material marksman rifle that uses highly charged particle beams moving at extreme velocities to decimate whatever is unfortunate enough to be targeted by one. \ - Hold down left click while scoped to aim, when weapon is fully aimed (Tracer goes from red to green as it charges), release to fire. Moving while aiming or \ - changing where you're pointing at while aiming will delay the aiming process depending on how much you changed." - icon = 'icons/obj/guns/energy.dmi' - icon_state = "esniper" - item_state = "esniper" - fire_sound = 'sound/weapons/beam_sniper.ogg' - slot_flags = ITEM_SLOT_BACK - force = 15 - materials = list() - recoil = 4 - ammo_x_offset = 3 - ammo_y_offset = 3 - modifystate = FALSE - weapon_weight = WEAPON_HEAVY - w_class = WEIGHT_CLASS_BULKY - ammo_type = list(/obj/item/ammo_casing/energy/beam_rifle/hitscan) - cell_type = /obj/item/stock_parts/cell/beam_rifle - canMouseDown = TRUE - //Cit changes: beam rifle stats. - slowdown = 1 - item_flags = NO_MAT_REDEMPTION | SLOWS_WHILE_IN_HAND | NEEDS_PERMIT - pin = null - var/aiming = FALSE - var/aiming_time = 14 - var/aiming_time_fire_threshold = 5 - var/aiming_time_left = 14 - var/aiming_time_increase_user_movement = 7 - var/aiming_time_increase_angle_multiplier = 0.30 - var/last_process = 0 - - var/lastangle = 0 - var/aiming_lastangle = 0 - var/mob/current_user = null - var/list/obj/effect/projectile/tracer/current_tracers - - var/structure_piercing = 1 - var/structure_bleed_coeff = 0.7 - var/wall_pierce_amount = 0 - var/wall_devastate = 0 - var/aoe_structure_range = 1 - var/aoe_structure_damage = 35 - var/aoe_fire_range = 1 - var/aoe_fire_chance = 100 - var/aoe_mob_range = 1 - var/aoe_mob_damage = 20 - var/impact_structure_damage = 75 - var/projectile_damage = 40 - var/projectile_stun = 0 - var/projectile_setting_pierce = TRUE - var/delay = 30 - var/lastfire = 0 - - //ZOOMING - var/zoom_current_view_increase = 0 - var/zoom_target_view_increase = 10 - var/zooming = FALSE - var/zoom_lock = ZOOM_LOCK_OFF - var/zooming_angle - var/current_zoom_x = 0 - var/current_zoom_y = 0 - - var/static/image/charged_overlay = image(icon = 'icons/obj/guns/energy.dmi', icon_state = "esniper_charged") - var/static/image/drained_overlay = image(icon = 'icons/obj/guns/energy.dmi', icon_state = "esniper_empty") - - var/datum/action/item_action/zoom_lock_action/zoom_lock_action - var/mob/listeningTo - -/obj/item/gun/energy/beam_rifle/debug - delay = 0 - cell_type = /obj/item/stock_parts/cell/infinite - aiming_time = 0 - recoil = 0 - pin = /obj/item/firing_pin - -/obj/item/gun/energy/beam_rifle/equipped(mob/user) - set_user(user) - . = ..() - -/obj/item/gun/energy/beam_rifle/pickup(mob/user) - set_user(user) - . = ..() - -/obj/item/gun/energy/beam_rifle/dropped(mob/user) - set_user() - . = ..() - -/obj/item/gun/energy/beam_rifle/ui_action_click(owner, action) - if(istype(action, /datum/action/item_action/zoom_lock_action)) - zoom_lock++ - if(zoom_lock > 3) - zoom_lock = 0 - switch(zoom_lock) - if(ZOOM_LOCK_AUTOZOOM_FREEMOVE) - to_chat(owner, "You switch [src]'s zooming processor to free directional.") - if(ZOOM_LOCK_AUTOZOOM_ANGLELOCK) - to_chat(owner, "You switch [src]'s zooming processor to locked directional.") - if(ZOOM_LOCK_CENTER_VIEW) - to_chat(owner, "You switch [src]'s zooming processor to center mode.") - if(ZOOM_LOCK_OFF) - to_chat(owner, "You disable [src]'s zooming system.") - reset_zooming() - else - return ..() - -/obj/item/gun/energy/beam_rifle/proc/set_autozoom_pixel_offsets_immediate(current_angle) - if(zoom_lock == ZOOM_LOCK_CENTER_VIEW || zoom_lock == ZOOM_LOCK_OFF) - return - current_zoom_x = sin(current_angle) + sin(current_angle) * AUTOZOOM_PIXEL_STEP_FACTOR * zoom_current_view_increase - current_zoom_y = cos(current_angle) + cos(current_angle) * AUTOZOOM_PIXEL_STEP_FACTOR * zoom_current_view_increase - -/obj/item/gun/energy/beam_rifle/proc/handle_zooming() - if(!zooming || !check_user()) - return - set_autozoom_pixel_offsets_immediate(zooming_angle) - -/obj/item/gun/energy/beam_rifle/proc/start_zooming() - if(zoom_lock == ZOOM_LOCK_OFF) - return - zooming = TRUE - current_user.client.change_view(world.view + zoom_target_view_increase) - zoom_current_view_increase = zoom_target_view_increase - -/obj/item/gun/energy/beam_rifle/proc/stop_zooming(mob/user) - if(zooming) - zooming = FALSE - reset_zooming(user) - -/obj/item/gun/energy/beam_rifle/proc/reset_zooming(mob/user) - if(!user) - user = current_user - if(!user || !user.client) - return FALSE - animate(user.client, pixel_x = 0, pixel_y = 0, 0, FALSE, LINEAR_EASING, ANIMATION_END_NOW) - zoom_current_view_increase = 0 - user.client.change_view(CONFIG_GET(string/default_view)) - zooming_angle = 0 - current_zoom_x = 0 - current_zoom_y = 0 - -/obj/item/gun/energy/beam_rifle/update_icon() - cut_overlays() - var/obj/item/ammo_casing/energy/primary_ammo = ammo_type[1] - if(!QDELETED(cell) && (cell.charge > primary_ammo.e_cost)) - add_overlay(charged_overlay) - else - add_overlay(drained_overlay) - -/obj/item/gun/energy/beam_rifle/attack_self(mob/user) - projectile_setting_pierce = !projectile_setting_pierce - to_chat(user, "You set \the [src] to [projectile_setting_pierce? "pierce":"impact"] mode.") - aiming_beam() - -/obj/item/gun/energy/beam_rifle/Initialize() - . = ..() - fire_delay = delay - current_tracers = list() - START_PROCESSING(SSfastprocess, src) - zoom_lock_action = new(src) - -/obj/item/gun/energy/beam_rifle/Destroy() - STOP_PROCESSING(SSfastprocess, src) - set_user(null) - QDEL_LIST(current_tracers) - listeningTo = null - return ..() - -/obj/item/gun/energy/beam_rifle/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chambered = null - recharge_newshot() - -/obj/item/gun/energy/beam_rifle/proc/aiming_beam(force_update = FALSE) - var/diff = abs(aiming_lastangle - lastangle) - check_user() - if(diff < AIMING_BEAM_ANGLE_CHANGE_THRESHOLD && !force_update) - return - aiming_lastangle = lastangle - var/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/P = new - P.gun = src - P.wall_pierce_amount = wall_pierce_amount - P.structure_pierce_amount = structure_piercing - P.do_pierce = projectile_setting_pierce - if(aiming_time) - var/percent = ((100/aiming_time)*aiming_time_left) - P.color = rgb(255 * percent,255 * ((100 - percent) / 100),0) - else - P.color = rgb(0, 255, 0) - var/turf/curloc = get_turf(src) - var/turf/targloc = get_turf(current_user.client.mouseObject) - if(!istype(targloc)) - if(!istype(curloc)) - return - targloc = get_turf_in_angle(lastangle, curloc, 10) - P.preparePixelProjectile(targloc, current_user, current_user.client.mouseParams, 0) - P.fire(lastangle) - -/obj/item/gun/energy/beam_rifle/process() - if(!aiming) - last_process = world.time - return - check_user() - handle_zooming() - aiming_time_left = max(0, aiming_time_left - (world.time - last_process)) - aiming_beam(TRUE) - last_process = world.time - -/obj/item/gun/energy/beam_rifle/proc/check_user(automatic_cleanup = TRUE) - if(!istype(current_user) || !isturf(current_user.loc) || !(src in current_user.held_items) || current_user.incapacitated()) //Doesn't work if you're not holding it! - if(automatic_cleanup) - stop_aiming() - set_user(null) - return FALSE - return TRUE - -/obj/item/gun/energy/beam_rifle/proc/process_aim() - if(istype(current_user) && current_user.client && current_user.client.mouseParams) - var/angle = mouse_angle_from_client(current_user.client) - current_user.setDir(angle2dir_cardinal(angle)) - var/difference = abs(closer_angle_difference(lastangle, angle)) - delay_penalty(difference * aiming_time_increase_angle_multiplier) - lastangle = angle - -/obj/item/gun/energy/beam_rifle/proc/on_mob_move() - check_user() - if(aiming) - delay_penalty(aiming_time_increase_user_movement) - process_aim() - aiming_beam(TRUE) - -/obj/item/gun/energy/beam_rifle/proc/start_aiming() - aiming_time_left = aiming_time - aiming = TRUE - process_aim() - aiming_beam(TRUE) - zooming_angle = lastangle - start_zooming() - -/obj/item/gun/energy/beam_rifle/proc/stop_aiming(mob/user) - set waitfor = FALSE - aiming_time_left = aiming_time - aiming = FALSE - QDEL_LIST(current_tracers) - stop_zooming(user) - -/obj/item/gun/energy/beam_rifle/proc/set_user(mob/user) - if(user == current_user) - return - stop_aiming(current_user) - if(listeningTo) - UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) - listeningTo = null - if(istype(current_user)) - LAZYREMOVE(current_user.mousemove_intercept_objects, src) - current_user = null - if(istype(user)) - current_user = user - LAZYOR(current_user.mousemove_intercept_objects, src) - RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/on_mob_move) - listeningTo = user - -/obj/item/gun/energy/beam_rifle/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - if(aiming) - process_aim() - aiming_beam() - if(zoom_lock == ZOOM_LOCK_AUTOZOOM_FREEMOVE) - zooming_angle = lastangle - set_autozoom_pixel_offsets_immediate(zooming_angle) - return ..() - -/obj/item/gun/energy/beam_rifle/onMouseDown(object, location, params, mob/mob) - if(istype(mob)) - set_user(mob) - if(istype(object, /obj/screen) && !istype(object, /obj/screen/click_catcher)) - return - if((object in mob.contents) || (object == mob)) - return - start_aiming() - return ..() - -/obj/item/gun/energy/beam_rifle/onMouseUp(object, location, params, mob/M) - if(istype(object, /obj/screen) && !istype(object, /obj/screen/click_catcher)) - return - process_aim() - if(aiming_time_left <= aiming_time_fire_threshold && check_user()) - sync_ammo() - afterattack(M.client.mouseObject, M, FALSE, M.client.mouseParams, passthrough = TRUE) - stop_aiming() - QDEL_LIST(current_tracers) - return ..() - -/obj/item/gun/energy/beam_rifle/afterattack(atom/target, mob/living/user, flag, params, passthrough = FALSE) - if(flag) //It's adjacent, is the user, or is on the user's person - if(target in user.contents) //can't shoot stuff inside us. - return - if(!ismob(target) || user.a_intent == INTENT_HARM) //melee attack - return - if(target == user && user.zone_selected != BODY_ZONE_PRECISE_MOUTH) //so we can't shoot ourselves (unless mouth selected) - return - if(!passthrough && (aiming_time > aiming_time_fire_threshold)) - return - if(lastfire > world.time + delay) - return - lastfire = world.time - . = ..() - stop_aiming() - -/obj/item/gun/energy/beam_rifle/proc/sync_ammo() - for(var/obj/item/ammo_casing/energy/beam_rifle/AC in contents) - AC.sync_stats() - -/obj/item/gun/energy/beam_rifle/proc/delay_penalty(amount) - aiming_time_left = CLAMP(aiming_time_left + amount, 0, aiming_time) - -/obj/item/ammo_casing/energy/beam_rifle - name = "particle acceleration lens" - desc = "Don't look into barrel!" - var/wall_pierce_amount = 0 - var/wall_devastate = 0 - var/aoe_structure_range = 1 - var/aoe_structure_damage = 30 - var/aoe_fire_range = 2 - var/aoe_fire_chance = 66 - var/aoe_mob_range = 1 - var/aoe_mob_damage = 20 - var/impact_structure_damage = 50 - var/projectile_damage = 40 - var/projectile_stun = 0 - var/structure_piercing = 2 - var/structure_bleed_coeff = 0.7 - var/do_pierce = TRUE - var/obj/item/gun/energy/beam_rifle/host - -/obj/item/ammo_casing/energy/beam_rifle/proc/sync_stats() - var/obj/item/gun/energy/beam_rifle/BR = loc - if(!istype(BR)) - stack_trace("Beam rifle syncing error") - host = BR - do_pierce = BR.projectile_setting_pierce - wall_pierce_amount = BR.wall_pierce_amount - wall_devastate = BR.wall_devastate - aoe_structure_range = BR.aoe_structure_range - aoe_structure_damage = BR.aoe_structure_damage - aoe_fire_range = BR.aoe_fire_range - aoe_fire_chance = BR.aoe_fire_chance - aoe_mob_range = BR.aoe_mob_range - aoe_mob_damage = BR.aoe_mob_damage - impact_structure_damage = BR.impact_structure_damage - projectile_damage = BR.projectile_damage - projectile_stun = BR.projectile_stun - delay = BR.delay - structure_piercing = BR.structure_piercing - structure_bleed_coeff = BR.structure_bleed_coeff - -/obj/item/ammo_casing/energy/beam_rifle/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") - . = ..() - var/obj/item/projectile/beam/beam_rifle/hitscan/HS_BB = BB - if(!istype(HS_BB)) - return - HS_BB.impact_direct_damage = projectile_damage - HS_BB.stun = projectile_stun - HS_BB.impact_structure_damage = impact_structure_damage - HS_BB.aoe_mob_damage = aoe_mob_damage - HS_BB.aoe_mob_range = CLAMP(aoe_mob_range, 0, 15) //Badmin safety lock - HS_BB.aoe_fire_chance = aoe_fire_chance - HS_BB.aoe_fire_range = aoe_fire_range - HS_BB.aoe_structure_damage = aoe_structure_damage - HS_BB.aoe_structure_range = CLAMP(aoe_structure_range, 0, 15) //Badmin safety lock - HS_BB.wall_devastate = wall_devastate - HS_BB.wall_pierce_amount = wall_pierce_amount - HS_BB.structure_pierce_amount = structure_piercing - HS_BB.structure_bleed_coeff = structure_bleed_coeff - HS_BB.do_pierce = do_pierce - HS_BB.gun = host - -/obj/item/ammo_casing/energy/beam_rifle/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread) - var/turf/curloc = get_turf(user) - if(!istype(curloc) || !BB) - return FALSE - var/obj/item/gun/energy/beam_rifle/gun = loc - if(!targloc && gun) - targloc = get_turf_in_angle(gun.lastangle, curloc, 10) - else if(!targloc) - return FALSE - var/firing_dir - if(BB.firer) - firing_dir = BB.firer.dir - if(!BB.suppressed && firing_effect_type) - new firing_effect_type(get_turf(src), firing_dir) - BB.preparePixelProjectile(target, user, params, spread) - BB.fire(gun? gun.lastangle : null, null) - BB = null - return TRUE - -/obj/item/ammo_casing/energy/beam_rifle/hitscan - projectile_type = /obj/item/projectile/beam/beam_rifle/hitscan - select_name = "beam" - e_cost = 5000 - fire_sound = 'sound/weapons/beam_sniper.ogg' - -/obj/item/projectile/beam/beam_rifle - name = "particle beam" - icon = null - hitsound = 'sound/effects/explosion3.ogg' - damage = 0 //Handled manually. - damage_type = BURN - flag = "energy" - range = 150 - jitter = 10 - var/obj/item/gun/energy/beam_rifle/gun - var/structure_pierce_amount = 0 //All set to 0 so the gun can manually set them during firing. - var/structure_bleed_coeff = 0 - var/structure_pierce = 0 - var/do_pierce = TRUE - var/wall_pierce_amount = 0 - var/wall_pierce = 0 - var/wall_devastate = 0 - var/aoe_structure_range = 0 - var/aoe_structure_damage = 0 - var/aoe_fire_range = 0 - var/aoe_fire_chance = 0 - var/aoe_mob_range = 0 - var/aoe_mob_damage = 0 - var/impact_structure_damage = 0 - var/impact_direct_damage = 0 - var/turf/cached - var/list/pierced = list() - -/obj/item/projectile/beam/beam_rifle/proc/AOE(turf/epicenter) - set waitfor = FALSE - if(!epicenter) - return - new /obj/effect/temp_visual/explosion/fast(epicenter) - for(var/mob/living/L in range(aoe_mob_range, epicenter)) //handle aoe mob damage - L.adjustFireLoss(aoe_mob_damage) - to_chat(L, "\The [src] sears you!") - for(var/turf/T in range(aoe_fire_range, epicenter)) //handle aoe fire - if(prob(aoe_fire_chance)) - new /obj/effect/hotspot(T) - for(var/obj/O in range(aoe_structure_range, epicenter)) - if(!isitem(O)) - if(O.level == 1) //Please don't break underfloor items! - continue - O.take_damage(aoe_structure_damage * get_damage_coeff(O), BURN, "laser", FALSE) - -/obj/item/projectile/beam/beam_rifle/proc/check_pierce(atom/target) - if(!do_pierce) - return FALSE - if(pierced[target]) //we already pierced them go away - return TRUE - if(isclosedturf(target)) - if(wall_pierce++ < wall_pierce_amount) - if(prob(wall_devastate)) - if(iswallturf(target)) - var/turf/closed/wall/W = target - W.dismantle_wall(TRUE, TRUE) - else - target.ex_act(EXPLODE_HEAVY) - return TRUE - if(ismovableatom(target)) - var/atom/movable/AM = target - if(AM.density && !AM.CanPass(src, get_turf(target)) && !ismob(AM)) - if(structure_pierce < structure_pierce_amount) - if(isobj(AM)) - var/obj/O = AM - O.take_damage((impact_structure_damage + aoe_structure_damage) * structure_bleed_coeff * get_damage_coeff(AM), BURN, "energy", FALSE) - pierced[AM] = TRUE - structure_pierce++ - return TRUE - return FALSE - -/obj/item/projectile/beam/beam_rifle/proc/get_damage_coeff(atom/target) - if(istype(target, /obj/machinery/door)) - return 0.4 - if(istype(target, /obj/structure/window)) - return 0.5 - if(istype(target, /obj/structure/blob)) - return 0.65 //CIT CHANGE. - return 1 - -/obj/item/projectile/beam/beam_rifle/proc/handle_impact(atom/target) - if(isobj(target)) - var/obj/O = target - O.take_damage(impact_structure_damage * get_damage_coeff(target), BURN, "laser", FALSE) - if(isliving(target)) - var/mob/living/L = target - L.adjustFireLoss(impact_direct_damage) - L.emote("scream") - -/obj/item/projectile/beam/beam_rifle/proc/handle_hit(atom/target) - set waitfor = FALSE - if(!cached && !QDELETED(target)) - cached = get_turf(target) - if(nodamage) - return FALSE - playsound(cached, 'sound/effects/explosion3.ogg', 100, 1) - AOE(cached) - if(!QDELETED(target)) - handle_impact(target) - -/obj/item/projectile/beam/beam_rifle/Bump(atom/target) - if(check_pierce(target)) - permutated += target - trajectory_ignore_forcemove = TRUE - forceMove(target.loc) - trajectory_ignore_forcemove = FALSE - return FALSE - if(!QDELETED(target)) - cached = get_turf(target) - . = ..() - -/obj/item/projectile/beam/beam_rifle/on_hit(atom/target, blocked = FALSE) - if(!QDELETED(target)) - cached = get_turf(target) - handle_hit(target) - . = ..() - -/obj/item/projectile/beam/beam_rifle/hitscan - icon_state = "" - hitscan = TRUE - tracer_type = /obj/effect/projectile/tracer/tracer/beam_rifle - var/constant_tracer = FALSE - -/obj/item/projectile/beam/beam_rifle/hitscan/generate_hitscan_tracers(cleanup = TRUE, duration = 5, impacting = TRUE, highlander) - set waitfor = FALSE - if(isnull(highlander)) - highlander = constant_tracer - if(highlander && istype(gun)) - QDEL_LIST(gun.current_tracers) - for(var/datum/point/p in beam_segments) - gun.current_tracers += generate_tracer_between_points(p, beam_segments[p], tracer_type, color, 0, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity) - else - for(var/datum/point/p in beam_segments) - generate_tracer_between_points(p, beam_segments[p], tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity) - if(cleanup) - QDEL_LIST(beam_segments) - beam_segments = null - QDEL_NULL(beam_index) - -/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam - tracer_type = /obj/effect/projectile/tracer/tracer/aiming - name = "aiming beam" - hitsound = null - hitsound_wall = null - nodamage = TRUE - damage = 0 - constant_tracer = TRUE - hitscan_light_range = 0 - hitscan_light_intensity = 0 - hitscan_light_color_override = "#99ff99" - -/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/prehit(atom/target) - qdel(src) - return FALSE - -/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/on_hit() - qdel(src) - return FALSE + +#define ZOOM_LOCK_AUTOZOOM_FREEMOVE 0 +#define ZOOM_LOCK_AUTOZOOM_ANGLELOCK 1 +#define ZOOM_LOCK_CENTER_VIEW 2 +#define ZOOM_LOCK_OFF 3 + +#define AUTOZOOM_PIXEL_STEP_FACTOR 48 + +#define AIMING_BEAM_ANGLE_CHANGE_THRESHOLD 0.1 + +/obj/item/gun/energy/beam_rifle + name = "particle acceleration rifle" + desc = "An energy-based anti material marksman rifle that uses highly charged particle beams moving at extreme velocities to decimate whatever is unfortunate enough to be targeted by one. \ + Hold down left click while scoped to aim, when weapon is fully aimed (Tracer goes from red to green as it charges), release to fire. Moving while aiming or \ + changing where you're pointing at while aiming will delay the aiming process depending on how much you changed." + icon = 'icons/obj/guns/energy.dmi' + icon_state = "esniper" + item_state = "esniper" + fire_sound = 'sound/weapons/beam_sniper.ogg' + slot_flags = ITEM_SLOT_BACK + force = 15 + materials = list() + recoil = 4 + ammo_x_offset = 3 + ammo_y_offset = 3 + modifystate = FALSE + weapon_weight = WEAPON_HEAVY + w_class = WEIGHT_CLASS_BULKY + ammo_type = list(/obj/item/ammo_casing/energy/beam_rifle/hitscan) + cell_type = /obj/item/stock_parts/cell/beam_rifle + canMouseDown = TRUE + //Cit changes: beam rifle stats. + slowdown = 1 + item_flags = NO_MAT_REDEMPTION | SLOWS_WHILE_IN_HAND | NEEDS_PERMIT + pin = null + var/aiming = FALSE + var/aiming_time = 14 + var/aiming_time_fire_threshold = 5 + var/aiming_time_left = 14 + var/aiming_time_increase_user_movement = 7 + var/aiming_time_increase_angle_multiplier = 0.30 + var/last_process = 0 + + var/lastangle = 0 + var/aiming_lastangle = 0 + var/mob/current_user = null + var/list/obj/effect/projectile/tracer/current_tracers + + var/structure_piercing = 1 + var/structure_bleed_coeff = 0.7 + var/wall_pierce_amount = 0 + var/wall_devastate = 0 + var/aoe_structure_range = 1 + var/aoe_structure_damage = 35 + var/aoe_fire_range = 1 + var/aoe_fire_chance = 100 + var/aoe_mob_range = 1 + var/aoe_mob_damage = 20 + var/impact_structure_damage = 75 + var/projectile_damage = 40 + var/projectile_stun = 0 + var/projectile_setting_pierce = TRUE + var/delay = 30 + var/lastfire = 0 + + //ZOOMING + var/zoom_current_view_increase = 0 + var/zoom_target_view_increase = 10 + var/zooming = FALSE + var/zoom_lock = ZOOM_LOCK_OFF + var/zooming_angle + var/current_zoom_x = 0 + var/current_zoom_y = 0 + + var/static/image/charged_overlay = image(icon = 'icons/obj/guns/energy.dmi', icon_state = "esniper_charged") + var/static/image/drained_overlay = image(icon = 'icons/obj/guns/energy.dmi', icon_state = "esniper_empty") + + var/datum/action/item_action/zoom_lock_action/zoom_lock_action + var/mob/listeningTo + +/obj/item/gun/energy/beam_rifle/debug + delay = 0 + cell_type = /obj/item/stock_parts/cell/infinite + aiming_time = 0 + recoil = 0 + pin = /obj/item/firing_pin + +/obj/item/gun/energy/beam_rifle/equipped(mob/user) + set_user(user) + . = ..() + +/obj/item/gun/energy/beam_rifle/pickup(mob/user) + set_user(user) + . = ..() + +/obj/item/gun/energy/beam_rifle/dropped(mob/user) + set_user() + . = ..() + +/obj/item/gun/energy/beam_rifle/ui_action_click(owner, action) + if(istype(action, /datum/action/item_action/zoom_lock_action)) + zoom_lock++ + if(zoom_lock > 3) + zoom_lock = 0 + switch(zoom_lock) + if(ZOOM_LOCK_AUTOZOOM_FREEMOVE) + to_chat(owner, "You switch [src]'s zooming processor to free directional.") + if(ZOOM_LOCK_AUTOZOOM_ANGLELOCK) + to_chat(owner, "You switch [src]'s zooming processor to locked directional.") + if(ZOOM_LOCK_CENTER_VIEW) + to_chat(owner, "You switch [src]'s zooming processor to center mode.") + if(ZOOM_LOCK_OFF) + to_chat(owner, "You disable [src]'s zooming system.") + reset_zooming() + else + return ..() + +/obj/item/gun/energy/beam_rifle/proc/set_autozoom_pixel_offsets_immediate(current_angle) + if(zoom_lock == ZOOM_LOCK_CENTER_VIEW || zoom_lock == ZOOM_LOCK_OFF) + return + current_zoom_x = sin(current_angle) + sin(current_angle) * AUTOZOOM_PIXEL_STEP_FACTOR * zoom_current_view_increase + current_zoom_y = cos(current_angle) + cos(current_angle) * AUTOZOOM_PIXEL_STEP_FACTOR * zoom_current_view_increase + +/obj/item/gun/energy/beam_rifle/proc/handle_zooming() + if(!zooming || !check_user()) + return + set_autozoom_pixel_offsets_immediate(zooming_angle) + +/obj/item/gun/energy/beam_rifle/proc/start_zooming() + if(zoom_lock == ZOOM_LOCK_OFF) + return + zooming = TRUE + current_user.client.change_view(world.view + zoom_target_view_increase) + zoom_current_view_increase = zoom_target_view_increase + +/obj/item/gun/energy/beam_rifle/proc/stop_zooming(mob/user) + if(zooming) + zooming = FALSE + reset_zooming(user) + +/obj/item/gun/energy/beam_rifle/proc/reset_zooming(mob/user) + if(!user) + user = current_user + if(!user || !user.client) + return FALSE + animate(user.client, pixel_x = 0, pixel_y = 0, 0, FALSE, LINEAR_EASING, ANIMATION_END_NOW) + zoom_current_view_increase = 0 + user.client.change_view(CONFIG_GET(string/default_view)) + zooming_angle = 0 + current_zoom_x = 0 + current_zoom_y = 0 + +/obj/item/gun/energy/beam_rifle/update_icon() + cut_overlays() + var/obj/item/ammo_casing/energy/primary_ammo = ammo_type[1] + if(!QDELETED(cell) && (cell.charge > primary_ammo.e_cost)) + add_overlay(charged_overlay) + else + add_overlay(drained_overlay) + +/obj/item/gun/energy/beam_rifle/attack_self(mob/user) + projectile_setting_pierce = !projectile_setting_pierce + to_chat(user, "You set \the [src] to [projectile_setting_pierce? "pierce":"impact"] mode.") + aiming_beam() + +/obj/item/gun/energy/beam_rifle/Initialize() + . = ..() + fire_delay = delay + current_tracers = list() + START_PROCESSING(SSfastprocess, src) + zoom_lock_action = new(src) + +/obj/item/gun/energy/beam_rifle/Destroy() + STOP_PROCESSING(SSfastprocess, src) + set_user(null) + QDEL_LIST(current_tracers) + listeningTo = null + return ..() + +/obj/item/gun/energy/beam_rifle/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + chambered = null + recharge_newshot() + +/obj/item/gun/energy/beam_rifle/proc/aiming_beam(force_update = FALSE) + var/diff = abs(aiming_lastangle - lastangle) + check_user() + if(diff < AIMING_BEAM_ANGLE_CHANGE_THRESHOLD && !force_update) + return + aiming_lastangle = lastangle + var/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/P = new + P.gun = src + P.wall_pierce_amount = wall_pierce_amount + P.structure_pierce_amount = structure_piercing + P.do_pierce = projectile_setting_pierce + if(aiming_time) + var/percent = ((100/aiming_time)*aiming_time_left) + P.color = rgb(255 * percent,255 * ((100 - percent) / 100),0) + else + P.color = rgb(0, 255, 0) + var/turf/curloc = get_turf(src) + var/turf/targloc = get_turf(current_user.client.mouseObject) + if(!istype(targloc)) + if(!istype(curloc)) + return + targloc = get_turf_in_angle(lastangle, curloc, 10) + P.preparePixelProjectile(targloc, current_user, current_user.client.mouseParams, 0) + P.fire(lastangle) + +/obj/item/gun/energy/beam_rifle/process() + if(!aiming) + last_process = world.time + return + check_user() + handle_zooming() + aiming_time_left = max(0, aiming_time_left - (world.time - last_process)) + aiming_beam(TRUE) + last_process = world.time + +/obj/item/gun/energy/beam_rifle/proc/check_user(automatic_cleanup = TRUE) + if(!istype(current_user) || !isturf(current_user.loc) || !(src in current_user.held_items) || current_user.incapacitated()) //Doesn't work if you're not holding it! + if(automatic_cleanup) + stop_aiming() + set_user(null) + return FALSE + return TRUE + +/obj/item/gun/energy/beam_rifle/proc/process_aim() + if(istype(current_user) && current_user.client && current_user.client.mouseParams) + var/angle = mouse_angle_from_client(current_user.client) + current_user.setDir(angle2dir_cardinal(angle)) + var/difference = abs(closer_angle_difference(lastangle, angle)) + delay_penalty(difference * aiming_time_increase_angle_multiplier) + lastangle = angle + +/obj/item/gun/energy/beam_rifle/proc/on_mob_move() + check_user() + if(aiming) + delay_penalty(aiming_time_increase_user_movement) + process_aim() + aiming_beam(TRUE) + +/obj/item/gun/energy/beam_rifle/proc/start_aiming() + aiming_time_left = aiming_time + aiming = TRUE + process_aim() + aiming_beam(TRUE) + zooming_angle = lastangle + start_zooming() + +/obj/item/gun/energy/beam_rifle/proc/stop_aiming(mob/user) + set waitfor = FALSE + aiming_time_left = aiming_time + aiming = FALSE + QDEL_LIST(current_tracers) + stop_zooming(user) + +/obj/item/gun/energy/beam_rifle/proc/set_user(mob/user) + if(user == current_user) + return + stop_aiming(current_user) + if(listeningTo) + UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED) + listeningTo = null + if(istype(current_user)) + LAZYREMOVE(current_user.mousemove_intercept_objects, src) + current_user = null + if(istype(user)) + current_user = user + LAZYOR(current_user.mousemove_intercept_objects, src) + RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/on_mob_move) + listeningTo = user + +/obj/item/gun/energy/beam_rifle/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) + if(aiming) + process_aim() + aiming_beam() + if(zoom_lock == ZOOM_LOCK_AUTOZOOM_FREEMOVE) + zooming_angle = lastangle + set_autozoom_pixel_offsets_immediate(zooming_angle) + return ..() + +/obj/item/gun/energy/beam_rifle/onMouseDown(object, location, params, mob/mob) + if(istype(mob)) + set_user(mob) + if(istype(object, /obj/screen) && !istype(object, /obj/screen/click_catcher)) + return + if((object in mob.contents) || (object == mob)) + return + start_aiming() + return ..() + +/obj/item/gun/energy/beam_rifle/onMouseUp(object, location, params, mob/M) + if(istype(object, /obj/screen) && !istype(object, /obj/screen/click_catcher)) + return + process_aim() + if(aiming_time_left <= aiming_time_fire_threshold && check_user()) + sync_ammo() + afterattack(M.client.mouseObject, M, FALSE, M.client.mouseParams, passthrough = TRUE) + stop_aiming() + QDEL_LIST(current_tracers) + return ..() + +/obj/item/gun/energy/beam_rifle/afterattack(atom/target, mob/living/user, flag, params, passthrough = FALSE) + if(flag) //It's adjacent, is the user, or is on the user's person + if(target in user.contents) //can't shoot stuff inside us. + return + if(!ismob(target) || user.a_intent == INTENT_HARM) //melee attack + return + if(target == user && user.zone_selected != BODY_ZONE_PRECISE_MOUTH) //so we can't shoot ourselves (unless mouth selected) + return + if(!passthrough && (aiming_time > aiming_time_fire_threshold)) + return + if(lastfire > world.time + delay) + return + lastfire = world.time + . = ..() + stop_aiming() + +/obj/item/gun/energy/beam_rifle/proc/sync_ammo() + for(var/obj/item/ammo_casing/energy/beam_rifle/AC in contents) + AC.sync_stats() + +/obj/item/gun/energy/beam_rifle/proc/delay_penalty(amount) + aiming_time_left = CLAMP(aiming_time_left + amount, 0, aiming_time) + +/obj/item/ammo_casing/energy/beam_rifle + name = "particle acceleration lens" + desc = "Don't look into barrel!" + var/wall_pierce_amount = 0 + var/wall_devastate = 0 + var/aoe_structure_range = 1 + var/aoe_structure_damage = 30 + var/aoe_fire_range = 2 + var/aoe_fire_chance = 66 + var/aoe_mob_range = 1 + var/aoe_mob_damage = 20 + var/impact_structure_damage = 50 + var/projectile_damage = 40 + var/projectile_stun = 0 + var/structure_piercing = 2 + var/structure_bleed_coeff = 0.7 + var/do_pierce = TRUE + var/obj/item/gun/energy/beam_rifle/host + +/obj/item/ammo_casing/energy/beam_rifle/proc/sync_stats() + var/obj/item/gun/energy/beam_rifle/BR = loc + if(!istype(BR)) + stack_trace("Beam rifle syncing error") + host = BR + do_pierce = BR.projectile_setting_pierce + wall_pierce_amount = BR.wall_pierce_amount + wall_devastate = BR.wall_devastate + aoe_structure_range = BR.aoe_structure_range + aoe_structure_damage = BR.aoe_structure_damage + aoe_fire_range = BR.aoe_fire_range + aoe_fire_chance = BR.aoe_fire_chance + aoe_mob_range = BR.aoe_mob_range + aoe_mob_damage = BR.aoe_mob_damage + impact_structure_damage = BR.impact_structure_damage + projectile_damage = BR.projectile_damage + projectile_stun = BR.projectile_stun + delay = BR.delay + structure_piercing = BR.structure_piercing + structure_bleed_coeff = BR.structure_bleed_coeff + +/obj/item/ammo_casing/energy/beam_rifle/ready_proj(atom/target, mob/living/user, quiet, zone_override = "") + . = ..() + var/obj/item/projectile/beam/beam_rifle/hitscan/HS_BB = BB + if(!istype(HS_BB)) + return + HS_BB.impact_direct_damage = projectile_damage + HS_BB.stun = projectile_stun + HS_BB.impact_structure_damage = impact_structure_damage + HS_BB.aoe_mob_damage = aoe_mob_damage + HS_BB.aoe_mob_range = CLAMP(aoe_mob_range, 0, 15) //Badmin safety lock + HS_BB.aoe_fire_chance = aoe_fire_chance + HS_BB.aoe_fire_range = aoe_fire_range + HS_BB.aoe_structure_damage = aoe_structure_damage + HS_BB.aoe_structure_range = CLAMP(aoe_structure_range, 0, 15) //Badmin safety lock + HS_BB.wall_devastate = wall_devastate + HS_BB.wall_pierce_amount = wall_pierce_amount + HS_BB.structure_pierce_amount = structure_piercing + HS_BB.structure_bleed_coeff = structure_bleed_coeff + HS_BB.do_pierce = do_pierce + HS_BB.gun = host + +/obj/item/ammo_casing/energy/beam_rifle/throw_proj(atom/target, turf/targloc, mob/living/user, params, spread) + var/turf/curloc = get_turf(user) + if(!istype(curloc) || !BB) + return FALSE + var/obj/item/gun/energy/beam_rifle/gun = loc + if(!targloc && gun) + targloc = get_turf_in_angle(gun.lastangle, curloc, 10) + else if(!targloc) + return FALSE + var/firing_dir + if(BB.firer) + firing_dir = BB.firer.dir + if(!BB.suppressed && firing_effect_type) + new firing_effect_type(get_turf(src), firing_dir) + BB.preparePixelProjectile(target, user, params, spread) + BB.fire(gun? gun.lastangle : null, null) + BB = null + return TRUE + +/obj/item/ammo_casing/energy/beam_rifle/hitscan + projectile_type = /obj/item/projectile/beam/beam_rifle/hitscan + select_name = "beam" + e_cost = 5000 + fire_sound = 'sound/weapons/beam_sniper.ogg' + +/obj/item/projectile/beam/beam_rifle + name = "particle beam" + icon = null + hitsound = 'sound/effects/explosion3.ogg' + damage = 0 //Handled manually. + damage_type = BURN + flag = "energy" + range = 150 + jitter = 10 + var/obj/item/gun/energy/beam_rifle/gun + var/structure_pierce_amount = 0 //All set to 0 so the gun can manually set them during firing. + var/structure_bleed_coeff = 0 + var/structure_pierce = 0 + var/do_pierce = TRUE + var/wall_pierce_amount = 0 + var/wall_pierce = 0 + var/wall_devastate = 0 + var/aoe_structure_range = 0 + var/aoe_structure_damage = 0 + var/aoe_fire_range = 0 + var/aoe_fire_chance = 0 + var/aoe_mob_range = 0 + var/aoe_mob_damage = 0 + var/impact_structure_damage = 0 + var/impact_direct_damage = 0 + var/turf/cached + var/list/pierced = list() + +/obj/item/projectile/beam/beam_rifle/proc/AOE(turf/epicenter) + set waitfor = FALSE + if(!epicenter) + return + new /obj/effect/temp_visual/explosion/fast(epicenter) + for(var/mob/living/L in range(aoe_mob_range, epicenter)) //handle aoe mob damage + L.adjustFireLoss(aoe_mob_damage) + to_chat(L, "\The [src] sears you!") + for(var/turf/T in range(aoe_fire_range, epicenter)) //handle aoe fire + if(prob(aoe_fire_chance)) + new /obj/effect/hotspot(T) + for(var/obj/O in range(aoe_structure_range, epicenter)) + if(!isitem(O)) + if(O.level == 1) //Please don't break underfloor items! + continue + O.take_damage(aoe_structure_damage * get_damage_coeff(O), BURN, "laser", FALSE) + +/obj/item/projectile/beam/beam_rifle/proc/check_pierce(atom/target) + if(!do_pierce) + return FALSE + if(pierced[target]) //we already pierced them go away + return TRUE + if(isclosedturf(target)) + if(wall_pierce++ < wall_pierce_amount) + if(prob(wall_devastate)) + if(iswallturf(target)) + var/turf/closed/wall/W = target + W.dismantle_wall(TRUE, TRUE) + else + target.ex_act(EXPLODE_HEAVY) + return TRUE + if(ismovableatom(target)) + var/atom/movable/AM = target + if(AM.density && !AM.CanPass(src, get_turf(target)) && !ismob(AM)) + if(structure_pierce < structure_pierce_amount) + if(isobj(AM)) + var/obj/O = AM + O.take_damage((impact_structure_damage + aoe_structure_damage) * structure_bleed_coeff * get_damage_coeff(AM), BURN, "energy", FALSE) + pierced[AM] = TRUE + structure_pierce++ + return TRUE + return FALSE + +/obj/item/projectile/beam/beam_rifle/proc/get_damage_coeff(atom/target) + if(istype(target, /obj/machinery/door)) + return 0.4 + if(istype(target, /obj/structure/window)) + return 0.5 + if(istype(target, /obj/structure/blob)) + return 0.65 //CIT CHANGE. + return 1 + +/obj/item/projectile/beam/beam_rifle/proc/handle_impact(atom/target) + if(isobj(target)) + var/obj/O = target + O.take_damage(impact_structure_damage * get_damage_coeff(target), BURN, "laser", FALSE) + if(isliving(target)) + var/mob/living/L = target + L.adjustFireLoss(impact_direct_damage) + L.emote("scream") + +/obj/item/projectile/beam/beam_rifle/proc/handle_hit(atom/target) + set waitfor = FALSE + if(!cached && !QDELETED(target)) + cached = get_turf(target) + if(nodamage) + return FALSE + playsound(cached, 'sound/effects/explosion3.ogg', 100, 1) + AOE(cached) + if(!QDELETED(target)) + handle_impact(target) + +/obj/item/projectile/beam/beam_rifle/Bump(atom/target) + if(check_pierce(target)) + permutated += target + trajectory_ignore_forcemove = TRUE + forceMove(target.loc) + trajectory_ignore_forcemove = FALSE + return FALSE + if(!QDELETED(target)) + cached = get_turf(target) + . = ..() + +/obj/item/projectile/beam/beam_rifle/on_hit(atom/target, blocked = FALSE) + if(!QDELETED(target)) + cached = get_turf(target) + handle_hit(target) + . = ..() + +/obj/item/projectile/beam/beam_rifle/hitscan + icon_state = "" + hitscan = TRUE + tracer_type = /obj/effect/projectile/tracer/tracer/beam_rifle + var/constant_tracer = FALSE + +/obj/item/projectile/beam/beam_rifle/hitscan/generate_hitscan_tracers(cleanup = TRUE, duration = 5, impacting = TRUE, highlander) + set waitfor = FALSE + if(isnull(highlander)) + highlander = constant_tracer + if(highlander && istype(gun)) + QDEL_LIST(gun.current_tracers) + for(var/datum/point/p in beam_segments) + gun.current_tracers += generate_tracer_between_points(p, beam_segments[p], tracer_type, color, 0, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity) + else + for(var/datum/point/p in beam_segments) + generate_tracer_between_points(p, beam_segments[p], tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity) + if(cleanup) + QDEL_LIST(beam_segments) + beam_segments = null + QDEL_NULL(beam_index) + +/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam + tracer_type = /obj/effect/projectile/tracer/tracer/aiming + name = "aiming beam" + hitsound = null + hitsound_wall = null + nodamage = TRUE + damage = 0 + constant_tracer = TRUE + hitscan_light_range = 0 + hitscan_light_intensity = 0 + hitscan_light_color_override = "#99ff99" + +/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/prehit(atom/target) + qdel(src) + return FALSE + +/obj/item/projectile/beam/beam_rifle/hitscan/aiming_beam/on_hit() + qdel(src) + return FALSE diff --git a/code/modules/projectiles/guns/misc/chem_gun.dm b/code/modules/projectiles/guns/misc/chem_gun.dm index 9f65c5ec24..9f8e92a8f8 100644 --- a/code/modules/projectiles/guns/misc/chem_gun.dm +++ b/code/modules/projectiles/guns/misc/chem_gun.dm @@ -1,46 +1,46 @@ -//his isn't a subtype of the syringe gun because the syringegun subtype is made to hold syringes -//this is meant to hold reagents/obj/item/gun/syringe -/obj/item/gun/chem - name = "reagent gun" - desc = "A Nanotrasen syringe gun, modified to automatically synthesise chemical darts, and instead hold reagents." - icon_state = "chemgun" - item_state = "chemgun" - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 3 - throw_range = 7 - force = 4 - materials = list(MAT_METAL=2000) - clumsy_check = FALSE - fire_sound = 'sound/items/syringeproj.ogg' - var/time_per_syringe = 250 - var/syringes_left = 4 - var/max_syringes = 4 - var/last_synth = 0 - -/obj/item/gun/chem/Initialize() - . = ..() - chambered = new /obj/item/ammo_casing/chemgun(src) - START_PROCESSING(SSobj, src) - create_reagents(100, OPENCONTAINER) - -/obj/item/gun/chem/Destroy() - . = ..() - STOP_PROCESSING(SSobj, src) - -/obj/item/gun/chem/can_shoot() - return syringes_left - -/obj/item/gun/chem/process_chamber() - if(chambered && !chambered.BB && syringes_left) - chambered.newshot() - -/obj/item/gun/chem/process() - if(syringes_left >= max_syringes) - return - if(world.time < last_synth+time_per_syringe) - return - to_chat(loc, "You hear a click as [src] synthesizes a new dart.") - syringes_left++ - if(chambered && !chambered.BB) - chambered.newshot() +//his isn't a subtype of the syringe gun because the syringegun subtype is made to hold syringes +//this is meant to hold reagents/obj/item/gun/syringe +/obj/item/gun/chem + name = "reagent gun" + desc = "A Nanotrasen syringe gun, modified to automatically synthesise chemical darts, and instead hold reagents." + icon_state = "chemgun" + item_state = "chemgun" + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 3 + throw_range = 7 + force = 4 + materials = list(MAT_METAL=2000) + clumsy_check = FALSE + fire_sound = 'sound/items/syringeproj.ogg' + var/time_per_syringe = 250 + var/syringes_left = 4 + var/max_syringes = 4 + var/last_synth = 0 + +/obj/item/gun/chem/Initialize() + . = ..() + chambered = new /obj/item/ammo_casing/chemgun(src) + START_PROCESSING(SSobj, src) + create_reagents(100, OPENCONTAINER) + +/obj/item/gun/chem/Destroy() + . = ..() + STOP_PROCESSING(SSobj, src) + +/obj/item/gun/chem/can_shoot() + return syringes_left + +/obj/item/gun/chem/process_chamber() + if(chambered && !chambered.BB && syringes_left) + chambered.newshot() + +/obj/item/gun/chem/process() + if(syringes_left >= max_syringes) + return + if(world.time < last_synth+time_per_syringe) + return + to_chat(loc, "You hear a click as [src] synthesizes a new dart.") + syringes_left++ + if(chambered && !chambered.BB) + chambered.newshot() last_synth = world.time \ No newline at end of file diff --git a/code/modules/projectiles/guns/misc/grenade_launcher.dm b/code/modules/projectiles/guns/misc/grenade_launcher.dm index dc44a1f4ff..5a8c5a066e 100644 --- a/code/modules/projectiles/guns/misc/grenade_launcher.dm +++ b/code/modules/projectiles/guns/misc/grenade_launcher.dm @@ -1,46 +1,46 @@ -/obj/item/gun/grenadelauncher - name = "grenade launcher" - desc = "A terrible, terrible thing. It's really awful!" - icon = 'icons/obj/guns/projectile.dmi' - icon_state = "riotgun" - item_state = "riotgun" - w_class = WEIGHT_CLASS_BULKY - throw_speed = 2 - throw_range = 7 - force = 5 - var/list/grenades = new/list() - var/max_grenades = 3 - materials = list(MAT_METAL=2000) - -/obj/item/gun/grenadelauncher/examine(mob/user) - . = ..() - . += "[grenades.len] / [max_grenades] grenades loaded." - -/obj/item/gun/grenadelauncher/attackby(obj/item/I, mob/user, params) - - if((istype(I, /obj/item/grenade))) - if(grenades.len < max_grenades) - if(!user.transferItemToLoc(I, src)) - return - grenades += I - to_chat(user, "You put the grenade in the grenade launcher.") - to_chat(user, "[grenades.len] / [max_grenades] Grenades.") - else - to_chat(usr, "The grenade launcher cannot hold more grenades.") - -/obj/item/gun/grenadelauncher/can_shoot() - return grenades.len - -/obj/item/gun/grenadelauncher/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) - user.visible_message("[user] fired a grenade!", \ - "You fire the grenade launcher!") - var/obj/item/grenade/F = grenades[1] //Now with less copypasta! - grenades -= F - F.forceMove(user.loc) - F.throw_at(target, 30, 2, user) - message_admins("[ADMIN_LOOKUPFLW(user)] fired a grenade ([F.name]) from a grenade launcher ([src]) from [AREACOORD(user)] at [target] [AREACOORD(target)].") - log_game("[key_name(user)] fired a grenade ([F.name]) with a grenade launcher ([src]) from [AREACOORD(user)] at [target] [AREACOORD(target)].") - F.active = 1 - F.icon_state = initial(F.icon_state) + "_active" - playsound(user.loc, 'sound/weapons/armbomb.ogg', 75, 1, -3) - addtimer(CALLBACK(F, /obj/item/grenade.proc/prime), 15) +/obj/item/gun/grenadelauncher + name = "grenade launcher" + desc = "A terrible, terrible thing. It's really awful!" + icon = 'icons/obj/guns/projectile.dmi' + icon_state = "riotgun" + item_state = "riotgun" + w_class = WEIGHT_CLASS_BULKY + throw_speed = 2 + throw_range = 7 + force = 5 + var/list/grenades = new/list() + var/max_grenades = 3 + materials = list(MAT_METAL=2000) + +/obj/item/gun/grenadelauncher/examine(mob/user) + . = ..() + . += "[grenades.len] / [max_grenades] grenades loaded." + +/obj/item/gun/grenadelauncher/attackby(obj/item/I, mob/user, params) + + if((istype(I, /obj/item/grenade))) + if(grenades.len < max_grenades) + if(!user.transferItemToLoc(I, src)) + return + grenades += I + to_chat(user, "You put the grenade in the grenade launcher.") + to_chat(user, "[grenades.len] / [max_grenades] Grenades.") + else + to_chat(usr, "The grenade launcher cannot hold more grenades.") + +/obj/item/gun/grenadelauncher/can_shoot() + return grenades.len + +/obj/item/gun/grenadelauncher/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) + user.visible_message("[user] fired a grenade!", \ + "You fire the grenade launcher!") + var/obj/item/grenade/F = grenades[1] //Now with less copypasta! + grenades -= F + F.forceMove(user.loc) + F.throw_at(target, 30, 2, user) + message_admins("[ADMIN_LOOKUPFLW(user)] fired a grenade ([F.name]) from a grenade launcher ([src]) from [AREACOORD(user)] at [target] [AREACOORD(target)].") + log_game("[key_name(user)] fired a grenade ([F.name]) with a grenade launcher ([src]) from [AREACOORD(user)] at [target] [AREACOORD(target)].") + F.active = 1 + F.icon_state = initial(F.icon_state) + "_active" + playsound(user.loc, 'sound/weapons/armbomb.ogg', 75, 1, -3) + addtimer(CALLBACK(F, /obj/item/grenade.proc/prime), 15) diff --git a/code/modules/projectiles/guns/misc/medbeam.dm b/code/modules/projectiles/guns/misc/medbeam.dm index 7bdefe5e91..8d800357df 100644 --- a/code/modules/projectiles/guns/misc/medbeam.dm +++ b/code/modules/projectiles/guns/misc/medbeam.dm @@ -1,135 +1,135 @@ -/obj/item/gun/medbeam - name = "Medical Beamgun" - desc = "Don't cross the streams!" - icon = 'icons/obj/chronos.dmi' - icon_state = "chronogun" - item_state = "chronogun" - w_class = WEIGHT_CLASS_NORMAL - - var/mob/living/current_target - var/last_check = 0 - var/check_delay = 10 //Check los as often as possible, max resolution is SSobj tick though - var/max_range = 8 - var/active = 0 - var/datum/beam/current_beam = null - var/mounted = 0 //Denotes if this is a handheld or mounted version - - weapon_weight = WEAPON_MEDIUM - -/obj/item/gun/medbeam/Initialize() - . = ..() - START_PROCESSING(SSobj, src) - -/obj/item/gun/medbeam/Destroy(mob/user) - STOP_PROCESSING(SSobj, src) - LoseTarget() - return ..() - -/obj/item/gun/medbeam/dropped(mob/user) - ..() - LoseTarget() - -/obj/item/gun/medbeam/equipped(mob/user) - ..() - LoseTarget() - -/obj/item/gun/medbeam/proc/LoseTarget() - if(active) - qdel(current_beam) - current_beam = null - active = 0 - on_beam_release(current_target) - current_target = null - -/obj/item/gun/medbeam/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) - if(isliving(user)) - add_fingerprint(user) - - if(current_target) - LoseTarget() - if(!isliving(target)) - return - - current_target = target - active = TRUE - current_beam = new(user,current_target,time=6000,beam_icon_state="medbeam",btype=/obj/effect/ebeam/medical) - INVOKE_ASYNC(current_beam, /datum/beam.proc/Start) - - SSblackbox.record_feedback("tally", "gun_fired", 1, type) - -/obj/item/gun/medbeam/process() - - var/source = loc - if(!mounted && !isliving(source)) - LoseTarget() - return - - if(!current_target) - LoseTarget() - return - - if(world.time <= last_check+check_delay) - return - - last_check = world.time - - if(get_dist(source, current_target)>max_range || !los_check(source, current_target)) - LoseTarget() - if(isliving(source)) - to_chat(source, "You lose control of the beam!") - return - - if(current_target) - on_beam_tick(current_target) - -/obj/item/gun/medbeam/proc/los_check(atom/movable/user, mob/target) - var/turf/user_turf = user.loc - if(mounted) - user_turf = get_turf(user) - else if(!istype(user_turf)) - return 0 - var/obj/dummy = new(user_turf) - dummy.pass_flags |= PASSTABLE|PASSGLASS|PASSGRILLE //Grille/Glass so it can be used through common windows - for(var/turf/turf in getline(user_turf,target)) - if(mounted && turf == user_turf) - continue //Mechs are dense and thus fail the check - if(turf.density) - qdel(dummy) - return 0 - for(var/atom/movable/AM in turf) - if(!AM.CanPass(dummy,turf,1)) - qdel(dummy) - return 0 - for(var/obj/effect/ebeam/medical/B in turf)// Don't cross the str-beams! - if(B.owner.origin != current_beam.origin) - explosion(B.loc,0,3,5,8) - qdel(dummy) - return 0 - qdel(dummy) - return 1 - -/obj/item/gun/medbeam/proc/on_beam_hit(var/mob/living/target) - return - -/obj/item/gun/medbeam/proc/on_beam_tick(var/mob/living/target) - if(target.health != target.maxHealth) - new /obj/effect/temp_visual/heal(get_turf(target), "#80F5FF") - target.adjustBruteLoss(-4) - target.adjustFireLoss(-4) - target.adjustToxLoss(-1, forced = TRUE) - target.adjustOxyLoss(-1) - return - -/obj/item/gun/medbeam/proc/on_beam_release(var/mob/living/target) - return - -/obj/effect/ebeam/medical - name = "medical beam" - -//////////////////////////////Mech Version/////////////////////////////// -/obj/item/gun/medbeam/mech - mounted = 1 - -/obj/item/gun/medbeam/mech/Initialize() - . = ..() - STOP_PROCESSING(SSobj, src) //Mech mediguns do not process until installed, and are controlled by the holder obj +/obj/item/gun/medbeam + name = "Medical Beamgun" + desc = "Don't cross the streams!" + icon = 'icons/obj/chronos.dmi' + icon_state = "chronogun" + item_state = "chronogun" + w_class = WEIGHT_CLASS_NORMAL + + var/mob/living/current_target + var/last_check = 0 + var/check_delay = 10 //Check los as often as possible, max resolution is SSobj tick though + var/max_range = 8 + var/active = 0 + var/datum/beam/current_beam = null + var/mounted = 0 //Denotes if this is a handheld or mounted version + + weapon_weight = WEAPON_MEDIUM + +/obj/item/gun/medbeam/Initialize() + . = ..() + START_PROCESSING(SSobj, src) + +/obj/item/gun/medbeam/Destroy(mob/user) + STOP_PROCESSING(SSobj, src) + LoseTarget() + return ..() + +/obj/item/gun/medbeam/dropped(mob/user) + ..() + LoseTarget() + +/obj/item/gun/medbeam/equipped(mob/user) + ..() + LoseTarget() + +/obj/item/gun/medbeam/proc/LoseTarget() + if(active) + qdel(current_beam) + current_beam = null + active = 0 + on_beam_release(current_target) + current_target = null + +/obj/item/gun/medbeam/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) + if(isliving(user)) + add_fingerprint(user) + + if(current_target) + LoseTarget() + if(!isliving(target)) + return + + current_target = target + active = TRUE + current_beam = new(user,current_target,time=6000,beam_icon_state="medbeam",btype=/obj/effect/ebeam/medical) + INVOKE_ASYNC(current_beam, /datum/beam.proc/Start) + + SSblackbox.record_feedback("tally", "gun_fired", 1, type) + +/obj/item/gun/medbeam/process() + + var/source = loc + if(!mounted && !isliving(source)) + LoseTarget() + return + + if(!current_target) + LoseTarget() + return + + if(world.time <= last_check+check_delay) + return + + last_check = world.time + + if(get_dist(source, current_target)>max_range || !los_check(source, current_target)) + LoseTarget() + if(isliving(source)) + to_chat(source, "You lose control of the beam!") + return + + if(current_target) + on_beam_tick(current_target) + +/obj/item/gun/medbeam/proc/los_check(atom/movable/user, mob/target) + var/turf/user_turf = user.loc + if(mounted) + user_turf = get_turf(user) + else if(!istype(user_turf)) + return 0 + var/obj/dummy = new(user_turf) + dummy.pass_flags |= PASSTABLE|PASSGLASS|PASSGRILLE //Grille/Glass so it can be used through common windows + for(var/turf/turf in getline(user_turf,target)) + if(mounted && turf == user_turf) + continue //Mechs are dense and thus fail the check + if(turf.density) + qdel(dummy) + return 0 + for(var/atom/movable/AM in turf) + if(!AM.CanPass(dummy,turf,1)) + qdel(dummy) + return 0 + for(var/obj/effect/ebeam/medical/B in turf)// Don't cross the str-beams! + if(B.owner.origin != current_beam.origin) + explosion(B.loc,0,3,5,8) + qdel(dummy) + return 0 + qdel(dummy) + return 1 + +/obj/item/gun/medbeam/proc/on_beam_hit(var/mob/living/target) + return + +/obj/item/gun/medbeam/proc/on_beam_tick(var/mob/living/target) + if(target.health != target.maxHealth) + new /obj/effect/temp_visual/heal(get_turf(target), "#80F5FF") + target.adjustBruteLoss(-4) + target.adjustFireLoss(-4) + target.adjustToxLoss(-1, forced = TRUE) + target.adjustOxyLoss(-1) + return + +/obj/item/gun/medbeam/proc/on_beam_release(var/mob/living/target) + return + +/obj/effect/ebeam/medical + name = "medical beam" + +//////////////////////////////Mech Version/////////////////////////////// +/obj/item/gun/medbeam/mech + mounted = 1 + +/obj/item/gun/medbeam/mech/Initialize() + . = ..() + STOP_PROCESSING(SSobj, src) //Mech mediguns do not process until installed, and are controlled by the holder obj diff --git a/code/modules/projectiles/guns/misc/syringe_gun.dm b/code/modules/projectiles/guns/misc/syringe_gun.dm index d947e3155d..3a39d53d82 100644 --- a/code/modules/projectiles/guns/misc/syringe_gun.dm +++ b/code/modules/projectiles/guns/misc/syringe_gun.dm @@ -1,167 +1,167 @@ -/obj/item/gun/syringe - name = "syringe gun" - desc = "A spring loaded rifle designed to fit syringes, used to incapacitate unruly patients from a distance." - icon_state = "syringegun" - item_state = "syringegun" - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 3 - throw_range = 7 - force = 4 - materials = list(MAT_METAL=2000) - clumsy_check = 0 - fire_sound = 'sound/items/syringeproj.ogg' - var/list/syringes = list() - var/max_syringes = 1 - -/obj/item/gun/syringe/Initialize() - . = ..() - chambered = new /obj/item/ammo_casing/syringegun(src) - -/obj/item/gun/syringe/recharge_newshot() - if(!syringes.len) - return - chambered.newshot() - -/obj/item/gun/syringe/can_shoot() - return syringes.len - -/obj/item/gun/syringe/process_chamber() - if(chambered && !chambered.BB) //we just fired - recharge_newshot() - -/obj/item/gun/syringe/examine(mob/user) - . = ..() - . += "Can hold [max_syringes] syringe\s. Has [syringes.len] syringe\s remaining." - -/obj/item/gun/syringe/attack_self(mob/living/user) - if(!syringes.len) - to_chat(user, "[src] is empty!") - return 0 - - var/obj/item/reagent_containers/syringe/S = syringes[syringes.len] - - if(!S) - return 0 - S.forceMove(user.loc) - - syringes.Remove(S) - to_chat(user, "You unload [S] from \the [src].") - - return 1 - -/obj/item/gun/syringe/attackby(obj/item/A, mob/user, params, show_msg = TRUE) - if(istype(A, /obj/item/reagent_containers/syringe)) - if(syringes.len < max_syringes) - if(!user.transferItemToLoc(A, src)) - return FALSE - to_chat(user, "You load [A] into \the [src].") - syringes += A - recharge_newshot() - return TRUE - else - to_chat(user, "[src] cannot hold more syringes!") - return FALSE - -/obj/item/gun/syringe/rapidsyringe - name = "rapid syringe gun" - desc = "A modification of the syringe gun design, using a rotating cylinder to store up to six syringes." - icon_state = "rapidsyringegun" - max_syringes = 6 - -/obj/item/gun/syringe/syndicate - name = "dart pistol" - desc = "A small spring-loaded sidearm that functions identically to a syringe gun." - icon_state = "syringe_pistol" - item_state = "gun" //Smaller inhand - w_class = WEIGHT_CLASS_SMALL - force = 2 //Also very weak because it's smaller - suppressed = TRUE //Softer fire sound - can_unsuppress = FALSE //Permanently silenced - -/obj/item/gun/syringe/dna - name = "modified syringe gun" - desc = "A syringe gun that has been modified to fit DNA injectors instead of normal syringes." - -/obj/item/gun/syringe/dna/Initialize() - . = ..() - chambered = new /obj/item/ammo_casing/dnainjector(src) - -/obj/item/gun/syringe/dna/attackby(obj/item/A, mob/user, params, show_msg = TRUE) - if(istype(A, /obj/item/dnainjector)) - var/obj/item/dnainjector/D = A - if(D.used) - to_chat(user, "This injector is used up!") - return - if(syringes.len < max_syringes) - if(!user.transferItemToLoc(D, src)) - return FALSE - to_chat(user, "You load \the [D] into \the [src].") - syringes += D - recharge_newshot() - return TRUE - else - to_chat(user, "[src] cannot hold more syringes!") - return FALSE - -/obj/item/gun/syringe/dart - name = "dart gun" - desc = "A compressed air gun, designed to fit medicinal darts for application of medicine for those patients just out of reach." - icon_state = "dartgun" - item_state = "dartgun" - materials = list(MAT_METAL=2000, MAT_GLASS=500) - suppressed = TRUE //Softer fire sound - can_unsuppress = FALSE - -/obj/item/gun/syringe/dart/Initialize() - ..() - chambered = new /obj/item/ammo_casing/syringegun/dart(src) - -/obj/item/gun/syringe/dart/attackby(obj/item/A, mob/user, params, show_msg = TRUE) - if(istype(A, /obj/item/reagent_containers/syringe/dart)) - ..() - else - to_chat(user, "You can't put the [A] into \the [src]!") - return FALSE - -/obj/item/gun/syringe/dart/rapiddart - name = "Repeating dart gun" - icon_state = "rapiddartgun" - item_state = "rapiddartgun" - -/obj/item/gun/syringe/dart/rapiddart/CheckParts(list/parts_list) - var/obj/item/reagent_containers/glass/beaker/B = locate(/obj/item/reagent_containers/glass/beaker) in parts_list - - if(istype(B, /obj/item/reagent_containers/glass/beaker/large)) - desc = "[initial(desc)] A modification of the dart gun's pressure chamber has been perfomed using a [B], extending it's holding size to [max_syringes]." - max_syringes = 2 - return - else if(istype(B, /obj/item/reagent_containers/glass/beaker/plastic)) - desc = "[initial(desc)] A modification of the dart gun's pressure chamber has been perfomed using a [B], extending it's holding size to [max_syringes]." - max_syringes = 3 - return - else if(istype(B, /obj/item/reagent_containers/glass/beaker/meta)) - desc = "[initial(desc)] A modification of the dart gun's pressure chamber has been perfomed using a [B], extending it's holding size to [max_syringes]." - max_syringes = 4 - return - else if(istype(B, /obj/item/reagent_containers/glass/beaker/bluespace)) - desc = "[initial(desc)] A modification of the dart gun's pressure chamber has been perfomed using a [B], extending it's holding size to [max_syringes]." - max_syringes = 6 - return - else - max_syringes = 1 - desc = "[initial(desc)] It has a [B] strapped to it, but it doesn't seem to be doing anything." - ..() - -/obj/item/gun/syringe/blowgun - name = "blowgun" - desc = "Fire syringes at a short distance." - icon_state = "blowgun" - item_state = "blowgun" - fire_sound = 'sound/items/syringeproj.ogg' - -/obj/item/gun/syringe/blowgun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) - visible_message("[user] starts aiming with a blowgun!") - if(do_after(user, 25, target = src)) - user.adjustStaminaLoss(20) - user.adjustOxyLoss(20) - ..() +/obj/item/gun/syringe + name = "syringe gun" + desc = "A spring loaded rifle designed to fit syringes, used to incapacitate unruly patients from a distance." + icon_state = "syringegun" + item_state = "syringegun" + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 3 + throw_range = 7 + force = 4 + materials = list(MAT_METAL=2000) + clumsy_check = 0 + fire_sound = 'sound/items/syringeproj.ogg' + var/list/syringes = list() + var/max_syringes = 1 + +/obj/item/gun/syringe/Initialize() + . = ..() + chambered = new /obj/item/ammo_casing/syringegun(src) + +/obj/item/gun/syringe/recharge_newshot() + if(!syringes.len) + return + chambered.newshot() + +/obj/item/gun/syringe/can_shoot() + return syringes.len + +/obj/item/gun/syringe/process_chamber() + if(chambered && !chambered.BB) //we just fired + recharge_newshot() + +/obj/item/gun/syringe/examine(mob/user) + . = ..() + . += "Can hold [max_syringes] syringe\s. Has [syringes.len] syringe\s remaining." + +/obj/item/gun/syringe/attack_self(mob/living/user) + if(!syringes.len) + to_chat(user, "[src] is empty!") + return 0 + + var/obj/item/reagent_containers/syringe/S = syringes[syringes.len] + + if(!S) + return 0 + S.forceMove(user.loc) + + syringes.Remove(S) + to_chat(user, "You unload [S] from \the [src].") + + return 1 + +/obj/item/gun/syringe/attackby(obj/item/A, mob/user, params, show_msg = TRUE) + if(istype(A, /obj/item/reagent_containers/syringe)) + if(syringes.len < max_syringes) + if(!user.transferItemToLoc(A, src)) + return FALSE + to_chat(user, "You load [A] into \the [src].") + syringes += A + recharge_newshot() + return TRUE + else + to_chat(user, "[src] cannot hold more syringes!") + return FALSE + +/obj/item/gun/syringe/rapidsyringe + name = "rapid syringe gun" + desc = "A modification of the syringe gun design, using a rotating cylinder to store up to six syringes." + icon_state = "rapidsyringegun" + max_syringes = 6 + +/obj/item/gun/syringe/syndicate + name = "dart pistol" + desc = "A small spring-loaded sidearm that functions identically to a syringe gun." + icon_state = "syringe_pistol" + item_state = "gun" //Smaller inhand + w_class = WEIGHT_CLASS_SMALL + force = 2 //Also very weak because it's smaller + suppressed = TRUE //Softer fire sound + can_unsuppress = FALSE //Permanently silenced + +/obj/item/gun/syringe/dna + name = "modified syringe gun" + desc = "A syringe gun that has been modified to fit DNA injectors instead of normal syringes." + +/obj/item/gun/syringe/dna/Initialize() + . = ..() + chambered = new /obj/item/ammo_casing/dnainjector(src) + +/obj/item/gun/syringe/dna/attackby(obj/item/A, mob/user, params, show_msg = TRUE) + if(istype(A, /obj/item/dnainjector)) + var/obj/item/dnainjector/D = A + if(D.used) + to_chat(user, "This injector is used up!") + return + if(syringes.len < max_syringes) + if(!user.transferItemToLoc(D, src)) + return FALSE + to_chat(user, "You load \the [D] into \the [src].") + syringes += D + recharge_newshot() + return TRUE + else + to_chat(user, "[src] cannot hold more syringes!") + return FALSE + +/obj/item/gun/syringe/dart + name = "dart gun" + desc = "A compressed air gun, designed to fit medicinal darts for application of medicine for those patients just out of reach." + icon_state = "dartgun" + item_state = "dartgun" + materials = list(MAT_METAL=2000, MAT_GLASS=500) + suppressed = TRUE //Softer fire sound + can_unsuppress = FALSE + +/obj/item/gun/syringe/dart/Initialize() + ..() + chambered = new /obj/item/ammo_casing/syringegun/dart(src) + +/obj/item/gun/syringe/dart/attackby(obj/item/A, mob/user, params, show_msg = TRUE) + if(istype(A, /obj/item/reagent_containers/syringe/dart)) + ..() + else + to_chat(user, "You can't put the [A] into \the [src]!") + return FALSE + +/obj/item/gun/syringe/dart/rapiddart + name = "Repeating dart gun" + icon_state = "rapiddartgun" + item_state = "rapiddartgun" + +/obj/item/gun/syringe/dart/rapiddart/CheckParts(list/parts_list) + var/obj/item/reagent_containers/glass/beaker/B = locate(/obj/item/reagent_containers/glass/beaker) in parts_list + + if(istype(B, /obj/item/reagent_containers/glass/beaker/large)) + desc = "[initial(desc)] A modification of the dart gun's pressure chamber has been perfomed using a [B], extending it's holding size to [max_syringes]." + max_syringes = 2 + return + else if(istype(B, /obj/item/reagent_containers/glass/beaker/plastic)) + desc = "[initial(desc)] A modification of the dart gun's pressure chamber has been perfomed using a [B], extending it's holding size to [max_syringes]." + max_syringes = 3 + return + else if(istype(B, /obj/item/reagent_containers/glass/beaker/meta)) + desc = "[initial(desc)] A modification of the dart gun's pressure chamber has been perfomed using a [B], extending it's holding size to [max_syringes]." + max_syringes = 4 + return + else if(istype(B, /obj/item/reagent_containers/glass/beaker/bluespace)) + desc = "[initial(desc)] A modification of the dart gun's pressure chamber has been perfomed using a [B], extending it's holding size to [max_syringes]." + max_syringes = 6 + return + else + max_syringes = 1 + desc = "[initial(desc)] It has a [B] strapped to it, but it doesn't seem to be doing anything." + ..() + +/obj/item/gun/syringe/blowgun + name = "blowgun" + desc = "Fire syringes at a short distance." + icon_state = "blowgun" + item_state = "blowgun" + fire_sound = 'sound/items/syringeproj.ogg' + +/obj/item/gun/syringe/blowgun/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) + visible_message("[user] starts aiming with a blowgun!") + if(do_after(user, 25, target = src)) + user.adjustStaminaLoss(20) + user.adjustOxyLoss(20) + ..() diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 04fffaf431..31eb34a4e1 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1,648 +1,648 @@ - -#define MOVES_HITSCAN -1 //Not actually hitscan but close as we get without actual hitscan. -#define MUZZLE_EFFECT_PIXEL_INCREMENT 17 //How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers. - -/obj/item/projectile - name = "projectile" - icon = 'icons/obj/projectiles.dmi' - icon_state = "bullet" - density = FALSE - anchored = TRUE - item_flags = ABSTRACT - pass_flags = PASSTABLE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - hitsound = 'sound/weapons/pierce.ogg' - var/hitsound_wall = "" - - resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/def_zone = "" //Aiming at - var/atom/movable/firer = null//Who shot it - var/atom/fired_from = null // the atom that the projectile was fired from (gun, turret) - var/suppressed = FALSE //Attack message - var/candink = FALSE //Can this projectile play the dink sound when hitting the head? - var/yo = null - var/xo = null - var/atom/original = null // the original target clicked - var/turf/starting = null // the projectile's starting turf - var/list/permutated = list() // we've passed through these atoms, don't try to hit them again - var/p_x = 16 - var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center - - //Fired processing vars - var/fired = FALSE //Have we been fired yet - var/paused = FALSE //for suspending the projectile midair - var/last_projectile_move = 0 - var/last_process = 0 - var/time_offset = 0 - var/datum/point/vector/trajectory - var/trajectory_ignore_forcemove = FALSE //instructs forceMove to NOT reset our trajectory to the new location! - - var/speed = 0.8 //Amount of deciseconds it takes for projectile to travel - var/Angle = 0 - var/original_angle = 0 //Angle at firing - var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle - var/spread = 0 //amount (in degrees) of projectile spread - animate_movement = 0 //Use SLIDE_STEPS in conjunction with legacy - var/ricochets = 0 - var/ricochets_max = 2 - var/ricochet_chance = 30 - - //Hitscan - var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored. - var/list/beam_segments //assoc list of datum/point or datum/point/vector, start = end. Used for hitscan effect generation. - var/datum/point/beam_index - var/turf/hitscan_last //last turf touched during hitscanning. - var/tracer_type - var/muzzle_type - var/impact_type - - //Fancy hitscan lighting effects! - var/hitscan_light_intensity = 1.5 - var/hitscan_light_range = 0.75 - var/hitscan_light_color_override - var/muzzle_flash_intensity = 3 - var/muzzle_flash_range = 1.5 - var/muzzle_flash_color_override - var/impact_light_intensity = 3 - var/impact_light_range = 2 - var/impact_light_color_override - - //Homing - var/homing = FALSE - var/atom/homing_target - var/homing_turn_speed = 10 //Angle per tick. - var/homing_inaccuracy_min = 0 //in pixels for these. offsets are set once when setting target. - var/homing_inaccuracy_max = 0 - var/homing_offset_x = 0 - var/homing_offset_y = 0 - - var/ignore_source_check = FALSE - - var/damage = 10 - var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here - var/nodamage = 0 //Determines if the projectile will skip any damage inflictions - var/flag = "bullet" //Defines what armor to use when it hits things. Must be set to bullet, laser, energy,or bomb - var/projectile_type = /obj/item/projectile - var/range = 50 //This will de-increment every step. When 0, it will deletze the projectile. - var/decayedRange - var/is_reflectable = FALSE // Can it be reflected or not? - //Effects - var/stun = 0 - var/knockdown = 0 - var/knockdown_stamoverride - var/knockdown_stam_max - var/unconscious = 0 - var/irradiate = 0 - var/stutter = 0 - var/slur = 0 - var/eyeblur = 0 - var/drowsy = 0 - var/stamina = 0 - var/jitter = 0 - var/forcedodge = 0 //to pass through everything - var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all. - var/impact_effect_type //what type of impact effect to show when hitting something - var/log_override = FALSE //is this type spammed enough to not log? (KAs) - -/obj/item/projectile/Initialize() - . = ..() - permutated = list() - decayedRange = range - -/obj/item/projectile/proc/Range() - range-- - if(range <= 0 && loc) - on_range() - -/obj/item/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range - qdel(src) - -//to get the correct limb (if any) for the projectile hit message -/mob/living/proc/check_limb_hit(hit_zone) - if(has_limbs) - return hit_zone - -/mob/living/carbon/check_limb_hit(hit_zone) - if(get_bodypart(hit_zone)) - return hit_zone - else //when a limb is missing the damage is actually passed to the chest - return BODY_ZONE_CHEST - -/obj/item/projectile/proc/prehit(atom/target) - return TRUE - -/obj/item/projectile/proc/on_hit(atom/target, blocked = FALSE) - if(fired_from) - SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle) - var/turf/target_loca = get_turf(target) - - var/hitx - var/hity - if(target == original) - hitx = target.pixel_x + p_x - 16 - hity = target.pixel_y + p_y - 16 - else - hitx = target.pixel_x + rand(-8, 8) - hity = target.pixel_y + rand(-8, 8) - - if(!nodamage && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_loca) && prob(75)) - var/turf/closed/wall/W = target_loca - if(impact_effect_type && !hitscan) - new impact_effect_type(target_loca, hitx, hity) - - W.add_dent(WALL_DENT_SHOT, hitx, hity) - - return 0 - - if(!isliving(target)) - if(impact_effect_type && !hitscan) - new impact_effect_type(target_loca, hitx, hity) - return 0 - - var/mob/living/L = target - - if(blocked != 100) // not completely blocked - if(damage && L.blood_volume && damage_type == BRUTE) - var/splatter_dir = dir - if(starting) - splatter_dir = get_dir(starting, target_loca) - var/obj/item/bodypart/B = L.get_bodypart(def_zone) - if(B && B.status == BODYPART_ROBOTIC) // So if you hit a robotic, it sparks instead of bloodspatters - do_sparks(2, FALSE, target.loc) - if(prob(25)) - new /obj/effect/decal/cleanable/oil(target_loca) - else if(isalien(L)) - new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir) - else - if(ishuman(target)) - var/mob/living/carbon/human/H = target - new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir, bloodtype_to_color(H.dna.blood_type)) - else - new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir, bloodtype_to_color()) - - if(iscarbon(L) && !HAS_TRAIT(L, TRAIT_NOMARROW)) - var/mob/living/carbon/C = L - C.bleed(damage) - else - L.add_splatter_floor(target_loca) - - else if(impact_effect_type && !hitscan) - new impact_effect_type(target_loca, hitx, hity) - - var/organ_hit_text = "" - var/limb_hit = L.check_limb_hit(def_zone)//to get the correct message info. - if(limb_hit) - organ_hit_text = " in \the [parse_zone(limb_hit)]" - if(suppressed) - playsound(loc, hitsound, 5, 1, -1) - to_chat(L, "You're shot by \a [src][organ_hit_text]!") - else - if(hitsound) - var/volume = vol_by_damage() - playsound(loc, hitsound, volume, 1, -1) - L.visible_message("[L] is hit by \a [src][organ_hit_text]!", \ - "[L] is hit by \a [src][organ_hit_text]!", null, COMBAT_MESSAGE_RANGE) - if(candink && def_zone == BODY_ZONE_HEAD) - playsound(src, 'sound/weapons/dink.ogg', 30, 1) - L.on_hit(src) - - var/reagent_note - if(reagents && reagents.reagent_list) - reagent_note = " REAGENTS:" - for(var/datum/reagent/R in reagents.reagent_list) - reagent_note += R.type + " (" - reagent_note += num2text(R.volume) + ") " - - if(ismob(firer)) - log_combat(firer, L, "shot", src, reagent_note) - else - L.log_message("has been shot by [firer] with [src]", LOG_ATTACK, color="orange") - - return L.apply_effects(stun, knockdown, unconscious, irradiate, slur, stutter, eyeblur, drowsy, blocked, stamina, jitter, knockdown_stamoverride, knockdown_stam_max) - -/obj/item/projectile/proc/vol_by_damage() - if(src.damage) - return CLAMP((src.damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then CLAMP the value between 30 and 100 - else - return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume - -/obj/item/projectile/proc/on_ricochet(atom/A) - return - -/obj/item/projectile/proc/store_hitscan_collision(datum/point/pcache) - beam_segments[beam_index] = pcache - beam_index = pcache - beam_segments[beam_index] = null - -/obj/item/projectile/Bump(atom/A) - if(trajectory && check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max) - var/datum/point/pcache = trajectory.copy_to() - ricochets++ - if(A.handle_ricochet(src)) - on_ricochet(A) - ignore_source_check = TRUE - range = initial(range) - if(hitscan) - store_hitscan_collision(pcache) - return TRUE - if(firer && !ignore_source_check) - if(A == firer || (A == firer.loc && ismecha(A))) //cannot shoot yourself or your mech - trajectory_ignore_forcemove = TRUE - forceMove(get_turf(A)) - trajectory_ignore_forcemove = FALSE - return FALSE - - var/distance = get_dist(get_turf(A), starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations. - def_zone = ran_zone(def_zone, max(100-(7*distance), 5)) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use. - - if(isturf(A) && hitsound_wall) - var/volume = CLAMP(vol_by_damage() + 20, 0, 100) - if(suppressed) - volume = 5 - playsound(loc, hitsound_wall, volume, 1, -1) - - var/turf/target_turf = get_turf(A) - - if(!prehit(A)) - if(forcedodge) - trajectory_ignore_forcemove = TRUE - forceMove(target_turf) - trajectory_ignore_forcemove = FALSE - return FALSE - - var/permutation = A.bullet_act(src, def_zone) // searches for return value, could be deleted after run so check A isn't null - if(permutation == -1 || forcedodge)// the bullet passes through a dense object! - trajectory_ignore_forcemove = TRUE - forceMove(target_turf) - trajectory_ignore_forcemove = FALSE - if(A) - permutated.Add(A) - return FALSE - else - var/atom/alt = select_target(A) - if(alt) - if(!prehit(alt)) - return FALSE - alt.bullet_act(src, def_zone) - qdel(src) - return TRUE - -/obj/item/projectile/proc/select_target(atom/A) //Selects another target from a wall if we hit a wall. - if(!A || !A.density || (A.flags_1 & ON_BORDER_1) || ismob(A) || A == original) //if we hit a dense non-border obj or dense turf then we also hit one of the mobs or machines/structures on that tile. - return - var/turf/T = get_turf(A) - if(original in T) - return original - var/list/mob/possible_mobs = typecache_filter_list(T, GLOB.typecache_mob) - A - var/list/mob/mobs = list() - for(var/i in possible_mobs) - var/mob/M = i - if(M.lying) - continue - mobs += M - var/mob/M = safepick(mobs) - if(M) - return M.lowest_buckled_mob() - var/obj/O = safepick(typecache_filter_list(T, GLOB.typecache_machine_or_structure) - A) - if(O) - return O - -/obj/item/projectile/proc/check_ricochet() - if(prob(ricochet_chance)) - return TRUE - return FALSE - -/obj/item/projectile/proc/check_ricochet_flag(atom/A) - if(A.flags_1 & CHECK_RICOCHET_1) - return TRUE - return FALSE - -/obj/item/projectile/proc/return_predicted_turf_after_moves(moves, forced_angle) //I say predicted because there's no telling that the projectile won't change direction/location in flight. - if(!trajectory && isnull(forced_angle) && isnull(Angle)) - return FALSE - var/datum/point/vector/current = trajectory - if(!current) - var/turf/T = get_turf(src) - current = new(T.x, T.y, T.z, pixel_x, pixel_y, isnull(forced_angle)? Angle : forced_angle, SSprojectiles.global_pixel_speed) - var/datum/point/vector/v = current.return_vector_after_increments(moves * SSprojectiles.global_iterations_per_move) - return v.return_turf() - -/obj/item/projectile/proc/return_pathing_turfs_in_moves(moves, forced_angle) - var/turf/current = get_turf(src) - var/turf/ending = return_predicted_turf_after_moves(moves, forced_angle) - return getline(current, ending) - -/obj/item/projectile/Process_Spacemove(var/movement_dir = 0) - return TRUE //Bullets don't drift in space - -/obj/item/projectile/process() - last_process = world.time - if(!loc || !fired || !trajectory) - fired = FALSE - return PROCESS_KILL - if(paused || !isturf(loc)) - last_projectile_move += world.time - last_process //Compensates for pausing, so it doesn't become a hitscan projectile when unpaused from charged up ticks. - return - var/elapsed_time_deciseconds = (world.time - last_projectile_move) + time_offset - time_offset = 0 - var/required_moves = speed > 0? FLOOR(elapsed_time_deciseconds / speed, 1) : MOVES_HITSCAN //Would be better if a 0 speed made hitscan but everyone hates those so I can't make it a universal system :< - if(required_moves == MOVES_HITSCAN) - required_moves = SSprojectiles.global_max_tick_moves - else - if(required_moves > SSprojectiles.global_max_tick_moves) - var/overrun = required_moves - SSprojectiles.global_max_tick_moves - required_moves = SSprojectiles.global_max_tick_moves - time_offset += overrun * speed - time_offset += MODULUS(elapsed_time_deciseconds, speed) - - for(var/i in 1 to required_moves) - pixel_move(1, FALSE) - -/obj/item/projectile/proc/fire(angle, atom/direct_target) - if(fired_from) - SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) - //If no angle needs to resolve it from xo/yo! - if(!log_override && firer && original) - log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]") - if(direct_target) - if(prehit(direct_target)) - direct_target.bullet_act(src, def_zone) - qdel(src) - return - if(isnum(angle)) - setAngle(angle) - if(spread) - setAngle(Angle + ((rand() - 0.5) * spread)) - var/turf/starting = get_turf(src) - if(isnull(Angle)) //Try to resolve through offsets if there's no angle set. - if(isnull(xo) || isnull(yo)) - stack_trace("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!") - qdel(src) - return - var/turf/target = locate(CLAMP(starting + xo, 1, world.maxx), CLAMP(starting + yo, 1, world.maxy), starting.z) - setAngle(Get_Angle(src, target)) - original_angle = Angle - if(!nondirectional_sprite) - var/matrix/M = new - M.Turn(Angle) - transform = M - trajectory_ignore_forcemove = TRUE - forceMove(starting) - trajectory_ignore_forcemove = FALSE - trajectory = new(starting.x, starting.y, starting.z, pixel_x, pixel_y, Angle, SSprojectiles.global_pixel_speed) - last_projectile_move = world.time - fired = TRUE - if(hitscan) - process_hitscan() - if(!(datum_flags & DF_ISPROCESSING)) - START_PROCESSING(SSprojectiles, src) - pixel_move(1, FALSE) //move it now! - -/obj/item/projectile/proc/setAngle(new_angle) //wrapper for overrides. - Angle = new_angle - if(!nondirectional_sprite) - var/matrix/M = new - M.Turn(Angle) - transform = M - if(trajectory) - trajectory.set_angle(new_angle) - return TRUE - -/obj/item/projectile/forceMove(atom/target) - if(!isloc(target) || !isloc(loc) || !z) - return ..() - var/zc = target.z != z - var/old = loc - if(zc) - before_z_change(old, target) - . = ..() - if(trajectory && !trajectory_ignore_forcemove && isturf(target)) - if(hitscan) - finalize_hitscan_and_generate_tracers(FALSE) - trajectory.initialize_location(target.x, target.y, target.z, 0, 0) - if(hitscan) - record_hitscan_start(RETURN_PRECISE_POINT(src)) - if(zc) - after_z_change(old, target) - -/obj/item/projectile/proc/after_z_change(atom/olcloc, atom/newloc) - -/obj/item/projectile/proc/before_z_change(atom/oldloc, atom/newloc) - -/obj/item/projectile/vv_edit_var(var_name, var_value) - switch(var_name) - if(NAMEOF(src, Angle)) - setAngle(var_value) - return TRUE - else - return ..() - -/obj/item/projectile/proc/set_pixel_speed(new_speed) - if(trajectory) - trajectory.set_speed(new_speed) - return TRUE - return FALSE - -/obj/item/projectile/proc/record_hitscan_start(datum/point/pcache) - if(pcache) - beam_segments = list() - beam_index = pcache - beam_segments[beam_index] = null //record start. - -/obj/item/projectile/proc/process_hitscan() - var/safety = range * 3 - record_hitscan_start(RETURN_POINT_VECTOR_INCREMENT(src, Angle, MUZZLE_EFFECT_PIXEL_INCREMENT, 1)) - while(loc && !QDELETED(src)) - if(paused) - stoplag(1) - continue - if(safety-- <= 0) - if(loc) - Bump(loc) - if(!QDELETED(src)) - qdel(src) - return //Kill! - pixel_move(1, TRUE) - -/obj/item/projectile/proc/pixel_move(trajectory_multiplier, hitscanning = FALSE) - if(!loc || !trajectory) - return - last_projectile_move = world.time - if(!nondirectional_sprite && !hitscanning) - var/matrix/M = new - M.Turn(Angle) - transform = M - if(homing) - process_homing() - var/forcemoved = FALSE - for(var/i in 1 to SSprojectiles.global_iterations_per_move) - if(QDELETED(src)) - return - trajectory.increment(trajectory_multiplier) - var/turf/T = trajectory.return_turf() - if(!istype(T)) - qdel(src) - return - if(T.z != loc.z) - var/old = loc - before_z_change(loc, T) - trajectory_ignore_forcemove = TRUE - forceMove(T) - trajectory_ignore_forcemove = FALSE - after_z_change(old, loc) - if(!hitscanning) - pixel_x = trajectory.return_px() - pixel_y = trajectory.return_py() - forcemoved = TRUE - hitscan_last = loc - else if(T != loc) - step_towards(src, T) - hitscan_last = loc - if(can_hit_target(original, permutated)) - Bump(original) - if(!hitscanning && !forcemoved) - pixel_x = trajectory.return_px() - trajectory.mpx * trajectory_multiplier * SSprojectiles.global_iterations_per_move - pixel_y = trajectory.return_py() - trajectory.mpy * trajectory_multiplier * SSprojectiles.global_iterations_per_move - animate(src, pixel_x = trajectory.return_px(), pixel_y = trajectory.return_py(), time = 1, flags = ANIMATION_END_NOW) - Range() - -/obj/item/projectile/proc/process_homing() //may need speeding up in the future performance wise. - if(!homing_target) - return FALSE - var/datum/point/PT = RETURN_PRECISE_POINT(homing_target) - PT.x += CLAMP(homing_offset_x, 1, world.maxx) - PT.y += CLAMP(homing_offset_y, 1, world.maxy) - var/angle = closer_angle_difference(Angle, angle_between_points(RETURN_PRECISE_POINT(src), PT)) - setAngle(Angle + CLAMP(angle, -homing_turn_speed, homing_turn_speed)) - -/obj/item/projectile/proc/set_homing_target(atom/A) - if(!A || (!isturf(A) && !isturf(A.loc))) - return FALSE - homing = TRUE - homing_target = A - homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max) - homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max) - if(prob(50)) - homing_offset_x = -homing_offset_x - if(prob(50)) - homing_offset_y = -homing_offset_y - -//Returns true if the target atom is on our current turf and above the right layer -/obj/item/projectile/proc/can_hit_target(atom/target, var/list/passthrough) - return (target && ((target.layer >= PROJECTILE_HIT_THRESHHOLD_LAYER) || ismob(target)) && (loc == get_turf(target)) && (!(target in passthrough))) - -//Spread is FORCED! -/obj/item/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0) - var/turf/curloc = get_turf(source) - var/turf/targloc = get_turf(target) - trajectory_ignore_forcemove = TRUE - forceMove(get_turf(source)) - trajectory_ignore_forcemove = FALSE - starting = get_turf(source) - original = target - if(targloc || !params) - yo = targloc.y - curloc.y - xo = targloc.x - curloc.x - setAngle(Get_Angle(src, targloc) + spread) - - if(isliving(source) && params) - var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, params) - p_x = calculated[2] - p_y = calculated[3] - - setAngle(calculated[1] + spread) - else if(targloc) - yo = targloc.y - curloc.y - xo = targloc.x - curloc.x - setAngle(Get_Angle(src, targloc) + spread) - else - stack_trace("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!") - qdel(src) - -/proc/calculate_projectile_angle_and_pixel_offsets(mob/user, params) - var/list/mouse_control = params2list(params) - var/p_x = 0 - var/p_y = 0 - var/angle = 0 - if(mouse_control["icon-x"]) - p_x = text2num(mouse_control["icon-x"]) - if(mouse_control["icon-y"]) - p_y = text2num(mouse_control["icon-y"]) - if(mouse_control["screen-loc"]) - //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/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 - - //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average. - var/list/screenview = getviewsize(user.client.view) - var/screenviewX = screenview[1] * world.icon_size - var/screenviewY = screenview[2] * world.icon_size - - var/ox = round(screenviewX/2) - user.client.pixel_x //"origin" x - var/oy = round(screenviewY/2) - user.client.pixel_y //"origin" y - angle = ATAN2(y - oy, x - ox) - return list(angle, p_x, p_y) - -/obj/item/projectile/Crossed(atom/movable/AM) //A mob moving on a tile with a projectile is hit by it. - ..() - if(isliving(AM) && (AM.density || AM == original) && !(src.pass_flags & PASSMOB)) - Bump(AM) - -/obj/item/projectile/Destroy() - if(hitscan) - finalize_hitscan_and_generate_tracers() - STOP_PROCESSING(SSprojectiles, src) - cleanup_beam_segments() - qdel(trajectory) - return ..() - -/obj/item/projectile/proc/cleanup_beam_segments() - QDEL_LIST_ASSOC(beam_segments) - beam_segments = list() - qdel(beam_index) - -/obj/item/projectile/proc/finalize_hitscan_and_generate_tracers(impacting = TRUE) - if(trajectory && beam_index) - var/datum/point/pcache = trajectory.copy_to() - beam_segments[beam_index] = pcache - generate_hitscan_tracers(null, null, impacting) - -/obj/item/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 3, impacting = TRUE) - if(!length(beam_segments)) - return - if(tracer_type) - var/tempref = REF(src) - for(var/datum/point/p in beam_segments) - generate_tracer_between_points(p, beam_segments[p], tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref) - if(muzzle_type && duration > 0) - var/datum/point/p = beam_segments[1] - var/atom/movable/thing = new muzzle_type - p.move_atom_to_src(thing) - var/matrix/M = new - M.Turn(original_angle) - thing.transform = M - thing.color = color - thing.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override? muzzle_flash_color_override : color) - QDEL_IN(thing, duration) - if(impacting && impact_type && duration > 0) - var/datum/point/p = beam_segments[beam_segments[beam_segments.len]] - var/atom/movable/thing = new impact_type - p.move_atom_to_src(thing) - var/matrix/M = new - M.Turn(Angle) - thing.transform = M - thing.color = color - thing.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color) - QDEL_IN(thing, duration) - if(cleanup) - cleanup_beam_segments() - -/obj/item/projectile/experience_pressure_difference() - return + +#define MOVES_HITSCAN -1 //Not actually hitscan but close as we get without actual hitscan. +#define MUZZLE_EFFECT_PIXEL_INCREMENT 17 //How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers. + +/obj/item/projectile + name = "projectile" + icon = 'icons/obj/projectiles.dmi' + icon_state = "bullet" + density = FALSE + anchored = TRUE + item_flags = ABSTRACT + pass_flags = PASSTABLE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + hitsound = 'sound/weapons/pierce.ogg' + var/hitsound_wall = "" + + resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/def_zone = "" //Aiming at + var/atom/movable/firer = null//Who shot it + var/atom/fired_from = null // the atom that the projectile was fired from (gun, turret) + var/suppressed = FALSE //Attack message + var/candink = FALSE //Can this projectile play the dink sound when hitting the head? + var/yo = null + var/xo = null + var/atom/original = null // the original target clicked + var/turf/starting = null // the projectile's starting turf + var/list/permutated = list() // we've passed through these atoms, don't try to hit them again + var/p_x = 16 + var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center + + //Fired processing vars + var/fired = FALSE //Have we been fired yet + var/paused = FALSE //for suspending the projectile midair + var/last_projectile_move = 0 + var/last_process = 0 + var/time_offset = 0 + var/datum/point/vector/trajectory + var/trajectory_ignore_forcemove = FALSE //instructs forceMove to NOT reset our trajectory to the new location! + + var/speed = 0.8 //Amount of deciseconds it takes for projectile to travel + var/Angle = 0 + var/original_angle = 0 //Angle at firing + var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle + var/spread = 0 //amount (in degrees) of projectile spread + animate_movement = 0 //Use SLIDE_STEPS in conjunction with legacy + var/ricochets = 0 + var/ricochets_max = 2 + var/ricochet_chance = 30 + + //Hitscan + var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored. + var/list/beam_segments //assoc list of datum/point or datum/point/vector, start = end. Used for hitscan effect generation. + var/datum/point/beam_index + var/turf/hitscan_last //last turf touched during hitscanning. + var/tracer_type + var/muzzle_type + var/impact_type + + //Fancy hitscan lighting effects! + var/hitscan_light_intensity = 1.5 + var/hitscan_light_range = 0.75 + var/hitscan_light_color_override + var/muzzle_flash_intensity = 3 + var/muzzle_flash_range = 1.5 + var/muzzle_flash_color_override + var/impact_light_intensity = 3 + var/impact_light_range = 2 + var/impact_light_color_override + + //Homing + var/homing = FALSE + var/atom/homing_target + var/homing_turn_speed = 10 //Angle per tick. + var/homing_inaccuracy_min = 0 //in pixels for these. offsets are set once when setting target. + var/homing_inaccuracy_max = 0 + var/homing_offset_x = 0 + var/homing_offset_y = 0 + + var/ignore_source_check = FALSE + + var/damage = 10 + var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here + var/nodamage = 0 //Determines if the projectile will skip any damage inflictions + var/flag = "bullet" //Defines what armor to use when it hits things. Must be set to bullet, laser, energy,or bomb + var/projectile_type = /obj/item/projectile + var/range = 50 //This will de-increment every step. When 0, it will deletze the projectile. + var/decayedRange + var/is_reflectable = FALSE // Can it be reflected or not? + //Effects + var/stun = 0 + var/knockdown = 0 + var/knockdown_stamoverride + var/knockdown_stam_max + var/unconscious = 0 + var/irradiate = 0 + var/stutter = 0 + var/slur = 0 + var/eyeblur = 0 + var/drowsy = 0 + var/stamina = 0 + var/jitter = 0 + var/forcedodge = 0 //to pass through everything + var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all. + var/impact_effect_type //what type of impact effect to show when hitting something + var/log_override = FALSE //is this type spammed enough to not log? (KAs) + +/obj/item/projectile/Initialize() + . = ..() + permutated = list() + decayedRange = range + +/obj/item/projectile/proc/Range() + range-- + if(range <= 0 && loc) + on_range() + +/obj/item/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range + qdel(src) + +//to get the correct limb (if any) for the projectile hit message +/mob/living/proc/check_limb_hit(hit_zone) + if(has_limbs) + return hit_zone + +/mob/living/carbon/check_limb_hit(hit_zone) + if(get_bodypart(hit_zone)) + return hit_zone + else //when a limb is missing the damage is actually passed to the chest + return BODY_ZONE_CHEST + +/obj/item/projectile/proc/prehit(atom/target) + return TRUE + +/obj/item/projectile/proc/on_hit(atom/target, blocked = FALSE) + if(fired_from) + SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle) + var/turf/target_loca = get_turf(target) + + var/hitx + var/hity + if(target == original) + hitx = target.pixel_x + p_x - 16 + hity = target.pixel_y + p_y - 16 + else + hitx = target.pixel_x + rand(-8, 8) + hity = target.pixel_y + rand(-8, 8) + + if(!nodamage && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_loca) && prob(75)) + var/turf/closed/wall/W = target_loca + if(impact_effect_type && !hitscan) + new impact_effect_type(target_loca, hitx, hity) + + W.add_dent(WALL_DENT_SHOT, hitx, hity) + + return 0 + + if(!isliving(target)) + if(impact_effect_type && !hitscan) + new impact_effect_type(target_loca, hitx, hity) + return 0 + + var/mob/living/L = target + + if(blocked != 100) // not completely blocked + if(damage && L.blood_volume && damage_type == BRUTE) + var/splatter_dir = dir + if(starting) + splatter_dir = get_dir(starting, target_loca) + var/obj/item/bodypart/B = L.get_bodypart(def_zone) + if(B && B.status == BODYPART_ROBOTIC) // So if you hit a robotic, it sparks instead of bloodspatters + do_sparks(2, FALSE, target.loc) + if(prob(25)) + new /obj/effect/decal/cleanable/oil(target_loca) + else if(isalien(L)) + new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir) + else + if(ishuman(target)) + var/mob/living/carbon/human/H = target + new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir, bloodtype_to_color(H.dna.blood_type)) + else + new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir, bloodtype_to_color()) + + if(iscarbon(L) && !HAS_TRAIT(L, TRAIT_NOMARROW)) + var/mob/living/carbon/C = L + C.bleed(damage) + else + L.add_splatter_floor(target_loca) + + else if(impact_effect_type && !hitscan) + new impact_effect_type(target_loca, hitx, hity) + + var/organ_hit_text = "" + var/limb_hit = L.check_limb_hit(def_zone)//to get the correct message info. + if(limb_hit) + organ_hit_text = " in \the [parse_zone(limb_hit)]" + if(suppressed) + playsound(loc, hitsound, 5, 1, -1) + to_chat(L, "You're shot by \a [src][organ_hit_text]!") + else + if(hitsound) + var/volume = vol_by_damage() + playsound(loc, hitsound, volume, 1, -1) + L.visible_message("[L] is hit by \a [src][organ_hit_text]!", \ + "[L] is hit by \a [src][organ_hit_text]!", null, COMBAT_MESSAGE_RANGE) + if(candink && def_zone == BODY_ZONE_HEAD) + playsound(src, 'sound/weapons/dink.ogg', 30, 1) + L.on_hit(src) + + var/reagent_note + if(reagents && reagents.reagent_list) + reagent_note = " REAGENTS:" + for(var/datum/reagent/R in reagents.reagent_list) + reagent_note += R.type + " (" + reagent_note += num2text(R.volume) + ") " + + if(ismob(firer)) + log_combat(firer, L, "shot", src, reagent_note) + else + L.log_message("has been shot by [firer] with [src]", LOG_ATTACK, color="orange") + + return L.apply_effects(stun, knockdown, unconscious, irradiate, slur, stutter, eyeblur, drowsy, blocked, stamina, jitter, knockdown_stamoverride, knockdown_stam_max) + +/obj/item/projectile/proc/vol_by_damage() + if(src.damage) + return CLAMP((src.damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then CLAMP the value between 30 and 100 + else + return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume + +/obj/item/projectile/proc/on_ricochet(atom/A) + return + +/obj/item/projectile/proc/store_hitscan_collision(datum/point/pcache) + beam_segments[beam_index] = pcache + beam_index = pcache + beam_segments[beam_index] = null + +/obj/item/projectile/Bump(atom/A) + if(trajectory && check_ricochet(A) && check_ricochet_flag(A) && ricochets < ricochets_max) + var/datum/point/pcache = trajectory.copy_to() + ricochets++ + if(A.handle_ricochet(src)) + on_ricochet(A) + ignore_source_check = TRUE + range = initial(range) + if(hitscan) + store_hitscan_collision(pcache) + return TRUE + if(firer && !ignore_source_check) + if(A == firer || (A == firer.loc && ismecha(A))) //cannot shoot yourself or your mech + trajectory_ignore_forcemove = TRUE + forceMove(get_turf(A)) + trajectory_ignore_forcemove = FALSE + return FALSE + + var/distance = get_dist(get_turf(A), starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations. + def_zone = ran_zone(def_zone, max(100-(7*distance), 5)) //Lower accurancy/longer range tradeoff. 7 is a balanced number to use. + + if(isturf(A) && hitsound_wall) + var/volume = CLAMP(vol_by_damage() + 20, 0, 100) + if(suppressed) + volume = 5 + playsound(loc, hitsound_wall, volume, 1, -1) + + var/turf/target_turf = get_turf(A) + + if(!prehit(A)) + if(forcedodge) + trajectory_ignore_forcemove = TRUE + forceMove(target_turf) + trajectory_ignore_forcemove = FALSE + return FALSE + + var/permutation = A.bullet_act(src, def_zone) // searches for return value, could be deleted after run so check A isn't null + if(permutation == -1 || forcedodge)// the bullet passes through a dense object! + trajectory_ignore_forcemove = TRUE + forceMove(target_turf) + trajectory_ignore_forcemove = FALSE + if(A) + permutated.Add(A) + return FALSE + else + var/atom/alt = select_target(A) + if(alt) + if(!prehit(alt)) + return FALSE + alt.bullet_act(src, def_zone) + qdel(src) + return TRUE + +/obj/item/projectile/proc/select_target(atom/A) //Selects another target from a wall if we hit a wall. + if(!A || !A.density || (A.flags_1 & ON_BORDER_1) || ismob(A) || A == original) //if we hit a dense non-border obj or dense turf then we also hit one of the mobs or machines/structures on that tile. + return + var/turf/T = get_turf(A) + if(original in T) + return original + var/list/mob/possible_mobs = typecache_filter_list(T, GLOB.typecache_mob) - A + var/list/mob/mobs = list() + for(var/i in possible_mobs) + var/mob/M = i + if(M.lying) + continue + mobs += M + var/mob/M = safepick(mobs) + if(M) + return M.lowest_buckled_mob() + var/obj/O = safepick(typecache_filter_list(T, GLOB.typecache_machine_or_structure) - A) + if(O) + return O + +/obj/item/projectile/proc/check_ricochet() + if(prob(ricochet_chance)) + return TRUE + return FALSE + +/obj/item/projectile/proc/check_ricochet_flag(atom/A) + if(A.flags_1 & CHECK_RICOCHET_1) + return TRUE + return FALSE + +/obj/item/projectile/proc/return_predicted_turf_after_moves(moves, forced_angle) //I say predicted because there's no telling that the projectile won't change direction/location in flight. + if(!trajectory && isnull(forced_angle) && isnull(Angle)) + return FALSE + var/datum/point/vector/current = trajectory + if(!current) + var/turf/T = get_turf(src) + current = new(T.x, T.y, T.z, pixel_x, pixel_y, isnull(forced_angle)? Angle : forced_angle, SSprojectiles.global_pixel_speed) + var/datum/point/vector/v = current.return_vector_after_increments(moves * SSprojectiles.global_iterations_per_move) + return v.return_turf() + +/obj/item/projectile/proc/return_pathing_turfs_in_moves(moves, forced_angle) + var/turf/current = get_turf(src) + var/turf/ending = return_predicted_turf_after_moves(moves, forced_angle) + return getline(current, ending) + +/obj/item/projectile/Process_Spacemove(var/movement_dir = 0) + return TRUE //Bullets don't drift in space + +/obj/item/projectile/process() + last_process = world.time + if(!loc || !fired || !trajectory) + fired = FALSE + return PROCESS_KILL + if(paused || !isturf(loc)) + last_projectile_move += world.time - last_process //Compensates for pausing, so it doesn't become a hitscan projectile when unpaused from charged up ticks. + return + var/elapsed_time_deciseconds = (world.time - last_projectile_move) + time_offset + time_offset = 0 + var/required_moves = speed > 0? FLOOR(elapsed_time_deciseconds / speed, 1) : MOVES_HITSCAN //Would be better if a 0 speed made hitscan but everyone hates those so I can't make it a universal system :< + if(required_moves == MOVES_HITSCAN) + required_moves = SSprojectiles.global_max_tick_moves + else + if(required_moves > SSprojectiles.global_max_tick_moves) + var/overrun = required_moves - SSprojectiles.global_max_tick_moves + required_moves = SSprojectiles.global_max_tick_moves + time_offset += overrun * speed + time_offset += MODULUS(elapsed_time_deciseconds, speed) + + for(var/i in 1 to required_moves) + pixel_move(1, FALSE) + +/obj/item/projectile/proc/fire(angle, atom/direct_target) + if(fired_from) + SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) + //If no angle needs to resolve it from xo/yo! + if(!log_override && firer && original) + log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]") + if(direct_target) + if(prehit(direct_target)) + direct_target.bullet_act(src, def_zone) + qdel(src) + return + if(isnum(angle)) + setAngle(angle) + if(spread) + setAngle(Angle + ((rand() - 0.5) * spread)) + var/turf/starting = get_turf(src) + if(isnull(Angle)) //Try to resolve through offsets if there's no angle set. + if(isnull(xo) || isnull(yo)) + stack_trace("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!") + qdel(src) + return + var/turf/target = locate(CLAMP(starting + xo, 1, world.maxx), CLAMP(starting + yo, 1, world.maxy), starting.z) + setAngle(Get_Angle(src, target)) + original_angle = Angle + if(!nondirectional_sprite) + var/matrix/M = new + M.Turn(Angle) + transform = M + trajectory_ignore_forcemove = TRUE + forceMove(starting) + trajectory_ignore_forcemove = FALSE + trajectory = new(starting.x, starting.y, starting.z, pixel_x, pixel_y, Angle, SSprojectiles.global_pixel_speed) + last_projectile_move = world.time + fired = TRUE + if(hitscan) + process_hitscan() + if(!(datum_flags & DF_ISPROCESSING)) + START_PROCESSING(SSprojectiles, src) + pixel_move(1, FALSE) //move it now! + +/obj/item/projectile/proc/setAngle(new_angle) //wrapper for overrides. + Angle = new_angle + if(!nondirectional_sprite) + var/matrix/M = new + M.Turn(Angle) + transform = M + if(trajectory) + trajectory.set_angle(new_angle) + return TRUE + +/obj/item/projectile/forceMove(atom/target) + if(!isloc(target) || !isloc(loc) || !z) + return ..() + var/zc = target.z != z + var/old = loc + if(zc) + before_z_change(old, target) + . = ..() + if(trajectory && !trajectory_ignore_forcemove && isturf(target)) + if(hitscan) + finalize_hitscan_and_generate_tracers(FALSE) + trajectory.initialize_location(target.x, target.y, target.z, 0, 0) + if(hitscan) + record_hitscan_start(RETURN_PRECISE_POINT(src)) + if(zc) + after_z_change(old, target) + +/obj/item/projectile/proc/after_z_change(atom/olcloc, atom/newloc) + +/obj/item/projectile/proc/before_z_change(atom/oldloc, atom/newloc) + +/obj/item/projectile/vv_edit_var(var_name, var_value) + switch(var_name) + if(NAMEOF(src, Angle)) + setAngle(var_value) + return TRUE + else + return ..() + +/obj/item/projectile/proc/set_pixel_speed(new_speed) + if(trajectory) + trajectory.set_speed(new_speed) + return TRUE + return FALSE + +/obj/item/projectile/proc/record_hitscan_start(datum/point/pcache) + if(pcache) + beam_segments = list() + beam_index = pcache + beam_segments[beam_index] = null //record start. + +/obj/item/projectile/proc/process_hitscan() + var/safety = range * 3 + record_hitscan_start(RETURN_POINT_VECTOR_INCREMENT(src, Angle, MUZZLE_EFFECT_PIXEL_INCREMENT, 1)) + while(loc && !QDELETED(src)) + if(paused) + stoplag(1) + continue + if(safety-- <= 0) + if(loc) + Bump(loc) + if(!QDELETED(src)) + qdel(src) + return //Kill! + pixel_move(1, TRUE) + +/obj/item/projectile/proc/pixel_move(trajectory_multiplier, hitscanning = FALSE) + if(!loc || !trajectory) + return + last_projectile_move = world.time + if(!nondirectional_sprite && !hitscanning) + var/matrix/M = new + M.Turn(Angle) + transform = M + if(homing) + process_homing() + var/forcemoved = FALSE + for(var/i in 1 to SSprojectiles.global_iterations_per_move) + if(QDELETED(src)) + return + trajectory.increment(trajectory_multiplier) + var/turf/T = trajectory.return_turf() + if(!istype(T)) + qdel(src) + return + if(T.z != loc.z) + var/old = loc + before_z_change(loc, T) + trajectory_ignore_forcemove = TRUE + forceMove(T) + trajectory_ignore_forcemove = FALSE + after_z_change(old, loc) + if(!hitscanning) + pixel_x = trajectory.return_px() + pixel_y = trajectory.return_py() + forcemoved = TRUE + hitscan_last = loc + else if(T != loc) + step_towards(src, T) + hitscan_last = loc + if(can_hit_target(original, permutated)) + Bump(original) + if(!hitscanning && !forcemoved) + pixel_x = trajectory.return_px() - trajectory.mpx * trajectory_multiplier * SSprojectiles.global_iterations_per_move + pixel_y = trajectory.return_py() - trajectory.mpy * trajectory_multiplier * SSprojectiles.global_iterations_per_move + animate(src, pixel_x = trajectory.return_px(), pixel_y = trajectory.return_py(), time = 1, flags = ANIMATION_END_NOW) + Range() + +/obj/item/projectile/proc/process_homing() //may need speeding up in the future performance wise. + if(!homing_target) + return FALSE + var/datum/point/PT = RETURN_PRECISE_POINT(homing_target) + PT.x += CLAMP(homing_offset_x, 1, world.maxx) + PT.y += CLAMP(homing_offset_y, 1, world.maxy) + var/angle = closer_angle_difference(Angle, angle_between_points(RETURN_PRECISE_POINT(src), PT)) + setAngle(Angle + CLAMP(angle, -homing_turn_speed, homing_turn_speed)) + +/obj/item/projectile/proc/set_homing_target(atom/A) + if(!A || (!isturf(A) && !isturf(A.loc))) + return FALSE + homing = TRUE + homing_target = A + homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max) + homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max) + if(prob(50)) + homing_offset_x = -homing_offset_x + if(prob(50)) + homing_offset_y = -homing_offset_y + +//Returns true if the target atom is on our current turf and above the right layer +/obj/item/projectile/proc/can_hit_target(atom/target, var/list/passthrough) + return (target && ((target.layer >= PROJECTILE_HIT_THRESHHOLD_LAYER) || ismob(target)) && (loc == get_turf(target)) && (!(target in passthrough))) + +//Spread is FORCED! +/obj/item/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0) + var/turf/curloc = get_turf(source) + var/turf/targloc = get_turf(target) + trajectory_ignore_forcemove = TRUE + forceMove(get_turf(source)) + trajectory_ignore_forcemove = FALSE + starting = get_turf(source) + original = target + if(targloc || !params) + yo = targloc.y - curloc.y + xo = targloc.x - curloc.x + setAngle(Get_Angle(src, targloc) + spread) + + if(isliving(source) && params) + var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, params) + p_x = calculated[2] + p_y = calculated[3] + + setAngle(calculated[1] + spread) + else if(targloc) + yo = targloc.y - curloc.y + xo = targloc.x - curloc.x + setAngle(Get_Angle(src, targloc) + spread) + else + stack_trace("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!") + qdel(src) + +/proc/calculate_projectile_angle_and_pixel_offsets(mob/user, params) + var/list/mouse_control = params2list(params) + var/p_x = 0 + var/p_y = 0 + var/angle = 0 + if(mouse_control["icon-x"]) + p_x = text2num(mouse_control["icon-x"]) + if(mouse_control["icon-y"]) + p_y = text2num(mouse_control["icon-y"]) + if(mouse_control["screen-loc"]) + //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/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 + + //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average. + var/list/screenview = getviewsize(user.client.view) + var/screenviewX = screenview[1] * world.icon_size + var/screenviewY = screenview[2] * world.icon_size + + var/ox = round(screenviewX/2) - user.client.pixel_x //"origin" x + var/oy = round(screenviewY/2) - user.client.pixel_y //"origin" y + angle = ATAN2(y - oy, x - ox) + return list(angle, p_x, p_y) + +/obj/item/projectile/Crossed(atom/movable/AM) //A mob moving on a tile with a projectile is hit by it. + ..() + if(isliving(AM) && (AM.density || AM == original) && !(src.pass_flags & PASSMOB)) + Bump(AM) + +/obj/item/projectile/Destroy() + if(hitscan) + finalize_hitscan_and_generate_tracers() + STOP_PROCESSING(SSprojectiles, src) + cleanup_beam_segments() + qdel(trajectory) + return ..() + +/obj/item/projectile/proc/cleanup_beam_segments() + QDEL_LIST_ASSOC(beam_segments) + beam_segments = list() + qdel(beam_index) + +/obj/item/projectile/proc/finalize_hitscan_and_generate_tracers(impacting = TRUE) + if(trajectory && beam_index) + var/datum/point/pcache = trajectory.copy_to() + beam_segments[beam_index] = pcache + generate_hitscan_tracers(null, null, impacting) + +/obj/item/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 3, impacting = TRUE) + if(!length(beam_segments)) + return + if(tracer_type) + var/tempref = REF(src) + for(var/datum/point/p in beam_segments) + generate_tracer_between_points(p, beam_segments[p], tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref) + if(muzzle_type && duration > 0) + var/datum/point/p = beam_segments[1] + var/atom/movable/thing = new muzzle_type + p.move_atom_to_src(thing) + var/matrix/M = new + M.Turn(original_angle) + thing.transform = M + thing.color = color + thing.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override? muzzle_flash_color_override : color) + QDEL_IN(thing, duration) + if(impacting && impact_type && duration > 0) + var/datum/point/p = beam_segments[beam_segments[beam_segments.len]] + var/atom/movable/thing = new impact_type + p.move_atom_to_src(thing) + var/matrix/M = new + M.Turn(Angle) + thing.transform = M + thing.color = color + thing.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color) + QDEL_IN(thing, duration) + if(cleanup) + cleanup_beam_segments() + +/obj/item/projectile/experience_pressure_difference() + return diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index be73e22e6d..d984217f8c 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -1,210 +1,210 @@ -/obj/item/projectile/beam - name = "laser" - icon_state = "laser" - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - damage = 20 - light_range = 2 - damage_type = BURN - hitsound = 'sound/weapons/sear.ogg' - hitsound_wall = 'sound/weapons/effects/searwall.ogg' - flag = "laser" - eyeblur = 2 - impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser - light_color = LIGHT_COLOR_RED - ricochets_max = 50 //Honk! - ricochet_chance = 80 - is_reflectable = TRUE - -/obj/item/projectile/beam/laser - tracer_type = /obj/effect/projectile/tracer/laser - muzzle_type = /obj/effect/projectile/muzzle/laser - impact_type = /obj/effect/projectile/impact/laser - -/obj/item/projectile/beam/laser/heavylaser - name = "heavy laser" - icon_state = "heavylaser" - damage = 40 - tracer_type = /obj/effect/projectile/tracer/heavy_laser - muzzle_type = /obj/effect/projectile/muzzle/heavy_laser - impact_type = /obj/effect/projectile/impact/heavy_laser - -/obj/item/projectile/beam/laser/on_hit(atom/target, blocked = FALSE) - . = ..() - if(iscarbon(target)) - var/mob/living/carbon/M = target - M.IgniteMob() - else if(isturf(target)) - impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser/wall - -/obj/item/projectile/beam/weak - damage = 15 - -/obj/item/projectile/beam/weak/penetrator - armour_penetration = 50 - -/obj/item/projectile/beam/practice - name = "practice laser" - damage = 0 - nodamage = 1 - -/obj/item/projectile/beam/scatter - name = "laser pellet" - icon_state = "scatterlaser" - damage = 5 - -/obj/item/projectile/beam/xray - name = "\improper X-ray beam" - icon_state = "xray" - damage = 15 - irradiate = 300 - range = 15 - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF - - impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser - light_color = LIGHT_COLOR_GREEN - tracer_type = /obj/effect/projectile/tracer/xray - muzzle_type = /obj/effect/projectile/muzzle/xray - impact_type = /obj/effect/projectile/impact/xray - -/obj/item/projectile/beam/disabler - name = "disabler beam" - icon_state = "omnilaser" - damage = 28 // Citadel change for balance from 36 - damage_type = STAMINA - flag = "energy" - hitsound = 'sound/weapons/tap.ogg' - eyeblur = 0 - speed = 0.6 - impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser - light_color = LIGHT_COLOR_BLUE - tracer_type = /obj/effect/projectile/tracer/disabler - muzzle_type = /obj/effect/projectile/muzzle/disabler - impact_type = /obj/effect/projectile/impact/disabler - -/obj/item/projectile/beam/pulse - name = "pulse" - icon_state = "u_laser" - damage = 50 - impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser - light_color = LIGHT_COLOR_BLUE - tracer_type = /obj/effect/projectile/tracer/pulse - muzzle_type = /obj/effect/projectile/muzzle/pulse - impact_type = /obj/effect/projectile/impact/pulse - -/obj/item/projectile/beam/pulse/on_hit(atom/target, blocked = FALSE) - . = ..() - if (!QDELETED(target) && (isturf(target) || istype(target, /obj/structure/))) - target.ex_act(EXPLODE_HEAVY) - -/obj/item/projectile/beam/pulse/shotgun - damage = 40 - -/obj/item/projectile/beam/pulse/heavy - name = "heavy pulse laser" - icon_state = "pulse1_bl" - var/life = 20 - -/obj/item/projectile/beam/pulse/heavy/on_hit(atom/target, blocked = FALSE) - life -= 10 - if(life > 0) - . = -1 - ..() - -/obj/item/projectile/beam/emitter - name = "emitter beam" - icon_state = "emitter" - damage = 30 - impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser - light_color = LIGHT_COLOR_GREEN - -/obj/item/projectile/beam/emitter/singularity_pull() - return //don't want the emitters to miss - -/obj/item/projectile/beam/lasertag - name = "laser tag beam" - icon_state = "omnilaser" - hitsound = null - damage = 0 - damage_type = STAMINA - flag = "laser" - var/suit_types = list(/obj/item/clothing/suit/redtag, /obj/item/clothing/suit/bluetag) - impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser - light_color = LIGHT_COLOR_BLUE - -/obj/item/projectile/beam/lasertag/on_hit(atom/target, blocked = FALSE) - . = ..() - if(ishuman(target)) - var/mob/living/carbon/human/M = target - if(istype(M.wear_suit)) - if(M.wear_suit.type in suit_types) - M.adjustStaminaLoss(34) - -/obj/item/projectile/beam/lasertag/redtag - icon_state = "laser" - suit_types = list(/obj/item/clothing/suit/bluetag) - impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser - light_color = LIGHT_COLOR_RED - tracer_type = /obj/effect/projectile/tracer/laser - muzzle_type = /obj/effect/projectile/muzzle/laser - impact_type = /obj/effect/projectile/impact/laser - -/obj/item/projectile/beam/lasertag/redtag/hitscan - hitscan = TRUE - -/obj/item/projectile/beam/lasertag/redtag/hitscan/holy - name = "lasrifle beam" - damage = 0.1 - damage_type = BURN - -/obj/item/projectile/beam/lasertag/bluetag - icon_state = "bluelaser" - suit_types = list(/obj/item/clothing/suit/redtag) - tracer_type = /obj/effect/projectile/tracer/laser/blue - muzzle_type = /obj/effect/projectile/muzzle/laser/blue - impact_type = /obj/effect/projectile/impact/laser/blue - -/obj/item/projectile/beam/lasertag/bluetag/hitscan - hitscan = TRUE - -/obj/item/projectile/beam/instakill - name = "instagib laser" - icon_state = "purple_laser" - damage = 200 - damage_type = BURN - impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser - light_color = LIGHT_COLOR_PURPLE - -/obj/item/projectile/beam/instakill/blue - icon_state = "blue_laser" - impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser - light_color = LIGHT_COLOR_BLUE - -/obj/item/projectile/beam/instakill/red - icon_state = "red_laser" - impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser - light_color = LIGHT_COLOR_RED - -/obj/item/projectile/beam/instakill/on_hit(atom/target) - . = ..() - if(iscarbon(target)) - var/mob/living/carbon/M = target - M.visible_message("[M] explodes into a shower of gibs!") - M.gib() - -//a shrink ray that shrinks stuff, which grows back after a short while. -/obj/item/projectile/beam/shrink - name = "shrink ray" - icon_state = "blue_laser" - hitsound = 'sound/weapons/shrink_hit.ogg' - damage = 0 - damage_type = STAMINA - flag = "energy" - impact_effect_type = /obj/effect/temp_visual/impact_effect/shrink - light_color = LIGHT_COLOR_BLUE - var/shrink_time = 90 - -/obj/item/projectile/beam/shrink/on_hit(atom/target, blocked = FALSE) - . = ..() - if(isopenturf(target) || istype(target, /turf/closed/indestructible))//shrunk floors wouldnt do anything except look weird, i-walls shouldnt be bypassable - return +/obj/item/projectile/beam + name = "laser" + icon_state = "laser" + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + damage = 20 + light_range = 2 + damage_type = BURN + hitsound = 'sound/weapons/sear.ogg' + hitsound_wall = 'sound/weapons/effects/searwall.ogg' + flag = "laser" + eyeblur = 2 + impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser + light_color = LIGHT_COLOR_RED + ricochets_max = 50 //Honk! + ricochet_chance = 80 + is_reflectable = TRUE + +/obj/item/projectile/beam/laser + tracer_type = /obj/effect/projectile/tracer/laser + muzzle_type = /obj/effect/projectile/muzzle/laser + impact_type = /obj/effect/projectile/impact/laser + +/obj/item/projectile/beam/laser/heavylaser + name = "heavy laser" + icon_state = "heavylaser" + damage = 40 + tracer_type = /obj/effect/projectile/tracer/heavy_laser + muzzle_type = /obj/effect/projectile/muzzle/heavy_laser + impact_type = /obj/effect/projectile/impact/heavy_laser + +/obj/item/projectile/beam/laser/on_hit(atom/target, blocked = FALSE) + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/M = target + M.IgniteMob() + else if(isturf(target)) + impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser/wall + +/obj/item/projectile/beam/weak + damage = 15 + +/obj/item/projectile/beam/weak/penetrator + armour_penetration = 50 + +/obj/item/projectile/beam/practice + name = "practice laser" + damage = 0 + nodamage = 1 + +/obj/item/projectile/beam/scatter + name = "laser pellet" + icon_state = "scatterlaser" + damage = 5 + +/obj/item/projectile/beam/xray + name = "\improper X-ray beam" + icon_state = "xray" + damage = 15 + irradiate = 300 + range = 15 + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF + + impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser + light_color = LIGHT_COLOR_GREEN + tracer_type = /obj/effect/projectile/tracer/xray + muzzle_type = /obj/effect/projectile/muzzle/xray + impact_type = /obj/effect/projectile/impact/xray + +/obj/item/projectile/beam/disabler + name = "disabler beam" + icon_state = "omnilaser" + damage = 28 // Citadel change for balance from 36 + damage_type = STAMINA + flag = "energy" + hitsound = 'sound/weapons/tap.ogg' + eyeblur = 0 + speed = 0.6 + impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser + light_color = LIGHT_COLOR_BLUE + tracer_type = /obj/effect/projectile/tracer/disabler + muzzle_type = /obj/effect/projectile/muzzle/disabler + impact_type = /obj/effect/projectile/impact/disabler + +/obj/item/projectile/beam/pulse + name = "pulse" + icon_state = "u_laser" + damage = 50 + impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser + light_color = LIGHT_COLOR_BLUE + tracer_type = /obj/effect/projectile/tracer/pulse + muzzle_type = /obj/effect/projectile/muzzle/pulse + impact_type = /obj/effect/projectile/impact/pulse + +/obj/item/projectile/beam/pulse/on_hit(atom/target, blocked = FALSE) + . = ..() + if (!QDELETED(target) && (isturf(target) || istype(target, /obj/structure/))) + target.ex_act(EXPLODE_HEAVY) + +/obj/item/projectile/beam/pulse/shotgun + damage = 40 + +/obj/item/projectile/beam/pulse/heavy + name = "heavy pulse laser" + icon_state = "pulse1_bl" + var/life = 20 + +/obj/item/projectile/beam/pulse/heavy/on_hit(atom/target, blocked = FALSE) + life -= 10 + if(life > 0) + . = -1 + ..() + +/obj/item/projectile/beam/emitter + name = "emitter beam" + icon_state = "emitter" + damage = 30 + impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser + light_color = LIGHT_COLOR_GREEN + +/obj/item/projectile/beam/emitter/singularity_pull() + return //don't want the emitters to miss + +/obj/item/projectile/beam/lasertag + name = "laser tag beam" + icon_state = "omnilaser" + hitsound = null + damage = 0 + damage_type = STAMINA + flag = "laser" + var/suit_types = list(/obj/item/clothing/suit/redtag, /obj/item/clothing/suit/bluetag) + impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser + light_color = LIGHT_COLOR_BLUE + +/obj/item/projectile/beam/lasertag/on_hit(atom/target, blocked = FALSE) + . = ..() + if(ishuman(target)) + var/mob/living/carbon/human/M = target + if(istype(M.wear_suit)) + if(M.wear_suit.type in suit_types) + M.adjustStaminaLoss(34) + +/obj/item/projectile/beam/lasertag/redtag + icon_state = "laser" + suit_types = list(/obj/item/clothing/suit/bluetag) + impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser + light_color = LIGHT_COLOR_RED + tracer_type = /obj/effect/projectile/tracer/laser + muzzle_type = /obj/effect/projectile/muzzle/laser + impact_type = /obj/effect/projectile/impact/laser + +/obj/item/projectile/beam/lasertag/redtag/hitscan + hitscan = TRUE + +/obj/item/projectile/beam/lasertag/redtag/hitscan/holy + name = "lasrifle beam" + damage = 0.1 + damage_type = BURN + +/obj/item/projectile/beam/lasertag/bluetag + icon_state = "bluelaser" + suit_types = list(/obj/item/clothing/suit/redtag) + tracer_type = /obj/effect/projectile/tracer/laser/blue + muzzle_type = /obj/effect/projectile/muzzle/laser/blue + impact_type = /obj/effect/projectile/impact/laser/blue + +/obj/item/projectile/beam/lasertag/bluetag/hitscan + hitscan = TRUE + +/obj/item/projectile/beam/instakill + name = "instagib laser" + icon_state = "purple_laser" + damage = 200 + damage_type = BURN + impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser + light_color = LIGHT_COLOR_PURPLE + +/obj/item/projectile/beam/instakill/blue + icon_state = "blue_laser" + impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser + light_color = LIGHT_COLOR_BLUE + +/obj/item/projectile/beam/instakill/red + icon_state = "red_laser" + impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser + light_color = LIGHT_COLOR_RED + +/obj/item/projectile/beam/instakill/on_hit(atom/target) + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/M = target + M.visible_message("[M] explodes into a shower of gibs!") + M.gib() + +//a shrink ray that shrinks stuff, which grows back after a short while. +/obj/item/projectile/beam/shrink + name = "shrink ray" + icon_state = "blue_laser" + hitsound = 'sound/weapons/shrink_hit.ogg' + damage = 0 + damage_type = STAMINA + flag = "energy" + impact_effect_type = /obj/effect/temp_visual/impact_effect/shrink + light_color = LIGHT_COLOR_BLUE + var/shrink_time = 90 + +/obj/item/projectile/beam/shrink/on_hit(atom/target, blocked = FALSE) + . = ..() + if(isopenturf(target) || istype(target, /turf/closed/indestructible))//shrunk floors wouldnt do anything except look weird, i-walls shouldnt be bypassable + return target.AddComponent(/datum/component/shrink, shrink_time) \ No newline at end of file diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index 7daeca91ea..6d03012315 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -1,10 +1,10 @@ -/obj/item/projectile/bullet - name = "bullet" - icon_state = "bullet" - damage = 60 - damage_type = BRUTE - nodamage = FALSE - candink = TRUE - flag = "bullet" - hitsound_wall = "ricochet" - impact_effect_type = /obj/effect/temp_visual/impact_effect +/obj/item/projectile/bullet + name = "bullet" + icon_state = "bullet" + damage = 60 + damage_type = BRUTE + nodamage = FALSE + candink = TRUE + flag = "bullet" + hitsound_wall = "ricochet" + impact_effect_type = /obj/effect/temp_visual/impact_effect diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm index b6cca4a31a..66b65ac78f 100644 --- a/code/modules/projectiles/projectile/bullets/_incendiary.dm +++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm @@ -1,17 +1,17 @@ -/obj/item/projectile/bullet/incendiary - damage = 20 - var/fire_stacks = 4 - -/obj/item/projectile/bullet/incendiary/on_hit(atom/target, blocked = FALSE) - . = ..() - if(iscarbon(target)) - var/mob/living/carbon/M = target - M.adjust_fire_stacks(fire_stacks) - M.IgniteMob() - -/obj/item/projectile/bullet/incendiary/Move() - . = ..() - var/turf/location = get_turf(src) - if(location) - new /obj/effect/hotspot(location) - location.hotspot_expose(700, 5, 1) +/obj/item/projectile/bullet/incendiary + damage = 20 + var/fire_stacks = 4 + +/obj/item/projectile/bullet/incendiary/on_hit(atom/target, blocked = FALSE) + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/M = target + M.adjust_fire_stacks(fire_stacks) + M.IgniteMob() + +/obj/item/projectile/bullet/incendiary/Move() + . = ..() + var/turf/location = get_turf(src) + if(location) + new /obj/effect/hotspot(location) + location.hotspot_expose(700, 5, 1) diff --git a/code/modules/projectiles/projectile/bullets/dart_syringe.dm b/code/modules/projectiles/projectile/bullets/dart_syringe.dm index 29e6c39c31..9d60f72de0 100644 --- a/code/modules/projectiles/projectile/bullets/dart_syringe.dm +++ b/code/modules/projectiles/projectile/bullets/dart_syringe.dm @@ -1,80 +1,80 @@ -/obj/item/projectile/bullet/dart - name = "dart" - icon_state = "cbbolt" - damage = 6 - var/piercing = FALSE - -/obj/item/projectile/bullet/dart/Initialize() - . = ..() - create_reagents(50, NO_REACT) - -/obj/item/projectile/bullet/dart/on_hit(atom/target, blocked = FALSE, skip = FALSE) - if(iscarbon(target)) - var/mob/living/carbon/M = target - if(blocked != 100) // not completely blocked - if(M.can_inject(null, FALSE, def_zone, piercing)) // Pass the hit zone to see if it can inject by whether it hit the head or the body. - ..() - if(skip == TRUE) - return - reagents.reaction(M, INJECT) - reagents.trans_to(M, reagents.total_volume) - return TRUE - else - blocked = 100 - target.visible_message("\The [src] was deflected!", \ - "You were protected against \the [src]!") - - ..(target, blocked) - DISABLE_BITFIELD(reagents.reagents_holder_flags, NO_REACT) - reagents.handle_reactions() - return TRUE - -/obj/item/projectile/bullet/dart/metalfoam/Initialize() - . = ..() - reagents.add_reagent(/datum/reagent/aluminium, 15) - reagents.add_reagent(/datum/reagent/foaming_agent, 5) - reagents.add_reagent(/datum/reagent/toxin/acid, 5) - -/obj/item/projectile/bullet/dart/syringe - name = "syringe" - icon_state = "syringeproj" - -//I am in a mess of my own making -/obj/item/projectile/bullet/dart/syringe/dart - name = "Smartdart" - icon_state = "dartproj" - damage = 0 - var/emptrig = FALSE - -/obj/item/projectile/bullet/dart/syringe/dart/on_hit(atom/target, blocked = FALSE) - if(iscarbon(target)) - var/mob/living/carbon/M = target - if(blocked != 100) - if(M.can_inject(null, FALSE, def_zone, piercing)) // Pass the hit zone to see if it can inject by whether it hit the head or the body. - ..(target, blocked, TRUE) - for(var/datum/reagent/medicine/R in reagents.reagent_list) //OD prevention time! - if(M.reagents.has_reagent(R.type)) - if(R.overdose_threshold == 0 || emptrig == TRUE) //Is there a possible OD? - M.reagents.add_reagent(R.type, R.volume) - else - var/transVol = CLAMP(R.volume, 0, (R.overdose_threshold - M.reagents.get_reagent_amount(R.type)) -1) - M.reagents.add_reagent(R.type, transVol) - else - if(!R.overdose_threshold == 0) - var/transVol = CLAMP(R.volume, 0, R.overdose_threshold-1) - M.reagents.add_reagent(R.type, transVol) - else - M.reagents.add_reagent(R.type, R.volume) - - - - target.visible_message("\The [src] beeps!") - to_chat("You feel a tiny prick as a smartdart embeds itself in you with a beep.") - return TRUE - else - blocked = 100 - target.visible_message("\The [src] was deflected!", \ - "You see a [src] bounce off you, booping sadly!") - - target.visible_message("\The [src] fails to land on target!") - return TRUE +/obj/item/projectile/bullet/dart + name = "dart" + icon_state = "cbbolt" + damage = 6 + var/piercing = FALSE + +/obj/item/projectile/bullet/dart/Initialize() + . = ..() + create_reagents(50, NO_REACT) + +/obj/item/projectile/bullet/dart/on_hit(atom/target, blocked = FALSE, skip = FALSE) + if(iscarbon(target)) + var/mob/living/carbon/M = target + if(blocked != 100) // not completely blocked + if(M.can_inject(null, FALSE, def_zone, piercing)) // Pass the hit zone to see if it can inject by whether it hit the head or the body. + ..() + if(skip == TRUE) + return + reagents.reaction(M, INJECT) + reagents.trans_to(M, reagents.total_volume) + return TRUE + else + blocked = 100 + target.visible_message("\The [src] was deflected!", \ + "You were protected against \the [src]!") + + ..(target, blocked) + DISABLE_BITFIELD(reagents.reagents_holder_flags, NO_REACT) + reagents.handle_reactions() + return TRUE + +/obj/item/projectile/bullet/dart/metalfoam/Initialize() + . = ..() + reagents.add_reagent(/datum/reagent/aluminium, 15) + reagents.add_reagent(/datum/reagent/foaming_agent, 5) + reagents.add_reagent(/datum/reagent/toxin/acid, 5) + +/obj/item/projectile/bullet/dart/syringe + name = "syringe" + icon_state = "syringeproj" + +//I am in a mess of my own making +/obj/item/projectile/bullet/dart/syringe/dart + name = "Smartdart" + icon_state = "dartproj" + damage = 0 + var/emptrig = FALSE + +/obj/item/projectile/bullet/dart/syringe/dart/on_hit(atom/target, blocked = FALSE) + if(iscarbon(target)) + var/mob/living/carbon/M = target + if(blocked != 100) + if(M.can_inject(null, FALSE, def_zone, piercing)) // Pass the hit zone to see if it can inject by whether it hit the head or the body. + ..(target, blocked, TRUE) + for(var/datum/reagent/medicine/R in reagents.reagent_list) //OD prevention time! + if(M.reagents.has_reagent(R.type)) + if(R.overdose_threshold == 0 || emptrig == TRUE) //Is there a possible OD? + M.reagents.add_reagent(R.type, R.volume) + else + var/transVol = CLAMP(R.volume, 0, (R.overdose_threshold - M.reagents.get_reagent_amount(R.type)) -1) + M.reagents.add_reagent(R.type, transVol) + else + if(!R.overdose_threshold == 0) + var/transVol = CLAMP(R.volume, 0, R.overdose_threshold-1) + M.reagents.add_reagent(R.type, transVol) + else + M.reagents.add_reagent(R.type, R.volume) + + + + target.visible_message("\The [src] beeps!") + to_chat("You feel a tiny prick as a smartdart embeds itself in you with a beep.") + return TRUE + else + blocked = 100 + target.visible_message("\The [src] was deflected!", \ + "You see a [src] bounce off you, booping sadly!") + + target.visible_message("\The [src] fails to land on target!") + return TRUE diff --git a/code/modules/projectiles/projectile/bullets/dnainjector.dm b/code/modules/projectiles/projectile/bullets/dnainjector.dm index 861ead5393..3bfe2add01 100644 --- a/code/modules/projectiles/projectile/bullets/dnainjector.dm +++ b/code/modules/projectiles/projectile/bullets/dnainjector.dm @@ -1,24 +1,24 @@ -/obj/item/projectile/bullet/dnainjector - name = "\improper DNA injector" - icon_state = "syringeproj" - var/obj/item/dnainjector/injector - damage = 5 - hitsound_wall = "shatter" - -/obj/item/projectile/bullet/dnainjector/on_hit(atom/target, blocked = FALSE) - if(iscarbon(target)) - var/mob/living/carbon/M = target - if(blocked != 100) - if(M.can_inject(null, FALSE, def_zone, FALSE)) - if(injector.inject(M, firer)) - QDEL_NULL(injector) - return TRUE - else - blocked = 100 - target.visible_message("\The [src] was deflected!", \ - "You were protected against \the [src]!") - return ..() - -/obj/item/projectile/bullet/dnainjector/Destroy() - QDEL_NULL(injector) - return ..() +/obj/item/projectile/bullet/dnainjector + name = "\improper DNA injector" + icon_state = "syringeproj" + var/obj/item/dnainjector/injector + damage = 5 + hitsound_wall = "shatter" + +/obj/item/projectile/bullet/dnainjector/on_hit(atom/target, blocked = FALSE) + if(iscarbon(target)) + var/mob/living/carbon/M = target + if(blocked != 100) + if(M.can_inject(null, FALSE, def_zone, FALSE)) + if(injector.inject(M, firer)) + QDEL_NULL(injector) + return TRUE + else + blocked = 100 + target.visible_message("\The [src] was deflected!", \ + "You were protected against \the [src]!") + return ..() + +/obj/item/projectile/bullet/dnainjector/Destroy() + QDEL_NULL(injector) + return ..() diff --git a/code/modules/projectiles/projectile/bullets/grenade.dm b/code/modules/projectiles/projectile/bullets/grenade.dm index 965001b55f..3e01f167f6 100644 --- a/code/modules/projectiles/projectile/bullets/grenade.dm +++ b/code/modules/projectiles/projectile/bullets/grenade.dm @@ -1,12 +1,12 @@ -// 40mm (Grenade Launcher - -/obj/item/projectile/bullet/a40mm - name ="40mm grenade" - desc = "USE A WEEL GUN" - icon_state= "bolter" - damage = 60 - -/obj/item/projectile/bullet/a40mm/on_hit(atom/target, blocked = FALSE) - ..() - explosion(target, -1, 0, 2, 1, 0, flame_range = 3) - return TRUE +// 40mm (Grenade Launcher + +/obj/item/projectile/bullet/a40mm + name ="40mm grenade" + desc = "USE A WEEL GUN" + icon_state= "bolter" + damage = 60 + +/obj/item/projectile/bullet/a40mm/on_hit(atom/target, blocked = FALSE) + ..() + explosion(target, -1, 0, 2, 1, 0, flame_range = 3) + return TRUE diff --git a/code/modules/projectiles/projectile/bullets/lmg.dm b/code/modules/projectiles/projectile/bullets/lmg.dm index cf0e565cfc..2ea1fe7c9a 100644 --- a/code/modules/projectiles/projectile/bullets/lmg.dm +++ b/code/modules/projectiles/projectile/bullets/lmg.dm @@ -1,44 +1,44 @@ -// C3D (Borgs) - -/obj/item/projectile/bullet/c3d - damage = 20 - -// Mech LMG - -/obj/item/projectile/bullet/lmg - damage = 20 - -// Mech FNX-99 - -/obj/item/projectile/bullet/incendiary/fnx99 - damage = 20 - -// Turrets - -/obj/item/projectile/bullet/manned_turret - damage = 20 - -/obj/item/projectile/bullet/syndicate_turret - damage = 20 - -// 1.95x129mm (SAW) - -/obj/item/projectile/bullet/mm195x129 - name = "1.95x129mm bullet" - damage = 45 - armour_penetration = 5 - -/obj/item/projectile/bullet/mm195x129_ap - name = "1.95x129mm armor-piercing bullet" - damage = 40 - armour_penetration = 75 - -/obj/item/projectile/bullet/mm195x129_hp - name = "1.95x129mm hollow-point bullet" - damage = 60 - armour_penetration = -60 - -/obj/item/projectile/bullet/incendiary/mm195x129 - name = "1.95x129mm incendiary bullet" - damage = 20 - fire_stacks = 3 +// C3D (Borgs) + +/obj/item/projectile/bullet/c3d + damage = 20 + +// Mech LMG + +/obj/item/projectile/bullet/lmg + damage = 20 + +// Mech FNX-99 + +/obj/item/projectile/bullet/incendiary/fnx99 + damage = 20 + +// Turrets + +/obj/item/projectile/bullet/manned_turret + damage = 20 + +/obj/item/projectile/bullet/syndicate_turret + damage = 20 + +// 1.95x129mm (SAW) + +/obj/item/projectile/bullet/mm195x129 + name = "1.95x129mm bullet" + damage = 45 + armour_penetration = 5 + +/obj/item/projectile/bullet/mm195x129_ap + name = "1.95x129mm armor-piercing bullet" + damage = 40 + armour_penetration = 75 + +/obj/item/projectile/bullet/mm195x129_hp + name = "1.95x129mm hollow-point bullet" + damage = 60 + armour_penetration = -60 + +/obj/item/projectile/bullet/incendiary/mm195x129 + name = "1.95x129mm incendiary bullet" + damage = 20 + fire_stacks = 3 diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm index ac14fa563c..b68c3847e2 100644 --- a/code/modules/projectiles/projectile/bullets/pistol.dm +++ b/code/modules/projectiles/projectile/bullets/pistol.dm @@ -1,36 +1,36 @@ -// 9mm (Stechkin APS) - -/obj/item/projectile/bullet/c9mm - name = "9mm bullet" - damage = 20 - -/obj/item/projectile/bullet/c9mm_ap - name = "9mm armor-piercing bullet" - damage = 15 - armour_penetration = 40 - -/obj/item/projectile/bullet/incendiary/c9mm - name = "9mm incendiary bullet" - damage = 10 - fire_stacks = 1 - -// 10mm (Stechkin) - -/obj/item/projectile/bullet/c10mm - name = "10mm bullet" - damage = 30 - -/obj/item/projectile/bullet/c10mm_ap - name = "10mm armor-piercing bullet" - damage = 27 - armour_penetration = 40 - -/obj/item/projectile/bullet/c10mm_hp - name = "10mm hollow-point bullet" - damage = 40 - armour_penetration = -50 - -/obj/item/projectile/bullet/incendiary/c10mm - name = "10mm incendiary bullet" - damage = 15 - fire_stacks = 2 +// 9mm (Stechkin APS) + +/obj/item/projectile/bullet/c9mm + name = "9mm bullet" + damage = 20 + +/obj/item/projectile/bullet/c9mm_ap + name = "9mm armor-piercing bullet" + damage = 15 + armour_penetration = 40 + +/obj/item/projectile/bullet/incendiary/c9mm + name = "9mm incendiary bullet" + damage = 10 + fire_stacks = 1 + +// 10mm (Stechkin) + +/obj/item/projectile/bullet/c10mm + name = "10mm bullet" + damage = 30 + +/obj/item/projectile/bullet/c10mm_ap + name = "10mm armor-piercing bullet" + damage = 27 + armour_penetration = 40 + +/obj/item/projectile/bullet/c10mm_hp + name = "10mm hollow-point bullet" + damage = 40 + armour_penetration = -50 + +/obj/item/projectile/bullet/incendiary/c10mm + name = "10mm incendiary bullet" + damage = 15 + fire_stacks = 2 diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm index 3bbfe437fe..c5c649a57e 100644 --- a/code/modules/projectiles/projectile/bullets/revolver.dm +++ b/code/modules/projectiles/projectile/bullets/revolver.dm @@ -1,71 +1,71 @@ -// 7.62x38mmR (Nagant Revolver) - -/obj/item/projectile/bullet/n762 - name = "7.62x38mmR bullet" - damage = 60 - -// .50AE (Desert Eagle) - -/obj/item/projectile/bullet/a50AE - name = ".50AE bullet" - damage = 60 - -// .38 (Detective's Gun) - -/obj/item/projectile/bullet/c38 - name = ".38 bullet" - damage = 25 - -/obj/item/projectile/bullet/c38/rubber - name = ".38 rubber bullet" - damage = 15 - stamina = 48 - -/obj/item/projectile/bullet/c38/trac - name = ".38 TRAC bullet" - damage = 10 - -/obj/item/projectile/bullet/c38/trac/on_hit(atom/target, blocked = FALSE) - . = ..() - var/mob/living/carbon/M = target - var/obj/item/implant/tracking/c38/imp - for(var/obj/item/implant/tracking/c38/TI in M.implants) //checks if the target already contains a tracking implant - imp = TI - return - if(!imp) - imp = new /obj/item/implant/tracking/c38(M) - imp.implant(M) - -/obj/item/projectile/bullet/c38/hotshot //similar to incendiary bullets, but do not leave a flaming trail - name = ".38 Hot Shot bullet" - damage = 20 - -/obj/item/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = FALSE) - . = ..() - if(iscarbon(target)) - var/mob/living/carbon/M = target - M.adjust_fire_stacks(6) - M.IgniteMob() - -/obj/item/projectile/bullet/c38/iceblox //see /obj/item/projectile/temp for the original code - name = ".38 Iceblox bullet" - damage = 20 - var/temperature = 100 - -/obj/item/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = FALSE) - . = ..() - if(isliving(target)) - var/mob/living/M = target - M.adjust_bodytemperature(((100-blocked)/100)*(temperature - M.bodytemperature)) - - -// .357 (Syndie Revolver) - -/obj/item/projectile/bullet/a357 - name = ".357 bullet" - damage = 60 - -/obj/item/projectile/bullet/a357/ap - name = ".357 armor-piercing bullet" - damage = 45 +// 7.62x38mmR (Nagant Revolver) + +/obj/item/projectile/bullet/n762 + name = "7.62x38mmR bullet" + damage = 60 + +// .50AE (Desert Eagle) + +/obj/item/projectile/bullet/a50AE + name = ".50AE bullet" + damage = 60 + +// .38 (Detective's Gun) + +/obj/item/projectile/bullet/c38 + name = ".38 bullet" + damage = 25 + +/obj/item/projectile/bullet/c38/rubber + name = ".38 rubber bullet" + damage = 15 + stamina = 48 + +/obj/item/projectile/bullet/c38/trac + name = ".38 TRAC bullet" + damage = 10 + +/obj/item/projectile/bullet/c38/trac/on_hit(atom/target, blocked = FALSE) + . = ..() + var/mob/living/carbon/M = target + var/obj/item/implant/tracking/c38/imp + for(var/obj/item/implant/tracking/c38/TI in M.implants) //checks if the target already contains a tracking implant + imp = TI + return + if(!imp) + imp = new /obj/item/implant/tracking/c38(M) + imp.implant(M) + +/obj/item/projectile/bullet/c38/hotshot //similar to incendiary bullets, but do not leave a flaming trail + name = ".38 Hot Shot bullet" + damage = 20 + +/obj/item/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = FALSE) + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/M = target + M.adjust_fire_stacks(6) + M.IgniteMob() + +/obj/item/projectile/bullet/c38/iceblox //see /obj/item/projectile/temp for the original code + name = ".38 Iceblox bullet" + damage = 20 + var/temperature = 100 + +/obj/item/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = FALSE) + . = ..() + if(isliving(target)) + var/mob/living/M = target + M.adjust_bodytemperature(((100-blocked)/100)*(temperature - M.bodytemperature)) + + +// .357 (Syndie Revolver) + +/obj/item/projectile/bullet/a357 + name = ".357 bullet" + damage = 60 + +/obj/item/projectile/bullet/a357/ap + name = ".357 armor-piercing bullet" + damage = 45 armour_penetration = 45 \ No newline at end of file diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm index a019c05ef1..ae1611cb00 100644 --- a/code/modules/projectiles/projectile/bullets/rifle.dm +++ b/code/modules/projectiles/projectile/bullets/rifle.dm @@ -1,16 +1,16 @@ -// 5.56mm (M-90gl Carbine) - -/obj/item/projectile/bullet/a556 - name = "5.56mm bullet" - damage = 35 - -// 7.62 (Nagant Rifle) - -/obj/item/projectile/bullet/a762 - name = "7.62 bullet" - damage = 60 - -/obj/item/projectile/bullet/a762_enchanted - name = "enchanted 7.62 bullet" - damage = 5 - stamina = 80 +// 5.56mm (M-90gl Carbine) + +/obj/item/projectile/bullet/a556 + name = "5.56mm bullet" + damage = 35 + +// 7.62 (Nagant Rifle) + +/obj/item/projectile/bullet/a762 + name = "7.62 bullet" + damage = 60 + +/obj/item/projectile/bullet/a762_enchanted + name = "enchanted 7.62 bullet" + damage = 5 + stamina = 80 diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm index 4a1c954b1b..ed9478e0ee 100644 --- a/code/modules/projectiles/projectile/bullets/shotgun.dm +++ b/code/modules/projectiles/projectile/bullets/shotgun.dm @@ -1,100 +1,100 @@ -/obj/item/projectile/bullet/shotgun_slug - name = "12g shotgun slug" - damage = 60 - -/obj/item/projectile/bullet/shotgun_beanbag - name = "beanbag slug" - damage = 5 - stamina = 70 - -/obj/item/projectile/bullet/incendiary/shotgun - name = "incendiary slug" - damage = 20 - -/obj/item/projectile/bullet/incendiary/shotgun/dragonsbreath - name = "dragonsbreath pellet" - damage = 5 - -/obj/item/projectile/bullet/shotgun_stunslug - name = "stunslug" - damage = 5 - stamina = 20 - knockdown = 100 - stutter = 5 - jitter = 20 - range = 7 - icon_state = "spark" - color = "#FFFF00" - -/obj/item/projectile/bullet/shotgun_meteorslug - name = "meteorslug" - icon = 'icons/obj/meteor.dmi' - icon_state = "dust" - damage = 20 - knockdown = 80 - hitsound = 'sound/effects/meteorimpact.ogg' - -/obj/item/projectile/bullet/shotgun_meteorslug/on_hit(atom/target, blocked = FALSE) - . = ..() - if(ismovableatom(target)) - var/atom/movable/M = target - var/atom/throw_target = get_edge_target_turf(M, get_dir(src, get_step_away(M, src))) - M.safe_throw_at(throw_target, 3, 2) - -/obj/item/projectile/bullet/shotgun_meteorslug/Initialize() - . = ..() - SpinAnimation() - -/obj/item/projectile/bullet/shotgun_frag12 - name ="frag12 slug" - damage = 25 - knockdown = 50 - -/obj/item/projectile/bullet/shotgun_frag12/on_hit(atom/target, blocked = FALSE) - ..() - explosion(target, -1, 0, 1) - return TRUE - -/obj/item/projectile/bullet/pellet - var/tile_dropoff = 0.75 - var/tile_dropoff_s = 1.25 - -/obj/item/projectile/bullet/pellet/shotgun_buckshot - name = "buckshot pellet" - damage = 10 - -/obj/item/projectile/bullet/pellet/shotgun_rubbershot - name = "rubbershot pellet" - damage = 2 - stamina = 15 - -/obj/item/projectile/bullet/pellet/Range() - ..() - if(damage > 0) - damage -= tile_dropoff - if(stamina > 0) - stamina -= tile_dropoff_s - if(damage < 0 && stamina < 0) - qdel(src) - -/obj/item/projectile/bullet/pellet/shotgun_improvised - tile_dropoff = 0.55 //Come on it does 6 damage don't be like that. - damage = 6 - -/obj/item/projectile/bullet/pellet/shotgun_improvised/Initialize() - . = ..() - range = rand(1, 8) - -/obj/item/projectile/bullet/pellet/shotgun_improvised/on_range() - do_sparks(1, TRUE, src) - ..() - -// Mech Scattershots - -/obj/item/projectile/bullet/scattershot - damage = 20 - stamina = 65 - -/obj/item/projectile/bullet/seed - damage = 4 - stamina = 1 +/obj/item/projectile/bullet/shotgun_slug + name = "12g shotgun slug" + damage = 60 + +/obj/item/projectile/bullet/shotgun_beanbag + name = "beanbag slug" + damage = 5 + stamina = 70 + +/obj/item/projectile/bullet/incendiary/shotgun + name = "incendiary slug" + damage = 20 + +/obj/item/projectile/bullet/incendiary/shotgun/dragonsbreath + name = "dragonsbreath pellet" + damage = 5 + +/obj/item/projectile/bullet/shotgun_stunslug + name = "stunslug" + damage = 5 + stamina = 20 + knockdown = 100 + stutter = 5 + jitter = 20 + range = 7 + icon_state = "spark" + color = "#FFFF00" + +/obj/item/projectile/bullet/shotgun_meteorslug + name = "meteorslug" + icon = 'icons/obj/meteor.dmi' + icon_state = "dust" + damage = 20 + knockdown = 80 + hitsound = 'sound/effects/meteorimpact.ogg' + +/obj/item/projectile/bullet/shotgun_meteorslug/on_hit(atom/target, blocked = FALSE) + . = ..() + if(ismovableatom(target)) + var/atom/movable/M = target + var/atom/throw_target = get_edge_target_turf(M, get_dir(src, get_step_away(M, src))) + M.safe_throw_at(throw_target, 3, 2) + +/obj/item/projectile/bullet/shotgun_meteorslug/Initialize() + . = ..() + SpinAnimation() + +/obj/item/projectile/bullet/shotgun_frag12 + name ="frag12 slug" + damage = 25 + knockdown = 50 + +/obj/item/projectile/bullet/shotgun_frag12/on_hit(atom/target, blocked = FALSE) + ..() + explosion(target, -1, 0, 1) + return TRUE + +/obj/item/projectile/bullet/pellet + var/tile_dropoff = 0.75 + var/tile_dropoff_s = 1.25 + +/obj/item/projectile/bullet/pellet/shotgun_buckshot + name = "buckshot pellet" + damage = 10 + +/obj/item/projectile/bullet/pellet/shotgun_rubbershot + name = "rubbershot pellet" + damage = 2 + stamina = 15 + +/obj/item/projectile/bullet/pellet/Range() + ..() + if(damage > 0) + damage -= tile_dropoff + if(stamina > 0) + stamina -= tile_dropoff_s + if(damage < 0 && stamina < 0) + qdel(src) + +/obj/item/projectile/bullet/pellet/shotgun_improvised + tile_dropoff = 0.55 //Come on it does 6 damage don't be like that. + damage = 6 + +/obj/item/projectile/bullet/pellet/shotgun_improvised/Initialize() + . = ..() + range = rand(1, 8) + +/obj/item/projectile/bullet/pellet/shotgun_improvised/on_range() + do_sparks(1, TRUE, src) + ..() + +// Mech Scattershots + +/obj/item/projectile/bullet/scattershot + damage = 20 + stamina = 65 + +/obj/item/projectile/bullet/seed + damage = 4 + stamina = 1 diff --git a/code/modules/projectiles/projectile/bullets/sniper.dm b/code/modules/projectiles/projectile/bullets/sniper.dm index 3ffac7804e..6519421284 100644 --- a/code/modules/projectiles/projectile/bullets/sniper.dm +++ b/code/modules/projectiles/projectile/bullets/sniper.dm @@ -1,46 +1,46 @@ -// .50 (Sniper) - -/obj/item/projectile/bullet/p50 - name =".50 bullet" - speed = 0.4 - damage = 70 - knockdown = 100 - dismemberment = 50 - armour_penetration = 50 - var/breakthings = TRUE - -/obj/item/projectile/bullet/p50/on_hit(atom/target, blocked = 0) - if(isobj(target) && (blocked != 100) && breakthings) - var/obj/O = target - O.take_damage(80, BRUTE, "bullet", FALSE) - return ..() - -/obj/item/projectile/bullet/p50/soporific - name =".50 soporific bullet" - armour_penetration = 0 - damage = 0 - dismemberment = 0 - knockdown = 0 - breakthings = FALSE - -/obj/item/projectile/bullet/p50/soporific/on_hit(atom/target, blocked = FALSE) - if((blocked != 100) && isliving(target)) - var/mob/living/L = target - L.Sleeping(400) - return ..() - -/obj/item/projectile/bullet/p50/penetrator - name =".50 penetrator bullet" - icon_state = "gauss" - name = "penetrator round" - damage = 60 - forcedodge = TRUE - dismemberment = 0 //It goes through you cleanly. - knockdown = 0 - breakthings = FALSE - -/obj/item/projectile/bullet/p50/penetrator/shuttle //Nukeop Shuttle Variety - icon_state = "gaussstrong" - damage = 25 - speed = 0.3 - range = 16 +// .50 (Sniper) + +/obj/item/projectile/bullet/p50 + name =".50 bullet" + speed = 0.4 + damage = 70 + knockdown = 100 + dismemberment = 50 + armour_penetration = 50 + var/breakthings = TRUE + +/obj/item/projectile/bullet/p50/on_hit(atom/target, blocked = 0) + if(isobj(target) && (blocked != 100) && breakthings) + var/obj/O = target + O.take_damage(80, BRUTE, "bullet", FALSE) + return ..() + +/obj/item/projectile/bullet/p50/soporific + name =".50 soporific bullet" + armour_penetration = 0 + damage = 0 + dismemberment = 0 + knockdown = 0 + breakthings = FALSE + +/obj/item/projectile/bullet/p50/soporific/on_hit(atom/target, blocked = FALSE) + if((blocked != 100) && isliving(target)) + var/mob/living/L = target + L.Sleeping(400) + return ..() + +/obj/item/projectile/bullet/p50/penetrator + name =".50 penetrator bullet" + icon_state = "gauss" + name = "penetrator round" + damage = 60 + forcedodge = TRUE + dismemberment = 0 //It goes through you cleanly. + knockdown = 0 + breakthings = FALSE + +/obj/item/projectile/bullet/p50/penetrator/shuttle //Nukeop Shuttle Variety + icon_state = "gaussstrong" + damage = 25 + speed = 0.3 + range = 16 diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm index 38a570c75f..0d3ed847d8 100644 --- a/code/modules/projectiles/projectile/bullets/special.dm +++ b/code/modules/projectiles/projectile/bullets/special.dm @@ -1,27 +1,27 @@ -// Honker - -/obj/item/projectile/bullet/honker - damage = 0 - knockdown = 60 - forcedodge = TRUE - nodamage = TRUE - candink = FALSE - hitsound = 'sound/items/bikehorn.ogg' - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "banana" - range = 200 - -/obj/item/projectile/bullet/honker/Initialize() - . = ..() - SpinAnimation() - -// Mime - -/obj/item/projectile/bullet/mime - damage = 20 - -/obj/item/projectile/bullet/mime/on_hit(atom/target, blocked = FALSE) - . = ..() - if(iscarbon(target)) - var/mob/living/carbon/M = target - M.silent = max(M.silent, 10) +// Honker + +/obj/item/projectile/bullet/honker + damage = 0 + knockdown = 60 + forcedodge = TRUE + nodamage = TRUE + candink = FALSE + hitsound = 'sound/items/bikehorn.ogg' + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "banana" + range = 200 + +/obj/item/projectile/bullet/honker/Initialize() + . = ..() + SpinAnimation() + +// Mime + +/obj/item/projectile/bullet/mime + damage = 20 + +/obj/item/projectile/bullet/mime/on_hit(atom/target, blocked = FALSE) + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/M = target + M.silent = max(M.silent, 10) diff --git a/code/modules/projectiles/projectile/energy/_energy.dm b/code/modules/projectiles/projectile/energy/_energy.dm index 3df1eb74d3..ec80ae1e74 100644 --- a/code/modules/projectiles/projectile/energy/_energy.dm +++ b/code/modules/projectiles/projectile/energy/_energy.dm @@ -1,8 +1,8 @@ -/obj/item/projectile/energy - name = "energy" - icon_state = "spark" - damage = 0 - damage_type = BURN - flag = "energy" - is_reflectable = TRUE - +/obj/item/projectile/energy + name = "energy" + icon_state = "spark" + damage = 0 + damage_type = BURN + flag = "energy" + is_reflectable = TRUE + diff --git a/code/modules/projectiles/projectile/energy/ebow.dm b/code/modules/projectiles/projectile/energy/ebow.dm index 1e3dbebcb1..d3f5162bf6 100644 --- a/code/modules/projectiles/projectile/energy/ebow.dm +++ b/code/modules/projectiles/projectile/energy/ebow.dm @@ -1,16 +1,16 @@ -/obj/item/projectile/energy/bolt //ebow bolts - name = "bolt" - icon_state = "cbbolt" - damage = 8 - damage_type = TOX - nodamage = 0 - knockdown = 100 - stutter = 5 - drowsy = 50 - -/obj/item/projectile/energy/bolt/halloween - name = "candy corn" - icon_state = "candy_corn" - -/obj/item/projectile/energy/bolt/large - damage = 20 +/obj/item/projectile/energy/bolt //ebow bolts + name = "bolt" + icon_state = "cbbolt" + damage = 8 + damage_type = TOX + nodamage = 0 + knockdown = 100 + stutter = 5 + drowsy = 50 + +/obj/item/projectile/energy/bolt/halloween + name = "candy corn" + icon_state = "candy_corn" + +/obj/item/projectile/energy/bolt/large + damage = 20 diff --git a/code/modules/projectiles/projectile/energy/misc.dm b/code/modules/projectiles/projectile/energy/misc.dm index 86f4cc060c..d5346b954d 100644 --- a/code/modules/projectiles/projectile/energy/misc.dm +++ b/code/modules/projectiles/projectile/energy/misc.dm @@ -1,15 +1,15 @@ -/obj/item/projectile/energy/declone - name = "radiation beam" - icon_state = "declone" - damage = 20 - damage_type = CLONE - irradiate = 100 - impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser - -/obj/item/projectile/energy/dart //ninja throwing dart - name = "dart" - icon_state = "toxin" - damage = 5 - damage_type = TOX - knockdown = 100 - range = 7 +/obj/item/projectile/energy/declone + name = "radiation beam" + icon_state = "declone" + damage = 20 + damage_type = CLONE + irradiate = 100 + impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser + +/obj/item/projectile/energy/dart //ninja throwing dart + name = "dart" + icon_state = "toxin" + damage = 5 + damage_type = TOX + knockdown = 100 + range = 7 diff --git a/code/modules/projectiles/projectile/energy/stun.dm b/code/modules/projectiles/projectile/energy/stun.dm index a3c139bcff..88e70154d1 100644 --- a/code/modules/projectiles/projectile/energy/stun.dm +++ b/code/modules/projectiles/projectile/energy/stun.dm @@ -1,35 +1,35 @@ -/obj/item/projectile/energy/electrode - name = "electrode" - icon_state = "spark" - color = "#FFFF00" - nodamage = 1 - knockdown = 60 - knockdown_stamoverride = 36 - knockdown_stam_max = 50 - stutter = 5 - jitter = 20 - hitsound = 'sound/weapons/taserhit.ogg' - range = 7 - tracer_type = /obj/effect/projectile/tracer/stun - muzzle_type = /obj/effect/projectile/muzzle/stun - impact_type = /obj/effect/projectile/impact/stun - var/tase_duration = 50 - -/obj/item/projectile/energy/electrode/on_hit(atom/target, blocked = FALSE) - . = ..() - if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks! - do_sparks(1, TRUE, src) - else if(iscarbon(target)) - var/mob/living/carbon/C = target - SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "tased", /datum/mood_event/tased) - SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK) - C.IgniteMob() - if(C.dna && C.dna.check_mutation(HULK)) - C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") - else if((C.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(C, TRAIT_STUNIMMUNE)) - C.apply_status_effect(STATUS_EFFECT_TASED, tase_duration) - addtimer(CALLBACK(C, /mob/living/carbon.proc/do_jitter_animation, jitter), 5) - -/obj/item/projectile/energy/electrode/on_range() //to ensure the bolt sparks when it reaches the end of its range if it didn't hit a target yet - do_sparks(1, TRUE, src) - ..() +/obj/item/projectile/energy/electrode + name = "electrode" + icon_state = "spark" + color = "#FFFF00" + nodamage = 1 + knockdown = 60 + knockdown_stamoverride = 36 + knockdown_stam_max = 50 + stutter = 5 + jitter = 20 + hitsound = 'sound/weapons/taserhit.ogg' + range = 7 + tracer_type = /obj/effect/projectile/tracer/stun + muzzle_type = /obj/effect/projectile/muzzle/stun + impact_type = /obj/effect/projectile/impact/stun + var/tase_duration = 50 + +/obj/item/projectile/energy/electrode/on_hit(atom/target, blocked = FALSE) + . = ..() + if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks! + do_sparks(1, TRUE, src) + else if(iscarbon(target)) + var/mob/living/carbon/C = target + SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "tased", /datum/mood_event/tased) + SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK) + C.IgniteMob() + if(C.dna && C.dna.check_mutation(HULK)) + C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") + else if((C.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(C, TRAIT_STUNIMMUNE)) + C.apply_status_effect(STATUS_EFFECT_TASED, tase_duration) + addtimer(CALLBACK(C, /mob/living/carbon.proc/do_jitter_animation, jitter), 5) + +/obj/item/projectile/energy/electrode/on_range() //to ensure the bolt sparks when it reaches the end of its range if it didn't hit a target yet + do_sparks(1, TRUE, src) + ..() diff --git a/code/modules/projectiles/projectile/energy/tesla.dm b/code/modules/projectiles/projectile/energy/tesla.dm index 7ca2bcb294..43c31816cd 100644 --- a/code/modules/projectiles/projectile/energy/tesla.dm +++ b/code/modules/projectiles/projectile/energy/tesla.dm @@ -1,29 +1,29 @@ -/obj/item/projectile/energy/tesla - name = "tesla bolt" - icon_state = "tesla_projectile" - impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser - var/chain - var/tesla_flags = TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE - var/zap_range = 3 - var/power = 10000 - -/obj/item/projectile/energy/tesla/fire(setAngle) - if(firer) - chain = firer.Beam(src, icon_state = "lightning[rand(1, 12)]", time = INFINITY, maxdistance = INFINITY) - ..() - -/obj/item/projectile/energy/tesla/on_hit(atom/target) - . = ..() - tesla_zap(target, zap_range, power, tesla_flags) - qdel(src) - -/obj/item/projectile/energy/tesla/Destroy() - qdel(chain) - return ..() - -/obj/item/projectile/energy/tesla/revolver - name = "energy orb" - -/obj/item/projectile/energy/tesla/cannon - name = "tesla orb" - power = 20000 +/obj/item/projectile/energy/tesla + name = "tesla bolt" + icon_state = "tesla_projectile" + impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser + var/chain + var/tesla_flags = TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE + var/zap_range = 3 + var/power = 10000 + +/obj/item/projectile/energy/tesla/fire(setAngle) + if(firer) + chain = firer.Beam(src, icon_state = "lightning[rand(1, 12)]", time = INFINITY, maxdistance = INFINITY) + ..() + +/obj/item/projectile/energy/tesla/on_hit(atom/target) + . = ..() + tesla_zap(target, zap_range, power, tesla_flags) + qdel(src) + +/obj/item/projectile/energy/tesla/Destroy() + qdel(chain) + return ..() + +/obj/item/projectile/energy/tesla/revolver + name = "energy orb" + +/obj/item/projectile/energy/tesla/cannon + name = "tesla orb" + power = 20000 diff --git a/code/modules/projectiles/projectile/reusable/_reusable.dm b/code/modules/projectiles/projectile/reusable/_reusable.dm index 33c9678fe4..df19d5a0b5 100644 --- a/code/modules/projectiles/projectile/reusable/_reusable.dm +++ b/code/modules/projectiles/projectile/reusable/_reusable.dm @@ -1,20 +1,20 @@ -/obj/item/projectile/bullet/reusable - name = "reusable bullet" - desc = "How do you even reuse a bullet?" - var/ammo_type = /obj/item/ammo_casing/caseless - var/dropped = FALSE - impact_effect_type = null - -/obj/item/projectile/bullet/reusable/on_hit(atom/target, blocked = FALSE) - . = ..() - handle_drop() - -/obj/item/projectile/bullet/reusable/on_range() - handle_drop() - ..() - -/obj/item/projectile/bullet/reusable/proc/handle_drop() - if(!dropped) - var/turf/T = get_turf(src) - new ammo_type(T) - dropped = TRUE +/obj/item/projectile/bullet/reusable + name = "reusable bullet" + desc = "How do you even reuse a bullet?" + var/ammo_type = /obj/item/ammo_casing/caseless + var/dropped = FALSE + impact_effect_type = null + +/obj/item/projectile/bullet/reusable/on_hit(atom/target, blocked = FALSE) + . = ..() + handle_drop() + +/obj/item/projectile/bullet/reusable/on_range() + handle_drop() + ..() + +/obj/item/projectile/bullet/reusable/proc/handle_drop() + if(!dropped) + var/turf/T = get_turf(src) + new ammo_type(T) + dropped = TRUE diff --git a/code/modules/projectiles/projectile/reusable/foam_dart.dm b/code/modules/projectiles/projectile/reusable/foam_dart.dm index d360c4f4c7..42f6218b6c 100644 --- a/code/modules/projectiles/projectile/reusable/foam_dart.dm +++ b/code/modules/projectiles/projectile/reusable/foam_dart.dm @@ -1,41 +1,41 @@ -/obj/item/projectile/bullet/reusable/foam_dart - name = "foam dart" - desc = "I hope you're wearing eye protection." - damage = 0 // It's a damn toy. - damage_type = OXY - nodamage = TRUE - icon = 'icons/obj/guns/toy.dmi' - icon_state = "foamdart_proj" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart - range = 10 - var/modified = FALSE - var/obj/item/pen/pen = null - -/obj/item/projectile/bullet/reusable/foam_dart/handle_drop() - if(dropped) - return - var/turf/T = get_turf(src) - dropped = 1 - var/obj/item/ammo_casing/caseless/foam_dart/newcasing = new ammo_type(T) - newcasing.modified = modified - var/obj/item/projectile/bullet/reusable/foam_dart/newdart = newcasing.BB - newdart.modified = modified - newdart.damage = damage - newdart.nodamage = nodamage - newdart.damage_type = damage_type - if(pen) - newdart.pen = pen - pen.forceMove(newdart) - pen = null - newdart.update_icon() - - -/obj/item/projectile/bullet/reusable/foam_dart/Destroy() - pen = null - return ..() - -/obj/item/projectile/bullet/reusable/foam_dart/riot - name = "riot foam dart" - icon_state = "foamdart_riot_proj" - ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot - stamina = 25 +/obj/item/projectile/bullet/reusable/foam_dart + name = "foam dart" + desc = "I hope you're wearing eye protection." + damage = 0 // It's a damn toy. + damage_type = OXY + nodamage = TRUE + icon = 'icons/obj/guns/toy.dmi' + icon_state = "foamdart_proj" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart + range = 10 + var/modified = FALSE + var/obj/item/pen/pen = null + +/obj/item/projectile/bullet/reusable/foam_dart/handle_drop() + if(dropped) + return + var/turf/T = get_turf(src) + dropped = 1 + var/obj/item/ammo_casing/caseless/foam_dart/newcasing = new ammo_type(T) + newcasing.modified = modified + var/obj/item/projectile/bullet/reusable/foam_dart/newdart = newcasing.BB + newdart.modified = modified + newdart.damage = damage + newdart.nodamage = nodamage + newdart.damage_type = damage_type + if(pen) + newdart.pen = pen + pen.forceMove(newdart) + pen = null + newdart.update_icon() + + +/obj/item/projectile/bullet/reusable/foam_dart/Destroy() + pen = null + return ..() + +/obj/item/projectile/bullet/reusable/foam_dart/riot + name = "riot foam dart" + icon_state = "foamdart_riot_proj" + ammo_type = /obj/item/ammo_casing/caseless/foam_dart/riot + stamina = 25 diff --git a/code/modules/projectiles/projectile/reusable/magspear.dm b/code/modules/projectiles/projectile/reusable/magspear.dm index 7fcd6b80a6..9c7c7d201c 100644 --- a/code/modules/projectiles/projectile/reusable/magspear.dm +++ b/code/modules/projectiles/projectile/reusable/magspear.dm @@ -1,6 +1,6 @@ -/obj/item/projectile/bullet/reusable/magspear - name = "magnetic spear" - desc = "WHITE WHALE, HOLY GRAIL" - damage = 30 //takes 3 spears to kill a mega carp, one to kill a normal carp - icon_state = "magspear" - ammo_type = /obj/item/ammo_casing/caseless/magspear +/obj/item/projectile/bullet/reusable/magspear + name = "magnetic spear" + desc = "WHITE WHALE, HOLY GRAIL" + damage = 30 //takes 3 spears to kill a mega carp, one to kill a normal carp + icon_state = "magspear" + ammo_type = /obj/item/ammo_casing/caseless/magspear diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm index adce5dad57..0464f93cd3 100644 --- a/code/modules/projectiles/projectile/special/curse.dm +++ b/code/modules/projectiles/projectile/special/curse.dm @@ -1,52 +1,52 @@ -/obj/effect/ebeam/curse_arm - name = "curse arm" - layer = LARGE_MOB_LAYER - -/obj/item/projectile/curse_hand - name = "curse hand" - icon_state = "cursehand0" - hitsound = 'sound/effects/curse4.ogg' - layer = LARGE_MOB_LAYER - damage_type = BURN - damage = 10 - knockdown = 20 - speed = 2 - range = 16 - forcedodge = TRUE - var/datum/beam/arm - var/handedness = 0 - -/obj/item/projectile/curse_hand/Initialize(mapload) - . = ..() - handedness = prob(50) - icon_state = "cursehand[handedness]" - -/obj/item/projectile/curse_hand/fire(setAngle) - if(starting) - arm = starting.Beam(src, icon_state = "curse[handedness]", time = INFINITY, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm) - ..() - -/obj/item/projectile/curse_hand/prehit(atom/target) - if(target == original) - forcedodge = FALSE - else if(!isturf(target)) - return FALSE - return ..() - -/obj/item/projectile/curse_hand/Destroy() - if(arm) - arm.End() - arm = null - if(forcedodge) - playsound(src, 'sound/effects/curse3.ogg', 25, 1, -1) - var/turf/T = get_step(src, dir) - new/obj/effect/temp_visual/dir_setting/curse/hand(T, dir, handedness) - for(var/obj/effect/temp_visual/dir_setting/curse/grasp_portal/G in starting) - qdel(G) - new /obj/effect/temp_visual/dir_setting/curse/grasp_portal/fading(starting, dir) - var/datum/beam/D = starting.Beam(T, icon_state = "curse[handedness]", time = 32, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm, beam_sleep_time = 1) - for(var/b in D.elements) - var/obj/effect/ebeam/B = b - animate(B, alpha = 0, time = 32) - return ..() - +/obj/effect/ebeam/curse_arm + name = "curse arm" + layer = LARGE_MOB_LAYER + +/obj/item/projectile/curse_hand + name = "curse hand" + icon_state = "cursehand0" + hitsound = 'sound/effects/curse4.ogg' + layer = LARGE_MOB_LAYER + damage_type = BURN + damage = 10 + knockdown = 20 + speed = 2 + range = 16 + forcedodge = TRUE + var/datum/beam/arm + var/handedness = 0 + +/obj/item/projectile/curse_hand/Initialize(mapload) + . = ..() + handedness = prob(50) + icon_state = "cursehand[handedness]" + +/obj/item/projectile/curse_hand/fire(setAngle) + if(starting) + arm = starting.Beam(src, icon_state = "curse[handedness]", time = INFINITY, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm) + ..() + +/obj/item/projectile/curse_hand/prehit(atom/target) + if(target == original) + forcedodge = FALSE + else if(!isturf(target)) + return FALSE + return ..() + +/obj/item/projectile/curse_hand/Destroy() + if(arm) + arm.End() + arm = null + if(forcedodge) + playsound(src, 'sound/effects/curse3.ogg', 25, 1, -1) + var/turf/T = get_step(src, dir) + new/obj/effect/temp_visual/dir_setting/curse/hand(T, dir, handedness) + for(var/obj/effect/temp_visual/dir_setting/curse/grasp_portal/G in starting) + qdel(G) + new /obj/effect/temp_visual/dir_setting/curse/grasp_portal/fading(starting, dir) + var/datum/beam/D = starting.Beam(T, icon_state = "curse[handedness]", time = 32, maxdistance = INFINITY, beam_type=/obj/effect/ebeam/curse_arm, beam_sleep_time = 1) + for(var/b in D.elements) + var/obj/effect/ebeam/B = b + animate(B, alpha = 0, time = 32) + return ..() + diff --git a/code/modules/projectiles/projectile/special/floral.dm b/code/modules/projectiles/projectile/special/floral.dm index 295f89148a..c3fe1f0fcb 100644 --- a/code/modules/projectiles/projectile/special/floral.dm +++ b/code/modules/projectiles/projectile/special/floral.dm @@ -1,25 +1,25 @@ -/obj/item/projectile/energy/floramut - name = "alpha somatoray" - icon_state = "energy" - damage = 0 - damage_type = TOX - nodamage = 1 - flag = "energy" - -/obj/item/projectile/energy/floramut/on_hit(atom/target, blocked = FALSE) - . = ..() - if(iscarbon(target)) - var/mob/living/carbon/C = target - if(C.dna.species.id == "pod") - C.randmuti() - C.randmut() - C.updateappearance() - C.domutcheck() - -/obj/item/projectile/energy/florayield - name = "beta somatoray" - icon_state = "energy2" - damage = 0 - damage_type = TOX - nodamage = 1 - flag = "energy" +/obj/item/projectile/energy/floramut + name = "alpha somatoray" + icon_state = "energy" + damage = 0 + damage_type = TOX + nodamage = 1 + flag = "energy" + +/obj/item/projectile/energy/floramut/on_hit(atom/target, blocked = FALSE) + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(C.dna.species.id == "pod") + C.randmuti() + C.randmut() + C.updateappearance() + C.domutcheck() + +/obj/item/projectile/energy/florayield + name = "beta somatoray" + icon_state = "energy2" + damage = 0 + damage_type = TOX + nodamage = 1 + flag = "energy" diff --git a/code/modules/projectiles/projectile/special/gravity.dm b/code/modules/projectiles/projectile/special/gravity.dm index fdc6b92ec9..0ef9bc6653 100644 --- a/code/modules/projectiles/projectile/special/gravity.dm +++ b/code/modules/projectiles/projectile/special/gravity.dm @@ -1,90 +1,90 @@ -/obj/item/projectile/gravityrepulse - name = "repulsion bolt" - icon = 'icons/effects/effects.dmi' - icon_state = "chronofield" - hitsound = 'sound/weapons/wave.ogg' - damage = 0 - damage_type = BRUTE - nodamage = 1 - color = "#33CCFF" - var/turf/T - var/power = 4 - var/list/thrown_items = list() - -/obj/item/projectile/gravityrepulse/Initialize() - . = ..() - var/obj/item/ammo_casing/energy/gravity/repulse/C = loc - if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items - power = min(C.gun.power, 15) - -/obj/item/projectile/gravityrepulse/on_hit() - . = ..() - T = get_turf(src) - for(var/atom/movable/A in range(T, power)) - if(A == src || (firer && A == src.firer) || A.anchored || thrown_items[A]) - continue - var/throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(A, src))) - A.throw_at(throwtarget,power+1,1) - thrown_items[A] = A - for(var/turf/F in range(T,power)) - new /obj/effect/temp_visual/gravpush(F) - -/obj/item/projectile/gravityattract - name = "attraction bolt" - icon = 'icons/effects/effects.dmi' - icon_state = "chronofield" - hitsound = 'sound/weapons/wave.ogg' - damage = 0 - damage_type = BRUTE - nodamage = 1 - color = "#FF6600" - var/turf/T - var/power = 4 - var/list/thrown_items = list() - -/obj/item/projectile/gravityattract/Initialize() - . = ..() - var/obj/item/ammo_casing/energy/gravity/attract/C = loc - if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items - power = min(C.gun.power, 15) - -/obj/item/projectile/gravityattract/on_hit() - . = ..() - T = get_turf(src) - for(var/atom/movable/A in range(T, power)) - if(A == src || (firer && A == src.firer) || A.anchored || thrown_items[A]) - continue - A.throw_at(T, power+1, 1) - thrown_items[A] = A - for(var/turf/F in range(T,power)) - new /obj/effect/temp_visual/gravpush(F) - -/obj/item/projectile/gravitychaos - name = "gravitational blast" - icon = 'icons/effects/effects.dmi' - icon_state = "chronofield" - hitsound = 'sound/weapons/wave.ogg' - damage = 0 - damage_type = BRUTE - nodamage = 1 - color = "#101010" - var/turf/T - var/power = 4 - var/list/thrown_items = list() - -/obj/item/projectile/gravitychaos/Initialize() - . = ..() - var/obj/item/ammo_casing/energy/gravity/chaos/C = loc - if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items - power = min(C.gun.power, 15) - -/obj/item/projectile/gravitychaos/on_hit() - . = ..() - T = get_turf(src) - for(var/atom/movable/A in range(T, power)) - if(A == src|| (firer && A == src.firer) || A.anchored || thrown_items[A]) - continue - A.throw_at(get_edge_target_turf(A, pick(GLOB.cardinals)), power+1, 1) - thrown_items[A] = A - for(var/turf/Z in range(T,power)) - new /obj/effect/temp_visual/gravpush(Z) +/obj/item/projectile/gravityrepulse + name = "repulsion bolt" + icon = 'icons/effects/effects.dmi' + icon_state = "chronofield" + hitsound = 'sound/weapons/wave.ogg' + damage = 0 + damage_type = BRUTE + nodamage = 1 + color = "#33CCFF" + var/turf/T + var/power = 4 + var/list/thrown_items = list() + +/obj/item/projectile/gravityrepulse/Initialize() + . = ..() + var/obj/item/ammo_casing/energy/gravity/repulse/C = loc + if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items + power = min(C.gun.power, 15) + +/obj/item/projectile/gravityrepulse/on_hit() + . = ..() + T = get_turf(src) + for(var/atom/movable/A in range(T, power)) + if(A == src || (firer && A == src.firer) || A.anchored || thrown_items[A]) + continue + var/throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(A, src))) + A.throw_at(throwtarget,power+1,1) + thrown_items[A] = A + for(var/turf/F in range(T,power)) + new /obj/effect/temp_visual/gravpush(F) + +/obj/item/projectile/gravityattract + name = "attraction bolt" + icon = 'icons/effects/effects.dmi' + icon_state = "chronofield" + hitsound = 'sound/weapons/wave.ogg' + damage = 0 + damage_type = BRUTE + nodamage = 1 + color = "#FF6600" + var/turf/T + var/power = 4 + var/list/thrown_items = list() + +/obj/item/projectile/gravityattract/Initialize() + . = ..() + var/obj/item/ammo_casing/energy/gravity/attract/C = loc + if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items + power = min(C.gun.power, 15) + +/obj/item/projectile/gravityattract/on_hit() + . = ..() + T = get_turf(src) + for(var/atom/movable/A in range(T, power)) + if(A == src || (firer && A == src.firer) || A.anchored || thrown_items[A]) + continue + A.throw_at(T, power+1, 1) + thrown_items[A] = A + for(var/turf/F in range(T,power)) + new /obj/effect/temp_visual/gravpush(F) + +/obj/item/projectile/gravitychaos + name = "gravitational blast" + icon = 'icons/effects/effects.dmi' + icon_state = "chronofield" + hitsound = 'sound/weapons/wave.ogg' + damage = 0 + damage_type = BRUTE + nodamage = 1 + color = "#101010" + var/turf/T + var/power = 4 + var/list/thrown_items = list() + +/obj/item/projectile/gravitychaos/Initialize() + . = ..() + var/obj/item/ammo_casing/energy/gravity/chaos/C = loc + if(istype(C)) //Hard-coded maximum power so servers can't be crashed by trying to throw the entire Z level's items + power = min(C.gun.power, 15) + +/obj/item/projectile/gravitychaos/on_hit() + . = ..() + T = get_turf(src) + for(var/atom/movable/A in range(T, power)) + if(A == src|| (firer && A == src.firer) || A.anchored || thrown_items[A]) + continue + A.throw_at(get_edge_target_turf(A, pick(GLOB.cardinals)), power+1, 1) + thrown_items[A] = A + for(var/turf/Z in range(T,power)) + new /obj/effect/temp_visual/gravpush(Z) diff --git a/code/modules/projectiles/projectile/special/hallucination.dm b/code/modules/projectiles/projectile/special/hallucination.dm index 5814e7138e..a6da1f26bd 100644 --- a/code/modules/projectiles/projectile/special/hallucination.dm +++ b/code/modules/projectiles/projectile/special/hallucination.dm @@ -1,230 +1,230 @@ -/obj/item/projectile/hallucination - name = "bullet" - icon = null - icon_state = null - hitsound = "" - suppressed = TRUE - ricochets_max = 0 - ricochet_chance = 0 - damage = 0 - nodamage = TRUE - projectile_type = /obj/item/projectile/hallucination - log_override = TRUE - var/hal_icon_state - var/image/fake_icon - var/mob/living/carbon/hal_target - var/hal_fire_sound - var/hal_hitsound - var/hal_hitsound_wall - var/hal_impact_effect - var/hal_impact_effect_wall - var/hit_duration - var/hit_duration_wall - -/obj/item/projectile/hallucination/fire() - ..() - fake_icon = image('icons/obj/projectiles.dmi', src, hal_icon_state, ABOVE_MOB_LAYER) - if(hal_target.client) - hal_target.client.images += fake_icon - -/obj/item/projectile/hallucination/Destroy() - if(hal_target.client) - hal_target.client.images -= fake_icon - QDEL_NULL(fake_icon) - return ..() - -/obj/item/projectile/hallucination/Bump(atom/A) - if(!ismob(A)) - if(hal_hitsound_wall) - hal_target.playsound_local(loc, hal_hitsound_wall, 40, 1) - if(hal_impact_effect_wall) - spawn_hit(A, TRUE) - else if(A == hal_target) - if(hal_hitsound) - hal_target.playsound_local(A, hal_hitsound, 100, 1) - target_on_hit(A) - qdel(src) - return TRUE - -/obj/item/projectile/hallucination/proc/target_on_hit(mob/M) - if(M == hal_target) - to_chat(hal_target, "[M] is hit by \a [src] in the chest!") - hal_apply_effect() - else if(M in view(hal_target)) - to_chat(hal_target, "[M] is hit by \a [src] in the chest!!") - if(damage_type == BRUTE) - var/splatter_dir = dir - if(starting) - splatter_dir = get_dir(starting, get_turf(M)) - spawn_blood(M, splatter_dir) - else if(hal_impact_effect) - spawn_hit(M, FALSE) - -/obj/item/projectile/hallucination/proc/spawn_blood(mob/M, set_dir) - set waitfor = 0 - if(!hal_target.client) - return - - var/splatter_icon_state - if(set_dir in GLOB.diagonals) - splatter_icon_state = "splatter[pick(1, 2, 6)]" - else - splatter_icon_state = "splatter[pick(3, 4, 5)]" - - var/image/blood = image('icons/effects/blood.dmi', M, splatter_icon_state, ABOVE_MOB_LAYER) - var/target_pixel_x = 0 - var/target_pixel_y = 0 - switch(set_dir) - if(NORTH) - target_pixel_y = 16 - if(SOUTH) - target_pixel_y = -16 - layer = ABOVE_MOB_LAYER - if(EAST) - target_pixel_x = 16 - if(WEST) - target_pixel_x = -16 - if(NORTHEAST) - target_pixel_x = 16 - target_pixel_y = 16 - if(NORTHWEST) - target_pixel_x = -16 - target_pixel_y = 16 - if(SOUTHEAST) - target_pixel_x = 16 - target_pixel_y = -16 - layer = ABOVE_MOB_LAYER - if(SOUTHWEST) - target_pixel_x = -16 - target_pixel_y = -16 - layer = ABOVE_MOB_LAYER - hal_target.client.images += blood - animate(blood, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = 5) - addtimer(CALLBACK(src, .proc/cleanup_blood), 5) - -/obj/item/projectile/hallucination/proc/cleanup_blood(image/blood) - hal_target.client.images -= blood - qdel(blood) - -/obj/item/projectile/hallucination/proc/spawn_hit(atom/A, is_wall) - set waitfor = 0 - if(!hal_target.client) - return - - var/image/hit_effect = image('icons/effects/blood.dmi', A, is_wall ? hal_impact_effect_wall : hal_impact_effect, ABOVE_MOB_LAYER) - hit_effect.pixel_x = A.pixel_x + rand(-4,4) - hit_effect.pixel_y = A.pixel_y + rand(-4,4) - hal_target.client.images += hit_effect - sleep(is_wall ? hit_duration_wall : hit_duration) - hal_target.client.images -= hit_effect - qdel(hit_effect) - - -/obj/item/projectile/hallucination/proc/hal_apply_effect() - return - -/obj/item/projectile/hallucination/bullet - name = "bullet" - hal_icon_state = "bullet" - hal_fire_sound = "gunshot" - hal_hitsound = 'sound/weapons/pierce.ogg' - hal_hitsound_wall = "ricochet" - hal_impact_effect = "impact_bullet" - hal_impact_effect_wall = "impact_bullet" - hit_duration = 5 - hit_duration_wall = 5 - -/obj/item/projectile/hallucination/bullet/hal_apply_effect() - hal_target.adjustStaminaLoss(60) - -/obj/item/projectile/hallucination/laser - name = "laser" - damage_type = BURN - hal_icon_state = "laser" - hal_fire_sound = 'sound/weapons/laser.ogg' - hal_hitsound = 'sound/weapons/sear.ogg' - hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' - hal_impact_effect = "impact_laser" - hal_impact_effect_wall = "impact_laser_wall" - hit_duration = 4 - hit_duration_wall = 10 - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - -/obj/item/projectile/hallucination/laser/hal_apply_effect() - hal_target.adjustStaminaLoss(20) - hal_target.blur_eyes(2) - -/obj/item/projectile/hallucination/taser - name = "electrode" - damage_type = BURN - hal_icon_state = "spark" - color = "#FFFF00" - hal_fire_sound = 'sound/weapons/taser.ogg' - hal_hitsound = 'sound/weapons/taserhit.ogg' - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/item/projectile/hallucination/taser/hal_apply_effect() - hal_target.Knockdown(100) - hal_target.stuttering += 20 - if(hal_target.dna && hal_target.dna.check_mutation(HULK)) - hal_target.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") - else if((hal_target.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(hal_target, TRAIT_STUNIMMUNE)) - addtimer(CALLBACK(hal_target, /mob/living/carbon.proc/do_jitter_animation, 20), 5) - -/obj/item/projectile/hallucination/disabler - name = "disabler beam" - damage_type = STAMINA - hal_icon_state = "omnilaser" - hal_fire_sound = 'sound/weapons/taser2.ogg' - hal_hitsound = 'sound/weapons/tap.ogg' - hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' - hal_impact_effect = "impact_laser_blue" - hal_impact_effect_wall = null - hit_duration = 4 - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - -/obj/item/projectile/hallucination/disabler/hal_apply_effect() - hal_target.adjustStaminaLoss(25) - -/obj/item/projectile/hallucination/ebow - name = "bolt" - damage_type = TOX - hal_icon_state = "cbbolt" - hal_fire_sound = 'sound/weapons/genhit.ogg' - hal_hitsound = null - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/item/projectile/hallucination/ebow/hal_apply_effect() - hal_target.Knockdown(100) - hal_target.stuttering += 5 - hal_target.adjustStaminaLoss(8) - -/obj/item/projectile/hallucination/change - name = "bolt of change" - damage_type = BURN - hal_icon_state = "ice_1" - hal_fire_sound = 'sound/magic/staff_change.ogg' - hal_hitsound = null - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/item/projectile/hallucination/change/hal_apply_effect() - new /datum/hallucination/self_delusion(hal_target, TRUE, wabbajack = FALSE) - -/obj/item/projectile/hallucination/death - name = "bolt of death" - damage_type = BURN - hal_icon_state = "pulse1_bl" - hal_fire_sound = 'sound/magic/wandodeath.ogg' - hal_hitsound = null - hal_hitsound_wall = null - hal_impact_effect = null - hal_impact_effect_wall = null - -/obj/item/projectile/hallucination/death/hal_apply_effect() - new /datum/hallucination/death(hal_target, TRUE) +/obj/item/projectile/hallucination + name = "bullet" + icon = null + icon_state = null + hitsound = "" + suppressed = TRUE + ricochets_max = 0 + ricochet_chance = 0 + damage = 0 + nodamage = TRUE + projectile_type = /obj/item/projectile/hallucination + log_override = TRUE + var/hal_icon_state + var/image/fake_icon + var/mob/living/carbon/hal_target + var/hal_fire_sound + var/hal_hitsound + var/hal_hitsound_wall + var/hal_impact_effect + var/hal_impact_effect_wall + var/hit_duration + var/hit_duration_wall + +/obj/item/projectile/hallucination/fire() + ..() + fake_icon = image('icons/obj/projectiles.dmi', src, hal_icon_state, ABOVE_MOB_LAYER) + if(hal_target.client) + hal_target.client.images += fake_icon + +/obj/item/projectile/hallucination/Destroy() + if(hal_target.client) + hal_target.client.images -= fake_icon + QDEL_NULL(fake_icon) + return ..() + +/obj/item/projectile/hallucination/Bump(atom/A) + if(!ismob(A)) + if(hal_hitsound_wall) + hal_target.playsound_local(loc, hal_hitsound_wall, 40, 1) + if(hal_impact_effect_wall) + spawn_hit(A, TRUE) + else if(A == hal_target) + if(hal_hitsound) + hal_target.playsound_local(A, hal_hitsound, 100, 1) + target_on_hit(A) + qdel(src) + return TRUE + +/obj/item/projectile/hallucination/proc/target_on_hit(mob/M) + if(M == hal_target) + to_chat(hal_target, "[M] is hit by \a [src] in the chest!") + hal_apply_effect() + else if(M in view(hal_target)) + to_chat(hal_target, "[M] is hit by \a [src] in the chest!!") + if(damage_type == BRUTE) + var/splatter_dir = dir + if(starting) + splatter_dir = get_dir(starting, get_turf(M)) + spawn_blood(M, splatter_dir) + else if(hal_impact_effect) + spawn_hit(M, FALSE) + +/obj/item/projectile/hallucination/proc/spawn_blood(mob/M, set_dir) + set waitfor = 0 + if(!hal_target.client) + return + + var/splatter_icon_state + if(set_dir in GLOB.diagonals) + splatter_icon_state = "splatter[pick(1, 2, 6)]" + else + splatter_icon_state = "splatter[pick(3, 4, 5)]" + + var/image/blood = image('icons/effects/blood.dmi', M, splatter_icon_state, ABOVE_MOB_LAYER) + var/target_pixel_x = 0 + var/target_pixel_y = 0 + switch(set_dir) + if(NORTH) + target_pixel_y = 16 + if(SOUTH) + target_pixel_y = -16 + layer = ABOVE_MOB_LAYER + if(EAST) + target_pixel_x = 16 + if(WEST) + target_pixel_x = -16 + if(NORTHEAST) + target_pixel_x = 16 + target_pixel_y = 16 + if(NORTHWEST) + target_pixel_x = -16 + target_pixel_y = 16 + if(SOUTHEAST) + target_pixel_x = 16 + target_pixel_y = -16 + layer = ABOVE_MOB_LAYER + if(SOUTHWEST) + target_pixel_x = -16 + target_pixel_y = -16 + layer = ABOVE_MOB_LAYER + hal_target.client.images += blood + animate(blood, pixel_x = target_pixel_x, pixel_y = target_pixel_y, alpha = 0, time = 5) + addtimer(CALLBACK(src, .proc/cleanup_blood), 5) + +/obj/item/projectile/hallucination/proc/cleanup_blood(image/blood) + hal_target.client.images -= blood + qdel(blood) + +/obj/item/projectile/hallucination/proc/spawn_hit(atom/A, is_wall) + set waitfor = 0 + if(!hal_target.client) + return + + var/image/hit_effect = image('icons/effects/blood.dmi', A, is_wall ? hal_impact_effect_wall : hal_impact_effect, ABOVE_MOB_LAYER) + hit_effect.pixel_x = A.pixel_x + rand(-4,4) + hit_effect.pixel_y = A.pixel_y + rand(-4,4) + hal_target.client.images += hit_effect + sleep(is_wall ? hit_duration_wall : hit_duration) + hal_target.client.images -= hit_effect + qdel(hit_effect) + + +/obj/item/projectile/hallucination/proc/hal_apply_effect() + return + +/obj/item/projectile/hallucination/bullet + name = "bullet" + hal_icon_state = "bullet" + hal_fire_sound = "gunshot" + hal_hitsound = 'sound/weapons/pierce.ogg' + hal_hitsound_wall = "ricochet" + hal_impact_effect = "impact_bullet" + hal_impact_effect_wall = "impact_bullet" + hit_duration = 5 + hit_duration_wall = 5 + +/obj/item/projectile/hallucination/bullet/hal_apply_effect() + hal_target.adjustStaminaLoss(60) + +/obj/item/projectile/hallucination/laser + name = "laser" + damage_type = BURN + hal_icon_state = "laser" + hal_fire_sound = 'sound/weapons/laser.ogg' + hal_hitsound = 'sound/weapons/sear.ogg' + hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' + hal_impact_effect = "impact_laser" + hal_impact_effect_wall = "impact_laser_wall" + hit_duration = 4 + hit_duration_wall = 10 + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + +/obj/item/projectile/hallucination/laser/hal_apply_effect() + hal_target.adjustStaminaLoss(20) + hal_target.blur_eyes(2) + +/obj/item/projectile/hallucination/taser + name = "electrode" + damage_type = BURN + hal_icon_state = "spark" + color = "#FFFF00" + hal_fire_sound = 'sound/weapons/taser.ogg' + hal_hitsound = 'sound/weapons/taserhit.ogg' + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/item/projectile/hallucination/taser/hal_apply_effect() + hal_target.Knockdown(100) + hal_target.stuttering += 20 + if(hal_target.dna && hal_target.dna.check_mutation(HULK)) + hal_target.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") + else if((hal_target.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(hal_target, TRAIT_STUNIMMUNE)) + addtimer(CALLBACK(hal_target, /mob/living/carbon.proc/do_jitter_animation, 20), 5) + +/obj/item/projectile/hallucination/disabler + name = "disabler beam" + damage_type = STAMINA + hal_icon_state = "omnilaser" + hal_fire_sound = 'sound/weapons/taser2.ogg' + hal_hitsound = 'sound/weapons/tap.ogg' + hal_hitsound_wall = 'sound/weapons/effects/searwall.ogg' + hal_impact_effect = "impact_laser_blue" + hal_impact_effect_wall = null + hit_duration = 4 + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + +/obj/item/projectile/hallucination/disabler/hal_apply_effect() + hal_target.adjustStaminaLoss(25) + +/obj/item/projectile/hallucination/ebow + name = "bolt" + damage_type = TOX + hal_icon_state = "cbbolt" + hal_fire_sound = 'sound/weapons/genhit.ogg' + hal_hitsound = null + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/item/projectile/hallucination/ebow/hal_apply_effect() + hal_target.Knockdown(100) + hal_target.stuttering += 5 + hal_target.adjustStaminaLoss(8) + +/obj/item/projectile/hallucination/change + name = "bolt of change" + damage_type = BURN + hal_icon_state = "ice_1" + hal_fire_sound = 'sound/magic/staff_change.ogg' + hal_hitsound = null + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/item/projectile/hallucination/change/hal_apply_effect() + new /datum/hallucination/self_delusion(hal_target, TRUE, wabbajack = FALSE) + +/obj/item/projectile/hallucination/death + name = "bolt of death" + damage_type = BURN + hal_icon_state = "pulse1_bl" + hal_fire_sound = 'sound/magic/wandodeath.ogg' + hal_hitsound = null + hal_hitsound_wall = null + hal_impact_effect = null + hal_impact_effect_wall = null + +/obj/item/projectile/hallucination/death/hal_apply_effect() + new /datum/hallucination/death(hal_target, TRUE) diff --git a/code/modules/projectiles/projectile/special/ion.dm b/code/modules/projectiles/projectile/special/ion.dm index 403a1bedd7..231b4de021 100644 --- a/code/modules/projectiles/projectile/special/ion.dm +++ b/code/modules/projectiles/projectile/special/ion.dm @@ -1,20 +1,20 @@ -/obj/item/projectile/ion - name = "ion bolt" - icon_state = "ion" - damage = 0 - damage_type = BURN - nodamage = 1 - flag = "energy" - impact_effect_type = /obj/effect/temp_visual/impact_effect/ion - -/obj/item/projectile/ion/on_hit(atom/target, blocked = FALSE) - ..() - empulse(target, 1, 1) - return TRUE - -/obj/item/projectile/ion/weak - -/obj/item/projectile/ion/weak/on_hit(atom/target, blocked = FALSE) - ..() - empulse(target, 0, 0) - return TRUE +/obj/item/projectile/ion + name = "ion bolt" + icon_state = "ion" + damage = 0 + damage_type = BURN + nodamage = 1 + flag = "energy" + impact_effect_type = /obj/effect/temp_visual/impact_effect/ion + +/obj/item/projectile/ion/on_hit(atom/target, blocked = FALSE) + ..() + empulse(target, 1, 1) + return TRUE + +/obj/item/projectile/ion/weak + +/obj/item/projectile/ion/weak/on_hit(atom/target, blocked = FALSE) + ..() + empulse(target, 0, 0) + return TRUE diff --git a/code/modules/projectiles/projectile/special/meteor.dm b/code/modules/projectiles/projectile/special/meteor.dm index a92f141399..ec54e8dd1e 100644 --- a/code/modules/projectiles/projectile/special/meteor.dm +++ b/code/modules/projectiles/projectile/special/meteor.dm @@ -1,19 +1,19 @@ -/obj/item/projectile/meteor - name = "meteor" - icon = 'icons/obj/meteor.dmi' - icon_state = "small1" - damage = 0 - damage_type = BRUTE - nodamage = 1 - flag = "bullet" - -/obj/item/projectile/meteor/Bump(atom/A) - if(A == firer) - forceMove(A.loc) - return - A.ex_act(EXPLODE_HEAVY) - playsound(src.loc, 'sound/effects/meteorimpact.ogg', 40, 1) - for(var/mob/M in urange(10, src)) - if(!M.stat) - shake_camera(M, 3, 1) - qdel(src) +/obj/item/projectile/meteor + name = "meteor" + icon = 'icons/obj/meteor.dmi' + icon_state = "small1" + damage = 0 + damage_type = BRUTE + nodamage = 1 + flag = "bullet" + +/obj/item/projectile/meteor/Bump(atom/A) + if(A == firer) + forceMove(A.loc) + return + A.ex_act(EXPLODE_HEAVY) + playsound(src.loc, 'sound/effects/meteorimpact.ogg', 40, 1) + for(var/mob/M in urange(10, src)) + if(!M.stat) + shake_camera(M, 3, 1) + qdel(src) diff --git a/code/modules/projectiles/projectile/special/mindflayer.dm b/code/modules/projectiles/projectile/special/mindflayer.dm index ac4488cae0..84276e7275 100644 --- a/code/modules/projectiles/projectile/special/mindflayer.dm +++ b/code/modules/projectiles/projectile/special/mindflayer.dm @@ -1,9 +1,9 @@ -/obj/item/projectile/beam/mindflayer - name = "flayer ray" - -/obj/item/projectile/beam/mindflayer/on_hit(atom/target, blocked = FALSE) - . = ..() - if(ishuman(target)) - var/mob/living/carbon/human/M = target - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20) - M.hallucination += 30 +/obj/item/projectile/beam/mindflayer + name = "flayer ray" + +/obj/item/projectile/beam/mindflayer/on_hit(atom/target, blocked = FALSE) + . = ..() + if(ishuman(target)) + var/mob/living/carbon/human/M = target + M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20) + M.hallucination += 30 diff --git a/code/modules/projectiles/projectile/special/neurotoxin.dm b/code/modules/projectiles/projectile/special/neurotoxin.dm index 1d359585c0..12f502f9f0 100644 --- a/code/modules/projectiles/projectile/special/neurotoxin.dm +++ b/code/modules/projectiles/projectile/special/neurotoxin.dm @@ -1,14 +1,14 @@ -/obj/item/projectile/bullet/neurotoxin - name = "neurotoxin spit" - icon_state = "neurotoxin" - damage = 5 - damage_type = TOX - -/obj/item/projectile/bullet/neurotoxin/on_hit(atom/target, blocked = FALSE) - if(isalien(target)) - knockdown = 0 - nodamage = TRUE - else if(iscarbon(target)) - var/mob/living/L = target - L.Knockdown(100, TRUE, FALSE, 30, 25) - return ..() +/obj/item/projectile/bullet/neurotoxin + name = "neurotoxin spit" + icon_state = "neurotoxin" + damage = 5 + damage_type = TOX + +/obj/item/projectile/bullet/neurotoxin/on_hit(atom/target, blocked = FALSE) + if(isalien(target)) + knockdown = 0 + nodamage = TRUE + else if(iscarbon(target)) + var/mob/living/L = target + L.Knockdown(100, TRUE, FALSE, 30, 25) + return ..() diff --git a/code/modules/projectiles/projectile/special/plasma.dm b/code/modules/projectiles/projectile/special/plasma.dm index 4c05c42fb3..3a9ce24674 100644 --- a/code/modules/projectiles/projectile/special/plasma.dm +++ b/code/modules/projectiles/projectile/special/plasma.dm @@ -1,55 +1,55 @@ -/obj/item/projectile/plasma - name = "plasma blast" - icon_state = "plasmacutter" - damage_type = BRUTE - damage = 20 - range = 4 - dismemberment = 20 - impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser - var/pressure_decrease_active = FALSE - var/pressure_decrease = 0.25 - var/mine_range = 3 //mines this many additional tiles of rock - tracer_type = /obj/effect/projectile/tracer/plasma_cutter - muzzle_type = /obj/effect/projectile/muzzle/plasma_cutter - impact_type = /obj/effect/projectile/impact/plasma_cutter - -/obj/item/projectile/plasma/Initialize() - . = ..() - if(!lavaland_equipment_pressure_check(get_turf(src))) - name = "weakened [name]" - damage = damage * pressure_decrease - pressure_decrease_active = TRUE - -/obj/item/projectile/plasma/on_hit(atom/target) - . = ..() - if(ismineralturf(target)) - var/turf/closed/mineral/M = target - M.gets_drilled(firer) - if(mine_range) - mine_range-- - range++ - if(range > 0) - return -1 - -/obj/item/projectile/plasma/adv - damage = 28 - range = 5 - mine_range = 5 - -/obj/item/projectile/plasma/adv/mech - damage = 40 - range = 9 - mine_range = 3 - -/obj/item/projectile/plasma/turret - //Between normal and advanced for damage, made a beam so not the turret does not destroy glass - name = "plasma beam" - damage = 24 - range = 7 - pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - -/obj/item/projectile/plasma/weak - dismemberment = 0 - damage = 10 - range = 4 +/obj/item/projectile/plasma + name = "plasma blast" + icon_state = "plasmacutter" + damage_type = BRUTE + damage = 20 + range = 4 + dismemberment = 20 + impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser + var/pressure_decrease_active = FALSE + var/pressure_decrease = 0.25 + var/mine_range = 3 //mines this many additional tiles of rock + tracer_type = /obj/effect/projectile/tracer/plasma_cutter + muzzle_type = /obj/effect/projectile/muzzle/plasma_cutter + impact_type = /obj/effect/projectile/impact/plasma_cutter + +/obj/item/projectile/plasma/Initialize() + . = ..() + if(!lavaland_equipment_pressure_check(get_turf(src))) + name = "weakened [name]" + damage = damage * pressure_decrease + pressure_decrease_active = TRUE + +/obj/item/projectile/plasma/on_hit(atom/target) + . = ..() + if(ismineralturf(target)) + var/turf/closed/mineral/M = target + M.gets_drilled(firer) + if(mine_range) + mine_range-- + range++ + if(range > 0) + return -1 + +/obj/item/projectile/plasma/adv + damage = 28 + range = 5 + mine_range = 5 + +/obj/item/projectile/plasma/adv/mech + damage = 40 + range = 9 + mine_range = 3 + +/obj/item/projectile/plasma/turret + //Between normal and advanced for damage, made a beam so not the turret does not destroy glass + name = "plasma beam" + damage = 24 + range = 7 + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE + +/obj/item/projectile/plasma/weak + dismemberment = 0 + damage = 10 + range = 4 mine_range = 0 \ No newline at end of file diff --git a/code/modules/projectiles/projectile/special/rocket.dm b/code/modules/projectiles/projectile/special/rocket.dm index e15810c6bb..03d85665d5 100644 --- a/code/modules/projectiles/projectile/special/rocket.dm +++ b/code/modules/projectiles/projectile/special/rocket.dm @@ -1,46 +1,46 @@ -/obj/item/projectile/bullet/gyro - name ="explosive bolt" - icon_state= "bolter" - damage = 50 - -/obj/item/projectile/bullet/gyro/on_hit(atom/target, blocked = FALSE) - ..() - explosion(target, -1, 0, 2) - return TRUE - -/obj/item/projectile/bullet/a84mm - name ="\improper HEDP rocket" - desc = "USE A WEEL GUN" - icon_state= "84mm-hedp" - damage = 80 - var/anti_armour_damage = 200 - armour_penetration = 100 - dismemberment = 100 - ricochets_max = 0 - -/obj/item/projectile/bullet/a84mm/on_hit(atom/target, blocked = FALSE) - ..() - explosion(target, -1, 1, 3, 1, 0, flame_range = 4) - - if(ismecha(target)) - var/obj/mecha/M = target - M.take_damage(anti_armour_damage) - if(issilicon(target)) - var/mob/living/silicon/S = target - S.take_overall_damage(anti_armour_damage*0.75, anti_armour_damage*0.25) - return TRUE - -/obj/item/projectile/bullet/a84mm_he - name ="\improper HE missile" - desc = "Boom." - icon_state = "missile" - damage = 30 - ricochets_max = 0 //it's a MISSILE - -/obj/item/projectile/bullet/a84mm_he/on_hit(atom/target, blocked=0) - ..() - if(!isliving(target)) //if the target isn't alive, so is a wall or something - explosion(target, 0, 1, 2, 4) - else - explosion(target, 0, 0, 2, 4) +/obj/item/projectile/bullet/gyro + name ="explosive bolt" + icon_state= "bolter" + damage = 50 + +/obj/item/projectile/bullet/gyro/on_hit(atom/target, blocked = FALSE) + ..() + explosion(target, -1, 0, 2) + return TRUE + +/obj/item/projectile/bullet/a84mm + name ="\improper HEDP rocket" + desc = "USE A WEEL GUN" + icon_state= "84mm-hedp" + damage = 80 + var/anti_armour_damage = 200 + armour_penetration = 100 + dismemberment = 100 + ricochets_max = 0 + +/obj/item/projectile/bullet/a84mm/on_hit(atom/target, blocked = FALSE) + ..() + explosion(target, -1, 1, 3, 1, 0, flame_range = 4) + + if(ismecha(target)) + var/obj/mecha/M = target + M.take_damage(anti_armour_damage) + if(issilicon(target)) + var/mob/living/silicon/S = target + S.take_overall_damage(anti_armour_damage*0.75, anti_armour_damage*0.25) + return TRUE + +/obj/item/projectile/bullet/a84mm_he + name ="\improper HE missile" + desc = "Boom." + icon_state = "missile" + damage = 30 + ricochets_max = 0 //it's a MISSILE + +/obj/item/projectile/bullet/a84mm_he/on_hit(atom/target, blocked=0) + ..() + if(!isliving(target)) //if the target isn't alive, so is a wall or something + explosion(target, 0, 1, 2, 4) + else + explosion(target, 0, 0, 2, 4) return TRUE \ No newline at end of file diff --git a/code/modules/projectiles/projectile/special/temperature.dm b/code/modules/projectiles/projectile/special/temperature.dm index 44c8df6c00..dae7d5f8b3 100644 --- a/code/modules/projectiles/projectile/special/temperature.dm +++ b/code/modules/projectiles/projectile/special/temperature.dm @@ -1,18 +1,18 @@ -/obj/item/projectile/temp - name = "freeze beam" - icon_state = "ice_2" - damage = 0 - damage_type = BURN - nodamage = FALSE - flag = "energy" - var/temperature = 100 - -/obj/item/projectile/temp/on_hit(atom/target, blocked = 0) - . = ..() - if(isliving(target)) - var/mob/living/L = target - L.adjust_bodytemperature(((100-blocked)/100)*(temperature - L.bodytemperature)) // the new body temperature is adjusted by 100-blocked % of the delta between body temperature and the bullet's effect temperature - -/obj/item/projectile/temp/hot - name = "heat beam" - temperature = 400 +/obj/item/projectile/temp + name = "freeze beam" + icon_state = "ice_2" + damage = 0 + damage_type = BURN + nodamage = FALSE + flag = "energy" + var/temperature = 100 + +/obj/item/projectile/temp/on_hit(atom/target, blocked = 0) + . = ..() + if(isliving(target)) + var/mob/living/L = target + L.adjust_bodytemperature(((100-blocked)/100)*(temperature - L.bodytemperature)) // the new body temperature is adjusted by 100-blocked % of the delta between body temperature and the bullet's effect temperature + +/obj/item/projectile/temp/hot + name = "heat beam" + temperature = 400 diff --git a/code/modules/projectiles/projectile/special/wormhole.dm b/code/modules/projectiles/projectile/special/wormhole.dm index 07b56a133f..0832f0cc76 100644 --- a/code/modules/projectiles/projectile/special/wormhole.dm +++ b/code/modules/projectiles/projectile/special/wormhole.dm @@ -1,29 +1,29 @@ -/obj/item/projectile/beam/wormhole - name = "bluespace beam" - icon_state = "spark" - hitsound = "sparks" - damage = 0 - nodamage = TRUE - pass_flags = PASSGLASS | PASSTABLE | PASSGRILLE | PASSMOB - var/obj/item/gun/energy/wormhole_projector/gun - color = "#33CCFF" - tracer_type = /obj/effect/projectile/tracer/wormhole - impact_type = /obj/effect/projectile/impact/wormhole - muzzle_type = /obj/effect/projectile/muzzle/wormhole - hitscan = TRUE - -/obj/item/projectile/beam/wormhole/orange - name = "orange bluespace beam" - color = "#FF6600" - -/obj/item/projectile/beam/wormhole/Initialize(mapload, obj/item/ammo_casing/energy/wormhole/casing) - . = ..() - if(casing) - gun = casing.gun - - -/obj/item/projectile/beam/wormhole/on_hit(atom/target) - if(!gun) - qdel(src) - return - gun.create_portal(src, get_turf(src)) +/obj/item/projectile/beam/wormhole + name = "bluespace beam" + icon_state = "spark" + hitsound = "sparks" + damage = 0 + nodamage = TRUE + pass_flags = PASSGLASS | PASSTABLE | PASSGRILLE | PASSMOB + var/obj/item/gun/energy/wormhole_projector/gun + color = "#33CCFF" + tracer_type = /obj/effect/projectile/tracer/wormhole + impact_type = /obj/effect/projectile/impact/wormhole + muzzle_type = /obj/effect/projectile/muzzle/wormhole + hitscan = TRUE + +/obj/item/projectile/beam/wormhole/orange + name = "orange bluespace beam" + color = "#FF6600" + +/obj/item/projectile/beam/wormhole/Initialize(mapload, obj/item/ammo_casing/energy/wormhole/casing) + . = ..() + if(casing) + gun = casing.gun + + +/obj/item/projectile/beam/wormhole/on_hit(atom/target) + if(!gun) + qdel(src) + return + gun.create_portal(src, get_turf(src)) diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm index 97bc054ecd..132c34c9f8 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -1,247 +1,247 @@ -#define PH_WEAK (1 << 0) -#define TEMP_WEAK (1 << 1) - -/obj/item/reagent_containers - name = "Container" - desc = "..." - icon = 'icons/obj/chemical.dmi' - icon_state = null - w_class = WEIGHT_CLASS_TINY - var/amount_per_transfer_from_this = 5 - var/list/possible_transfer_amounts = list(5,10,15,20,25,30) - var/APTFT_altclick = TRUE //will the set amount_per_transfer_from_this proc be called on AltClick() ? - var/volume = 30 - var/reagent_flags - var/list/list_reagents = null - var/spawned_disease = null - var/disease_amount = 20 - var/spillable = FALSE - var/beaker_weakness_bitflag = NONE//Bitflag! - var/container_HP = 2 - var/cached_icon - -/obj/item/reagent_containers/Initialize(mapload, vol) - . = ..() - if(isnum(vol) && vol > 0) - volume = vol - if(length(possible_transfer_amounts)) - verbs += /obj/item/reagent_containers/proc/set_APTFT - create_reagents(volume, reagent_flags) - if(spawned_disease) - var/datum/disease/F = new spawned_disease() - var/list/data = list("blood_DNA" = "UNKNOWN DNA", "blood_type" = "SY","viruses"= list(F)) - reagents.add_reagent(/datum/reagent/blood, disease_amount, data) - add_initial_reagents() - -/obj/item/reagent_containers/examine(mob/user) - . = ..() - if(length(possible_transfer_amounts) > 1) - . += "Currently transferring [amount_per_transfer_from_this] units per use." - if(APTFT_altclick && user.Adjacent(src)) - . += "Alt-click it to set its transfer amount." - -/obj/item/reagent_containers/AltClick(mob/user) - . = ..() - if(APTFT_altclick && length(possible_transfer_amounts) > 1 && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - set_APTFT() - return TRUE - -/obj/item/reagent_containers/proc/set_APTFT(mob/user) //set amount_per_transfer_from_this - set name = "Set Transfer Amount" - set category = "Object" - set waitfor = FALSE - var/N = input("Amount per transfer from this:","[src]") as null|anything in possible_transfer_amounts - if(N) - amount_per_transfer_from_this = N - to_chat(user, "[src]'s transfer amount is now [amount_per_transfer_from_this] units.") - -/obj/item/reagent_containers/proc/add_initial_reagents() - if(list_reagents) - reagents.add_reagent_list(list_reagents) - -/obj/item/reagent_containers/attack_self(mob/user) - if(possible_transfer_amounts.len) - var/i=0 - for(var/A in possible_transfer_amounts) - i++ - if(A == amount_per_transfer_from_this) - if(i[src]'s transfer amount is now [amount_per_transfer_from_this] units.") - return - -/obj/item/reagent_containers/attack(mob/M, mob/user, def_zone) - if(user.a_intent == INTENT_HARM) - return ..() - -/obj/item/reagent_containers/proc/canconsume(mob/eater, mob/user) - if(!iscarbon(eater)) - return 0 - 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(user) || eater == user) ? "your" : "[eater.p_their()]" - to_chat(user, "You have to remove [who] [covered] first!") - return 0 - return 1 - -/obj/item/reagent_containers/ex_act() - if(reagents) - for(var/datum/reagent/R in reagents.reagent_list) - R.on_ex_act() - if(!QDELETED(src)) - ..() - -/obj/item/reagent_containers/fire_act(exposed_temperature, exposed_volume) - reagents.expose_temperature(exposed_temperature) - ..() - -/obj/item/reagent_containers/throw_impact(atom/target) - . = ..() - SplashReagents(target, TRUE) - -/obj/item/reagent_containers/proc/bartender_check(atom/target) - . = FALSE - if(target.CanPass(src, get_turf(src)) && thrownby && thrownby.actions) - for(var/datum/action/innate/drink_fling/D in thrownby.actions) - if(D.active) - return TRUE - -/obj/item/reagent_containers/proc/ForceResetRotation() - transform = initial(transform) - -/obj/item/reagent_containers/proc/SplashReagents(atom/target, thrown = FALSE) - if(!reagents || !reagents.total_volume || !spillable) - return - - if(ismob(target) && target.reagents) - if(thrown) - reagents.total_volume *= rand(5,10) * 0.1 //Not all of it makes contact with the target - var/mob/M = target - var/R - target.visible_message("[M] has been splashed with something!", \ - "[M] has been splashed with something!") - for(var/datum/reagent/A in reagents.reagent_list) - R += A.type + " (" - R += num2text(A.volume) + ")," - - if(thrownby) - log_combat(thrownby, M, "splashed", R) - reagents.reaction(target, TOUCH) - - else if(bartender_check(target) && thrown) - visible_message("[src] lands onto the [target.name] without spilling a single drop.") - transform = initial(transform) - addtimer(CALLBACK(src, .proc/ForceResetRotation), 1) - return - - else - if(isturf(target) && reagents.reagent_list.len && thrownby) - log_combat(thrownby, target, "splashed (thrown) [english_list(reagents.reagent_list)]", "in [AREACOORD(target)]") - log_game("[key_name(thrownby)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] in [AREACOORD(target)].") - message_admins("[ADMIN_LOOKUPFLW(thrownby)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] in [ADMIN_VERBOSEJMP(target)].") - visible_message("[src] spills its contents all over [target].") - reagents.reaction(target, TOUCH) - if(QDELETED(src)) - return - - reagents.clear_reagents() - -//melts plastic beakers -/obj/item/reagent_containers/microwave_act(obj/machinery/microwave/M) - reagents.expose_temperature(1000) - if(beaker_weakness_bitflag & TEMP_WEAK) - var/list/seen = viewers(5, get_turf(src)) - var/iconhtml = icon2html(src, seen) - for(var/mob/H in seen) - to_chat(H, "[iconhtml] \The [src]'s melts from the temperature!") - playsound(get_turf(src), 'sound/FermiChem/heatmelt.ogg', 80, 1) - qdel(src) - ..() - -//melts plastic beakers -/obj/item/reagent_containers/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - reagents.expose_temperature(exposed_temperature) - temp_check() - -/obj/item/reagent_containers/proc/temp_check() - if(beaker_weakness_bitflag & TEMP_WEAK) - if(reagents.chem_temp >= 444)//assuming polypropylene - START_PROCESSING(SSobj, src) - -//melts glass beakers -/obj/item/reagent_containers/proc/pH_check() - if(beaker_weakness_bitflag & PH_WEAK) - if((reagents.pH < 1.5) || (reagents.pH > 12.5)) - START_PROCESSING(SSobj, src) - else if((reagents.pH < -3) || (reagents.pH > 17)) - visible_message("[icon2html(src, viewers(src))] \The [src] is damaged by the super pH and begins to deform!") - reagents.pH = CLAMP(reagents.pH, -3, 17) - container_HP -= 1 - - -/obj/item/reagent_containers/process() - if(!cached_icon) - cached_icon = icon_state - var/damage - var/cause - if(beaker_weakness_bitflag & PH_WEAK) - if(reagents.pH < 2) - damage = (2 - reagents.pH)/20 - cause = "from the extreme pH" - playsound(get_turf(src), 'sound/FermiChem/bufferadd.ogg', 50, 1) - - if(reagents.pH > 12) - damage = (reagents.pH - 12)/20 - cause = "from the extreme pH" - playsound(get_turf(src), 'sound/FermiChem/bufferadd.ogg', 50, 1) - - if(beaker_weakness_bitflag & TEMP_WEAK) - if(reagents.chem_temp >= 444) - if(damage) - damage += (reagents.chem_temp/444)/5 - else - damage = (reagents.chem_temp/444)/5 - if(cause) - cause += " and " - cause += "from the high temperature" - playsound(get_turf(src), 'sound/FermiChem/heatdam.ogg', 50, 1) - - if(!damage || damage <= 0) - STOP_PROCESSING(SSobj, src) - - container_HP -= damage - - var/list/seen = viewers(5, get_turf(src)) - var/iconhtml = icon2html(src, seen) - - var/damage_percent = ((container_HP / initial(container_HP)*100)) - switch(damage_percent) - if(-INFINITY to 0) - for(var/mob/M in seen) - to_chat(M, "[iconhtml] \The [src]'s melts [cause]!") - playsound(get_turf(src), 'sound/FermiChem/acidmelt.ogg', 80, 1) - SSblackbox.record_feedback("tally", "fermi_chem", 1, "Times beakers have melted") - STOP_PROCESSING(SSobj, src) - qdel(src) - return - if(0 to 35) - icon_state = "[cached_icon]_m3" - desc = "[initial(desc)] It is severely deformed." - if(35 to 70) - icon_state = "[cached_icon]_m2" - desc = "[initial(desc)] It is deformed." - if(70 to 85) - desc = "[initial(desc)] It is mildly deformed." - icon_state = "[cached_icon]_m1" - - update_icon() - if(prob(25)) - for(var/mob/M in seen) - to_chat(M, "[iconhtml] \The [src]'s is damaged by [cause] and begins to deform!") +#define PH_WEAK (1 << 0) +#define TEMP_WEAK (1 << 1) + +/obj/item/reagent_containers + name = "Container" + desc = "..." + icon = 'icons/obj/chemical.dmi' + icon_state = null + w_class = WEIGHT_CLASS_TINY + var/amount_per_transfer_from_this = 5 + var/list/possible_transfer_amounts = list(5,10,15,20,25,30) + var/APTFT_altclick = TRUE //will the set amount_per_transfer_from_this proc be called on AltClick() ? + var/volume = 30 + var/reagent_flags + var/list/list_reagents = null + var/spawned_disease = null + var/disease_amount = 20 + var/spillable = FALSE + var/beaker_weakness_bitflag = NONE//Bitflag! + var/container_HP = 2 + var/cached_icon + +/obj/item/reagent_containers/Initialize(mapload, vol) + . = ..() + if(isnum(vol) && vol > 0) + volume = vol + if(length(possible_transfer_amounts)) + verbs += /obj/item/reagent_containers/proc/set_APTFT + create_reagents(volume, reagent_flags) + if(spawned_disease) + var/datum/disease/F = new spawned_disease() + var/list/data = list("blood_DNA" = "UNKNOWN DNA", "blood_type" = "SY","viruses"= list(F)) + reagents.add_reagent(/datum/reagent/blood, disease_amount, data) + add_initial_reagents() + +/obj/item/reagent_containers/examine(mob/user) + . = ..() + if(length(possible_transfer_amounts) > 1) + . += "Currently transferring [amount_per_transfer_from_this] units per use." + if(APTFT_altclick && user.Adjacent(src)) + . += "Alt-click it to set its transfer amount." + +/obj/item/reagent_containers/AltClick(mob/user) + . = ..() + if(APTFT_altclick && length(possible_transfer_amounts) > 1 && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + set_APTFT() + return TRUE + +/obj/item/reagent_containers/proc/set_APTFT(mob/user) //set amount_per_transfer_from_this + set name = "Set Transfer Amount" + set category = "Object" + set waitfor = FALSE + var/N = input("Amount per transfer from this:","[src]") as null|anything in possible_transfer_amounts + if(N) + amount_per_transfer_from_this = N + to_chat(user, "[src]'s transfer amount is now [amount_per_transfer_from_this] units.") + +/obj/item/reagent_containers/proc/add_initial_reagents() + if(list_reagents) + reagents.add_reagent_list(list_reagents) + +/obj/item/reagent_containers/attack_self(mob/user) + if(possible_transfer_amounts.len) + var/i=0 + for(var/A in possible_transfer_amounts) + i++ + if(A == amount_per_transfer_from_this) + if(i[src]'s transfer amount is now [amount_per_transfer_from_this] units.") + return + +/obj/item/reagent_containers/attack(mob/M, mob/user, def_zone) + if(user.a_intent == INTENT_HARM) + return ..() + +/obj/item/reagent_containers/proc/canconsume(mob/eater, mob/user) + if(!iscarbon(eater)) + return 0 + 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(user) || eater == user) ? "your" : "[eater.p_their()]" + to_chat(user, "You have to remove [who] [covered] first!") + return 0 + return 1 + +/obj/item/reagent_containers/ex_act() + if(reagents) + for(var/datum/reagent/R in reagents.reagent_list) + R.on_ex_act() + if(!QDELETED(src)) + ..() + +/obj/item/reagent_containers/fire_act(exposed_temperature, exposed_volume) + reagents.expose_temperature(exposed_temperature) + ..() + +/obj/item/reagent_containers/throw_impact(atom/target) + . = ..() + SplashReagents(target, TRUE) + +/obj/item/reagent_containers/proc/bartender_check(atom/target) + . = FALSE + if(target.CanPass(src, get_turf(src)) && thrownby && thrownby.actions) + for(var/datum/action/innate/drink_fling/D in thrownby.actions) + if(D.active) + return TRUE + +/obj/item/reagent_containers/proc/ForceResetRotation() + transform = initial(transform) + +/obj/item/reagent_containers/proc/SplashReagents(atom/target, thrown = FALSE) + if(!reagents || !reagents.total_volume || !spillable) + return + + if(ismob(target) && target.reagents) + if(thrown) + reagents.total_volume *= rand(5,10) * 0.1 //Not all of it makes contact with the target + var/mob/M = target + var/R + target.visible_message("[M] has been splashed with something!", \ + "[M] has been splashed with something!") + for(var/datum/reagent/A in reagents.reagent_list) + R += A.type + " (" + R += num2text(A.volume) + ")," + + if(thrownby) + log_combat(thrownby, M, "splashed", R) + reagents.reaction(target, TOUCH) + + else if(bartender_check(target) && thrown) + visible_message("[src] lands onto the [target.name] without spilling a single drop.") + transform = initial(transform) + addtimer(CALLBACK(src, .proc/ForceResetRotation), 1) + return + + else + if(isturf(target) && reagents.reagent_list.len && thrownby) + log_combat(thrownby, target, "splashed (thrown) [english_list(reagents.reagent_list)]", "in [AREACOORD(target)]") + log_game("[key_name(thrownby)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] in [AREACOORD(target)].") + message_admins("[ADMIN_LOOKUPFLW(thrownby)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] in [ADMIN_VERBOSEJMP(target)].") + visible_message("[src] spills its contents all over [target].") + reagents.reaction(target, TOUCH) + if(QDELETED(src)) + return + + reagents.clear_reagents() + +//melts plastic beakers +/obj/item/reagent_containers/microwave_act(obj/machinery/microwave/M) + reagents.expose_temperature(1000) + if(beaker_weakness_bitflag & TEMP_WEAK) + var/list/seen = viewers(5, get_turf(src)) + var/iconhtml = icon2html(src, seen) + for(var/mob/H in seen) + to_chat(H, "[iconhtml] \The [src]'s melts from the temperature!") + playsound(get_turf(src), 'sound/FermiChem/heatmelt.ogg', 80, 1) + qdel(src) + ..() + +//melts plastic beakers +/obj/item/reagent_containers/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + reagents.expose_temperature(exposed_temperature) + temp_check() + +/obj/item/reagent_containers/proc/temp_check() + if(beaker_weakness_bitflag & TEMP_WEAK) + if(reagents.chem_temp >= 444)//assuming polypropylene + START_PROCESSING(SSobj, src) + +//melts glass beakers +/obj/item/reagent_containers/proc/pH_check() + if(beaker_weakness_bitflag & PH_WEAK) + if((reagents.pH < 1.5) || (reagents.pH > 12.5)) + START_PROCESSING(SSobj, src) + else if((reagents.pH < -3) || (reagents.pH > 17)) + visible_message("[icon2html(src, viewers(src))] \The [src] is damaged by the super pH and begins to deform!") + reagents.pH = CLAMP(reagents.pH, -3, 17) + container_HP -= 1 + + +/obj/item/reagent_containers/process() + if(!cached_icon) + cached_icon = icon_state + var/damage + var/cause + if(beaker_weakness_bitflag & PH_WEAK) + if(reagents.pH < 2) + damage = (2 - reagents.pH)/20 + cause = "from the extreme pH" + playsound(get_turf(src), 'sound/FermiChem/bufferadd.ogg', 50, 1) + + if(reagents.pH > 12) + damage = (reagents.pH - 12)/20 + cause = "from the extreme pH" + playsound(get_turf(src), 'sound/FermiChem/bufferadd.ogg', 50, 1) + + if(beaker_weakness_bitflag & TEMP_WEAK) + if(reagents.chem_temp >= 444) + if(damage) + damage += (reagents.chem_temp/444)/5 + else + damage = (reagents.chem_temp/444)/5 + if(cause) + cause += " and " + cause += "from the high temperature" + playsound(get_turf(src), 'sound/FermiChem/heatdam.ogg', 50, 1) + + if(!damage || damage <= 0) + STOP_PROCESSING(SSobj, src) + + container_HP -= damage + + var/list/seen = viewers(5, get_turf(src)) + var/iconhtml = icon2html(src, seen) + + var/damage_percent = ((container_HP / initial(container_HP)*100)) + switch(damage_percent) + if(-INFINITY to 0) + for(var/mob/M in seen) + to_chat(M, "[iconhtml] \The [src]'s melts [cause]!") + playsound(get_turf(src), 'sound/FermiChem/acidmelt.ogg', 80, 1) + SSblackbox.record_feedback("tally", "fermi_chem", 1, "Times beakers have melted") + STOP_PROCESSING(SSobj, src) + qdel(src) + return + if(0 to 35) + icon_state = "[cached_icon]_m3" + desc = "[initial(desc)] It is severely deformed." + if(35 to 70) + icon_state = "[cached_icon]_m2" + desc = "[initial(desc)] It is deformed." + if(70 to 85) + desc = "[initial(desc)] It is mildly deformed." + icon_state = "[cached_icon]_m1" + + update_icon() + if(prob(25)) + for(var/mob/M in seen) + to_chat(M, "[iconhtml] \The [src]'s is damaged by [cause] and begins to deform!") diff --git a/code/modules/reagents/reagent_containers/borghydro.dm b/code/modules/reagents/reagent_containers/borghydro.dm index 48a5c10e3b..34dd97b0aa 100644 --- a/code/modules/reagents/reagent_containers/borghydro.dm +++ b/code/modules/reagents/reagent_containers/borghydro.dm @@ -1,264 +1,264 @@ -/* -Contains: -Borg Hypospray -Borg Shaker -Nothing to do with hydroponics in here. Sorry to dissapoint you. -*/ - -/* -Borg Hypospray -*/ -/obj/item/reagent_containers/borghypo - name = "cyborg hypospray" - desc = "An advanced chemical synthesizer and injection system, designed for heavy-duty medical equipment." - icon = 'icons/obj/syringe.dmi' - item_state = "hypo" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - icon_state = "borghypo" - amount_per_transfer_from_this = 5 - volume = 30 - possible_transfer_amounts = list() - var/mode = 1 - var/charge_cost = 50 - var/charge_tick = 0 - var/recharge_time = 5 //Time it takes for shots to recharge (in seconds) - var/bypass_protection = 0 //If the hypospray can go through armor or thick material - - var/list/datum/reagents/reagent_list = list() - var/list/reagent_ids = list(/datum/reagent/medicine/dexalin, /datum/reagent/medicine/kelotane, /datum/reagent/medicine/bicaridine, /datum/reagent/medicine/antitoxin, - /datum/reagent/medicine/epinephrine, /datum/reagent/medicine/spaceacillin, /datum/reagent/medicine/salglu_solution, /datum/reagent/medicine/insulin) - var/accepts_reagent_upgrades = TRUE //If upgrades can increase number of reagents dispensed. - var/list/modes = list() //Basically the inverse of reagent_ids. Instead of having numbers as "keys" and strings as values it has strings as keys and numbers as values. - //Used as list for input() in shakers. - var/list/reagent_names = list() - -/obj/item/reagent_containers/borghypo/Initialize() - . = ..() - - for(var/R in reagent_ids) - add_reagent(R) - - START_PROCESSING(SSobj, src) - -/obj/item/reagent_containers/borghypo/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/reagent_containers/borghypo/process() //Every [recharge_time] seconds, recharge some reagents for the cyborg - charge_tick++ - if(charge_tick >= recharge_time) - regenerate_reagents() - charge_tick = 0 - - //update_icon() - return 1 - -// Use this to add more chemicals for the borghypo to produce. -/obj/item/reagent_containers/borghypo/proc/add_reagent(datum/reagent/reagent) - reagent_ids |= reagent - var/datum/reagents/RG = new(30) - RG.my_atom = src - reagent_list += RG - - var/datum/reagents/R = reagent_list[reagent_list.len] - R.add_reagent(reagent, 30) - - modes[reagent] = modes.len + 1 - reagent_names[initial(reagent.name)] = reagent - -/obj/item/reagent_containers/borghypo/proc/del_reagent(datum/reagent/reagent) - reagent_ids -= reagent - reagent_names -= initial(reagent.name) - var/datum/reagents/RG - var/datum/reagents/TRG - for(var/i in 1 to reagent_ids.len) - TRG = reagent_list[i] - if (TRG.has_reagent(reagent)) - RG = TRG - break - if (RG) - reagent_list -= RG - RG.del_reagent(reagent) - - modes[reagent] = modes.len - 1 - -/obj/item/reagent_containers/borghypo/proc/regenerate_reagents() - if(iscyborg(src.loc)) - var/mob/living/silicon/robot/R = src.loc - if(R && R.cell) - for(var/i in 1 to reagent_ids.len) - var/datum/reagents/RG = reagent_list[i] - if(RG.total_volume < RG.maximum_volume) //Don't recharge reagents and drain power if the storage is full. - R.cell.use(charge_cost) //Take power from borg... - RG.add_reagent(reagent_ids[i], 5) //And fill hypo with reagent. - -/obj/item/reagent_containers/borghypo/attack(mob/living/carbon/M, mob/user) - var/datum/reagents/R = reagent_list[mode] - if(!R.total_volume) - to_chat(user, "The injector is empty.") - return - if(!istype(M)) - return - if(R.total_volume && M.can_inject(user, 1, user.zone_selected,bypass_protection)) - to_chat(M, "You feel a tiny prick!") - to_chat(user, "You inject [M] with the injector.") - var/fraction = min(amount_per_transfer_from_this/R.total_volume, 1) - R.reaction(M, INJECT, fraction) - if(M.reagents) - var/trans = R.trans_to(M, amount_per_transfer_from_this) - to_chat(user, "[trans] unit\s injected. [R.total_volume] unit\s remaining.") - - var/list/injected = list() - for(var/datum/reagent/RG in R.reagent_list) - injected += RG.name - log_combat(user, M, "injected", src, "(CHEMICALS: [english_list(injected)])") - -/obj/item/reagent_containers/borghypo/attack_self(mob/user) - var/chosen_reagent = modes[reagent_names[input(user, "What reagent do you want to dispense?") as null|anything in reagent_names]] - if(!chosen_reagent) - return - mode = chosen_reagent - playsound(loc, 'sound/effects/pop.ogg', 50, 0) - var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_ids[mode]] - to_chat(user, "[src] is now dispensing '[R.name]'.") - return - -/obj/item/reagent_containers/borghypo/examine(mob/user) - . = ..() - . += DescribeContents() //Because using the standardized reagents datum was just too cool for whatever fuckwit wrote this - -/obj/item/reagent_containers/borghypo/proc/DescribeContents() - var/empty = 1 - - for(var/datum/reagents/RS in reagent_list) - var/datum/reagent/R = locate() in RS.reagent_list - if(R) - . += "It currently has [R.volume] unit\s of [R.name] stored." - empty = 0 - - if(empty) - . += "It is currently empty! Allow some time for the internal syntheszier to produce more." - -/obj/item/reagent_containers/borghypo/hacked - icon_state = "borghypo_s" - reagent_ids = list (/datum/reagent/toxin/acid, /datum/reagent/toxin/mutetoxin, /datum/reagent/toxin/cyanide, /datum/reagent/toxin/sodium_thiopental, /datum/reagent/toxin/heparin, /datum/reagent/toxin/lexorin) - accepts_reagent_upgrades = FALSE - -/obj/item/reagent_containers/borghypo/clown - name = "laughter injector" - desc = "Keeps the crew happy and productive!" - reagent_ids = list(/datum/reagent/consumable/laughter) - accepts_reagent_upgrades = FALSE - -/obj/item/reagent_containers/borghypo/clown/hacked - name = "laughter injector" - desc = "Keeps the crew so happy they don't work!" - reagent_ids = list(/datum/reagent/consumable/superlaughter) - accepts_reagent_upgrades = FALSE - -/obj/item/reagent_containers/borghypo/syndicate - name = "syndicate cyborg hypospray" - desc = "An experimental piece of Syndicate technology used to produce powerful restorative nanites used to very quickly restore injuries of all types. Also metabolizes potassium iodide, for radiation poisoning, and morphine, for offense." - icon_state = "borghypo_s" - charge_cost = 20 - recharge_time = 2 - reagent_ids = list(/datum/reagent/medicine/syndicate_nanites, /datum/reagent/medicine/potass_iodide, /datum/reagent/medicine/morphine, /datum/reagent/medicine/insulin) - bypass_protection = 1 - accepts_reagent_upgrades = FALSE - -/* -Borg Shaker -*/ -/obj/item/reagent_containers/borghypo/borgshaker - name = "cyborg shaker" - desc = "An advanced drink synthesizer and mixer." - icon = 'icons/obj/drinks.dmi' - icon_state = "shaker" - possible_transfer_amounts = list(5,10,20) - charge_cost = 20 //Lots of reagents all regenerating at once, so the charge cost is lower. They also regenerate faster. - recharge_time = 3 - accepts_reagent_upgrades = FALSE - reagent_ids = list(/datum/reagent/consumable/ethanol/beer, /datum/reagent/consumable/orangejuice, /datum/reagent/consumable/grenadine, - /datum/reagent/consumable/limejuice, /datum/reagent/consumable/tomatojuice, /datum/reagent/consumable/space_cola, - /datum/reagent/consumable/tonic, /datum/reagent/consumable/sodawater, /datum/reagent/consumable/ice, - /datum/reagent/consumable/cream, /datum/reagent/consumable/ethanol/whiskey, /datum/reagent/consumable/ethanol/vodka, - /datum/reagent/consumable/ethanol/rum, /datum/reagent/consumable/ethanol/gin, /datum/reagent/consumable/ethanol/tequila, - /datum/reagent/consumable/ethanol/vermouth, /datum/reagent/consumable/ethanol/wine, /datum/reagent/consumable/ethanol/kahlua, - /datum/reagent/consumable/ethanol/cognac, /datum/reagent/consumable/ethanol/ale, /datum/reagent/consumable/milk, - /datum/reagent/consumable/coffee, /datum/reagent/consumable/banana, /datum/reagent/consumable/lemonjuice) - -/obj/item/reagent_containers/borghypo/borgshaker/attack(mob/M, mob/user) - return //Can't inject stuff with a shaker, can we? //not with that attitude - -/obj/item/reagent_containers/borghypo/borgshaker/regenerate_reagents() - if(iscyborg(src.loc)) - var/mob/living/silicon/robot/R = src.loc - if(R && R.cell) - for(var/i in modes) //Lots of reagents in this one, so it's best to regenrate them all at once to keep it from being tedious. - var/valueofi = modes[i] - var/datum/reagents/RG = reagent_list[valueofi] - if(RG.total_volume < RG.maximum_volume) - R.cell.use(charge_cost) - RG.add_reagent(reagent_ids[valueofi], 5) - -/obj/item/reagent_containers/borghypo/borgshaker/afterattack(obj/target, mob/user, proximity) - . = ..() - if(!proximity) - return - - else if(target.is_refillable()) - var/datum/reagents/R = reagent_list[mode] - if(!R.total_volume) - to_chat(user, "[src] is currently out of this ingredient! Please allow some time for the synthesizer to produce more.") - return - - if(target.reagents.total_volume >= target.reagents.maximum_volume) - to_chat(user, "[target] is full.") - return - - var/trans = R.trans_to(target, amount_per_transfer_from_this) - to_chat(user, "You transfer [trans] unit\s of the solution to [target].") - -/obj/item/reagent_containers/borghypo/borgshaker/DescribeContents() - var/empty = 1 - - var/datum/reagents/RS = reagent_list[mode] - var/datum/reagent/R = locate() in RS.reagent_list - if(R) - to_chat(usr, "It currently has [R.volume] unit\s of [R.name] stored.") - empty = 0 - - if(empty) - to_chat(usr, "It is currently empty! Please allow some time for the synthesizer to produce more.") - -/obj/item/reagent_containers/borghypo/borgshaker/hacked - name = "cyborg shaker" - desc = "Will mix drinks that knock them dead." - icon = 'icons/obj/drinks.dmi' - icon_state = "threemileislandglass" - possible_transfer_amounts = list(5,10,20) - charge_cost = 20 //Lots of reagents all regenerating at once, so the charge cost is lower. They also regenerate faster. - recharge_time = 3 - accepts_reagent_upgrades = FALSE - reagent_ids = list(/datum/reagent/toxin/fakebeer, /datum/reagent/consumable/ethanol/fernet) - -/obj/item/reagent_containers/borghypo/peace - name = "Peace Hypospray" - - reagent_ids = list(/datum/reagent/peaceborg_confuse, /datum/reagent/peaceborg_tire, /datum/reagent/pax/peaceborg, /datum/reagent/medicine/insulin) - accepts_reagent_upgrades = FALSE - -/obj/item/reagent_containers/borghypo/peace/hacked - desc = "Everything's peaceful in death!" - icon_state = "borghypo_s" - reagent_ids = list(/datum/reagent/peaceborg_confuse, /datum/reagent/peaceborg_tire, /datum/reagent/pax/peaceborg, - /datum/reagent/toxin/staminatoxin,/datum/reagent/toxin/sulfonal,/datum/reagent/toxin/sodium_thiopental, - /datum/reagent/toxin/cyanide,/datum/reagent/toxin/fentanyl) - accepts_reagent_upgrades = FALSE - -/obj/item/reagent_containers/borghypo/epi - name = "Stabilizer injector" - desc = "An advanced chemical synthesizer and injection system, designed to stabilize patients." - reagent_ids = list(/datum/reagent/medicine/epinephrine, /datum/reagent/medicine/insulin) - accepts_reagent_upgrades = FALSE +/* +Contains: +Borg Hypospray +Borg Shaker +Nothing to do with hydroponics in here. Sorry to dissapoint you. +*/ + +/* +Borg Hypospray +*/ +/obj/item/reagent_containers/borghypo + name = "cyborg hypospray" + desc = "An advanced chemical synthesizer and injection system, designed for heavy-duty medical equipment." + icon = 'icons/obj/syringe.dmi' + item_state = "hypo" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + icon_state = "borghypo" + amount_per_transfer_from_this = 5 + volume = 30 + possible_transfer_amounts = list() + var/mode = 1 + var/charge_cost = 50 + var/charge_tick = 0 + var/recharge_time = 5 //Time it takes for shots to recharge (in seconds) + var/bypass_protection = 0 //If the hypospray can go through armor or thick material + + var/list/datum/reagents/reagent_list = list() + var/list/reagent_ids = list(/datum/reagent/medicine/dexalin, /datum/reagent/medicine/kelotane, /datum/reagent/medicine/bicaridine, /datum/reagent/medicine/antitoxin, + /datum/reagent/medicine/epinephrine, /datum/reagent/medicine/spaceacillin, /datum/reagent/medicine/salglu_solution, /datum/reagent/medicine/insulin) + var/accepts_reagent_upgrades = TRUE //If upgrades can increase number of reagents dispensed. + var/list/modes = list() //Basically the inverse of reagent_ids. Instead of having numbers as "keys" and strings as values it has strings as keys and numbers as values. + //Used as list for input() in shakers. + var/list/reagent_names = list() + +/obj/item/reagent_containers/borghypo/Initialize() + . = ..() + + for(var/R in reagent_ids) + add_reagent(R) + + START_PROCESSING(SSobj, src) + +/obj/item/reagent_containers/borghypo/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/reagent_containers/borghypo/process() //Every [recharge_time] seconds, recharge some reagents for the cyborg + charge_tick++ + if(charge_tick >= recharge_time) + regenerate_reagents() + charge_tick = 0 + + //update_icon() + return 1 + +// Use this to add more chemicals for the borghypo to produce. +/obj/item/reagent_containers/borghypo/proc/add_reagent(datum/reagent/reagent) + reagent_ids |= reagent + var/datum/reagents/RG = new(30) + RG.my_atom = src + reagent_list += RG + + var/datum/reagents/R = reagent_list[reagent_list.len] + R.add_reagent(reagent, 30) + + modes[reagent] = modes.len + 1 + reagent_names[initial(reagent.name)] = reagent + +/obj/item/reagent_containers/borghypo/proc/del_reagent(datum/reagent/reagent) + reagent_ids -= reagent + reagent_names -= initial(reagent.name) + var/datum/reagents/RG + var/datum/reagents/TRG + for(var/i in 1 to reagent_ids.len) + TRG = reagent_list[i] + if (TRG.has_reagent(reagent)) + RG = TRG + break + if (RG) + reagent_list -= RG + RG.del_reagent(reagent) + + modes[reagent] = modes.len - 1 + +/obj/item/reagent_containers/borghypo/proc/regenerate_reagents() + if(iscyborg(src.loc)) + var/mob/living/silicon/robot/R = src.loc + if(R && R.cell) + for(var/i in 1 to reagent_ids.len) + var/datum/reagents/RG = reagent_list[i] + if(RG.total_volume < RG.maximum_volume) //Don't recharge reagents and drain power if the storage is full. + R.cell.use(charge_cost) //Take power from borg... + RG.add_reagent(reagent_ids[i], 5) //And fill hypo with reagent. + +/obj/item/reagent_containers/borghypo/attack(mob/living/carbon/M, mob/user) + var/datum/reagents/R = reagent_list[mode] + if(!R.total_volume) + to_chat(user, "The injector is empty.") + return + if(!istype(M)) + return + if(R.total_volume && M.can_inject(user, 1, user.zone_selected,bypass_protection)) + to_chat(M, "You feel a tiny prick!") + to_chat(user, "You inject [M] with the injector.") + var/fraction = min(amount_per_transfer_from_this/R.total_volume, 1) + R.reaction(M, INJECT, fraction) + if(M.reagents) + var/trans = R.trans_to(M, amount_per_transfer_from_this) + to_chat(user, "[trans] unit\s injected. [R.total_volume] unit\s remaining.") + + var/list/injected = list() + for(var/datum/reagent/RG in R.reagent_list) + injected += RG.name + log_combat(user, M, "injected", src, "(CHEMICALS: [english_list(injected)])") + +/obj/item/reagent_containers/borghypo/attack_self(mob/user) + var/chosen_reagent = modes[reagent_names[input(user, "What reagent do you want to dispense?") as null|anything in reagent_names]] + if(!chosen_reagent) + return + mode = chosen_reagent + playsound(loc, 'sound/effects/pop.ogg', 50, 0) + var/datum/reagent/R = GLOB.chemical_reagents_list[reagent_ids[mode]] + to_chat(user, "[src] is now dispensing '[R.name]'.") + return + +/obj/item/reagent_containers/borghypo/examine(mob/user) + . = ..() + . += DescribeContents() //Because using the standardized reagents datum was just too cool for whatever fuckwit wrote this + +/obj/item/reagent_containers/borghypo/proc/DescribeContents() + var/empty = 1 + + for(var/datum/reagents/RS in reagent_list) + var/datum/reagent/R = locate() in RS.reagent_list + if(R) + . += "It currently has [R.volume] unit\s of [R.name] stored." + empty = 0 + + if(empty) + . += "It is currently empty! Allow some time for the internal syntheszier to produce more." + +/obj/item/reagent_containers/borghypo/hacked + icon_state = "borghypo_s" + reagent_ids = list (/datum/reagent/toxin/acid, /datum/reagent/toxin/mutetoxin, /datum/reagent/toxin/cyanide, /datum/reagent/toxin/sodium_thiopental, /datum/reagent/toxin/heparin, /datum/reagent/toxin/lexorin) + accepts_reagent_upgrades = FALSE + +/obj/item/reagent_containers/borghypo/clown + name = "laughter injector" + desc = "Keeps the crew happy and productive!" + reagent_ids = list(/datum/reagent/consumable/laughter) + accepts_reagent_upgrades = FALSE + +/obj/item/reagent_containers/borghypo/clown/hacked + name = "laughter injector" + desc = "Keeps the crew so happy they don't work!" + reagent_ids = list(/datum/reagent/consumable/superlaughter) + accepts_reagent_upgrades = FALSE + +/obj/item/reagent_containers/borghypo/syndicate + name = "syndicate cyborg hypospray" + desc = "An experimental piece of Syndicate technology used to produce powerful restorative nanites used to very quickly restore injuries of all types. Also metabolizes potassium iodide, for radiation poisoning, and morphine, for offense." + icon_state = "borghypo_s" + charge_cost = 20 + recharge_time = 2 + reagent_ids = list(/datum/reagent/medicine/syndicate_nanites, /datum/reagent/medicine/potass_iodide, /datum/reagent/medicine/morphine, /datum/reagent/medicine/insulin) + bypass_protection = 1 + accepts_reagent_upgrades = FALSE + +/* +Borg Shaker +*/ +/obj/item/reagent_containers/borghypo/borgshaker + name = "cyborg shaker" + desc = "An advanced drink synthesizer and mixer." + icon = 'icons/obj/drinks.dmi' + icon_state = "shaker" + possible_transfer_amounts = list(5,10,20) + charge_cost = 20 //Lots of reagents all regenerating at once, so the charge cost is lower. They also regenerate faster. + recharge_time = 3 + accepts_reagent_upgrades = FALSE + reagent_ids = list(/datum/reagent/consumable/ethanol/beer, /datum/reagent/consumable/orangejuice, /datum/reagent/consumable/grenadine, + /datum/reagent/consumable/limejuice, /datum/reagent/consumable/tomatojuice, /datum/reagent/consumable/space_cola, + /datum/reagent/consumable/tonic, /datum/reagent/consumable/sodawater, /datum/reagent/consumable/ice, + /datum/reagent/consumable/cream, /datum/reagent/consumable/ethanol/whiskey, /datum/reagent/consumable/ethanol/vodka, + /datum/reagent/consumable/ethanol/rum, /datum/reagent/consumable/ethanol/gin, /datum/reagent/consumable/ethanol/tequila, + /datum/reagent/consumable/ethanol/vermouth, /datum/reagent/consumable/ethanol/wine, /datum/reagent/consumable/ethanol/kahlua, + /datum/reagent/consumable/ethanol/cognac, /datum/reagent/consumable/ethanol/ale, /datum/reagent/consumable/milk, + /datum/reagent/consumable/coffee, /datum/reagent/consumable/banana, /datum/reagent/consumable/lemonjuice) + +/obj/item/reagent_containers/borghypo/borgshaker/attack(mob/M, mob/user) + return //Can't inject stuff with a shaker, can we? //not with that attitude + +/obj/item/reagent_containers/borghypo/borgshaker/regenerate_reagents() + if(iscyborg(src.loc)) + var/mob/living/silicon/robot/R = src.loc + if(R && R.cell) + for(var/i in modes) //Lots of reagents in this one, so it's best to regenrate them all at once to keep it from being tedious. + var/valueofi = modes[i] + var/datum/reagents/RG = reagent_list[valueofi] + if(RG.total_volume < RG.maximum_volume) + R.cell.use(charge_cost) + RG.add_reagent(reagent_ids[valueofi], 5) + +/obj/item/reagent_containers/borghypo/borgshaker/afterattack(obj/target, mob/user, proximity) + . = ..() + if(!proximity) + return + + else if(target.is_refillable()) + var/datum/reagents/R = reagent_list[mode] + if(!R.total_volume) + to_chat(user, "[src] is currently out of this ingredient! Please allow some time for the synthesizer to produce more.") + return + + if(target.reagents.total_volume >= target.reagents.maximum_volume) + to_chat(user, "[target] is full.") + return + + var/trans = R.trans_to(target, amount_per_transfer_from_this) + to_chat(user, "You transfer [trans] unit\s of the solution to [target].") + +/obj/item/reagent_containers/borghypo/borgshaker/DescribeContents() + var/empty = 1 + + var/datum/reagents/RS = reagent_list[mode] + var/datum/reagent/R = locate() in RS.reagent_list + if(R) + to_chat(usr, "It currently has [R.volume] unit\s of [R.name] stored.") + empty = 0 + + if(empty) + to_chat(usr, "It is currently empty! Please allow some time for the synthesizer to produce more.") + +/obj/item/reagent_containers/borghypo/borgshaker/hacked + name = "cyborg shaker" + desc = "Will mix drinks that knock them dead." + icon = 'icons/obj/drinks.dmi' + icon_state = "threemileislandglass" + possible_transfer_amounts = list(5,10,20) + charge_cost = 20 //Lots of reagents all regenerating at once, so the charge cost is lower. They also regenerate faster. + recharge_time = 3 + accepts_reagent_upgrades = FALSE + reagent_ids = list(/datum/reagent/toxin/fakebeer, /datum/reagent/consumable/ethanol/fernet) + +/obj/item/reagent_containers/borghypo/peace + name = "Peace Hypospray" + + reagent_ids = list(/datum/reagent/peaceborg_confuse, /datum/reagent/peaceborg_tire, /datum/reagent/pax/peaceborg, /datum/reagent/medicine/insulin) + accepts_reagent_upgrades = FALSE + +/obj/item/reagent_containers/borghypo/peace/hacked + desc = "Everything's peaceful in death!" + icon_state = "borghypo_s" + reagent_ids = list(/datum/reagent/peaceborg_confuse, /datum/reagent/peaceborg_tire, /datum/reagent/pax/peaceborg, + /datum/reagent/toxin/staminatoxin,/datum/reagent/toxin/sulfonal,/datum/reagent/toxin/sodium_thiopental, + /datum/reagent/toxin/cyanide,/datum/reagent/toxin/fentanyl) + accepts_reagent_upgrades = FALSE + +/obj/item/reagent_containers/borghypo/epi + name = "Stabilizer injector" + desc = "An advanced chemical synthesizer and injection system, designed to stabilize patients." + reagent_ids = list(/datum/reagent/medicine/epinephrine, /datum/reagent/medicine/insulin) + accepts_reagent_upgrades = FALSE diff --git a/code/modules/reagents/reagent_containers/dropper.dm b/code/modules/reagents/reagent_containers/dropper.dm index a215d09517..cbfe5173dd 100644 --- a/code/modules/reagents/reagent_containers/dropper.dm +++ b/code/modules/reagents/reagent_containers/dropper.dm @@ -1,102 +1,102 @@ -/obj/item/reagent_containers/dropper - name = "dropper" - desc = "A dropper. Holds up to 5 units." - icon = 'icons/obj/chemical.dmi' - icon_state = "dropper0" - amount_per_transfer_from_this = 5 - possible_transfer_amounts = list(1, 2, 3, 4, 5) - volume = 5 - reagent_flags = TRANSPARENT - -/obj/item/reagent_containers/dropper/afterattack(obj/target, mob/user , proximity) - . = ..() - if(!proximity) - return - if(!target.reagents) - return - - if(reagents.total_volume > 0) - if(target.reagents.total_volume >= target.reagents.maximum_volume) - to_chat(user, "[target] is full.") - return - - if(!target.is_injectable()) - to_chat(user, "You cannot directly fill [target]!") - return - - var/trans = 0 - var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) - - if(ismob(target)) - if(ishuman(target)) - var/mob/living/carbon/human/victim = target - - var/obj/item/safe_thing = null - if(victim.wear_mask) - if(victim.wear_mask.flags_cover & MASKCOVERSEYES) - safe_thing = victim.wear_mask - if(victim.head) - if(victim.head.flags_cover & MASKCOVERSEYES) - safe_thing = victim.head - if(victim.glasses) - if(!safe_thing) - safe_thing = victim.glasses - - if(safe_thing) - if(!safe_thing.reagents) - safe_thing.create_reagents(100) - - reagents.reaction(safe_thing, TOUCH, fraction) - trans = reagents.trans_to(safe_thing, amount_per_transfer_from_this) - - target.visible_message("[user] tries to squirt something into [target]'s eyes, but fails!", \ - "[user] tries to squirt something into [target]'s eyes, but fails!") - - to_chat(user, "You transfer [trans] unit\s of the solution.") - update_icon() - return - else if(isalien(target)) //hiss-hiss has no eyes! - to_chat(target, "[target] does not seem to have any eyes!") - return - - target.visible_message("[user] squirts something into [target]'s eyes!", \ - "[user] squirts something into [target]'s eyes!") - - reagents.reaction(target, TOUCH, fraction) - var/mob/M = target - var/R - if(reagents) - for(var/datum/reagent/A in src.reagents.reagent_list) - R += A.type + " (" - R += num2text(A.volume) + ")," - log_combat(user, M, "squirted", R) - - trans = src.reagents.trans_to(target, amount_per_transfer_from_this) - to_chat(user, "You transfer [trans] unit\s of the solution.") - update_icon() - - else - - if(!target.is_drawable(FALSE)) //No drawing from mobs here - to_chat(user, "You cannot directly remove reagents from [target].") - return - - if(!target.reagents.total_volume) - to_chat(user, "[target] is empty!") - return - - var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) - - to_chat(user, "You fill [src] with [trans] unit\s of the solution.") - - update_icon() - -/obj/item/reagent_containers/dropper/update_icon() - cut_overlays() - if(reagents.total_volume) - var/mutable_appearance/filling = mutable_appearance('icons/obj/reagentfillings.dmi', "dropper") - filling.color = mix_color_from_reagents(reagents.reagent_list) - add_overlay(filling) - -/obj/item/reagent_containers/dropper/get_belt_overlay() - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "pouch") +/obj/item/reagent_containers/dropper + name = "dropper" + desc = "A dropper. Holds up to 5 units." + icon = 'icons/obj/chemical.dmi' + icon_state = "dropper0" + amount_per_transfer_from_this = 5 + possible_transfer_amounts = list(1, 2, 3, 4, 5) + volume = 5 + reagent_flags = TRANSPARENT + +/obj/item/reagent_containers/dropper/afterattack(obj/target, mob/user , proximity) + . = ..() + if(!proximity) + return + if(!target.reagents) + return + + if(reagents.total_volume > 0) + if(target.reagents.total_volume >= target.reagents.maximum_volume) + to_chat(user, "[target] is full.") + return + + if(!target.is_injectable()) + to_chat(user, "You cannot directly fill [target]!") + return + + var/trans = 0 + var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) + + if(ismob(target)) + if(ishuman(target)) + var/mob/living/carbon/human/victim = target + + var/obj/item/safe_thing = null + if(victim.wear_mask) + if(victim.wear_mask.flags_cover & MASKCOVERSEYES) + safe_thing = victim.wear_mask + if(victim.head) + if(victim.head.flags_cover & MASKCOVERSEYES) + safe_thing = victim.head + if(victim.glasses) + if(!safe_thing) + safe_thing = victim.glasses + + if(safe_thing) + if(!safe_thing.reagents) + safe_thing.create_reagents(100) + + reagents.reaction(safe_thing, TOUCH, fraction) + trans = reagents.trans_to(safe_thing, amount_per_transfer_from_this) + + target.visible_message("[user] tries to squirt something into [target]'s eyes, but fails!", \ + "[user] tries to squirt something into [target]'s eyes, but fails!") + + to_chat(user, "You transfer [trans] unit\s of the solution.") + update_icon() + return + else if(isalien(target)) //hiss-hiss has no eyes! + to_chat(target, "[target] does not seem to have any eyes!") + return + + target.visible_message("[user] squirts something into [target]'s eyes!", \ + "[user] squirts something into [target]'s eyes!") + + reagents.reaction(target, TOUCH, fraction) + var/mob/M = target + var/R + if(reagents) + for(var/datum/reagent/A in src.reagents.reagent_list) + R += A.type + " (" + R += num2text(A.volume) + ")," + log_combat(user, M, "squirted", R) + + trans = src.reagents.trans_to(target, amount_per_transfer_from_this) + to_chat(user, "You transfer [trans] unit\s of the solution.") + update_icon() + + else + + if(!target.is_drawable(FALSE)) //No drawing from mobs here + to_chat(user, "You cannot directly remove reagents from [target].") + return + + if(!target.reagents.total_volume) + to_chat(user, "[target] is empty!") + return + + var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) + + to_chat(user, "You fill [src] with [trans] unit\s of the solution.") + + update_icon() + +/obj/item/reagent_containers/dropper/update_icon() + cut_overlays() + if(reagents.total_volume) + var/mutable_appearance/filling = mutable_appearance('icons/obj/reagentfillings.dmi', "dropper") + filling.color = mix_color_from_reagents(reagents.reagent_list) + add_overlay(filling) + +/obj/item/reagent_containers/dropper/get_belt_overlay() + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "pouch") diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm index 98f5111501..624a5e62cb 100644 --- a/code/modules/reagents/reagent_containers/glass.dm +++ b/code/modules/reagents/reagent_containers/glass.dm @@ -1,368 +1,368 @@ -/obj/item/reagent_containers/glass - name = "glass" - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5, 10, 15, 20, 25, 30, 50) - volume = 50 - reagent_flags = OPENCONTAINER - spillable = TRUE - resistance_flags = ACID_PROOF - container_HP = 2 - - -/obj/item/reagent_containers/glass/attack(mob/M, mob/user, obj/target) - if(!canconsume(M, user)) - return - - if(!spillable) - return - - if(!reagents || !reagents.total_volume) - to_chat(user, "[src] is empty!") - return - - if(istype(M)) - if(user.a_intent == INTENT_HARM) - var/R - M.visible_message("[user] splashes the contents of [src] onto [M]!", \ - "[user] splashes the contents of [src] onto [M]!") - if(reagents) - for(var/datum/reagent/A in reagents.reagent_list) - R += A.type + " (" - R += num2text(A.volume) + ")," - if(isturf(target) && reagents.reagent_list.len && thrownby) - log_combat(thrownby, target, "splashed (thrown) [english_list(reagents.reagent_list)]") - message_admins("[ADMIN_LOOKUPFLW(thrownby)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] at [ADMIN_VERBOSEJMP(target)].") - reagents.reaction(M, TOUCH) - log_combat(user, M, "splashed", R) - reagents.clear_reagents() - else - if(M != user) - M.visible_message("[user] attempts to feed something to [M].", \ - "[user] attempts to feed something to you.") - if(!do_mob(user, M)) - return - if(!reagents || !reagents.total_volume) - return // The drink might be empty after the delay, such as by spam-feeding - M.visible_message("[user] feeds something to [M].", "[user] feeds something to you.") - log_combat(user, M, "fed", reagents.log_list()) - else - to_chat(user, "You swallow a gulp of [src].") - var/fraction = min(5/reagents.total_volume, 1) - reagents.reaction(M, INGEST, fraction) - addtimer(CALLBACK(reagents, /datum/reagents.proc/trans_to, M, 5), 5) - playsound(M.loc,'sound/items/drink.ogg', rand(10,50), 1) - -/obj/item/reagent_containers/glass/afterattack(obj/target, mob/user, proximity) - . = ..() - if((!proximity) || !check_allowed_items(target,target_self=1)) - return - - if(target.is_refillable()) //Something like a glass. Player probably wants to transfer TO it. - if(!reagents.total_volume) - to_chat(user, "[src] is empty!") - return - - if(target.reagents.holder_full()) - to_chat(user, "[target] is full.") - return - - var/trans = reagents.trans_to(target, amount_per_transfer_from_this) - to_chat(user, "You transfer [trans] unit\s of the solution to [target].") - - else if(target.is_drainable()) //A dispenser. Transfer FROM it TO us. - if(!target.reagents.total_volume) - to_chat(user, "[target] is empty and can't be refilled!") - return - - if(reagents.holder_full()) - to_chat(user, "[src] is full.") - return - - var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) - to_chat(user, "You fill [src] with [trans] unit\s of the contents of [target].") - - else if(reagents.total_volume) - if(user.a_intent == INTENT_HARM) - user.visible_message("[user] splashes the contents of [src] onto [target]!", \ - "You splash the contents of [src] onto [target].") - reagents.reaction(target, TOUCH) - reagents.clear_reagents() - -/obj/item/reagent_containers/glass/attackby(obj/item/I, mob/user, params) - var/hotness = I.get_temperature() - if(hotness && reagents) - reagents.expose_temperature(hotness) - to_chat(user, "You heat [name] with [I]!") - - if(istype(I, /obj/item/reagent_containers/food/snacks/egg)) //breaking eggs - var/obj/item/reagent_containers/food/snacks/egg/E = I - if(reagents) - if(reagents.total_volume >= reagents.maximum_volume) - to_chat(user, "[src] is full.") - else - to_chat(user, "You break [E] in [src].") - E.reagents.trans_to(src, E.reagents.total_volume) - qdel(E) - return - ..() - - -/obj/item/reagent_containers/glass/beaker - name = "beaker" - desc = "A beaker. It can hold up to 60 units. Unable to withstand extreme pHes." - icon = 'icons/obj/chemical.dmi' - volume = 60 - icon_state = "beaker" - item_state = "beaker" - materials = list(MAT_GLASS=500) - possible_transfer_amounts = list(5,10,15,20,25,30,50,60) - beaker_weakness_bitflag = PH_WEAK - -/obj/item/reagent_containers/glass/beaker/Initialize() - . = ..() - update_icon() - -/obj/item/reagent_containers/glass/beaker/get_part_rating() - return reagents.maximum_volume - -/obj/item/reagent_containers/glass/beaker/on_reagent_change(changetype) - update_icon() - -/obj/item/reagent_containers/glass/beaker/update_icon() - if(!cached_icon) - cached_icon = icon_state - cut_overlays() - - if(reagents.total_volume) - var/mutable_appearance/filling = mutable_appearance('icons/obj/reagentfillings.dmi', "[cached_icon]10") - - var/percent = round((reagents.total_volume / volume) * 100) - switch(percent) - if(0 to 9) - filling.icon_state = "[cached_icon]-10" - if(10 to 24) - filling.icon_state = "[cached_icon]10" - if(25 to 49) - filling.icon_state = "[cached_icon]25" - if(50 to 74) - filling.icon_state = "[cached_icon]50" - if(75 to 79) - filling.icon_state = "[cached_icon]75" - if(80 to 90) - filling.icon_state = "[cached_icon]80" - if(91 to INFINITY) - filling.icon_state = "[cached_icon]100" - - filling.color = mix_color_from_reagents(reagents.reagent_list) - add_overlay(filling) - -/obj/item/reagent_containers/glass/beaker/jar - name = "honey jar" - desc = "A jar for honey. It can hold up to 60 units of sweet delight. Unable to withstand reagents of an extreme pH." - icon = 'icons/obj/chemical.dmi' - icon_state = "vapour" - -/obj/item/reagent_containers/glass/beaker/large - name = "large beaker" - desc = "A large beaker. Can hold up to 120 units. Unable to withstand reagents of an extreme pH." - icon_state = "beakerlarge" - materials = list(MAT_GLASS=2500) - volume = 120 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,40,50,60,120) - container_HP = 3 - -/obj/item/reagent_containers/glass/beaker/plastic - name = "x-large beaker" - desc = "An extra-large beaker. Can hold up to 180 units. Is able to resist acid and alkaline solutions, but melts at 444 K." - icon_state = "beakerwhite" - materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000) - volume = 180 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,40,50,60,120,180) - -/obj/item/reagent_containers/glass/beaker/plastic/Initialize() - beaker_weakness_bitflag &= ~PH_WEAK - beaker_weakness_bitflag |= TEMP_WEAK - . = ..() - -/obj/item/reagent_containers/glass/beaker/plastic/update_icon() - icon_state = "beakerlarge" // hack to lets us reuse the large beaker reagent fill states - ..() - icon_state = "beakerwhite" - -/obj/item/reagent_containers/glass/beaker/meta - name = "metamaterial beaker" - desc = "A large beaker. Can hold up to 240 units, and is able to withstand all chemical situations." - icon_state = "beakergold" - materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000, MAT_GOLD=1000, MAT_TITANIUM=1000) - volume = 240 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,40,50,60,120,200,240) - -/obj/item/reagent_containers/glass/beaker/meta/Initialize() // why the fuck can't you just set the beaker weakness bitflags to nothing? fuck you - beaker_weakness_bitflag &= ~PH_WEAK - . = ..() - -/obj/item/reagent_containers/glass/beaker/noreact - name = "cryostasis beaker" - desc = "A cryostasis beaker that allows for chemical storage without \ - reactions. Can hold up to 50 units." - icon_state = "beakernoreact" - materials = list(MAT_METAL=3000) - reagent_flags = OPENCONTAINER | NO_REACT - volume = 50 - amount_per_transfer_from_this = 10 - container_HP = 10//shouldn't be needed - -/obj/item/reagent_containers/glass/beaker/noreact/Initialize() - beaker_weakness_bitflag &= ~PH_WEAK - . = ..() - //reagents.set_reacting(FALSE) was this removed in a recent pr? - -/obj/item/reagent_containers/glass/beaker/bluespace - name = "bluespace beaker" - desc = "A bluespace beaker, powered by experimental bluespace technology \ - and Element Cuban combined with the Compound Pete. Can hold up to \ - 300 units. Unable to withstand reagents of an extreme pH." - icon_state = "beakerbluespace" - materials = list(MAT_GLASS=3000) - volume = 300 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,50,100,300) - container_HP = 5 - -/obj/item/reagent_containers/glass/beaker/cryoxadone - list_reagents = list(/datum/reagent/medicine/cryoxadone = 30) - -/obj/item/reagent_containers/glass/beaker/sulphuric - list_reagents = list(/datum/reagent/toxin/acid = 50) - -/obj/item/reagent_containers/glass/beaker/slime - list_reagents = list(/datum/reagent/toxin/slimejelly = 50) - -/obj/item/reagent_containers/glass/beaker/large/styptic - name = "styptic reserve tank" - list_reagents = list(/datum/reagent/medicine/styptic_powder = 50) - -/obj/item/reagent_containers/glass/beaker/large/silver_sulfadiazine - name = "silver sulfadiazine reserve tank" - list_reagents = list(/datum/reagent/medicine/silver_sulfadiazine = 50) - -/obj/item/reagent_containers/glass/beaker/large/charcoal - name = "charcoal reserve tank" - list_reagents = list(/datum/reagent/medicine/charcoal = 50) - -/obj/item/reagent_containers/glass/beaker/large/epinephrine - name = "epinephrine reserve tank" - list_reagents = list(/datum/reagent/medicine/epinephrine = 50) - -/obj/item/reagent_containers/glass/beaker/synthflesh - list_reagents = list(/datum/reagent/medicine/synthflesh = 50) - -/obj/item/reagent_containers/glass/bucket - name = "bucket" - desc = "It's a bucket." - icon = 'icons/obj/janitor.dmi' - icon_state = "bucket" - item_state = "bucket" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - materials = list(MAT_METAL=200) - w_class = WEIGHT_CLASS_NORMAL - amount_per_transfer_from_this = 20 - possible_transfer_amounts = list(5,10,15,20,25,30,50,70) - volume = 70 - flags_inv = HIDEHAIR - slot_flags = ITEM_SLOT_HEAD - resistance_flags = NONE - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 50) //Weak melee protection, because you can wear it on your head - slot_equipment_priority = list( \ - SLOT_BACK, SLOT_WEAR_ID,\ - SLOT_W_UNIFORM, SLOT_WEAR_SUIT,\ - SLOT_WEAR_MASK, SLOT_HEAD, SLOT_NECK,\ - SLOT_SHOES, SLOT_GLOVES,\ - SLOT_EARS, SLOT_GLASSES,\ - SLOT_BELT, SLOT_S_STORE,\ - SLOT_L_STORE, SLOT_R_STORE,\ - SLOT_GENERC_DEXTROUS_STORAGE - ) - container_HP = 1 - -/obj/item/reagent_containers/glass/bucket/Initialize() - beaker_weakness_bitflag |= TEMP_WEAK - . = ..() - -/obj/item/reagent_containers/glass/bucket/attackby(obj/O, mob/user, params) - if(istype(O, /obj/item/mop)) - if(reagents.total_volume < 1) - to_chat(user, "[src] is out of water!") - else - reagents.trans_to(O, 5) - to_chat(user, "You wet [O] in [src].") - playsound(loc, 'sound/effects/slosh.ogg', 25, 1) - else if(isprox(O)) - to_chat(user, "You add [O] to [src].") - qdel(O) - qdel(src) - user.put_in_hands(new /obj/item/bot_assembly/cleanbot) - else - ..() - -/obj/item/reagent_containers/glass/bucket/equipped(mob/user, slot) - ..() - if (slot == SLOT_HEAD) - if(reagents.total_volume) - to_chat(user, "[src]'s contents spill all over you!") - reagents.reaction(user, TOUCH) - reagents.clear_reagents() - reagent_flags = NONE - -/obj/item/reagent_containers/glass/bucket/dropped(mob/user) - . = ..() - reagent_flags = initial(reagent_flags) - -/obj/item/reagent_containers/glass/bucket/equip_to_best_slot(var/mob/M) - if(reagents.total_volume) //If there is water in a bucket, don't quick equip it to the head - var/index = slot_equipment_priority.Find(SLOT_HEAD) - slot_equipment_priority.Remove(SLOT_HEAD) - . = ..() - slot_equipment_priority.Insert(index, SLOT_HEAD) - return - return ..() - -/obj/item/reagent_containers/glass/beaker/waterbottle - name = "bottle of water" - desc = "A bottle of water filled at an old Earth bottling facility." - icon = 'icons/obj/drinks.dmi' - icon_state = "smallbottle" - item_state = "bottle" - list_reagents = list(/datum/reagent/water = 49.5, /datum/reagent/fluorine = 0.5)//see desc, don't think about it too hard - materials = list(MAT_GLASS=0) - volume = 50 - amount_per_transfer_from_this = 10 - possible_transfer_amounts = list(5,10,15,20,25,30,50) - container_HP = 1 - -/obj/item/reagent_containers/glass/beaker/waterbottle/Initialize() - beaker_weakness_bitflag |= TEMP_WEAK - . = ..() - -/obj/item/reagent_containers/glass/beaker/waterbottle/empty - list_reagents = list() - -/obj/item/reagent_containers/glass/beaker/waterbottle/large - desc = "A fresh commercial-sized bottle of water." - icon_state = "largebottle" - materials = list(MAT_GLASS=0) - list_reagents = list(/datum/reagent/water = 100) - volume = 100 - amount_per_transfer_from_this = 20 - possible_transfer_amounts = list(5,10,15,20,25,30,50,100) - container_HP = 1 - -/obj/item/reagent_containers/glass/beaker/waterbottle/large/empty - list_reagents = list() - -/obj/item/reagent_containers/glass/get_belt_overlay() - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "bottle") +/obj/item/reagent_containers/glass + name = "glass" + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5, 10, 15, 20, 25, 30, 50) + volume = 50 + reagent_flags = OPENCONTAINER + spillable = TRUE + resistance_flags = ACID_PROOF + container_HP = 2 + + +/obj/item/reagent_containers/glass/attack(mob/M, mob/user, obj/target) + if(!canconsume(M, user)) + return + + if(!spillable) + return + + if(!reagents || !reagents.total_volume) + to_chat(user, "[src] is empty!") + return + + if(istype(M)) + if(user.a_intent == INTENT_HARM) + var/R + M.visible_message("[user] splashes the contents of [src] onto [M]!", \ + "[user] splashes the contents of [src] onto [M]!") + if(reagents) + for(var/datum/reagent/A in reagents.reagent_list) + R += A.type + " (" + R += num2text(A.volume) + ")," + if(isturf(target) && reagents.reagent_list.len && thrownby) + log_combat(thrownby, target, "splashed (thrown) [english_list(reagents.reagent_list)]") + message_admins("[ADMIN_LOOKUPFLW(thrownby)] splashed (thrown) [english_list(reagents.reagent_list)] on [target] at [ADMIN_VERBOSEJMP(target)].") + reagents.reaction(M, TOUCH) + log_combat(user, M, "splashed", R) + reagents.clear_reagents() + else + if(M != user) + M.visible_message("[user] attempts to feed something to [M].", \ + "[user] attempts to feed something to you.") + if(!do_mob(user, M)) + return + if(!reagents || !reagents.total_volume) + return // The drink might be empty after the delay, such as by spam-feeding + M.visible_message("[user] feeds something to [M].", "[user] feeds something to you.") + log_combat(user, M, "fed", reagents.log_list()) + else + to_chat(user, "You swallow a gulp of [src].") + var/fraction = min(5/reagents.total_volume, 1) + reagents.reaction(M, INGEST, fraction) + addtimer(CALLBACK(reagents, /datum/reagents.proc/trans_to, M, 5), 5) + playsound(M.loc,'sound/items/drink.ogg', rand(10,50), 1) + +/obj/item/reagent_containers/glass/afterattack(obj/target, mob/user, proximity) + . = ..() + if((!proximity) || !check_allowed_items(target,target_self=1)) + return + + if(target.is_refillable()) //Something like a glass. Player probably wants to transfer TO it. + if(!reagents.total_volume) + to_chat(user, "[src] is empty!") + return + + if(target.reagents.holder_full()) + to_chat(user, "[target] is full.") + return + + var/trans = reagents.trans_to(target, amount_per_transfer_from_this) + to_chat(user, "You transfer [trans] unit\s of the solution to [target].") + + else if(target.is_drainable()) //A dispenser. Transfer FROM it TO us. + if(!target.reagents.total_volume) + to_chat(user, "[target] is empty and can't be refilled!") + return + + if(reagents.holder_full()) + to_chat(user, "[src] is full.") + return + + var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) + to_chat(user, "You fill [src] with [trans] unit\s of the contents of [target].") + + else if(reagents.total_volume) + if(user.a_intent == INTENT_HARM) + user.visible_message("[user] splashes the contents of [src] onto [target]!", \ + "You splash the contents of [src] onto [target].") + reagents.reaction(target, TOUCH) + reagents.clear_reagents() + +/obj/item/reagent_containers/glass/attackby(obj/item/I, mob/user, params) + var/hotness = I.get_temperature() + if(hotness && reagents) + reagents.expose_temperature(hotness) + to_chat(user, "You heat [name] with [I]!") + + if(istype(I, /obj/item/reagent_containers/food/snacks/egg)) //breaking eggs + var/obj/item/reagent_containers/food/snacks/egg/E = I + if(reagents) + if(reagents.total_volume >= reagents.maximum_volume) + to_chat(user, "[src] is full.") + else + to_chat(user, "You break [E] in [src].") + E.reagents.trans_to(src, E.reagents.total_volume) + qdel(E) + return + ..() + + +/obj/item/reagent_containers/glass/beaker + name = "beaker" + desc = "A beaker. It can hold up to 60 units. Unable to withstand extreme pHes." + icon = 'icons/obj/chemical.dmi' + volume = 60 + icon_state = "beaker" + item_state = "beaker" + materials = list(MAT_GLASS=500) + possible_transfer_amounts = list(5,10,15,20,25,30,50,60) + beaker_weakness_bitflag = PH_WEAK + +/obj/item/reagent_containers/glass/beaker/Initialize() + . = ..() + update_icon() + +/obj/item/reagent_containers/glass/beaker/get_part_rating() + return reagents.maximum_volume + +/obj/item/reagent_containers/glass/beaker/on_reagent_change(changetype) + update_icon() + +/obj/item/reagent_containers/glass/beaker/update_icon() + if(!cached_icon) + cached_icon = icon_state + cut_overlays() + + if(reagents.total_volume) + var/mutable_appearance/filling = mutable_appearance('icons/obj/reagentfillings.dmi', "[cached_icon]10") + + var/percent = round((reagents.total_volume / volume) * 100) + switch(percent) + if(0 to 9) + filling.icon_state = "[cached_icon]-10" + if(10 to 24) + filling.icon_state = "[cached_icon]10" + if(25 to 49) + filling.icon_state = "[cached_icon]25" + if(50 to 74) + filling.icon_state = "[cached_icon]50" + if(75 to 79) + filling.icon_state = "[cached_icon]75" + if(80 to 90) + filling.icon_state = "[cached_icon]80" + if(91 to INFINITY) + filling.icon_state = "[cached_icon]100" + + filling.color = mix_color_from_reagents(reagents.reagent_list) + add_overlay(filling) + +/obj/item/reagent_containers/glass/beaker/jar + name = "honey jar" + desc = "A jar for honey. It can hold up to 60 units of sweet delight. Unable to withstand reagents of an extreme pH." + icon = 'icons/obj/chemical.dmi' + icon_state = "vapour" + +/obj/item/reagent_containers/glass/beaker/large + name = "large beaker" + desc = "A large beaker. Can hold up to 120 units. Unable to withstand reagents of an extreme pH." + icon_state = "beakerlarge" + materials = list(MAT_GLASS=2500) + volume = 120 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5,10,15,20,25,30,40,50,60,120) + container_HP = 3 + +/obj/item/reagent_containers/glass/beaker/plastic + name = "x-large beaker" + desc = "An extra-large beaker. Can hold up to 180 units. Is able to resist acid and alkaline solutions, but melts at 444 K." + icon_state = "beakerwhite" + materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000) + volume = 180 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5,10,15,20,25,30,40,50,60,120,180) + +/obj/item/reagent_containers/glass/beaker/plastic/Initialize() + beaker_weakness_bitflag &= ~PH_WEAK + beaker_weakness_bitflag |= TEMP_WEAK + . = ..() + +/obj/item/reagent_containers/glass/beaker/plastic/update_icon() + icon_state = "beakerlarge" // hack to lets us reuse the large beaker reagent fill states + ..() + icon_state = "beakerwhite" + +/obj/item/reagent_containers/glass/beaker/meta + name = "metamaterial beaker" + desc = "A large beaker. Can hold up to 240 units, and is able to withstand all chemical situations." + icon_state = "beakergold" + materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000, MAT_GOLD=1000, MAT_TITANIUM=1000) + volume = 240 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5,10,15,20,25,30,40,50,60,120,200,240) + +/obj/item/reagent_containers/glass/beaker/meta/Initialize() // why the fuck can't you just set the beaker weakness bitflags to nothing? fuck you + beaker_weakness_bitflag &= ~PH_WEAK + . = ..() + +/obj/item/reagent_containers/glass/beaker/noreact + name = "cryostasis beaker" + desc = "A cryostasis beaker that allows for chemical storage without \ + reactions. Can hold up to 50 units." + icon_state = "beakernoreact" + materials = list(MAT_METAL=3000) + reagent_flags = OPENCONTAINER | NO_REACT + volume = 50 + amount_per_transfer_from_this = 10 + container_HP = 10//shouldn't be needed + +/obj/item/reagent_containers/glass/beaker/noreact/Initialize() + beaker_weakness_bitflag &= ~PH_WEAK + . = ..() + //reagents.set_reacting(FALSE) was this removed in a recent pr? + +/obj/item/reagent_containers/glass/beaker/bluespace + name = "bluespace beaker" + desc = "A bluespace beaker, powered by experimental bluespace technology \ + and Element Cuban combined with the Compound Pete. Can hold up to \ + 300 units. Unable to withstand reagents of an extreme pH." + icon_state = "beakerbluespace" + materials = list(MAT_GLASS=3000) + volume = 300 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5,10,15,20,25,30,50,100,300) + container_HP = 5 + +/obj/item/reagent_containers/glass/beaker/cryoxadone + list_reagents = list(/datum/reagent/medicine/cryoxadone = 30) + +/obj/item/reagent_containers/glass/beaker/sulphuric + list_reagents = list(/datum/reagent/toxin/acid = 50) + +/obj/item/reagent_containers/glass/beaker/slime + list_reagents = list(/datum/reagent/toxin/slimejelly = 50) + +/obj/item/reagent_containers/glass/beaker/large/styptic + name = "styptic reserve tank" + list_reagents = list(/datum/reagent/medicine/styptic_powder = 50) + +/obj/item/reagent_containers/glass/beaker/large/silver_sulfadiazine + name = "silver sulfadiazine reserve tank" + list_reagents = list(/datum/reagent/medicine/silver_sulfadiazine = 50) + +/obj/item/reagent_containers/glass/beaker/large/charcoal + name = "charcoal reserve tank" + list_reagents = list(/datum/reagent/medicine/charcoal = 50) + +/obj/item/reagent_containers/glass/beaker/large/epinephrine + name = "epinephrine reserve tank" + list_reagents = list(/datum/reagent/medicine/epinephrine = 50) + +/obj/item/reagent_containers/glass/beaker/synthflesh + list_reagents = list(/datum/reagent/medicine/synthflesh = 50) + +/obj/item/reagent_containers/glass/bucket + name = "bucket" + desc = "It's a bucket." + icon = 'icons/obj/janitor.dmi' + icon_state = "bucket" + item_state = "bucket" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + materials = list(MAT_METAL=200) + w_class = WEIGHT_CLASS_NORMAL + amount_per_transfer_from_this = 20 + possible_transfer_amounts = list(5,10,15,20,25,30,50,70) + volume = 70 + flags_inv = HIDEHAIR + slot_flags = ITEM_SLOT_HEAD + resistance_flags = NONE + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 50) //Weak melee protection, because you can wear it on your head + slot_equipment_priority = list( \ + SLOT_BACK, SLOT_WEAR_ID,\ + SLOT_W_UNIFORM, SLOT_WEAR_SUIT,\ + SLOT_WEAR_MASK, SLOT_HEAD, SLOT_NECK,\ + SLOT_SHOES, SLOT_GLOVES,\ + SLOT_EARS, SLOT_GLASSES,\ + SLOT_BELT, SLOT_S_STORE,\ + SLOT_L_STORE, SLOT_R_STORE,\ + SLOT_GENERC_DEXTROUS_STORAGE + ) + container_HP = 1 + +/obj/item/reagent_containers/glass/bucket/Initialize() + beaker_weakness_bitflag |= TEMP_WEAK + . = ..() + +/obj/item/reagent_containers/glass/bucket/attackby(obj/O, mob/user, params) + if(istype(O, /obj/item/mop)) + if(reagents.total_volume < 1) + to_chat(user, "[src] is out of water!") + else + reagents.trans_to(O, 5) + to_chat(user, "You wet [O] in [src].") + playsound(loc, 'sound/effects/slosh.ogg', 25, 1) + else if(isprox(O)) + to_chat(user, "You add [O] to [src].") + qdel(O) + qdel(src) + user.put_in_hands(new /obj/item/bot_assembly/cleanbot) + else + ..() + +/obj/item/reagent_containers/glass/bucket/equipped(mob/user, slot) + ..() + if (slot == SLOT_HEAD) + if(reagents.total_volume) + to_chat(user, "[src]'s contents spill all over you!") + reagents.reaction(user, TOUCH) + reagents.clear_reagents() + reagent_flags = NONE + +/obj/item/reagent_containers/glass/bucket/dropped(mob/user) + . = ..() + reagent_flags = initial(reagent_flags) + +/obj/item/reagent_containers/glass/bucket/equip_to_best_slot(var/mob/M) + if(reagents.total_volume) //If there is water in a bucket, don't quick equip it to the head + var/index = slot_equipment_priority.Find(SLOT_HEAD) + slot_equipment_priority.Remove(SLOT_HEAD) + . = ..() + slot_equipment_priority.Insert(index, SLOT_HEAD) + return + return ..() + +/obj/item/reagent_containers/glass/beaker/waterbottle + name = "bottle of water" + desc = "A bottle of water filled at an old Earth bottling facility." + icon = 'icons/obj/drinks.dmi' + icon_state = "smallbottle" + item_state = "bottle" + list_reagents = list(/datum/reagent/water = 49.5, /datum/reagent/fluorine = 0.5)//see desc, don't think about it too hard + materials = list(MAT_GLASS=0) + volume = 50 + amount_per_transfer_from_this = 10 + possible_transfer_amounts = list(5,10,15,20,25,30,50) + container_HP = 1 + +/obj/item/reagent_containers/glass/beaker/waterbottle/Initialize() + beaker_weakness_bitflag |= TEMP_WEAK + . = ..() + +/obj/item/reagent_containers/glass/beaker/waterbottle/empty + list_reagents = list() + +/obj/item/reagent_containers/glass/beaker/waterbottle/large + desc = "A fresh commercial-sized bottle of water." + icon_state = "largebottle" + materials = list(MAT_GLASS=0) + list_reagents = list(/datum/reagent/water = 100) + volume = 100 + amount_per_transfer_from_this = 20 + possible_transfer_amounts = list(5,10,15,20,25,30,50,100) + container_HP = 1 + +/obj/item/reagent_containers/glass/beaker/waterbottle/large/empty + list_reagents = list() + +/obj/item/reagent_containers/glass/get_belt_overlay() + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "bottle") diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm index 664ba81f9e..f509ad0b10 100644 --- a/code/modules/reagents/reagent_containers/hypospray.dm +++ b/code/modules/reagents/reagent_containers/hypospray.dm @@ -1,512 +1,512 @@ -/obj/item/reagent_containers/hypospray - name = "hypospray" - desc = "The DeForest Medical Corporation hypospray is a sterile, air-needle autoinjector for rapid administration of drugs to patients." - icon = 'icons/obj/syringe.dmi' - item_state = "hypo" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - icon_state = "hypo" - amount_per_transfer_from_this = 5 - volume = 30 - possible_transfer_amounts = list() - resistance_flags = ACID_PROOF - reagent_flags = OPENCONTAINER - slot_flags = ITEM_SLOT_BELT - var/ignore_flags = 0 - var/infinite = FALSE - -/obj/item/reagent_containers/hypospray/attack_paw(mob/user) - return attack_hand(user) - -/obj/item/reagent_containers/hypospray/attack(mob/living/M, mob/user) - if(!reagents.total_volume) - to_chat(user, "[src] is empty!") - return - if(!iscarbon(M)) - return - - //Always log attemped injects for admins - var/list/injected = list() - for(var/datum/reagent/R in reagents.reagent_list) - injected += R.name - var/contained = english_list(injected) - log_combat(user, M, "attempted to inject", src, "([contained])") - - if(reagents.total_volume && (ignore_flags || M.can_inject(user, 1))) // Ignore flag should be checked first or there will be an error message. - to_chat(M, "You feel a tiny prick!") - to_chat(user, "You inject [M] with [src].") - - var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) - reagents.reaction(M, INJECT, fraction) - if(M.reagents) - var/trans = 0 - if(!infinite) - trans = reagents.trans_to(M, amount_per_transfer_from_this) - else - trans = reagents.copy_to(M, amount_per_transfer_from_this) - - to_chat(user, "[trans] unit\s injected. [reagents.total_volume] unit\s remaining in [src].") - - - log_combat(user, M, "injected", src, "([contained])") - -/obj/item/reagent_containers/hypospray/CMO - list_reagents = list(/datum/reagent/medicine/omnizine = 30) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/item/reagent_containers/hypospray/combat - name = "combat stimulant injector" - desc = "A modified air-needle autoinjector, used by support operatives to quickly heal injuries in combat and get people back in the fight." - amount_per_transfer_from_this = 10 - icon_state = "combat_hypo" - volume = 100 - ignore_flags = 1 // So they can heal their comrades. - list_reagents = list(/datum/reagent/medicine/epinephrine = 30, /datum/reagent/medicine/lesser_syndicate_nanites = 40, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/atropine = 15) - -/obj/item/reagent_containers/hypospray/combat/omnizine // owned idiot - desc = "A modified air-needle autoinjector, used by underfunded support operatives to slowly heal injuries in combat and limp away from a fight." - volume = 90 - list_reagents = list(/datum/reagent/medicine/epinephrine = 30, /datum/reagent/medicine/omnizine = 30, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/atropine = 15) - -/obj/item/reagent_containers/hypospray/combat/nanites - desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with experimental medical compounds for rapid healing." - volume = 100 - list_reagents = list(/datum/reagent/medicine/adminordrazine/quantum_heal = 80, /datum/reagent/medicine/synaptizine = 20) - -/obj/item/reagent_containers/hypospray/magillitis - name = "experimental autoinjector" - desc = "A modified air-needle autoinjector with a small single-use reservoir. It contains an experimental serum." - icon_state = "combat_hypo" - volume = 5 - reagent_flags = NONE - list_reagents = list(/datum/reagent/magillitis = 5) - -//MediPens - -/obj/item/reagent_containers/hypospray/medipen - name = "epinephrine medipen" - desc = "A rapid and safe way to stabilize patients in critical condition for personnel without advanced medical knowledge." - icon_state = "medipen" - item_state = "medipen" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - amount_per_transfer_from_this = 10 - volume = 10 - ignore_flags = 1 //so you can medipen through hardsuits - reagent_flags = DRAWABLE - flags_1 = null - list_reagents = list(/datum/reagent/medicine/epinephrine = 10) - -/obj/item/reagent_containers/hypospray/medipen/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to choke on \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS//ironic. he could save others from oxyloss, but not himself. - -/obj/item/reagent_containers/hypospray/medipen/attack(mob/M, mob/user) - if(!reagents.total_volume) - to_chat(user, "[src] is empty!") - return - ..() - if(!iscyborg(user)) - reagents.maximum_volume = 0 //Makes them useless afterwards - reagent_flags = NONE - update_icon() - addtimer(CALLBACK(src, .proc/cyborg_recharge, user), 80) - -/obj/item/reagent_containers/hypospray/medipen/proc/cyborg_recharge(mob/living/silicon/robot/user) - if(!reagents.total_volume && iscyborg(user)) - var/mob/living/silicon/robot/R = user - if(R.cell.use(100)) - reagents.add_reagent_list(list_reagents) - update_icon() - -/obj/item/reagent_containers/hypospray/medipen/update_icon() - if(reagents.total_volume > 0) - icon_state = initial(icon_state) - else - icon_state = "[initial(icon_state)]0" - -/obj/item/reagent_containers/hypospray/medipen/examine() - . = ..() - if(reagents && reagents.reagent_list.len) - . += "It is currently loaded." - else - . += "It is spent." - -/obj/item/reagent_containers/hypospray/medipen/stimulants - name = "illegal stimpack medipen" - desc = "A highly illegal medipen due to its load and small injections, allow for five uses before being drained" - volume = 50 - amount_per_transfer_from_this = 10 - list_reagents = list(/datum/reagent/medicine/stimulants = 50) - -/obj/item/reagent_containers/hypospray/medipen/stimulants/baseball - name = "the reason the syndicate major league team wins" - desc = "They say drugs never win, but look where you are now, then where they are." - icon_state = "baseballstim" - volume = 50 - amount_per_transfer_from_this = 50 - list_reagents = list(/datum/reagent/medicine/stimulants = 50) - -/obj/item/reagent_containers/hypospray/medipen/stimpack //goliath kiting - name = "stimpack medipen" - desc = "A rapid way to stimulate your body's adrenaline, allowing for freer movement in restrictive armor." - icon_state = "stimpen" - volume = 20 - amount_per_transfer_from_this = 20 - list_reagents = list(/datum/reagent/medicine/ephedrine = 10, /datum/reagent/consumable/coffee = 10) - -/obj/item/reagent_containers/hypospray/medipen/stimpack/traitor - desc = "A modified stimulants autoinjector for use in combat situations. Has a mild healing effect." - list_reagents = list(/datum/reagent/medicine/stimulants = 10, /datum/reagent/medicine/omnizine = 10) - -/obj/item/reagent_containers/hypospray/medipen/morphine - name = "morphine medipen" - desc = "A rapid way to get you out of a tight situation and fast! You'll feel rather drowsy, though." - list_reagents = list(/datum/reagent/medicine/morphine = 10) - -/obj/item/reagent_containers/hypospray/medipen/tuberculosiscure - name = "BVAK autoinjector" - desc = "Bio Virus Antidote Kit autoinjector. Has a two use system for yourself, and someone else. Inject when infected." - icon_state = "stimpen" - volume = 60 - amount_per_transfer_from_this = 30 - list_reagents = list(/datum/reagent/medicine/atropine = 10, /datum/reagent/medicine/epinephrine = 10, /datum/reagent/medicine/salbutamol = 20, /datum/reagent/medicine/spaceacillin = 20) - -/obj/item/reagent_containers/hypospray/medipen/survival - name = "survival medipen" - desc = "A medipen for surviving in the harshest of environments, heals and protects from environmental hazards. WARNING: Do not inject more than one pen in quick succession." - icon_state = "stimpen" - volume = 52 - amount_per_transfer_from_this = 52 - list_reagents = list(/datum/reagent/medicine/salbutamol = 10, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/neo_jelly = 15, /datum/reagent/medicine/epinephrine = 10, /datum/reagent/medicine/lavaland_extract = 2) - -/obj/item/reagent_containers/hypospray/medipen/firelocker - name = "fire treatment medipen" - desc = "A medipen that has been fulled with burn healing chemicals for personnel without advanced medical knowledge." - volume = 15 - amount_per_transfer_from_this = 15 - list_reagents = list(/datum/reagent/medicine/oxandrolone = 5, /datum/reagent/medicine/kelotane = 10) - -/obj/item/reagent_containers/hypospray/combat/heresypurge - name = "holy water autoinjector" - desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with 5 doses of a holy water mixture." - volume = 250 - list_reagents = list(/datum/reagent/water/holywater = 150, /datum/reagent/peaceborg_tire = 50, /datum/reagent/peaceborg_confuse = 50) - amount_per_transfer_from_this = 50 - -#define HYPO_SPRAY 0 -#define HYPO_INJECT 1 - -#define WAIT_SPRAY 25 -#define WAIT_INJECT 25 -#define SELF_SPRAY 15 -#define SELF_INJECT 15 - -#define DELUXE_WAIT_SPRAY 20 -#define DELUXE_WAIT_INJECT 20 -#define DELUXE_SELF_SPRAY 10 -#define DELUXE_SELF_INJECT 10 - -#define COMBAT_WAIT_SPRAY 0 -#define COMBAT_WAIT_INJECT 0 -#define COMBAT_SELF_SPRAY 0 -#define COMBAT_SELF_INJECT 0 - -//A vial-loaded hypospray. Cartridge-based! -/obj/item/hypospray/mkii - name = "hypospray mk.II" - icon_state = "hypo2" - icon = 'icons/obj/syringe.dmi' - desc = "A new development from DeForest Medical, this hypospray takes 30-unit vials as the drug supply for easy swapping." - w_class = WEIGHT_CLASS_TINY - var/list/allowed_containers = list(/obj/item/reagent_containers/glass/bottle/vial/tiny, /obj/item/reagent_containers/glass/bottle/vial/small) - var/mode = HYPO_INJECT - var/obj/item/reagent_containers/glass/bottle/vial/vial - var/start_vial = /obj/item/reagent_containers/glass/bottle/vial/small - var/spawnwithvial = TRUE - var/inject_wait = WAIT_INJECT - var/spray_wait = WAIT_SPRAY - var/spray_self = SELF_SPRAY - var/inject_self = SELF_INJECT - var/quickload = FALSE - var/penetrates = FALSE - -/obj/item/hypospray/mkii/brute - start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/bicaridine - -/obj/item/hypospray/mkii/toxin - start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/antitoxin - -/obj/item/hypospray/mkii/oxygen - start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/dexalin - -/obj/item/hypospray/mkii/burn - start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/kelotane - -/obj/item/hypospray/mkii/tricord - start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/tricord - -/obj/item/hypospray/mkii/enlarge - spawnwithvial = FALSE - -/obj/item/hypospray/mkii/CMO - name = "hypospray mk.II deluxe" - allowed_containers = list(/obj/item/reagent_containers/glass/bottle/vial/tiny, /obj/item/reagent_containers/glass/bottle/vial/small, /obj/item/reagent_containers/glass/bottle/vial/large) - icon_state = "cmo2" - desc = "The Deluxe Hypospray can take larger-size vials. It also acts faster and delivers more reagents per spray." - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - start_vial = /obj/item/reagent_containers/glass/bottle/vial/large/CMO - inject_wait = DELUXE_WAIT_INJECT - spray_wait = DELUXE_WAIT_SPRAY - spray_self = DELUXE_SELF_SPRAY - inject_self = DELUXE_SELF_INJECT - -/obj/item/hypospray/mkii/CMO/combat - name = "combat hypospray mk.II" - desc = "A combat-ready deluxe hypospray that acts almost instantly. It can be tactically reloaded by using a vial on it." - icon_state = "combat2" - start_vial = /obj/item/reagent_containers/glass/bottle/vial/large/combat - inject_wait = COMBAT_WAIT_INJECT - spray_wait = COMBAT_WAIT_SPRAY - spray_self = COMBAT_SELF_SPRAY - inject_self = COMBAT_SELF_INJECT - quickload = TRUE - penetrates = TRUE - -/obj/item/hypospray/mkii/Initialize() - . = ..() - if(!spawnwithvial) - update_icon() - return - if(start_vial) - vial = new start_vial - update_icon() - -/obj/item/hypospray/mkii/update_icon() - ..() - icon_state = "[initial(icon_state)][vial ? "" : "-e"]" - if(ismob(loc)) - var/mob/M = loc - M.update_inv_hands() - return - -/obj/item/hypospray/mkii/examine(mob/user) - . = ..() - if(vial) - . += "[vial] has [vial.reagents.total_volume]u remaining." - else - . += "It has no vial loaded in." - . += "[src] is set to [mode ? "Inject" : "Spray"] contents on application." - -/obj/item/hypospray/mkii/proc/unload_hypo(obj/item/I, mob/user) - if((istype(I, /obj/item/reagent_containers/glass/bottle/vial))) - var/obj/item/reagent_containers/glass/bottle/vial/V = I - V.forceMove(user.loc) - user.put_in_hands(V) - to_chat(user, "You remove [vial] from [src].") - vial = null - update_icon() - playsound(loc, 'sound/weapons/empty.ogg', 50, 1) - else - to_chat(user, "This hypo isn't loaded!") - return - -/obj/item/hypospray/mkii/attackby(obj/item/I, mob/living/user) - if((istype(I, /obj/item/reagent_containers/glass/bottle/vial) && vial != null)) - if(!quickload) - to_chat(user, "[src] can not hold more than one vial!") - return FALSE - unload_hypo(vial, user) - if((istype(I, /obj/item/reagent_containers/glass/bottle/vial))) - var/obj/item/reagent_containers/glass/bottle/vial/V = I - if(!is_type_in_list(V, allowed_containers)) - to_chat(user, "[src] doesn't accept this type of vial.") - return FALSE - if(!user.transferItemToLoc(V,src)) - return FALSE - vial = V - user.visible_message("[user] has loaded a vial into [src].","You have loaded [vial] into [src].") - update_icon() - playsound(loc, 'sound/weapons/autoguninsert.ogg', 35, 1) - return TRUE - else - to_chat(user, "This doesn't fit in [src].") - return FALSE - return FALSE - -/obj/item/hypospray/mkii/AltClick(mob/user) - . = ..() - if(vial) - vial.attack_self(user) - return TRUE - -// Gunna allow this for now, still really don't approve - Pooj -/obj/item/hypospray/mkii/emag_act(mob/user) - . = ..() - if(obj_flags & EMAGGED) - to_chat(user, "[src] happens to be already overcharged.") - return - inject_wait = COMBAT_WAIT_INJECT - spray_wait = COMBAT_WAIT_SPRAY - spray_self = COMBAT_SELF_INJECT - inject_self = COMBAT_SELF_SPRAY - penetrates = TRUE - to_chat(user, "You overcharge [src]'s control circuit.") - obj_flags |= EMAGGED - return TRUE - -/obj/item/hypospray/mkii/attack_hand(mob/user) - . = ..() //Don't bother changing this or removing it from containers will break. - -/obj/item/hypospray/mkii/attack(obj/item/I, mob/user, params) - return - -/obj/item/hypospray/mkii/afterattack(atom/target, mob/user, proximity) - if(!vial) - return - - if(!proximity) - return - - if(!ismob(target)) - return - - var/mob/living/L - if(isliving(target)) - L = target - if(!penetrates && !L.can_inject(user, 1)) //This check appears another four times, since otherwise the penetrating sprays will break in do_mob. - return - - if(!L && !target.is_injectable()) //only checks on non-living mobs, due to how can_inject() handles - to_chat(user, "You cannot directly fill [target]!") - return - - if(target.reagents.total_volume >= target.reagents.maximum_volume) - to_chat(user, "[target] is full.") - return - - if(ishuman(L)) - var/obj/item/bodypart/affecting = L.get_bodypart(check_zone(user.zone_selected)) - if(!affecting) - to_chat(user, "The limb is missing!") - return - if(affecting.status != BODYPART_ORGANIC) - to_chat(user, "Medicine won't work on a robotic limb!") - return - - var/contained = vial.reagents.log_list() - log_combat(user, L, "attemped to inject", src, addition="which had [contained]") -//Always log attemped injections for admins - if(vial != null) - switch(mode) - if(HYPO_INJECT) - if(L) //living mob - if(L != user) - L.visible_message("[user] is trying to inject [L] with [src]!", \ - "[user] is trying to inject [L] with [src]!") - if(!do_mob(user, L, inject_wait)) - return - if(!penetrates && !L.can_inject(user, 1)) - return - if(!vial.reagents.total_volume) - return - if(L.reagents.total_volume >= L.reagents.maximum_volume) - return - L.visible_message("[user] uses the [src] on [L]!", \ - "[user] uses the [src] on [L]!") - else - if(!do_mob(user, L, inject_self)) - return - if(!penetrates && !L.can_inject(user, 1)) - return - if(!vial.reagents.total_volume) - return - if(L.reagents.total_volume >= L.reagents.maximum_volume) - return - log_attack("[user.name] ([user.ckey]) applied [src] to [L.name] ([L.ckey]), which had [contained] (INTENT: [uppertext(user.a_intent)]) (MODE: [src.mode])") - L.log_message("applied [src] to themselves ([contained]).", INDIVIDUAL_ATTACK_LOG) - - var/fraction = min(vial.amount_per_transfer_from_this/vial.reagents.total_volume, 1) - vial.reagents.reaction(L, INJECT, fraction) - vial.reagents.trans_to(target, vial.amount_per_transfer_from_this) - if(vial.amount_per_transfer_from_this >= 15) - playsound(loc,'sound/items/hypospray_long.ogg',50, 1, -1) - if(vial.amount_per_transfer_from_this < 15) - playsound(loc, pick('sound/items/hypospray.ogg','sound/items/hypospray2.ogg'), 50, 1, -1) - to_chat(user, "You inject [vial.amount_per_transfer_from_this] units of the solution. The hypospray's cartridge now contains [vial.reagents.total_volume] units.") - - if(HYPO_SPRAY) - if(L) //living mob - if(L != user) - L.visible_message("[user] is trying to spray [L] with [src]!", \ - "[user] is trying to spray [L] with [src]!") - if(!do_mob(user, L, spray_wait)) - return - if(!penetrates && !L.can_inject(user, 1)) - return - if(!vial.reagents.total_volume) - return - if(L.reagents.total_volume >= L.reagents.maximum_volume) - return - L.visible_message("[user] uses the [src] on [L]!", \ - "[user] uses the [src] on [L]!") - else - if(!do_mob(user, L, spray_self)) - return - if(!penetrates && !L.can_inject(user, 1)) - return - if(!vial.reagents.total_volume) - return - if(L.reagents.total_volume >= L.reagents.maximum_volume) - return - log_attack("[user.name] ([user.ckey]) applied [src] to [L.name] ([L.ckey]), which had [contained] (INTENT: [uppertext(user.a_intent)]) (MODE: [src.mode])") - L.log_message("applied [src] to themselves ([contained]).", INDIVIDUAL_ATTACK_LOG) - var/fraction = min(vial.amount_per_transfer_from_this/vial.reagents.total_volume, 1) - vial.reagents.reaction(L, PATCH, fraction) - vial.reagents.trans_to(target, vial.amount_per_transfer_from_this) - if(vial.amount_per_transfer_from_this >= 15) - playsound(loc,'sound/items/hypospray_long.ogg',50, 1, -1) - if(vial.amount_per_transfer_from_this < 15) - playsound(loc, pick('sound/items/hypospray.ogg','sound/items/hypospray2.ogg'), 50, 1, -1) - to_chat(user, "You spray [vial.amount_per_transfer_from_this] units of the solution. The hypospray's cartridge now contains [vial.reagents.total_volume] units.") - else - to_chat(user, "[src] doesn't work here!") - return - -/obj/item/hypospray/mkii/attack_self(mob/living/user) - if(user) - if(user.incapacitated()) - return - else if(!vial) - to_chat(user, "This Hypo needs to be loaded first!") - return - else - unload_hypo(vial,user) - -/obj/item/hypospray/mkii/verb/modes() - set name = "Toggle Application Mode" - set category = "Object" - set src in usr - var/mob/M = usr - switch(mode) - if(HYPO_SPRAY) - mode = HYPO_INJECT - to_chat(M, "[src] is now set to inject contents on application.") - if(HYPO_INJECT) - mode = HYPO_SPRAY - to_chat(M, "[src] is now set to spray contents on application.") - -#undef HYPO_SPRAY -#undef HYPO_INJECT -#undef WAIT_SPRAY -#undef WAIT_INJECT -#undef SELF_SPRAY -#undef SELF_INJECT -#undef DELUXE_WAIT_SPRAY -#undef DELUXE_WAIT_INJECT -#undef DELUXE_SELF_SPRAY -#undef DELUXE_SELF_INJECT -#undef COMBAT_WAIT_SPRAY -#undef COMBAT_WAIT_INJECT -#undef COMBAT_SELF_SPRAY -#undef COMBAT_SELF_INJECT +/obj/item/reagent_containers/hypospray + name = "hypospray" + desc = "The DeForest Medical Corporation hypospray is a sterile, air-needle autoinjector for rapid administration of drugs to patients." + icon = 'icons/obj/syringe.dmi' + item_state = "hypo" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + icon_state = "hypo" + amount_per_transfer_from_this = 5 + volume = 30 + possible_transfer_amounts = list() + resistance_flags = ACID_PROOF + reagent_flags = OPENCONTAINER + slot_flags = ITEM_SLOT_BELT + var/ignore_flags = 0 + var/infinite = FALSE + +/obj/item/reagent_containers/hypospray/attack_paw(mob/user) + return attack_hand(user) + +/obj/item/reagent_containers/hypospray/attack(mob/living/M, mob/user) + if(!reagents.total_volume) + to_chat(user, "[src] is empty!") + return + if(!iscarbon(M)) + return + + //Always log attemped injects for admins + var/list/injected = list() + for(var/datum/reagent/R in reagents.reagent_list) + injected += R.name + var/contained = english_list(injected) + log_combat(user, M, "attempted to inject", src, "([contained])") + + if(reagents.total_volume && (ignore_flags || M.can_inject(user, 1))) // Ignore flag should be checked first or there will be an error message. + to_chat(M, "You feel a tiny prick!") + to_chat(user, "You inject [M] with [src].") + + var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) + reagents.reaction(M, INJECT, fraction) + if(M.reagents) + var/trans = 0 + if(!infinite) + trans = reagents.trans_to(M, amount_per_transfer_from_this) + else + trans = reagents.copy_to(M, amount_per_transfer_from_this) + + to_chat(user, "[trans] unit\s injected. [reagents.total_volume] unit\s remaining in [src].") + + + log_combat(user, M, "injected", src, "([contained])") + +/obj/item/reagent_containers/hypospray/CMO + list_reagents = list(/datum/reagent/medicine/omnizine = 30) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/item/reagent_containers/hypospray/combat + name = "combat stimulant injector" + desc = "A modified air-needle autoinjector, used by support operatives to quickly heal injuries in combat and get people back in the fight." + amount_per_transfer_from_this = 10 + icon_state = "combat_hypo" + volume = 100 + ignore_flags = 1 // So they can heal their comrades. + list_reagents = list(/datum/reagent/medicine/epinephrine = 30, /datum/reagent/medicine/lesser_syndicate_nanites = 40, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/atropine = 15) + +/obj/item/reagent_containers/hypospray/combat/omnizine // owned idiot + desc = "A modified air-needle autoinjector, used by underfunded support operatives to slowly heal injuries in combat and limp away from a fight." + volume = 90 + list_reagents = list(/datum/reagent/medicine/epinephrine = 30, /datum/reagent/medicine/omnizine = 30, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/atropine = 15) + +/obj/item/reagent_containers/hypospray/combat/nanites + desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with experimental medical compounds for rapid healing." + volume = 100 + list_reagents = list(/datum/reagent/medicine/adminordrazine/quantum_heal = 80, /datum/reagent/medicine/synaptizine = 20) + +/obj/item/reagent_containers/hypospray/magillitis + name = "experimental autoinjector" + desc = "A modified air-needle autoinjector with a small single-use reservoir. It contains an experimental serum." + icon_state = "combat_hypo" + volume = 5 + reagent_flags = NONE + list_reagents = list(/datum/reagent/magillitis = 5) + +//MediPens + +/obj/item/reagent_containers/hypospray/medipen + name = "epinephrine medipen" + desc = "A rapid and safe way to stabilize patients in critical condition for personnel without advanced medical knowledge." + icon_state = "medipen" + item_state = "medipen" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + amount_per_transfer_from_this = 10 + volume = 10 + ignore_flags = 1 //so you can medipen through hardsuits + reagent_flags = DRAWABLE + flags_1 = null + list_reagents = list(/datum/reagent/medicine/epinephrine = 10) + +/obj/item/reagent_containers/hypospray/medipen/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to choke on \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS//ironic. he could save others from oxyloss, but not himself. + +/obj/item/reagent_containers/hypospray/medipen/attack(mob/M, mob/user) + if(!reagents.total_volume) + to_chat(user, "[src] is empty!") + return + ..() + if(!iscyborg(user)) + reagents.maximum_volume = 0 //Makes them useless afterwards + reagent_flags = NONE + update_icon() + addtimer(CALLBACK(src, .proc/cyborg_recharge, user), 80) + +/obj/item/reagent_containers/hypospray/medipen/proc/cyborg_recharge(mob/living/silicon/robot/user) + if(!reagents.total_volume && iscyborg(user)) + var/mob/living/silicon/robot/R = user + if(R.cell.use(100)) + reagents.add_reagent_list(list_reagents) + update_icon() + +/obj/item/reagent_containers/hypospray/medipen/update_icon() + if(reagents.total_volume > 0) + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)]0" + +/obj/item/reagent_containers/hypospray/medipen/examine() + . = ..() + if(reagents && reagents.reagent_list.len) + . += "It is currently loaded." + else + . += "It is spent." + +/obj/item/reagent_containers/hypospray/medipen/stimulants + name = "illegal stimpack medipen" + desc = "A highly illegal medipen due to its load and small injections, allow for five uses before being drained" + volume = 50 + amount_per_transfer_from_this = 10 + list_reagents = list(/datum/reagent/medicine/stimulants = 50) + +/obj/item/reagent_containers/hypospray/medipen/stimulants/baseball + name = "the reason the syndicate major league team wins" + desc = "They say drugs never win, but look where you are now, then where they are." + icon_state = "baseballstim" + volume = 50 + amount_per_transfer_from_this = 50 + list_reagents = list(/datum/reagent/medicine/stimulants = 50) + +/obj/item/reagent_containers/hypospray/medipen/stimpack //goliath kiting + name = "stimpack medipen" + desc = "A rapid way to stimulate your body's adrenaline, allowing for freer movement in restrictive armor." + icon_state = "stimpen" + volume = 20 + amount_per_transfer_from_this = 20 + list_reagents = list(/datum/reagent/medicine/ephedrine = 10, /datum/reagent/consumable/coffee = 10) + +/obj/item/reagent_containers/hypospray/medipen/stimpack/traitor + desc = "A modified stimulants autoinjector for use in combat situations. Has a mild healing effect." + list_reagents = list(/datum/reagent/medicine/stimulants = 10, /datum/reagent/medicine/omnizine = 10) + +/obj/item/reagent_containers/hypospray/medipen/morphine + name = "morphine medipen" + desc = "A rapid way to get you out of a tight situation and fast! You'll feel rather drowsy, though." + list_reagents = list(/datum/reagent/medicine/morphine = 10) + +/obj/item/reagent_containers/hypospray/medipen/tuberculosiscure + name = "BVAK autoinjector" + desc = "Bio Virus Antidote Kit autoinjector. Has a two use system for yourself, and someone else. Inject when infected." + icon_state = "stimpen" + volume = 60 + amount_per_transfer_from_this = 30 + list_reagents = list(/datum/reagent/medicine/atropine = 10, /datum/reagent/medicine/epinephrine = 10, /datum/reagent/medicine/salbutamol = 20, /datum/reagent/medicine/spaceacillin = 20) + +/obj/item/reagent_containers/hypospray/medipen/survival + name = "survival medipen" + desc = "A medipen for surviving in the harshest of environments, heals and protects from environmental hazards. WARNING: Do not inject more than one pen in quick succession." + icon_state = "stimpen" + volume = 52 + amount_per_transfer_from_this = 52 + list_reagents = list(/datum/reagent/medicine/salbutamol = 10, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/neo_jelly = 15, /datum/reagent/medicine/epinephrine = 10, /datum/reagent/medicine/lavaland_extract = 2) + +/obj/item/reagent_containers/hypospray/medipen/firelocker + name = "fire treatment medipen" + desc = "A medipen that has been fulled with burn healing chemicals for personnel without advanced medical knowledge." + volume = 15 + amount_per_transfer_from_this = 15 + list_reagents = list(/datum/reagent/medicine/oxandrolone = 5, /datum/reagent/medicine/kelotane = 10) + +/obj/item/reagent_containers/hypospray/combat/heresypurge + name = "holy water autoinjector" + desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with 5 doses of a holy water mixture." + volume = 250 + list_reagents = list(/datum/reagent/water/holywater = 150, /datum/reagent/peaceborg_tire = 50, /datum/reagent/peaceborg_confuse = 50) + amount_per_transfer_from_this = 50 + +#define HYPO_SPRAY 0 +#define HYPO_INJECT 1 + +#define WAIT_SPRAY 25 +#define WAIT_INJECT 25 +#define SELF_SPRAY 15 +#define SELF_INJECT 15 + +#define DELUXE_WAIT_SPRAY 20 +#define DELUXE_WAIT_INJECT 20 +#define DELUXE_SELF_SPRAY 10 +#define DELUXE_SELF_INJECT 10 + +#define COMBAT_WAIT_SPRAY 0 +#define COMBAT_WAIT_INJECT 0 +#define COMBAT_SELF_SPRAY 0 +#define COMBAT_SELF_INJECT 0 + +//A vial-loaded hypospray. Cartridge-based! +/obj/item/hypospray/mkii + name = "hypospray mk.II" + icon_state = "hypo2" + icon = 'icons/obj/syringe.dmi' + desc = "A new development from DeForest Medical, this hypospray takes 30-unit vials as the drug supply for easy swapping." + w_class = WEIGHT_CLASS_TINY + var/list/allowed_containers = list(/obj/item/reagent_containers/glass/bottle/vial/tiny, /obj/item/reagent_containers/glass/bottle/vial/small) + var/mode = HYPO_INJECT + var/obj/item/reagent_containers/glass/bottle/vial/vial + var/start_vial = /obj/item/reagent_containers/glass/bottle/vial/small + var/spawnwithvial = TRUE + var/inject_wait = WAIT_INJECT + var/spray_wait = WAIT_SPRAY + var/spray_self = SELF_SPRAY + var/inject_self = SELF_INJECT + var/quickload = FALSE + var/penetrates = FALSE + +/obj/item/hypospray/mkii/brute + start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/bicaridine + +/obj/item/hypospray/mkii/toxin + start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/antitoxin + +/obj/item/hypospray/mkii/oxygen + start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/dexalin + +/obj/item/hypospray/mkii/burn + start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/kelotane + +/obj/item/hypospray/mkii/tricord + start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/tricord + +/obj/item/hypospray/mkii/enlarge + spawnwithvial = FALSE + +/obj/item/hypospray/mkii/CMO + name = "hypospray mk.II deluxe" + allowed_containers = list(/obj/item/reagent_containers/glass/bottle/vial/tiny, /obj/item/reagent_containers/glass/bottle/vial/small, /obj/item/reagent_containers/glass/bottle/vial/large) + icon_state = "cmo2" + desc = "The Deluxe Hypospray can take larger-size vials. It also acts faster and delivers more reagents per spray." + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + start_vial = /obj/item/reagent_containers/glass/bottle/vial/large/CMO + inject_wait = DELUXE_WAIT_INJECT + spray_wait = DELUXE_WAIT_SPRAY + spray_self = DELUXE_SELF_SPRAY + inject_self = DELUXE_SELF_INJECT + +/obj/item/hypospray/mkii/CMO/combat + name = "combat hypospray mk.II" + desc = "A combat-ready deluxe hypospray that acts almost instantly. It can be tactically reloaded by using a vial on it." + icon_state = "combat2" + start_vial = /obj/item/reagent_containers/glass/bottle/vial/large/combat + inject_wait = COMBAT_WAIT_INJECT + spray_wait = COMBAT_WAIT_SPRAY + spray_self = COMBAT_SELF_SPRAY + inject_self = COMBAT_SELF_INJECT + quickload = TRUE + penetrates = TRUE + +/obj/item/hypospray/mkii/Initialize() + . = ..() + if(!spawnwithvial) + update_icon() + return + if(start_vial) + vial = new start_vial + update_icon() + +/obj/item/hypospray/mkii/update_icon() + ..() + icon_state = "[initial(icon_state)][vial ? "" : "-e"]" + if(ismob(loc)) + var/mob/M = loc + M.update_inv_hands() + return + +/obj/item/hypospray/mkii/examine(mob/user) + . = ..() + if(vial) + . += "[vial] has [vial.reagents.total_volume]u remaining." + else + . += "It has no vial loaded in." + . += "[src] is set to [mode ? "Inject" : "Spray"] contents on application." + +/obj/item/hypospray/mkii/proc/unload_hypo(obj/item/I, mob/user) + if((istype(I, /obj/item/reagent_containers/glass/bottle/vial))) + var/obj/item/reagent_containers/glass/bottle/vial/V = I + V.forceMove(user.loc) + user.put_in_hands(V) + to_chat(user, "You remove [vial] from [src].") + vial = null + update_icon() + playsound(loc, 'sound/weapons/empty.ogg', 50, 1) + else + to_chat(user, "This hypo isn't loaded!") + return + +/obj/item/hypospray/mkii/attackby(obj/item/I, mob/living/user) + if((istype(I, /obj/item/reagent_containers/glass/bottle/vial) && vial != null)) + if(!quickload) + to_chat(user, "[src] can not hold more than one vial!") + return FALSE + unload_hypo(vial, user) + if((istype(I, /obj/item/reagent_containers/glass/bottle/vial))) + var/obj/item/reagent_containers/glass/bottle/vial/V = I + if(!is_type_in_list(V, allowed_containers)) + to_chat(user, "[src] doesn't accept this type of vial.") + return FALSE + if(!user.transferItemToLoc(V,src)) + return FALSE + vial = V + user.visible_message("[user] has loaded a vial into [src].","You have loaded [vial] into [src].") + update_icon() + playsound(loc, 'sound/weapons/autoguninsert.ogg', 35, 1) + return TRUE + else + to_chat(user, "This doesn't fit in [src].") + return FALSE + return FALSE + +/obj/item/hypospray/mkii/AltClick(mob/user) + . = ..() + if(vial) + vial.attack_self(user) + return TRUE + +// Gunna allow this for now, still really don't approve - Pooj +/obj/item/hypospray/mkii/emag_act(mob/user) + . = ..() + if(obj_flags & EMAGGED) + to_chat(user, "[src] happens to be already overcharged.") + return + inject_wait = COMBAT_WAIT_INJECT + spray_wait = COMBAT_WAIT_SPRAY + spray_self = COMBAT_SELF_INJECT + inject_self = COMBAT_SELF_SPRAY + penetrates = TRUE + to_chat(user, "You overcharge [src]'s control circuit.") + obj_flags |= EMAGGED + return TRUE + +/obj/item/hypospray/mkii/attack_hand(mob/user) + . = ..() //Don't bother changing this or removing it from containers will break. + +/obj/item/hypospray/mkii/attack(obj/item/I, mob/user, params) + return + +/obj/item/hypospray/mkii/afterattack(atom/target, mob/user, proximity) + if(!vial) + return + + if(!proximity) + return + + if(!ismob(target)) + return + + var/mob/living/L + if(isliving(target)) + L = target + if(!penetrates && !L.can_inject(user, 1)) //This check appears another four times, since otherwise the penetrating sprays will break in do_mob. + return + + if(!L && !target.is_injectable()) //only checks on non-living mobs, due to how can_inject() handles + to_chat(user, "You cannot directly fill [target]!") + return + + if(target.reagents.total_volume >= target.reagents.maximum_volume) + to_chat(user, "[target] is full.") + return + + if(ishuman(L)) + var/obj/item/bodypart/affecting = L.get_bodypart(check_zone(user.zone_selected)) + if(!affecting) + to_chat(user, "The limb is missing!") + return + if(affecting.status != BODYPART_ORGANIC) + to_chat(user, "Medicine won't work on a robotic limb!") + return + + var/contained = vial.reagents.log_list() + log_combat(user, L, "attemped to inject", src, addition="which had [contained]") +//Always log attemped injections for admins + if(vial != null) + switch(mode) + if(HYPO_INJECT) + if(L) //living mob + if(L != user) + L.visible_message("[user] is trying to inject [L] with [src]!", \ + "[user] is trying to inject [L] with [src]!") + if(!do_mob(user, L, inject_wait)) + return + if(!penetrates && !L.can_inject(user, 1)) + return + if(!vial.reagents.total_volume) + return + if(L.reagents.total_volume >= L.reagents.maximum_volume) + return + L.visible_message("[user] uses the [src] on [L]!", \ + "[user] uses the [src] on [L]!") + else + if(!do_mob(user, L, inject_self)) + return + if(!penetrates && !L.can_inject(user, 1)) + return + if(!vial.reagents.total_volume) + return + if(L.reagents.total_volume >= L.reagents.maximum_volume) + return + log_attack("[user.name] ([user.ckey]) applied [src] to [L.name] ([L.ckey]), which had [contained] (INTENT: [uppertext(user.a_intent)]) (MODE: [src.mode])") + L.log_message("applied [src] to themselves ([contained]).", INDIVIDUAL_ATTACK_LOG) + + var/fraction = min(vial.amount_per_transfer_from_this/vial.reagents.total_volume, 1) + vial.reagents.reaction(L, INJECT, fraction) + vial.reagents.trans_to(target, vial.amount_per_transfer_from_this) + if(vial.amount_per_transfer_from_this >= 15) + playsound(loc,'sound/items/hypospray_long.ogg',50, 1, -1) + if(vial.amount_per_transfer_from_this < 15) + playsound(loc, pick('sound/items/hypospray.ogg','sound/items/hypospray2.ogg'), 50, 1, -1) + to_chat(user, "You inject [vial.amount_per_transfer_from_this] units of the solution. The hypospray's cartridge now contains [vial.reagents.total_volume] units.") + + if(HYPO_SPRAY) + if(L) //living mob + if(L != user) + L.visible_message("[user] is trying to spray [L] with [src]!", \ + "[user] is trying to spray [L] with [src]!") + if(!do_mob(user, L, spray_wait)) + return + if(!penetrates && !L.can_inject(user, 1)) + return + if(!vial.reagents.total_volume) + return + if(L.reagents.total_volume >= L.reagents.maximum_volume) + return + L.visible_message("[user] uses the [src] on [L]!", \ + "[user] uses the [src] on [L]!") + else + if(!do_mob(user, L, spray_self)) + return + if(!penetrates && !L.can_inject(user, 1)) + return + if(!vial.reagents.total_volume) + return + if(L.reagents.total_volume >= L.reagents.maximum_volume) + return + log_attack("[user.name] ([user.ckey]) applied [src] to [L.name] ([L.ckey]), which had [contained] (INTENT: [uppertext(user.a_intent)]) (MODE: [src.mode])") + L.log_message("applied [src] to themselves ([contained]).", INDIVIDUAL_ATTACK_LOG) + var/fraction = min(vial.amount_per_transfer_from_this/vial.reagents.total_volume, 1) + vial.reagents.reaction(L, PATCH, fraction) + vial.reagents.trans_to(target, vial.amount_per_transfer_from_this) + if(vial.amount_per_transfer_from_this >= 15) + playsound(loc,'sound/items/hypospray_long.ogg',50, 1, -1) + if(vial.amount_per_transfer_from_this < 15) + playsound(loc, pick('sound/items/hypospray.ogg','sound/items/hypospray2.ogg'), 50, 1, -1) + to_chat(user, "You spray [vial.amount_per_transfer_from_this] units of the solution. The hypospray's cartridge now contains [vial.reagents.total_volume] units.") + else + to_chat(user, "[src] doesn't work here!") + return + +/obj/item/hypospray/mkii/attack_self(mob/living/user) + if(user) + if(user.incapacitated()) + return + else if(!vial) + to_chat(user, "This Hypo needs to be loaded first!") + return + else + unload_hypo(vial,user) + +/obj/item/hypospray/mkii/verb/modes() + set name = "Toggle Application Mode" + set category = "Object" + set src in usr + var/mob/M = usr + switch(mode) + if(HYPO_SPRAY) + mode = HYPO_INJECT + to_chat(M, "[src] is now set to inject contents on application.") + if(HYPO_INJECT) + mode = HYPO_SPRAY + to_chat(M, "[src] is now set to spray contents on application.") + +#undef HYPO_SPRAY +#undef HYPO_INJECT +#undef WAIT_SPRAY +#undef WAIT_INJECT +#undef SELF_SPRAY +#undef SELF_INJECT +#undef DELUXE_WAIT_SPRAY +#undef DELUXE_WAIT_INJECT +#undef DELUXE_SELF_SPRAY +#undef DELUXE_SELF_INJECT +#undef COMBAT_WAIT_SPRAY +#undef COMBAT_WAIT_INJECT +#undef COMBAT_SELF_SPRAY +#undef COMBAT_SELF_INJECT diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index ad8eb871e1..da8eb3846d 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -1,324 +1,324 @@ -/obj/item/reagent_containers/spray - name = "spray bottle" - desc = "A spray bottle, with an unscrewable top." - icon = 'icons/obj/janitor.dmi' - icon_state = "cleaner" - item_state = "cleaner" - lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' - item_flags = NOBLUDGEON - reagent_flags = OPENCONTAINER - slot_flags = ITEM_SLOT_BELT - throwforce = 0 - w_class = WEIGHT_CLASS_SMALL - throw_speed = 3 - throw_range = 7 - var/stream_mode = 0 //whether we use the more focused mode - var/current_range = 3 //the range of tiles the sprayer will reach. - var/spray_range = 3 //the range of tiles the sprayer will reach when in spray mode. - var/stream_range = 1 //the range of tiles the sprayer will reach when in stream mode. - var/stream_amount = 10 //the amount of reagents transfered when in stream mode. - var/spray_delay = 3 //The amount of sleep() delay between each chempuff step. - var/can_fill_from_container = TRUE - amount_per_transfer_from_this = 5 - volume = 250 - possible_transfer_amounts = list(5,10,15,20,25,30,50,100) - -/obj/item/reagent_containers/spray/afterattack(atom/A, mob/user) - . = ..() - if(istype(A, /obj/structure/sink) || istype(A, /obj/structure/janitorialcart) || istype(A, /obj/machinery/hydroponics)) - return - - if((A.is_drainable() && !A.is_refillable()) && get_dist(src,A) <= 1 && can_fill_from_container) - if(!A.reagents.total_volume) - to_chat(user, "[A] is empty.") - return - - if(reagents.holder_full()) - to_chat(user, "[src] is full.") - return - - var/trans = A.reagents.trans_to(src, 50) //transfer 50u , using the spray's transfer amount would take too long to refill - to_chat(user, "You fill \the [src] with [trans] units of the contents of \the [A].") - return - - if(reagents.total_volume < amount_per_transfer_from_this) - to_chat(user, "[src] is empty!") - return - - spray(A) - - playsound(src.loc, 'sound/effects/spray2.ogg', 50, 1, -6) - user.changeNext_move(CLICK_CD_RANGE*2) - user.newtonian_move(get_dir(A, user)) - var/turf/T = get_turf(src) - if(reagents.has_reagent(/datum/reagent/toxin/acid)) - message_admins("[ADMIN_LOOKUPFLW(user)] fired sulphuric acid from \a [src] at [ADMIN_VERBOSEJMP(T)].") - log_game("[key_name(user)] fired sulphuric acid from \a [src] at [AREACOORD(T)].") - if(reagents.has_reagent(/datum/reagent/toxin/acid/fluacid)) - message_admins("[ADMIN_LOOKUPFLW(user)] fired Fluacid from \a [src] at [ADMIN_VERBOSEJMP(T)].") - log_game("[key_name(user)] fired Fluacid from \a [src] at [AREACOORD(T)].") - if(reagents.has_reagent(/datum/reagent/lube)) - message_admins("[ADMIN_LOOKUPFLW(user)] fired Space lube from \a [src] at [ADMIN_VERBOSEJMP(T)].") - log_game("[key_name(user)] fired Space lube from \a [src] at [AREACOORD(T)].") - return - - -/obj/item/reagent_containers/spray/proc/spray(atom/A) - var/range = CLAMP(get_dist(src, A), 1, current_range) - var/obj/effect/decal/chempuff/D = new /obj/effect/decal/chempuff(get_turf(src)) - D.create_reagents(amount_per_transfer_from_this) - var/puff_reagent_left = range //how many turf, mob or dense objet we can react with before we consider the chem puff consumed - if(stream_mode) - reagents.trans_to(D, amount_per_transfer_from_this) - puff_reagent_left = 1 - else - reagents.trans_to(D, amount_per_transfer_from_this, 1/range) - D.color = mix_color_from_reagents(D.reagents.reagent_list) - var/wait_step = max(round(2+ spray_delay * INVERSE(range)), 2) - do_spray(A, wait_step, D, range, puff_reagent_left) - -/obj/item/reagent_containers/spray/proc/do_spray(atom/A, wait_step, obj/effect/decal/chempuff/D, range, puff_reagent_left) - set waitfor = FALSE - var/range_left = range - for(var/i=0, i 0 && (!stream_mode || !range_left)) - D.reagents.reaction(get_turf(D), VAPOR) - puff_reagent_left -= 1 - - if(puff_reagent_left <= 0) // we used all the puff so we delete it. - qdel(D) - return - qdel(D) - -/obj/item/reagent_containers/spray/attack_self(mob/user) - stream_mode = !stream_mode - if(stream_mode) - amount_per_transfer_from_this = stream_amount - current_range = stream_range - else - amount_per_transfer_from_this = initial(amount_per_transfer_from_this) - current_range = spray_range - to_chat(user, "You switch the nozzle setting to [stream_mode ? "\"stream\"":"\"spray\""]. You'll now use [amount_per_transfer_from_this] units per use.") - -/obj/item/reagent_containers/spray/attackby(obj/item/I, mob/user, params) - var/hotness = I.get_temperature() - if(hotness && reagents) - reagents.expose_temperature(hotness) - to_chat(user, "You heat [name] with [I]!") - return ..() - -/obj/item/reagent_containers/spray/verb/empty() - set name = "Empty Spray Bottle" - set category = "Object" - set src in usr - if(usr.incapacitated()) - return - if (alert(usr, "Are you sure you want to empty that?", "Empty Bottle:", "Yes", "No") != "Yes") - return - if(isturf(usr.loc) && src.loc == usr) - to_chat(usr, "You empty \the [src] onto the floor.") - reagents.reaction(usr.loc) - src.reagents.clear_reagents() - -//space cleaner -/obj/item/reagent_containers/spray/cleaner - name = "space cleaner" - desc = "BLAM!-brand non-foaming space cleaner!" - volume = 100 - list_reagents = list(/datum/reagent/space_cleaner = 100) - amount_per_transfer_from_this = 2 - stream_amount = 5 - -/obj/item/reagent_containers/spray/cleaner/suicide_act(mob/user) - user.visible_message("[user] is putting the nozzle of \the [src] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide!") - if(do_mob(user,user,30)) - if(reagents.total_volume >= amount_per_transfer_from_this)//if not empty - user.visible_message("[user] pulls the trigger!") - src.spray(user) - return BRUTELOSS - else - user.visible_message("[user] pulls the trigger...but \the [src] is empty!") - return SHAME - else - user.visible_message("[user] decided life was worth living.") - return - -//Drying Agent -/obj/item/reagent_containers/spray/drying_agent - name = "drying agent spray" - desc = "A spray bottle for drying agent." - volume = 100 - list_reagents = list(/datum/reagent/drying_agent = 100) - amount_per_transfer_from_this = 2 - stream_amount = 5 - -//spray tan -/obj/item/reagent_containers/spray/spraytan - name = "spray tan" - volume = 50 - desc = "Gyaro brand spray tan. Do not spray near eyes or other orifices." - list_reagents = list(/datum/reagent/spraytan = 50) - - -//pepperspray -/obj/item/reagent_containers/spray/pepper - name = "pepperspray" - desc = "Manufactured by UhangInc, used to blind and down an opponent quickly." - icon = 'icons/obj/items_and_weapons.dmi' - icon_state = "pepperspray" - item_state = "pepperspray" - lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' - volume = 40 - stream_range = 4 - spray_delay = 1 - amount_per_transfer_from_this = 5 - list_reagents = list(/datum/reagent/consumable/condensedcapsaicin = 40) - -/obj/item/reagent_containers/spray/pepper/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins huffing \the [src]! It looks like [user.p_theyre()] getting a dirty high!") - return OXYLOSS - -// Fix pepperspraying yourself -/obj/item/reagent_containers/spray/pepper/afterattack(atom/A as mob|obj, mob/user) - if (A.loc == user) - return - . = ..() - -//water flower -/obj/item/reagent_containers/spray/waterflower - name = "water flower" - desc = "A seemingly innocent sunflower...with a twist." - icon = 'icons/obj/hydroponics/harvest.dmi' - icon_state = "sunflower" - item_state = "sunflower" - amount_per_transfer_from_this = 1 - volume = 10 - list_reagents = list(/datum/reagent/water = 10) - -/obj/item/reagent_containers/spray/waterflower/attack_self(mob/user) //Don't allow changing how much the flower sprays - return - -///Subtype used for the lavaland clown ruin. -/obj/item/reagent_containers/spray/waterflower/superlube - name = "clown flower" - desc = "A delightly devilish flower... you got a feeling where this is going." - icon = 'icons/obj/chemical.dmi' - icon_state = "clownflower" - volume = 30 - list_reagents = list(/datum/reagent/lube/superlube = 30) - -/obj/item/reagent_containers/spray/waterflower/cyborg - reagent_flags = NONE - volume = 100 - list_reagents = list(/datum/reagent/water = 100) - var/generate_amount = 5 - var/generate_type = /datum/reagent/water - var/last_generate = 0 - var/generate_delay = 10 //deciseconds - can_fill_from_container = FALSE - -/obj/item/reagent_containers/spray/waterflower/cyborg/hacked - name = "nova flower" - desc = "This doesn't look safe at all..." - list_reagents = list(/datum/reagent/clf3 = 3) - volume = 3 - generate_type = /datum/reagent/clf3 - generate_amount = 1 - generate_delay = 40 //deciseconds - -/obj/item/reagent_containers/spray/waterflower/cyborg/Initialize() - . = ..() - START_PROCESSING(SSfastprocess, src) - -/obj/item/reagent_containers/spray/waterflower/cyborg/Destroy() - STOP_PROCESSING(SSfastprocess, src) - return ..() - -/obj/item/reagent_containers/spray/waterflower/cyborg/process() - if(world.time < last_generate + generate_delay) - return - last_generate = world.time - generate_reagents() - -/obj/item/reagent_containers/spray/waterflower/cyborg/empty() - to_chat(usr, "You can not empty this!") - return - -/obj/item/reagent_containers/spray/waterflower/cyborg/proc/generate_reagents() - reagents.add_reagent(generate_type, generate_amount) - -//chemsprayer -/obj/item/reagent_containers/spray/chemsprayer - name = "chem sprayer" - desc = "A utility used to spray large amounts of reagents in a given area." - icon = 'icons/obj/guns/projectile.dmi' - icon_state = "chemsprayer" - item_state = "chemsprayer" - lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' - righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' - throwforce = 0 - w_class = WEIGHT_CLASS_NORMAL - stream_mode = 1 - current_range = 7 - spray_range = 4 - stream_range = 7 - amount_per_transfer_from_this = 10 - volume = 600 - -/obj/item/reagent_containers/spray/chemsprayer/afterattack(atom/A as mob|obj, mob/user) - // Make it so the bioterror spray doesn't spray yourself when you click your inventory items - if (A.loc == user) - return - . = ..() - -/obj/item/reagent_containers/spray/chemsprayer/spray(atom/A) - var/direction = get_dir(src, A) - var/turf/T = get_turf(A) - 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) - - for(var/i=1, i<=3, i++) // intialize sprays - if(reagents.total_volume < 1) - return - ..(the_targets[i]) - -/obj/item/reagent_containers/spray/chemsprayer/bioterror - list_reagents = list(/datum/reagent/toxin/sodium_thiopental = 100, /datum/reagent/toxin/coniine = 100, /datum/reagent/toxin/venom = 100, /datum/reagent/consumable/condensedcapsaicin = 100, /datum/reagent/toxin/initropidril = 100, /datum/reagent/toxin/polonium = 100) - -// Plant-B-Gone -/obj/item/reagent_containers/spray/plantbgone // -- Skie - name = "Plant-B-Gone" - desc = "Kills those pesky weeds!" - icon = 'icons/obj/hydroponics/equipment.dmi' - icon_state = "plantbgone" - item_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/plantbgone = 100) +/obj/item/reagent_containers/spray + name = "spray bottle" + desc = "A spray bottle, with an unscrewable top." + icon = 'icons/obj/janitor.dmi' + icon_state = "cleaner" + item_state = "cleaner" + lefthand_file = 'icons/mob/inhands/equipment/custodial_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/custodial_righthand.dmi' + item_flags = NOBLUDGEON + reagent_flags = OPENCONTAINER + slot_flags = ITEM_SLOT_BELT + throwforce = 0 + w_class = WEIGHT_CLASS_SMALL + throw_speed = 3 + throw_range = 7 + var/stream_mode = 0 //whether we use the more focused mode + var/current_range = 3 //the range of tiles the sprayer will reach. + var/spray_range = 3 //the range of tiles the sprayer will reach when in spray mode. + var/stream_range = 1 //the range of tiles the sprayer will reach when in stream mode. + var/stream_amount = 10 //the amount of reagents transfered when in stream mode. + var/spray_delay = 3 //The amount of sleep() delay between each chempuff step. + var/can_fill_from_container = TRUE + amount_per_transfer_from_this = 5 + volume = 250 + possible_transfer_amounts = list(5,10,15,20,25,30,50,100) + +/obj/item/reagent_containers/spray/afterattack(atom/A, mob/user) + . = ..() + if(istype(A, /obj/structure/sink) || istype(A, /obj/structure/janitorialcart) || istype(A, /obj/machinery/hydroponics)) + return + + if((A.is_drainable() && !A.is_refillable()) && get_dist(src,A) <= 1 && can_fill_from_container) + if(!A.reagents.total_volume) + to_chat(user, "[A] is empty.") + return + + if(reagents.holder_full()) + to_chat(user, "[src] is full.") + return + + var/trans = A.reagents.trans_to(src, 50) //transfer 50u , using the spray's transfer amount would take too long to refill + to_chat(user, "You fill \the [src] with [trans] units of the contents of \the [A].") + return + + if(reagents.total_volume < amount_per_transfer_from_this) + to_chat(user, "[src] is empty!") + return + + spray(A) + + playsound(src.loc, 'sound/effects/spray2.ogg', 50, 1, -6) + user.changeNext_move(CLICK_CD_RANGE*2) + user.newtonian_move(get_dir(A, user)) + var/turf/T = get_turf(src) + if(reagents.has_reagent(/datum/reagent/toxin/acid)) + message_admins("[ADMIN_LOOKUPFLW(user)] fired sulphuric acid from \a [src] at [ADMIN_VERBOSEJMP(T)].") + log_game("[key_name(user)] fired sulphuric acid from \a [src] at [AREACOORD(T)].") + if(reagents.has_reagent(/datum/reagent/toxin/acid/fluacid)) + message_admins("[ADMIN_LOOKUPFLW(user)] fired Fluacid from \a [src] at [ADMIN_VERBOSEJMP(T)].") + log_game("[key_name(user)] fired Fluacid from \a [src] at [AREACOORD(T)].") + if(reagents.has_reagent(/datum/reagent/lube)) + message_admins("[ADMIN_LOOKUPFLW(user)] fired Space lube from \a [src] at [ADMIN_VERBOSEJMP(T)].") + log_game("[key_name(user)] fired Space lube from \a [src] at [AREACOORD(T)].") + return + + +/obj/item/reagent_containers/spray/proc/spray(atom/A) + var/range = CLAMP(get_dist(src, A), 1, current_range) + var/obj/effect/decal/chempuff/D = new /obj/effect/decal/chempuff(get_turf(src)) + D.create_reagents(amount_per_transfer_from_this) + var/puff_reagent_left = range //how many turf, mob or dense objet we can react with before we consider the chem puff consumed + if(stream_mode) + reagents.trans_to(D, amount_per_transfer_from_this) + puff_reagent_left = 1 + else + reagents.trans_to(D, amount_per_transfer_from_this, 1/range) + D.color = mix_color_from_reagents(D.reagents.reagent_list) + var/wait_step = max(round(2+ spray_delay * INVERSE(range)), 2) + do_spray(A, wait_step, D, range, puff_reagent_left) + +/obj/item/reagent_containers/spray/proc/do_spray(atom/A, wait_step, obj/effect/decal/chempuff/D, range, puff_reagent_left) + set waitfor = FALSE + var/range_left = range + for(var/i=0, i 0 && (!stream_mode || !range_left)) + D.reagents.reaction(get_turf(D), VAPOR) + puff_reagent_left -= 1 + + if(puff_reagent_left <= 0) // we used all the puff so we delete it. + qdel(D) + return + qdel(D) + +/obj/item/reagent_containers/spray/attack_self(mob/user) + stream_mode = !stream_mode + if(stream_mode) + amount_per_transfer_from_this = stream_amount + current_range = stream_range + else + amount_per_transfer_from_this = initial(amount_per_transfer_from_this) + current_range = spray_range + to_chat(user, "You switch the nozzle setting to [stream_mode ? "\"stream\"":"\"spray\""]. You'll now use [amount_per_transfer_from_this] units per use.") + +/obj/item/reagent_containers/spray/attackby(obj/item/I, mob/user, params) + var/hotness = I.get_temperature() + if(hotness && reagents) + reagents.expose_temperature(hotness) + to_chat(user, "You heat [name] with [I]!") + return ..() + +/obj/item/reagent_containers/spray/verb/empty() + set name = "Empty Spray Bottle" + set category = "Object" + set src in usr + if(usr.incapacitated()) + return + if (alert(usr, "Are you sure you want to empty that?", "Empty Bottle:", "Yes", "No") != "Yes") + return + if(isturf(usr.loc) && src.loc == usr) + to_chat(usr, "You empty \the [src] onto the floor.") + reagents.reaction(usr.loc) + src.reagents.clear_reagents() + +//space cleaner +/obj/item/reagent_containers/spray/cleaner + name = "space cleaner" + desc = "BLAM!-brand non-foaming space cleaner!" + volume = 100 + list_reagents = list(/datum/reagent/space_cleaner = 100) + amount_per_transfer_from_this = 2 + stream_amount = 5 + +/obj/item/reagent_containers/spray/cleaner/suicide_act(mob/user) + user.visible_message("[user] is putting the nozzle of \the [src] in [user.p_their()] mouth. It looks like [user.p_theyre()] trying to commit suicide!") + if(do_mob(user,user,30)) + if(reagents.total_volume >= amount_per_transfer_from_this)//if not empty + user.visible_message("[user] pulls the trigger!") + src.spray(user) + return BRUTELOSS + else + user.visible_message("[user] pulls the trigger...but \the [src] is empty!") + return SHAME + else + user.visible_message("[user] decided life was worth living.") + return + +//Drying Agent +/obj/item/reagent_containers/spray/drying_agent + name = "drying agent spray" + desc = "A spray bottle for drying agent." + volume = 100 + list_reagents = list(/datum/reagent/drying_agent = 100) + amount_per_transfer_from_this = 2 + stream_amount = 5 + +//spray tan +/obj/item/reagent_containers/spray/spraytan + name = "spray tan" + volume = 50 + desc = "Gyaro brand spray tan. Do not spray near eyes or other orifices." + list_reagents = list(/datum/reagent/spraytan = 50) + + +//pepperspray +/obj/item/reagent_containers/spray/pepper + name = "pepperspray" + desc = "Manufactured by UhangInc, used to blind and down an opponent quickly." + icon = 'icons/obj/items_and_weapons.dmi' + icon_state = "pepperspray" + item_state = "pepperspray" + lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi' + volume = 40 + stream_range = 4 + spray_delay = 1 + amount_per_transfer_from_this = 5 + list_reagents = list(/datum/reagent/consumable/condensedcapsaicin = 40) + +/obj/item/reagent_containers/spray/pepper/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins huffing \the [src]! It looks like [user.p_theyre()] getting a dirty high!") + return OXYLOSS + +// Fix pepperspraying yourself +/obj/item/reagent_containers/spray/pepper/afterattack(atom/A as mob|obj, mob/user) + if (A.loc == user) + return + . = ..() + +//water flower +/obj/item/reagent_containers/spray/waterflower + name = "water flower" + desc = "A seemingly innocent sunflower...with a twist." + icon = 'icons/obj/hydroponics/harvest.dmi' + icon_state = "sunflower" + item_state = "sunflower" + amount_per_transfer_from_this = 1 + volume = 10 + list_reagents = list(/datum/reagent/water = 10) + +/obj/item/reagent_containers/spray/waterflower/attack_self(mob/user) //Don't allow changing how much the flower sprays + return + +///Subtype used for the lavaland clown ruin. +/obj/item/reagent_containers/spray/waterflower/superlube + name = "clown flower" + desc = "A delightly devilish flower... you got a feeling where this is going." + icon = 'icons/obj/chemical.dmi' + icon_state = "clownflower" + volume = 30 + list_reagents = list(/datum/reagent/lube/superlube = 30) + +/obj/item/reagent_containers/spray/waterflower/cyborg + reagent_flags = NONE + volume = 100 + list_reagents = list(/datum/reagent/water = 100) + var/generate_amount = 5 + var/generate_type = /datum/reagent/water + var/last_generate = 0 + var/generate_delay = 10 //deciseconds + can_fill_from_container = FALSE + +/obj/item/reagent_containers/spray/waterflower/cyborg/hacked + name = "nova flower" + desc = "This doesn't look safe at all..." + list_reagents = list(/datum/reagent/clf3 = 3) + volume = 3 + generate_type = /datum/reagent/clf3 + generate_amount = 1 + generate_delay = 40 //deciseconds + +/obj/item/reagent_containers/spray/waterflower/cyborg/Initialize() + . = ..() + START_PROCESSING(SSfastprocess, src) + +/obj/item/reagent_containers/spray/waterflower/cyborg/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/obj/item/reagent_containers/spray/waterflower/cyborg/process() + if(world.time < last_generate + generate_delay) + return + last_generate = world.time + generate_reagents() + +/obj/item/reagent_containers/spray/waterflower/cyborg/empty() + to_chat(usr, "You can not empty this!") + return + +/obj/item/reagent_containers/spray/waterflower/cyborg/proc/generate_reagents() + reagents.add_reagent(generate_type, generate_amount) + +//chemsprayer +/obj/item/reagent_containers/spray/chemsprayer + name = "chem sprayer" + desc = "A utility used to spray large amounts of reagents in a given area." + icon = 'icons/obj/guns/projectile.dmi' + icon_state = "chemsprayer" + item_state = "chemsprayer" + lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi' + throwforce = 0 + w_class = WEIGHT_CLASS_NORMAL + stream_mode = 1 + current_range = 7 + spray_range = 4 + stream_range = 7 + amount_per_transfer_from_this = 10 + volume = 600 + +/obj/item/reagent_containers/spray/chemsprayer/afterattack(atom/A as mob|obj, mob/user) + // Make it so the bioterror spray doesn't spray yourself when you click your inventory items + if (A.loc == user) + return + . = ..() + +/obj/item/reagent_containers/spray/chemsprayer/spray(atom/A) + var/direction = get_dir(src, A) + var/turf/T = get_turf(A) + 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) + + for(var/i=1, i<=3, i++) // intialize sprays + if(reagents.total_volume < 1) + return + ..(the_targets[i]) + +/obj/item/reagent_containers/spray/chemsprayer/bioterror + list_reagents = list(/datum/reagent/toxin/sodium_thiopental = 100, /datum/reagent/toxin/coniine = 100, /datum/reagent/toxin/venom = 100, /datum/reagent/consumable/condensedcapsaicin = 100, /datum/reagent/toxin/initropidril = 100, /datum/reagent/toxin/polonium = 100) + +// Plant-B-Gone +/obj/item/reagent_containers/spray/plantbgone // -- Skie + name = "Plant-B-Gone" + desc = "Kills those pesky weeds!" + icon = 'icons/obj/hydroponics/equipment.dmi' + icon_state = "plantbgone" + item_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/plantbgone = 100) diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm index 7fafbc319c..763184da46 100644 --- a/code/modules/reagents/reagent_containers/syringes.dm +++ b/code/modules/reagents/reagent_containers/syringes.dm @@ -1,351 +1,351 @@ -/obj/item/reagent_containers/syringe - name = "syringe" - desc = "A syringe that can hold up to 15 units." - icon = 'icons/obj/syringe.dmi' - item_state = "syringe_0" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - icon_state = "0" - amount_per_transfer_from_this = 5 - possible_transfer_amounts = list() - volume = 15 - var/mode = SYRINGE_DRAW - var/busy = FALSE // needed for delayed drawing of blood - var/proj_piercing = 0 //does it pierce through thick clothes when shot with syringe gun - materials = list(MAT_METAL=10, MAT_GLASS=20) - reagent_flags = TRANSPARENT - -/obj/item/reagent_containers/syringe/Initialize() - . = ..() - if(list_reagents) //syringe starts in inject mode if its already got something inside - mode = SYRINGE_INJECT - update_icon() - -/obj/item/reagent_containers/syringe/on_reagent_change(changetype) - update_icon() - -/obj/item/reagent_containers/syringe/pickup(mob/user) - ..() - update_icon() - -/obj/item/reagent_containers/syringe/dropped(mob/user) - ..() - update_icon() - -/obj/item/reagent_containers/syringe/attack_self(mob/user) - mode = !mode - update_icon() - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/item/reagent_containers/syringe/attack_hand() - . = ..() - update_icon() - -/obj/item/reagent_containers/syringe/attack_paw(mob/user) - return attack_hand(user) - -/obj/item/reagent_containers/syringe/attackby(obj/item/I, mob/user, params) - return - -/obj/item/reagent_containers/syringe/afterattack(atom/target, mob/user , proximity) - . = ..() - if(busy) - return - if(!proximity) - return - if(!target.reagents) - return - - var/mob/living/L - if(isliving(target)) - L = target - if(!L.can_inject(user, 1)) - return - - // chance of monkey retaliation - if(ismonkey(target) && prob(MONKEY_SYRINGE_RETALIATION_PROB)) - var/mob/living/carbon/monkey/M - M = target - M.retaliate(user) - - switch(mode) - if(SYRINGE_DRAW) - - if(reagents.total_volume >= reagents.maximum_volume) - to_chat(user, "The syringe is full.") - return - - if(L) //living mob - var/drawn_amount = reagents.maximum_volume - reagents.total_volume - if(target != user) - target.visible_message("[user] is trying to take a blood sample from [target]!", \ - "[user] is trying to take a blood sample from [target]!") - busy = TRUE - if(!do_mob(user, target, extra_checks=CALLBACK(L, /mob/living/proc/can_inject,user,1))) - busy = FALSE - return - if(reagents.total_volume >= reagents.maximum_volume) - return - busy = FALSE - if(L.transfer_blood_to(src, drawn_amount)) - user.visible_message("[user] takes a blood sample from [L].") - else - to_chat(user, "You are unable to draw any blood from [L]!") - - else //if not mob - if(!target.reagents.total_volume) - to_chat(user, "[target] is empty!") - return - - if(!target.is_drawable()) - to_chat(user, "You cannot directly remove reagents from [target]!") - return - - var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) // transfer from, transfer to - who cares? - - to_chat(user, "You fill [src] with [trans] units of the solution. It now contains [reagents.total_volume] units.") - if (round(reagents.total_volume, 0.1) >= reagents.maximum_volume) - mode=!mode - update_icon() - - if(SYRINGE_INJECT) - // Always log attemped injections for admins - var/contained = reagents.log_list() - log_combat(user, target, "attempted to inject", src, addition="which had [contained]") - - if(!reagents.total_volume) - to_chat(user, "[src] is empty.") - return - - if(!L && !target.is_injectable()) //only checks on non-living mobs, due to how can_inject() handles - to_chat(user, "You cannot directly fill [target]!") - return - - if(target.reagents.total_volume >= target.reagents.maximum_volume) - to_chat(user, "[target] is full.") - return - - if(L) //living mob - if(!L.can_inject(user, TRUE)) - return - if(L != user) - L.visible_message("[user] is trying to inject [L]!", \ - "[user] is trying to inject [L]!") - if(!do_mob(user, L, extra_checks=CALLBACK(L, /mob/living/proc/can_inject,user,1))) - return - if(!reagents.total_volume) - return - if(L.reagents.total_volume >= L.reagents.maximum_volume) - return - L.visible_message("[user] injects [L] with the syringe!", \ - "[user] injects [L] with the syringe!") - - if(L != user) - log_combat(user, L, "injected", src, addition="which had [contained]") - else - L.log_message("injected themselves ([contained]) with [src.name]", LOG_ATTACK, color="orange") - var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) - reagents.reaction(L, INJECT, fraction) - reagents.trans_to(target, amount_per_transfer_from_this) - to_chat(user, "You inject [amount_per_transfer_from_this] units of the solution. The syringe now contains [reagents.total_volume] units.") - if (reagents.total_volume <= 0 && mode==SYRINGE_INJECT) - mode = SYRINGE_DRAW - update_icon() - - -/obj/item/reagent_containers/syringe/update_icon() - cut_overlays() - var/rounded_vol - if(reagents && reagents.total_volume) - rounded_vol = CLAMP(round((reagents.total_volume / volume * 15),5), 1, 15) - var/image/filling_overlay = mutable_appearance('icons/obj/reagentfillings.dmi', "syringe[rounded_vol]") - filling_overlay.color = mix_color_from_reagents(reagents.reagent_list) - add_overlay(filling_overlay) - else - rounded_vol = 0 - icon_state = "[rounded_vol]" - item_state = "syringe_[rounded_vol]" - if(ismob(loc)) - var/mob/M = loc - var/injoverlay - switch(mode) - if (SYRINGE_DRAW) - injoverlay = "draw" - if (SYRINGE_INJECT) - injoverlay = "inject" - add_overlay(injoverlay) - M.update_inv_hands() - -/obj/item/reagent_containers/syringe/epinephrine - name = "syringe (epinephrine)" - desc = "Contains epinephrine - used to stabilize patients." - list_reagents = list(/datum/reagent/medicine/epinephrine = 15) - -/obj/item/reagent_containers/syringe/charcoal - name = "syringe (charcoal)" - desc = "Contains charcoal." - list_reagents = list(/datum/reagent/medicine/charcoal = 15) - -/obj/item/reagent_containers/syringe/antiviral - name = "syringe (spaceacillin)" - desc = "Contains antiviral agents." - list_reagents = list(/datum/reagent/medicine/spaceacillin = 15) - -/obj/item/reagent_containers/syringe/bioterror - name = "bioterror syringe" - desc = "Contains several paralyzing reagents." - list_reagents = list(/datum/reagent/consumable/ethanol/neurotoxin = 5, /datum/reagent/toxin/mutetoxin = 5, /datum/reagent/toxin/sodium_thiopental = 5) - -/obj/item/reagent_containers/syringe/stimulants - name = "Stimpack" - desc = "Contains stimulants." - amount_per_transfer_from_this = 50 - volume = 50 - list_reagents = list(/datum/reagent/medicine/stimulants = 50) - -/obj/item/reagent_containers/syringe/calomel - name = "syringe (calomel)" - desc = "Contains calomel." - list_reagents = list(/datum/reagent/medicine/calomel = 15) - -/obj/item/reagent_containers/syringe/plasma - name = "syringe (plasma)" - desc = "Contains plasma." - list_reagents = list(/datum/reagent/toxin/plasma = 15) - -/obj/item/reagent_containers/syringe/lethal - name = "lethal injection syringe" - desc = "A syringe used for lethal injections. It can hold up to 50 units." - amount_per_transfer_from_this = 50 - volume = 50 - -/obj/item/reagent_containers/syringe/lethal/choral - list_reagents = list(/datum/reagent/toxin/chloralhydrate = 50) - -/obj/item/reagent_containers/syringe/lethal/execution - list_reagents = list(/datum/reagent/toxin/amatoxin = 15, /datum/reagent/toxin/formaldehyde = 15, /datum/reagent/toxin/cyanide = 10, /datum/reagent/toxin/acid/fluacid = 10) //Citadel edit, changing out plasma from lethals - -/obj/item/reagent_containers/syringe/mulligan - name = "Mulligan" - desc = "A syringe used to completely change the users identity." - amount_per_transfer_from_this = 1 - volume = 1 - list_reagents = list(/datum/reagent/mulligan = 1) - -/obj/item/reagent_containers/syringe/gluttony - name = "Gluttony's Blessing" - desc = "A syringe recovered from a dread place. It probably isn't wise to use." - amount_per_transfer_from_this = 1 - volume = 1 - list_reagents = list(/datum/reagent/gluttonytoxin = 1) - -/obj/item/reagent_containers/syringe/bluespace - name = "bluespace syringe" - desc = "An advanced syringe that can hold 60 units of chemicals." - amount_per_transfer_from_this = 20 - volume = 60 - -/obj/item/reagent_containers/syringe/noreact - name = "cryo syringe" - desc = "An advanced syringe that stops reagents inside from reacting. It can hold up to 20 units." - volume = 20 - reagent_flags = TRANSPARENT | NO_REACT - -/obj/item/reagent_containers/syringe/piercing - name = "piercing syringe" - desc = "A diamond-tipped syringe that pierces armor when launched at high velocity. It can hold up to 10 units." - volume = 10 - proj_piercing = 1 - -/obj/item/reagent_containers/syringe/get_belt_overlay() - return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "pouch") - -/obj/item/reagent_containers/syringe/dart - name = "medicinal smartdart" - desc = "A non-harmful dart that can administer medication from a range. Once it hits a patient using it's smart nanofilter technology only medicines contained within the dart are administered to the patient. Additonally, due to capillary action, injection of chemicals past the overdose limit is prevented." - volume = 20 - amount_per_transfer_from_this = 20 - icon_state = "empty" - item_state = "syringe_empty" - var/emptrig = FALSE - -/obj/item/reagent_containers/syringe/dart/afterattack(atom/target, mob/user , proximity) - - if(busy) - return - if(!proximity) - return - if(!target.reagents) - return - - var/mob/living/L - if(isliving(target)) - L = target - if(!L.can_inject(user, 1)) - return - - switch(mode) - if(SYRINGE_DRAW) - - if(reagents.total_volume >= reagents.maximum_volume) - to_chat(user, "The dart is full!") - return - - if(L) //living mob - to_chat(user, "You can't draw blood using a dart!") - return - - else //if not mob - if(!target.reagents.total_volume) - to_chat(user, "[target] is empty!") - return - - if(!target.is_drawable()) - to_chat(user, "You cannot directly remove reagents from [target]!") - return - - var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) - - to_chat(user, "You soak the [src] with [trans] units of the solution. It now contains [reagents.total_volume] units.") - if (round(reagents.total_volume,1) >= reagents.maximum_volume) - mode=!mode - update_icon() - - if(SYRINGE_INJECT) - src.visible_message("The smartdart gives a frustrated boop! It's fully saturated; You need to shoot someone with it!") - -/obj/item/reagent_containers/syringe/dart/attack_self(mob/user) - return - -/obj/item/reagent_containers/syringe/dart/update_icon() - cut_overlays() - var/rounded_vol - - rounded_vol = "empty" - if(reagents && reagents.total_volume) - if(volume/round(reagents.total_volume, 1) == 1) - rounded_vol="full" - mode = SYRINGE_INJECT - - icon_state = "[rounded_vol]" - item_state = "syringe_[rounded_vol]" - if(ismob(loc)) - var/mob/M = loc - var/injoverlay - switch(mode) - if (SYRINGE_DRAW) - injoverlay = "draw" - if (SYRINGE_INJECT) - injoverlay = "ready" - add_overlay(injoverlay) - M.update_inv_hands() - -/obj/item/reagent_containers/syringe/dart/emp_act(severity) - emptrig = TRUE - ..() - -/obj/item/reagent_containers/syringe/dart/bluespace - name = "bluespace smartdart" - desc = "A non-harmful dart that can administer medication from a range. Once it hits a patient using it's smart nanofilter technology only medicines contained within the dart are administered to the patient. Additonally, due to capillary action, injection of chemicals past the overdose limit is prevented. Has an extended volume capacity thanks to bluespace foam." - amount_per_transfer_from_this = 50 - volume = 50 +/obj/item/reagent_containers/syringe + name = "syringe" + desc = "A syringe that can hold up to 15 units." + icon = 'icons/obj/syringe.dmi' + item_state = "syringe_0" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + icon_state = "0" + amount_per_transfer_from_this = 5 + possible_transfer_amounts = list() + volume = 15 + var/mode = SYRINGE_DRAW + var/busy = FALSE // needed for delayed drawing of blood + var/proj_piercing = 0 //does it pierce through thick clothes when shot with syringe gun + materials = list(MAT_METAL=10, MAT_GLASS=20) + reagent_flags = TRANSPARENT + +/obj/item/reagent_containers/syringe/Initialize() + . = ..() + if(list_reagents) //syringe starts in inject mode if its already got something inside + mode = SYRINGE_INJECT + update_icon() + +/obj/item/reagent_containers/syringe/on_reagent_change(changetype) + update_icon() + +/obj/item/reagent_containers/syringe/pickup(mob/user) + ..() + update_icon() + +/obj/item/reagent_containers/syringe/dropped(mob/user) + ..() + update_icon() + +/obj/item/reagent_containers/syringe/attack_self(mob/user) + mode = !mode + update_icon() + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/item/reagent_containers/syringe/attack_hand() + . = ..() + update_icon() + +/obj/item/reagent_containers/syringe/attack_paw(mob/user) + return attack_hand(user) + +/obj/item/reagent_containers/syringe/attackby(obj/item/I, mob/user, params) + return + +/obj/item/reagent_containers/syringe/afterattack(atom/target, mob/user , proximity) + . = ..() + if(busy) + return + if(!proximity) + return + if(!target.reagents) + return + + var/mob/living/L + if(isliving(target)) + L = target + if(!L.can_inject(user, 1)) + return + + // chance of monkey retaliation + if(ismonkey(target) && prob(MONKEY_SYRINGE_RETALIATION_PROB)) + var/mob/living/carbon/monkey/M + M = target + M.retaliate(user) + + switch(mode) + if(SYRINGE_DRAW) + + if(reagents.total_volume >= reagents.maximum_volume) + to_chat(user, "The syringe is full.") + return + + if(L) //living mob + var/drawn_amount = reagents.maximum_volume - reagents.total_volume + if(target != user) + target.visible_message("[user] is trying to take a blood sample from [target]!", \ + "[user] is trying to take a blood sample from [target]!") + busy = TRUE + if(!do_mob(user, target, extra_checks=CALLBACK(L, /mob/living/proc/can_inject,user,1))) + busy = FALSE + return + if(reagents.total_volume >= reagents.maximum_volume) + return + busy = FALSE + if(L.transfer_blood_to(src, drawn_amount)) + user.visible_message("[user] takes a blood sample from [L].") + else + to_chat(user, "You are unable to draw any blood from [L]!") + + else //if not mob + if(!target.reagents.total_volume) + to_chat(user, "[target] is empty!") + return + + if(!target.is_drawable()) + to_chat(user, "You cannot directly remove reagents from [target]!") + return + + var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) // transfer from, transfer to - who cares? + + to_chat(user, "You fill [src] with [trans] units of the solution. It now contains [reagents.total_volume] units.") + if (round(reagents.total_volume, 0.1) >= reagents.maximum_volume) + mode=!mode + update_icon() + + if(SYRINGE_INJECT) + // Always log attemped injections for admins + var/contained = reagents.log_list() + log_combat(user, target, "attempted to inject", src, addition="which had [contained]") + + if(!reagents.total_volume) + to_chat(user, "[src] is empty.") + return + + if(!L && !target.is_injectable()) //only checks on non-living mobs, due to how can_inject() handles + to_chat(user, "You cannot directly fill [target]!") + return + + if(target.reagents.total_volume >= target.reagents.maximum_volume) + to_chat(user, "[target] is full.") + return + + if(L) //living mob + if(!L.can_inject(user, TRUE)) + return + if(L != user) + L.visible_message("[user] is trying to inject [L]!", \ + "[user] is trying to inject [L]!") + if(!do_mob(user, L, extra_checks=CALLBACK(L, /mob/living/proc/can_inject,user,1))) + return + if(!reagents.total_volume) + return + if(L.reagents.total_volume >= L.reagents.maximum_volume) + return + L.visible_message("[user] injects [L] with the syringe!", \ + "[user] injects [L] with the syringe!") + + if(L != user) + log_combat(user, L, "injected", src, addition="which had [contained]") + else + L.log_message("injected themselves ([contained]) with [src.name]", LOG_ATTACK, color="orange") + var/fraction = min(amount_per_transfer_from_this/reagents.total_volume, 1) + reagents.reaction(L, INJECT, fraction) + reagents.trans_to(target, amount_per_transfer_from_this) + to_chat(user, "You inject [amount_per_transfer_from_this] units of the solution. The syringe now contains [reagents.total_volume] units.") + if (reagents.total_volume <= 0 && mode==SYRINGE_INJECT) + mode = SYRINGE_DRAW + update_icon() + + +/obj/item/reagent_containers/syringe/update_icon() + cut_overlays() + var/rounded_vol + if(reagents && reagents.total_volume) + rounded_vol = CLAMP(round((reagents.total_volume / volume * 15),5), 1, 15) + var/image/filling_overlay = mutable_appearance('icons/obj/reagentfillings.dmi', "syringe[rounded_vol]") + filling_overlay.color = mix_color_from_reagents(reagents.reagent_list) + add_overlay(filling_overlay) + else + rounded_vol = 0 + icon_state = "[rounded_vol]" + item_state = "syringe_[rounded_vol]" + if(ismob(loc)) + var/mob/M = loc + var/injoverlay + switch(mode) + if (SYRINGE_DRAW) + injoverlay = "draw" + if (SYRINGE_INJECT) + injoverlay = "inject" + add_overlay(injoverlay) + M.update_inv_hands() + +/obj/item/reagent_containers/syringe/epinephrine + name = "syringe (epinephrine)" + desc = "Contains epinephrine - used to stabilize patients." + list_reagents = list(/datum/reagent/medicine/epinephrine = 15) + +/obj/item/reagent_containers/syringe/charcoal + name = "syringe (charcoal)" + desc = "Contains charcoal." + list_reagents = list(/datum/reagent/medicine/charcoal = 15) + +/obj/item/reagent_containers/syringe/antiviral + name = "syringe (spaceacillin)" + desc = "Contains antiviral agents." + list_reagents = list(/datum/reagent/medicine/spaceacillin = 15) + +/obj/item/reagent_containers/syringe/bioterror + name = "bioterror syringe" + desc = "Contains several paralyzing reagents." + list_reagents = list(/datum/reagent/consumable/ethanol/neurotoxin = 5, /datum/reagent/toxin/mutetoxin = 5, /datum/reagent/toxin/sodium_thiopental = 5) + +/obj/item/reagent_containers/syringe/stimulants + name = "Stimpack" + desc = "Contains stimulants." + amount_per_transfer_from_this = 50 + volume = 50 + list_reagents = list(/datum/reagent/medicine/stimulants = 50) + +/obj/item/reagent_containers/syringe/calomel + name = "syringe (calomel)" + desc = "Contains calomel." + list_reagents = list(/datum/reagent/medicine/calomel = 15) + +/obj/item/reagent_containers/syringe/plasma + name = "syringe (plasma)" + desc = "Contains plasma." + list_reagents = list(/datum/reagent/toxin/plasma = 15) + +/obj/item/reagent_containers/syringe/lethal + name = "lethal injection syringe" + desc = "A syringe used for lethal injections. It can hold up to 50 units." + amount_per_transfer_from_this = 50 + volume = 50 + +/obj/item/reagent_containers/syringe/lethal/choral + list_reagents = list(/datum/reagent/toxin/chloralhydrate = 50) + +/obj/item/reagent_containers/syringe/lethal/execution + list_reagents = list(/datum/reagent/toxin/amatoxin = 15, /datum/reagent/toxin/formaldehyde = 15, /datum/reagent/toxin/cyanide = 10, /datum/reagent/toxin/acid/fluacid = 10) //Citadel edit, changing out plasma from lethals + +/obj/item/reagent_containers/syringe/mulligan + name = "Mulligan" + desc = "A syringe used to completely change the users identity." + amount_per_transfer_from_this = 1 + volume = 1 + list_reagents = list(/datum/reagent/mulligan = 1) + +/obj/item/reagent_containers/syringe/gluttony + name = "Gluttony's Blessing" + desc = "A syringe recovered from a dread place. It probably isn't wise to use." + amount_per_transfer_from_this = 1 + volume = 1 + list_reagents = list(/datum/reagent/gluttonytoxin = 1) + +/obj/item/reagent_containers/syringe/bluespace + name = "bluespace syringe" + desc = "An advanced syringe that can hold 60 units of chemicals." + amount_per_transfer_from_this = 20 + volume = 60 + +/obj/item/reagent_containers/syringe/noreact + name = "cryo syringe" + desc = "An advanced syringe that stops reagents inside from reacting. It can hold up to 20 units." + volume = 20 + reagent_flags = TRANSPARENT | NO_REACT + +/obj/item/reagent_containers/syringe/piercing + name = "piercing syringe" + desc = "A diamond-tipped syringe that pierces armor when launched at high velocity. It can hold up to 10 units." + volume = 10 + proj_piercing = 1 + +/obj/item/reagent_containers/syringe/get_belt_overlay() + return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "pouch") + +/obj/item/reagent_containers/syringe/dart + name = "medicinal smartdart" + desc = "A non-harmful dart that can administer medication from a range. Once it hits a patient using it's smart nanofilter technology only medicines contained within the dart are administered to the patient. Additonally, due to capillary action, injection of chemicals past the overdose limit is prevented." + volume = 20 + amount_per_transfer_from_this = 20 + icon_state = "empty" + item_state = "syringe_empty" + var/emptrig = FALSE + +/obj/item/reagent_containers/syringe/dart/afterattack(atom/target, mob/user , proximity) + + if(busy) + return + if(!proximity) + return + if(!target.reagents) + return + + var/mob/living/L + if(isliving(target)) + L = target + if(!L.can_inject(user, 1)) + return + + switch(mode) + if(SYRINGE_DRAW) + + if(reagents.total_volume >= reagents.maximum_volume) + to_chat(user, "The dart is full!") + return + + if(L) //living mob + to_chat(user, "You can't draw blood using a dart!") + return + + else //if not mob + if(!target.reagents.total_volume) + to_chat(user, "[target] is empty!") + return + + if(!target.is_drawable()) + to_chat(user, "You cannot directly remove reagents from [target]!") + return + + var/trans = target.reagents.trans_to(src, amount_per_transfer_from_this) + + to_chat(user, "You soak the [src] with [trans] units of the solution. It now contains [reagents.total_volume] units.") + if (round(reagents.total_volume,1) >= reagents.maximum_volume) + mode=!mode + update_icon() + + if(SYRINGE_INJECT) + src.visible_message("The smartdart gives a frustrated boop! It's fully saturated; You need to shoot someone with it!") + +/obj/item/reagent_containers/syringe/dart/attack_self(mob/user) + return + +/obj/item/reagent_containers/syringe/dart/update_icon() + cut_overlays() + var/rounded_vol + + rounded_vol = "empty" + if(reagents && reagents.total_volume) + if(volume/round(reagents.total_volume, 1) == 1) + rounded_vol="full" + mode = SYRINGE_INJECT + + icon_state = "[rounded_vol]" + item_state = "syringe_[rounded_vol]" + if(ismob(loc)) + var/mob/M = loc + var/injoverlay + switch(mode) + if (SYRINGE_DRAW) + injoverlay = "draw" + if (SYRINGE_INJECT) + injoverlay = "ready" + add_overlay(injoverlay) + M.update_inv_hands() + +/obj/item/reagent_containers/syringe/dart/emp_act(severity) + emptrig = TRUE + ..() + +/obj/item/reagent_containers/syringe/dart/bluespace + name = "bluespace smartdart" + desc = "A non-harmful dart that can administer medication from a range. Once it hits a patient using it's smart nanofilter technology only medicines contained within the dart are administered to the patient. Additonally, due to capillary action, injection of chemicals past the overdose limit is prevented. Has an extended volume capacity thanks to bluespace foam." + amount_per_transfer_from_this = 50 + volume = 50 diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 1a8c960b2f..2e0c5b7619 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -1,253 +1,253 @@ -/obj/structure/reagent_dispensers - name = "Dispenser" - desc = "..." - icon = 'icons/obj/objects.dmi' - icon_state = "water" - density = TRUE - anchored = FALSE - pressure_resistance = 2*ONE_ATMOSPHERE - max_integrity = 300 - var/tank_volume = 1000 //In units, how much the dispenser can hold - var/reagent_id = /datum/reagent/water //The ID of the reagent that the dispenser uses - -/obj/structure/reagent_dispensers/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) - . = ..() - if(. && obj_integrity > 0) - if(tank_volume && (damage_flag == "bullet" || damage_flag == "laser")) - boom() - -/obj/structure/reagent_dispensers/attackby(obj/item/W, mob/user, params) - if(W.is_refillable()) - return 0 //so we can refill them via their afterattack. - else - return ..() - -/obj/structure/reagent_dispensers/Initialize() - create_reagents(tank_volume, DRAINABLE | AMOUNT_VISIBLE) - reagents.add_reagent(reagent_id, tank_volume) - . = ..() - -/obj/structure/reagent_dispensers/proc/boom() - visible_message("\The [src] ruptures!") - chem_splash(loc, 5, list(reagents)) - qdel(src) - -/obj/structure/reagent_dispensers/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(!disassembled) - boom() - else - qdel(src) - -/////////////// -//Water Tanks// -/////////////// - -/obj/structure/reagent_dispensers/watertank - name = "water tank" - desc = "A water tank." - icon_state = "water" - -/obj/structure/reagent_dispensers/watertank/high - name = "high-capacity water tank" - desc = "A highly pressurized water tank made to hold gargantuan amounts of water." - icon_state = "water_high" //I was gonna clean my room... - tank_volume = 100000 - -/obj/structure/reagent_dispensers/foamtank - name = "firefighting foam tank" - desc = "A tank full of firefighting foam." - icon_state = "foam" - reagent_id = /datum/reagent/firefighting_foam - tank_volume = 500 - -/obj/structure/reagent_dispensers/water_cooler - name = "liquid cooler" - desc = "A machine that dispenses liquid to drink." - icon = 'icons/obj/vending.dmi' - icon_state = "water_cooler" - anchored = TRUE - tank_volume = 500 - var/paper_cups = 25 //Paper cups left from the cooler - -/obj/structure/reagent_dispensers/water_cooler/examine(mob/user) - . = ..() - if (paper_cups > 1) - . += "There are [paper_cups] paper cups left." - else if (paper_cups == 1) - . += "There is one paper cup left." - else - . += "There are no paper cups left." - -/obj/structure/reagent_dispensers/water_cooler/attack_hand(mob/living/user) - . = ..() - if(.) - return - if(!paper_cups) - to_chat(user, "There aren't any cups left!") - return - user.visible_message("[user] takes a cup from [src].", "You take a paper cup from [src].") - var/obj/item/reagent_containers/food/drinks/sillycup/S = new(get_turf(src)) - user.put_in_hands(S) - paper_cups-- - -////////////// -//Fuel Tanks// -////////////// - -/obj/structure/reagent_dispensers/fueltank - name = "fuel tank" - desc = "A tank full of industrial welding fuel. Do not consume." - icon_state = "fuel" - reagent_id = /datum/reagent/fuel - -/obj/structure/reagent_dispensers/fueltank/high //Unused - Good for ghost roles - name = "high-capacity fuel tank" - desc = "A now illegal tank, full of highly pressurized industrial welding fuel. Do not consume or have a open flame close to this tank." - icon_state = "fuel_high" - tank_volume = 3000 - -/obj/structure/reagent_dispensers/fueltank/boom() - explosion(get_turf(src), 0, 1, 5, flame_range = 5) - qdel(src) - -/obj/structure/reagent_dispensers/fueltank/blob_act(obj/structure/blob/B) - boom() - -/obj/structure/reagent_dispensers/fueltank/ex_act() - boom() - -/obj/structure/reagent_dispensers/fueltank/fire_act(exposed_temperature, exposed_volume) - boom() - -/obj/structure/reagent_dispensers/fueltank/tesla_act() - ..() //extend the zap - boom() - -/obj/structure/reagent_dispensers/fueltank/bullet_act(obj/item/projectile/P) - ..() - if(!QDELETED(src)) //wasn't deleted by the projectile's effects. - if(!P.nodamage && ((P.damage_type == BURN) || (P.damage_type == BRUTE))) - var/boom_message = "[ADMIN_LOOKUPFLW(P.firer)] triggered a fueltank explosion via projectile." - GLOB.bombers += boom_message - message_admins(boom_message) - P.firer.log_message("triggered a fueltank explosion via projectile.", LOG_ATTACK) - boom() - -/obj/structure/reagent_dispensers/fueltank/attackby(obj/item/I, mob/living/user, params) - if(istype(I, /obj/item/weldingtool)) - if(!reagents.has_reagent(/datum/reagent/fuel)) - to_chat(user, "[src] is out of fuel!") - return - var/obj/item/weldingtool/W = I - if(!W.welding) - if(W.reagents.has_reagent(/datum/reagent/fuel, W.max_fuel)) - to_chat(user, "Your [W.name] is already full!") - return - reagents.trans_to(W, W.max_fuel) - user.visible_message("[user] refills [user.p_their()] [W.name].", "You refill [W].") - playsound(src, 'sound/effects/refill.ogg', 50, 1) - W.update_icon() - else - var/turf/T = get_turf(src) - user.visible_message("[user] catastrophically fails at refilling [user.p_their()] [W.name]!", "That was stupid of you.") - - var/message_admins = "[ADMIN_LOOKUPFLW(user)] triggered a fueltank explosion via welding tool at [ADMIN_VERBOSEJMP(T)]." - GLOB.bombers += message_admins - message_admins(message_admins) - - user.log_message("triggered a fueltank explosion via welding tool.", LOG_ATTACK) - boom() - return - return ..() - -/////////////////// -//Misc Dispenders// -/////////////////// - -/obj/structure/reagent_dispensers/peppertank - name = "pepper spray refiller" - desc = "Contains condensed capsaicin for use in law \"enforcement.\"" - icon_state = "pepper" - anchored = TRUE - density = FALSE - reagent_id = /datum/reagent/consumable/condensedcapsaicin - -/obj/structure/reagent_dispensers/peppertank/Initialize() - . = ..() - if(prob(1)) - desc = "IT'S PEPPER TIME, BITCH!" - -/obj/structure/reagent_dispensers/virusfood - name = "virus food dispenser" - desc = "A dispenser of low-potency virus mutagenic." - icon_state = "virus_food" - anchored = TRUE - density = FALSE - reagent_id = /datum/reagent/consumable/virus_food - -/obj/structure/reagent_dispensers/cooking_oil - name = "vat of cooking oil" - desc = "A huge metal vat with a tap on the front. Filled with cooking oil for use in frying food." - icon_state = "vat" - anchored = TRUE - reagent_id = /datum/reagent/consumable/cooking_oil - -//////// -//Kegs// -//////// - -/obj/structure/reagent_dispensers/beerkeg - name = "beer keg" - desc = "Beer is liquid bread, it's good for you..." - icon_state = "beer" - reagent_id = /datum/reagent/consumable/ethanol/beer - -/obj/structure/reagent_dispensers/beerkeg/blob_act(obj/structure/blob/B) - explosion(src.loc,0,3,5,7,10) - if(!QDELETED(src)) - qdel(src) - -/obj/structure/reagent_dispensers/keg - name = "keg" - desc = "A keg." - icon = 'modular_citadel/icons/obj/objects.dmi' - icon_state = "keg" - -/obj/structure/reagent_dispensers/keg/mead - name = "keg of mead" - desc = "A keg of mead." - icon_state = "orangekeg" - reagent_id = /datum/reagent/consumable/ethanol/mead - -/obj/structure/reagent_dispensers/keg/aphro - name = "keg of aphrodisiac" - desc = "A keg of aphrodisiac." - icon_state = "pinkkeg" - reagent_id = /datum/reagent/drug/aphrodisiac - tank_volume = 150 - -/obj/structure/reagent_dispensers/keg/aphro/strong - name = "keg of strong aphrodisiac" - desc = "A keg of strong and addictive aphrodisiac." - reagent_id = /datum/reagent/drug/aphrodisiacplus - tank_volume = 120 - -/obj/structure/reagent_dispensers/keg/milk - name = "keg of milk" - desc = "It's not quite what you were hoping for." - icon_state = "whitekeg" - reagent_id = /datum/reagent/consumable/milk - -/obj/structure/reagent_dispensers/keg/semen - name = "keg of semen" - desc = "Dear lord, where did this even come from?" - icon_state = "whitekeg" - reagent_id = /datum/reagent/consumable/semen - -/obj/structure/reagent_dispensers/keg/gargle - name = "keg of pan galactic gargleblaster" - desc = "A keg of... wow that's a long name." - icon_state = "bluekeg" - reagent_id = /datum/reagent/consumable/ethanol/gargle_blaster +/obj/structure/reagent_dispensers + name = "Dispenser" + desc = "..." + icon = 'icons/obj/objects.dmi' + icon_state = "water" + density = TRUE + anchored = FALSE + pressure_resistance = 2*ONE_ATMOSPHERE + max_integrity = 300 + var/tank_volume = 1000 //In units, how much the dispenser can hold + var/reagent_id = /datum/reagent/water //The ID of the reagent that the dispenser uses + +/obj/structure/reagent_dispensers/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) + . = ..() + if(. && obj_integrity > 0) + if(tank_volume && (damage_flag == "bullet" || damage_flag == "laser")) + boom() + +/obj/structure/reagent_dispensers/attackby(obj/item/W, mob/user, params) + if(W.is_refillable()) + return 0 //so we can refill them via their afterattack. + else + return ..() + +/obj/structure/reagent_dispensers/Initialize() + create_reagents(tank_volume, DRAINABLE | AMOUNT_VISIBLE) + reagents.add_reagent(reagent_id, tank_volume) + . = ..() + +/obj/structure/reagent_dispensers/proc/boom() + visible_message("\The [src] ruptures!") + chem_splash(loc, 5, list(reagents)) + qdel(src) + +/obj/structure/reagent_dispensers/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(!disassembled) + boom() + else + qdel(src) + +/////////////// +//Water Tanks// +/////////////// + +/obj/structure/reagent_dispensers/watertank + name = "water tank" + desc = "A water tank." + icon_state = "water" + +/obj/structure/reagent_dispensers/watertank/high + name = "high-capacity water tank" + desc = "A highly pressurized water tank made to hold gargantuan amounts of water." + icon_state = "water_high" //I was gonna clean my room... + tank_volume = 100000 + +/obj/structure/reagent_dispensers/foamtank + name = "firefighting foam tank" + desc = "A tank full of firefighting foam." + icon_state = "foam" + reagent_id = /datum/reagent/firefighting_foam + tank_volume = 500 + +/obj/structure/reagent_dispensers/water_cooler + name = "liquid cooler" + desc = "A machine that dispenses liquid to drink." + icon = 'icons/obj/vending.dmi' + icon_state = "water_cooler" + anchored = TRUE + tank_volume = 500 + var/paper_cups = 25 //Paper cups left from the cooler + +/obj/structure/reagent_dispensers/water_cooler/examine(mob/user) + . = ..() + if (paper_cups > 1) + . += "There are [paper_cups] paper cups left." + else if (paper_cups == 1) + . += "There is one paper cup left." + else + . += "There are no paper cups left." + +/obj/structure/reagent_dispensers/water_cooler/attack_hand(mob/living/user) + . = ..() + if(.) + return + if(!paper_cups) + to_chat(user, "There aren't any cups left!") + return + user.visible_message("[user] takes a cup from [src].", "You take a paper cup from [src].") + var/obj/item/reagent_containers/food/drinks/sillycup/S = new(get_turf(src)) + user.put_in_hands(S) + paper_cups-- + +////////////// +//Fuel Tanks// +////////////// + +/obj/structure/reagent_dispensers/fueltank + name = "fuel tank" + desc = "A tank full of industrial welding fuel. Do not consume." + icon_state = "fuel" + reagent_id = /datum/reagent/fuel + +/obj/structure/reagent_dispensers/fueltank/high //Unused - Good for ghost roles + name = "high-capacity fuel tank" + desc = "A now illegal tank, full of highly pressurized industrial welding fuel. Do not consume or have a open flame close to this tank." + icon_state = "fuel_high" + tank_volume = 3000 + +/obj/structure/reagent_dispensers/fueltank/boom() + explosion(get_turf(src), 0, 1, 5, flame_range = 5) + qdel(src) + +/obj/structure/reagent_dispensers/fueltank/blob_act(obj/structure/blob/B) + boom() + +/obj/structure/reagent_dispensers/fueltank/ex_act() + boom() + +/obj/structure/reagent_dispensers/fueltank/fire_act(exposed_temperature, exposed_volume) + boom() + +/obj/structure/reagent_dispensers/fueltank/tesla_act() + ..() //extend the zap + boom() + +/obj/structure/reagent_dispensers/fueltank/bullet_act(obj/item/projectile/P) + ..() + if(!QDELETED(src)) //wasn't deleted by the projectile's effects. + if(!P.nodamage && ((P.damage_type == BURN) || (P.damage_type == BRUTE))) + var/boom_message = "[ADMIN_LOOKUPFLW(P.firer)] triggered a fueltank explosion via projectile." + GLOB.bombers += boom_message + message_admins(boom_message) + P.firer.log_message("triggered a fueltank explosion via projectile.", LOG_ATTACK) + boom() + +/obj/structure/reagent_dispensers/fueltank/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/weldingtool)) + if(!reagents.has_reagent(/datum/reagent/fuel)) + to_chat(user, "[src] is out of fuel!") + return + var/obj/item/weldingtool/W = I + if(!W.welding) + if(W.reagents.has_reagent(/datum/reagent/fuel, W.max_fuel)) + to_chat(user, "Your [W.name] is already full!") + return + reagents.trans_to(W, W.max_fuel) + user.visible_message("[user] refills [user.p_their()] [W.name].", "You refill [W].") + playsound(src, 'sound/effects/refill.ogg', 50, 1) + W.update_icon() + else + var/turf/T = get_turf(src) + user.visible_message("[user] catastrophically fails at refilling [user.p_their()] [W.name]!", "That was stupid of you.") + + var/message_admins = "[ADMIN_LOOKUPFLW(user)] triggered a fueltank explosion via welding tool at [ADMIN_VERBOSEJMP(T)]." + GLOB.bombers += message_admins + message_admins(message_admins) + + user.log_message("triggered a fueltank explosion via welding tool.", LOG_ATTACK) + boom() + return + return ..() + +/////////////////// +//Misc Dispenders// +/////////////////// + +/obj/structure/reagent_dispensers/peppertank + name = "pepper spray refiller" + desc = "Contains condensed capsaicin for use in law \"enforcement.\"" + icon_state = "pepper" + anchored = TRUE + density = FALSE + reagent_id = /datum/reagent/consumable/condensedcapsaicin + +/obj/structure/reagent_dispensers/peppertank/Initialize() + . = ..() + if(prob(1)) + desc = "IT'S PEPPER TIME, BITCH!" + +/obj/structure/reagent_dispensers/virusfood + name = "virus food dispenser" + desc = "A dispenser of low-potency virus mutagenic." + icon_state = "virus_food" + anchored = TRUE + density = FALSE + reagent_id = /datum/reagent/consumable/virus_food + +/obj/structure/reagent_dispensers/cooking_oil + name = "vat of cooking oil" + desc = "A huge metal vat with a tap on the front. Filled with cooking oil for use in frying food." + icon_state = "vat" + anchored = TRUE + reagent_id = /datum/reagent/consumable/cooking_oil + +//////// +//Kegs// +//////// + +/obj/structure/reagent_dispensers/beerkeg + name = "beer keg" + desc = "Beer is liquid bread, it's good for you..." + icon_state = "beer" + reagent_id = /datum/reagent/consumable/ethanol/beer + +/obj/structure/reagent_dispensers/beerkeg/blob_act(obj/structure/blob/B) + explosion(src.loc,0,3,5,7,10) + if(!QDELETED(src)) + qdel(src) + +/obj/structure/reagent_dispensers/keg + name = "keg" + desc = "A keg." + icon = 'modular_citadel/icons/obj/objects.dmi' + icon_state = "keg" + +/obj/structure/reagent_dispensers/keg/mead + name = "keg of mead" + desc = "A keg of mead." + icon_state = "orangekeg" + reagent_id = /datum/reagent/consumable/ethanol/mead + +/obj/structure/reagent_dispensers/keg/aphro + name = "keg of aphrodisiac" + desc = "A keg of aphrodisiac." + icon_state = "pinkkeg" + reagent_id = /datum/reagent/drug/aphrodisiac + tank_volume = 150 + +/obj/structure/reagent_dispensers/keg/aphro/strong + name = "keg of strong aphrodisiac" + desc = "A keg of strong and addictive aphrodisiac." + reagent_id = /datum/reagent/drug/aphrodisiacplus + tank_volume = 120 + +/obj/structure/reagent_dispensers/keg/milk + name = "keg of milk" + desc = "It's not quite what you were hoping for." + icon_state = "whitekeg" + reagent_id = /datum/reagent/consumable/milk + +/obj/structure/reagent_dispensers/keg/semen + name = "keg of semen" + desc = "Dear lord, where did this even come from?" + icon_state = "whitekeg" + reagent_id = /datum/reagent/consumable/semen + +/obj/structure/reagent_dispensers/keg/gargle + name = "keg of pan galactic gargleblaster" + desc = "A keg of... wow that's a long name." + icon_state = "bluekeg" + reagent_id = /datum/reagent/consumable/ethanol/gargle_blaster tank_volume = 100 \ No newline at end of file diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm index f9eb1b85e5..8fb05337f2 100644 --- a/code/modules/recycling/sortingmachinery.dm +++ b/code/modules/recycling/sortingmachinery.dm @@ -1,196 +1,196 @@ -/obj/structure/bigDelivery - name = "large parcel" - desc = "A large delivery parcel." - icon = 'icons/obj/storage.dmi' - icon_state = "deliverycloset" - density = TRUE - mouse_drag_pointer = MOUSE_ACTIVE_POINTER - var/giftwrapped = FALSE - var/sortTag = 0 - -/obj/structure/bigDelivery/interact(mob/user) - playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) - qdel(src) - -/obj/structure/bigDelivery/Destroy() - var/turf/T = get_turf(src) - for(var/atom/movable/AM in contents) - AM.forceMove(T) - return ..() - -/obj/structure/bigDelivery/contents_explosion(severity, target) - for(var/atom/movable/AM in contents) - AM.ex_act() - -/obj/structure/bigDelivery/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/destTagger)) - var/obj/item/destTagger/O = W - - if(sortTag != O.currTag) - var/tag = uppertext(GLOB.TAGGERLOCATIONS[O.currTag]) - to_chat(user, "*[tag]*") - sortTag = O.currTag - playsound(loc, 'sound/machines/twobeep.ogg', 100, 1) - - else if(istype(W, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the side of [src]!") - return - var/str = copytext(sanitize(input(user,"Label text?","Set label","")),1,MAX_NAME_LEN) - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(!str || !length(str)) - to_chat(user, "Invalid text!") - return - user.visible_message("[user] labels [src] as [str].") - name = "[name] ([str])" - - else if(istype(W, /obj/item/stack/wrapping_paper) && !giftwrapped) - var/obj/item/stack/wrapping_paper/WP = W - if(WP.use(3)) - user.visible_message("[user] wraps the package in festive paper!") - giftwrapped = TRUE - icon_state = "gift[icon_state]" - else - to_chat(user, "You need more paper!") - else - return ..() - -/obj/structure/bigDelivery/relay_container_resist(mob/living/user, obj/O) - if(ismovableatom(loc)) - var/atom/movable/AM = loc //can't unwrap the wrapped container if it's inside something. - AM.relay_container_resist(user, O) - return - to_chat(user, "You lean on the back of [O] and start pushing to rip the wrapping around it.") - if(do_after(user, 50, target = O)) - if(!user || user.stat != CONSCIOUS || user.loc != O || O.loc != src ) - return - to_chat(user, "You successfully removed [O]'s wrapping !") - O.forceMove(loc) - playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) - qdel(src) - 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 remove [O]'s wrapping!") - - -/obj/item/smallDelivery - name = "parcel" - desc = "A brown paper delivery parcel." - icon = 'icons/obj/storage.dmi' - icon_state = "deliverypackage3" - var/giftwrapped = 0 - var/sortTag = 0 - -/obj/item/smallDelivery/contents_explosion(severity, target) - for(var/atom/movable/AM in contents) - AM.ex_act() - -/obj/item/smallDelivery/attack_self(mob/user) - user.temporarilyRemoveItemFromInventory(src, TRUE) - for(var/X in contents) - var/atom/movable/AM = X - user.put_in_hands(AM) - playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) - qdel(src) - -/obj/item/smallDelivery/attack_self_tk(mob/user) - if(ismob(loc)) - var/mob/M = loc - M.temporarilyRemoveItemFromInventory(src, TRUE) - for(var/X in contents) - var/atom/movable/AM = X - M.put_in_hands(AM) - else - for(var/X in contents) - var/atom/movable/AM = X - AM.forceMove(src.loc) - playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) - qdel(src) - -/obj/item/smallDelivery/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/destTagger)) - var/obj/item/destTagger/O = W - - if(sortTag != O.currTag) - var/tag = uppertext(GLOB.TAGGERLOCATIONS[O.currTag]) - to_chat(user, "*[tag]*") - sortTag = O.currTag - playsound(loc, 'sound/machines/twobeep.ogg', 100, 1) - - else if(istype(W, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the side of [src]!") - return - var/str = copytext(sanitize(input(user,"Label text?","Set label","")),1,MAX_NAME_LEN) - if(!user.canUseTopic(src, BE_CLOSE)) - return - if(!str || !length(str)) - to_chat(user, "Invalid text!") - return - user.visible_message("[user] labels [src] as [str].") - name = "[name] ([str])" - - else if(istype(W, /obj/item/stack/wrapping_paper) && !giftwrapped) - var/obj/item/stack/wrapping_paper/WP = W - if(WP.use(1)) - icon_state = "gift[icon_state]" - giftwrapped = 1 - user.visible_message("[user] wraps the package in festive paper!") - else - to_chat(user, "You need more paper!") - - -/obj/item/destTagger - name = "destination tagger" - desc = "Used to set the destination of properly wrapped packages." - icon = 'icons/obj/device.dmi' - icon_state = "cargotagger" - var/currTag = 0 //Destinations are stored in code\globalvars\lists\flavor_misc.dm - var/locked_destination = FALSE //if true, users can't open the destination tag window to prevent changing the tagger's current destination - w_class = WEIGHT_CLASS_TINY - item_state = "electronic" - 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_BELT - -/obj/item/destTagger/borg - name = "cyborg destination tagger" - desc = "Used to fool the disposal mail network into thinking that you're a harmless parcel. Does actually work as a regular destination tagger as well." - -/obj/item/destTagger/suicide_act(mob/living/user) - user.visible_message("[user] begins tagging [user.p_their()] final destination! It looks like [user.p_theyre()] trying to commit suicide!") - if (islizard(user)) - to_chat(user, "*HELL*")//lizard nerf - else - to_chat(user, "*HEAVEN*") - playsound(src, 'sound/machines/twobeep.ogg', 100, 1) - return BRUTELOSS - -/obj/item/destTagger/proc/openwindow(mob/user) - var/dat = "

                TagMaster 2.2

                " - - dat += "" - for (var/i = 1, i <= GLOB.TAGGERLOCATIONS.len, i++) - dat += "" - - if(i%4==0) - dat += "" - - dat += "
                [GLOB.TAGGERLOCATIONS[i]]

                Current Selection: [currTag ? GLOB.TAGGERLOCATIONS[currTag] : "None"]
                " - - user << browse(dat, "window=destTagScreen;size=450x350") - onclose(user, "destTagScreen") - -/obj/item/destTagger/attack_self(mob/user) - if(!locked_destination) - openwindow(user) - return - -/obj/item/destTagger/Topic(href, href_list) - add_fingerprint(usr) - if(href_list["nextTag"]) - var/n = text2num(href_list["nextTag"]) - currTag = n - openwindow(usr) +/obj/structure/bigDelivery + name = "large parcel" + desc = "A large delivery parcel." + icon = 'icons/obj/storage.dmi' + icon_state = "deliverycloset" + density = TRUE + mouse_drag_pointer = MOUSE_ACTIVE_POINTER + var/giftwrapped = FALSE + var/sortTag = 0 + +/obj/structure/bigDelivery/interact(mob/user) + playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) + qdel(src) + +/obj/structure/bigDelivery/Destroy() + var/turf/T = get_turf(src) + for(var/atom/movable/AM in contents) + AM.forceMove(T) + return ..() + +/obj/structure/bigDelivery/contents_explosion(severity, target) + for(var/atom/movable/AM in contents) + AM.ex_act() + +/obj/structure/bigDelivery/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/destTagger)) + var/obj/item/destTagger/O = W + + if(sortTag != O.currTag) + var/tag = uppertext(GLOB.TAGGERLOCATIONS[O.currTag]) + to_chat(user, "*[tag]*") + sortTag = O.currTag + playsound(loc, 'sound/machines/twobeep.ogg', 100, 1) + + else if(istype(W, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the side of [src]!") + return + var/str = copytext(sanitize(input(user,"Label text?","Set label","")),1,MAX_NAME_LEN) + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(!str || !length(str)) + to_chat(user, "Invalid text!") + return + user.visible_message("[user] labels [src] as [str].") + name = "[name] ([str])" + + else if(istype(W, /obj/item/stack/wrapping_paper) && !giftwrapped) + var/obj/item/stack/wrapping_paper/WP = W + if(WP.use(3)) + user.visible_message("[user] wraps the package in festive paper!") + giftwrapped = TRUE + icon_state = "gift[icon_state]" + else + to_chat(user, "You need more paper!") + else + return ..() + +/obj/structure/bigDelivery/relay_container_resist(mob/living/user, obj/O) + if(ismovableatom(loc)) + var/atom/movable/AM = loc //can't unwrap the wrapped container if it's inside something. + AM.relay_container_resist(user, O) + return + to_chat(user, "You lean on the back of [O] and start pushing to rip the wrapping around it.") + if(do_after(user, 50, target = O)) + if(!user || user.stat != CONSCIOUS || user.loc != O || O.loc != src ) + return + to_chat(user, "You successfully removed [O]'s wrapping !") + O.forceMove(loc) + playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) + qdel(src) + 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 remove [O]'s wrapping!") + + +/obj/item/smallDelivery + name = "parcel" + desc = "A brown paper delivery parcel." + icon = 'icons/obj/storage.dmi' + icon_state = "deliverypackage3" + var/giftwrapped = 0 + var/sortTag = 0 + +/obj/item/smallDelivery/contents_explosion(severity, target) + for(var/atom/movable/AM in contents) + AM.ex_act() + +/obj/item/smallDelivery/attack_self(mob/user) + user.temporarilyRemoveItemFromInventory(src, TRUE) + for(var/X in contents) + var/atom/movable/AM = X + user.put_in_hands(AM) + playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) + qdel(src) + +/obj/item/smallDelivery/attack_self_tk(mob/user) + if(ismob(loc)) + var/mob/M = loc + M.temporarilyRemoveItemFromInventory(src, TRUE) + for(var/X in contents) + var/atom/movable/AM = X + M.put_in_hands(AM) + else + for(var/X in contents) + var/atom/movable/AM = X + AM.forceMove(src.loc) + playsound(src.loc, 'sound/items/poster_ripped.ogg', 50, 1) + qdel(src) + +/obj/item/smallDelivery/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/destTagger)) + var/obj/item/destTagger/O = W + + if(sortTag != O.currTag) + var/tag = uppertext(GLOB.TAGGERLOCATIONS[O.currTag]) + to_chat(user, "*[tag]*") + sortTag = O.currTag + playsound(loc, 'sound/machines/twobeep.ogg', 100, 1) + + else if(istype(W, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the side of [src]!") + return + var/str = copytext(sanitize(input(user,"Label text?","Set label","")),1,MAX_NAME_LEN) + if(!user.canUseTopic(src, BE_CLOSE)) + return + if(!str || !length(str)) + to_chat(user, "Invalid text!") + return + user.visible_message("[user] labels [src] as [str].") + name = "[name] ([str])" + + else if(istype(W, /obj/item/stack/wrapping_paper) && !giftwrapped) + var/obj/item/stack/wrapping_paper/WP = W + if(WP.use(1)) + icon_state = "gift[icon_state]" + giftwrapped = 1 + user.visible_message("[user] wraps the package in festive paper!") + else + to_chat(user, "You need more paper!") + + +/obj/item/destTagger + name = "destination tagger" + desc = "Used to set the destination of properly wrapped packages." + icon = 'icons/obj/device.dmi' + icon_state = "cargotagger" + var/currTag = 0 //Destinations are stored in code\globalvars\lists\flavor_misc.dm + var/locked_destination = FALSE //if true, users can't open the destination tag window to prevent changing the tagger's current destination + w_class = WEIGHT_CLASS_TINY + item_state = "electronic" + 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_BELT + +/obj/item/destTagger/borg + name = "cyborg destination tagger" + desc = "Used to fool the disposal mail network into thinking that you're a harmless parcel. Does actually work as a regular destination tagger as well." + +/obj/item/destTagger/suicide_act(mob/living/user) + user.visible_message("[user] begins tagging [user.p_their()] final destination! It looks like [user.p_theyre()] trying to commit suicide!") + if (islizard(user)) + to_chat(user, "*HELL*")//lizard nerf + else + to_chat(user, "*HEAVEN*") + playsound(src, 'sound/machines/twobeep.ogg', 100, 1) + return BRUTELOSS + +/obj/item/destTagger/proc/openwindow(mob/user) + var/dat = "

                TagMaster 2.2

                " + + dat += "" + for (var/i = 1, i <= GLOB.TAGGERLOCATIONS.len, i++) + dat += "" + + if(i%4==0) + dat += "" + + dat += "
                [GLOB.TAGGERLOCATIONS[i]]

                Current Selection: [currTag ? GLOB.TAGGERLOCATIONS[currTag] : "None"]
                " + + user << browse(dat, "window=destTagScreen;size=450x350") + onclose(user, "destTagScreen") + +/obj/item/destTagger/attack_self(mob/user) + if(!locked_destination) + openwindow(user) + return + +/obj/item/destTagger/Topic(href, href_list) + add_fingerprint(usr) + if(href_list["nextTag"]) + var/n = text2num(href_list["nextTag"]) + currTag = n + openwindow(usr) diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm index 516ae5bddd..812637efed 100644 --- a/code/modules/research/designs.dm +++ b/code/modules/research/designs.dm @@ -1,88 +1,88 @@ -/*************************************************************** -** Design Datums ** -** All the data for building stuff. ** -***************************************************************/ -/* -For the materials datum, it assumes you need reagents unless specified otherwise. To designate a material that isn't a reagent, -you use one of the material IDs below. These are NOT ids in the usual sense (they aren't defined in the object or part of a datum), -they are simply references used as part of a "has materials?" type proc. They all start with a $ to denote that they aren't reagents. -The currently supporting non-reagent materials. All material amounts are set as the define MINERAL_MATERIAL_AMOUNT, which defaults to 2000 -- MAT_METAL (/obj/item/stack/metal). -- MAT_GLASS (/obj/item/stack/glass). -- MAT_PLASMA (/obj/item/stack/plasma). -- MAT_SILVER (/obj/item/stack/silver). -- MAT_GOLD (/obj/item/stack/gold). -- MAT_URANIUM (/obj/item/stack/uranium). -- MAT_DIAMOND (/obj/item/stack/diamond). -- MAT_BANANIUM (/obj/item/stack/bananium). -(Insert new ones here) - -Don't add new keyword/IDs if they are made from an existing one (such as rods which are made from metal). Only add raw materials. - -Design Guidelines -- When adding new designs, check rdreadme.dm to see what kind of things have already been made and where new stuff is needed. -- A single sheet of anything is 2000 units of material. Materials besides metal/glass require help from other jobs (mining for -other types of metals and chemistry for reagents). -- Add the AUTOLATHE tag to -*/ - -//DESIGNS ARE GLOBAL. DO NOT CREATE OR DESTROY THEM AT RUNTIME OUTSIDE OF INIT, JUST REFERENCE THEM TO WHATEVER YOU'RE DOING! //why are you yelling? -//DO NOT REFERENCE OUTSIDE OF SSRESEARCH. USE THE PROCS IN SSRESEARCH TO OBTAIN A REFERENCE. - -/datum/design //Datum for object designs, used in construction - var/name = "Name" //Name of the created object. - var/desc = "Desc" //Description of the created object. - var/id = DESIGN_ID_IGNORE //ID of the created object for easy refernece. Alphanumeric, lower-case, no symbols - var/build_type = null //Flag as to what kind machine the design is built in. See defines. - var/list/materials = list() //List of materials. Format: "id" = amount. - var/construction_time //Amount of time required for building the object - var/build_path = null //The file path of the object that gets created - var/list/make_reagents = list() //Reagents produced. Format: type = amount. Currently only supported by the biogenerator. - var/list/category = null //Primarily used for Mech Fabricators, but can be used for anything - var/list/reagents_list = list() //List of reagents. Format: type = amount. - var/maxstack = 1 - var/lathe_time_factor = 1 //How many times faster than normal is this to build on the protolathe - var/dangerous_construction = FALSE //notify and log for admin investigations if this is printed. - var/departmental_flags = ALL //bitflags for deplathes. - var/list/datum/techweb_node/unlocked_by = list() - var/research_icon //Replaces the item icon in the research console - var/research_icon_state - var/icon_cache - -/datum/design/error_design - name = "ERROR" - desc = "This usually means something in the database has corrupted. If this doesn't go away automatically, inform Central Command so their techs can fix this ASAP(tm)" - -/datum/design/Destroy() - SSresearch.techweb_designs -= id - return ..() - -/datum/design/proc/icon_html(client/user) - var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/research_designs) - sheet.send(user) - return sheet.icon_tag(id) - -//////////////////////////////////////// -//Disks for transporting design datums// -//////////////////////////////////////// - -/obj/item/disk/design_disk - name = "Component Design Disk" - desc = "A disk for storing device design data for construction in lathes." - icon_state = "datadisk1" - materials = list(MAT_METAL=300, MAT_GLASS=100) - var/list/blueprints = list() - var/max_blueprints = 1 - -/obj/item/disk/design_disk/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - for(var/i in 1 to max_blueprints) - blueprints += null - -/obj/item/disk/design_disk/adv - name = "Advanced Component Design Disk" - desc = "A disk for storing device design data for construction in lathes. This one has extra storage space." - materials = list(MAT_METAL=300, MAT_GLASS=100, MAT_SILVER = 50) - max_blueprints = 5 +/*************************************************************** +** Design Datums ** +** All the data for building stuff. ** +***************************************************************/ +/* +For the materials datum, it assumes you need reagents unless specified otherwise. To designate a material that isn't a reagent, +you use one of the material IDs below. These are NOT ids in the usual sense (they aren't defined in the object or part of a datum), +they are simply references used as part of a "has materials?" type proc. They all start with a $ to denote that they aren't reagents. +The currently supporting non-reagent materials. All material amounts are set as the define MINERAL_MATERIAL_AMOUNT, which defaults to 2000 +- MAT_METAL (/obj/item/stack/metal). +- MAT_GLASS (/obj/item/stack/glass). +- MAT_PLASMA (/obj/item/stack/plasma). +- MAT_SILVER (/obj/item/stack/silver). +- MAT_GOLD (/obj/item/stack/gold). +- MAT_URANIUM (/obj/item/stack/uranium). +- MAT_DIAMOND (/obj/item/stack/diamond). +- MAT_BANANIUM (/obj/item/stack/bananium). +(Insert new ones here) + +Don't add new keyword/IDs if they are made from an existing one (such as rods which are made from metal). Only add raw materials. + +Design Guidelines +- When adding new designs, check rdreadme.dm to see what kind of things have already been made and where new stuff is needed. +- A single sheet of anything is 2000 units of material. Materials besides metal/glass require help from other jobs (mining for +other types of metals and chemistry for reagents). +- Add the AUTOLATHE tag to +*/ + +//DESIGNS ARE GLOBAL. DO NOT CREATE OR DESTROY THEM AT RUNTIME OUTSIDE OF INIT, JUST REFERENCE THEM TO WHATEVER YOU'RE DOING! //why are you yelling? +//DO NOT REFERENCE OUTSIDE OF SSRESEARCH. USE THE PROCS IN SSRESEARCH TO OBTAIN A REFERENCE. + +/datum/design //Datum for object designs, used in construction + var/name = "Name" //Name of the created object. + var/desc = "Desc" //Description of the created object. + var/id = DESIGN_ID_IGNORE //ID of the created object for easy refernece. Alphanumeric, lower-case, no symbols + var/build_type = null //Flag as to what kind machine the design is built in. See defines. + var/list/materials = list() //List of materials. Format: "id" = amount. + var/construction_time //Amount of time required for building the object + var/build_path = null //The file path of the object that gets created + var/list/make_reagents = list() //Reagents produced. Format: type = amount. Currently only supported by the biogenerator. + var/list/category = null //Primarily used for Mech Fabricators, but can be used for anything + var/list/reagents_list = list() //List of reagents. Format: type = amount. + var/maxstack = 1 + var/lathe_time_factor = 1 //How many times faster than normal is this to build on the protolathe + var/dangerous_construction = FALSE //notify and log for admin investigations if this is printed. + var/departmental_flags = ALL //bitflags for deplathes. + var/list/datum/techweb_node/unlocked_by = list() + var/research_icon //Replaces the item icon in the research console + var/research_icon_state + var/icon_cache + +/datum/design/error_design + name = "ERROR" + desc = "This usually means something in the database has corrupted. If this doesn't go away automatically, inform Central Command so their techs can fix this ASAP(tm)" + +/datum/design/Destroy() + SSresearch.techweb_designs -= id + return ..() + +/datum/design/proc/icon_html(client/user) + var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/research_designs) + sheet.send(user) + return sheet.icon_tag(id) + +//////////////////////////////////////// +//Disks for transporting design datums// +//////////////////////////////////////// + +/obj/item/disk/design_disk + name = "Component Design Disk" + desc = "A disk for storing device design data for construction in lathes." + icon_state = "datadisk1" + materials = list(MAT_METAL=300, MAT_GLASS=100) + var/list/blueprints = list() + var/max_blueprints = 1 + +/obj/item/disk/design_disk/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + for(var/i in 1 to max_blueprints) + blueprints += null + +/obj/item/disk/design_disk/adv + name = "Advanced Component Design Disk" + desc = "A disk for storing device design data for construction in lathes. This one has extra storage space." + materials = list(MAT_METAL=300, MAT_GLASS=100, MAT_SILVER = 50) + max_blueprints = 5 diff --git a/code/modules/research/designs/bluespace_designs.dm b/code/modules/research/designs/bluespace_designs.dm index ec4309b69e..064e672d37 100644 --- a/code/modules/research/designs/bluespace_designs.dm +++ b/code/modules/research/designs/bluespace_designs.dm @@ -1,86 +1,86 @@ - -///////////////////////////////////////// -//////////////Blue Space///////////////// -///////////////////////////////////////// - -/datum/design/beacon - name = "Tracking Beacon" - desc = "A blue space tracking beacon." - id = "beacon" - build_type = PROTOLATHE - materials = list(MAT_METAL = 150, MAT_GLASS = 100) - build_path = /obj/item/beacon - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_SECURITY - -/datum/design/bag_holding - name = "Bag of Holding" - desc = "A backpack that opens into a localized pocket of bluespace." - id = "bag_holding" - build_type = PROTOLATHE - materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000) - build_path = /obj/item/storage/backpack/holding - category = list("Bluespace Designs") - dangerous_construction = TRUE - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/satchel_holding - name = "Satchel of Holding" - desc = "A satchel that opens into a localized pocket of bluespace." - id = "satchel_holding" - build_type = PROTOLATHE - materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000) - build_path = /obj/item/storage/backpack/holding/satchel - category = list("Bluespace Designs") - dangerous_construction = TRUE - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/biobag_holding - name = "Bio Bag of Holding" - desc = "A chemical holding thingy. Mostly used for xenobiology." - id = "biobag_holding" - build_type = PROTOLATHE - materials = list(MAT_GOLD = 1500, MAT_DIAMOND = 750, MAT_URANIUM = 250, MAT_BLUESPACE = 1000) - build_path = /obj/item/storage/bag/bio/holding - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/bluespace_crystal - name = "Artificial Bluespace Crystal" - desc = "A small blue crystal with mystical properties." - id = "bluespace_crystal" - build_type = PROTOLATHE - materials = list(MAT_DIAMOND = 1500, MAT_PLASMA = 1500) - build_path = /obj/item/stack/ore/bluespace_crystal/artificial - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/telesci_gps - name = "GPS Device" - desc = "Little thingie that can track its position at all times." - id = "telesci_gps" - build_type = PROTOLATHE - materials = list(MAT_METAL = 500, MAT_GLASS = 1000) - build_path = /obj/item/gps - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO - -/datum/design/desynchronizer - name = "Desynchronizer" - desc = "A device that can desynchronize the user from spacetime." - id = "desynchronizer" - build_type = PROTOLATHE - materials = list(MAT_METAL = 1000, MAT_GLASS = 500, MAT_SILVER = 1500, MAT_BLUESPACE = 1000) - build_path = /obj/item/desynchronizer - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/miningsatchel_holding - name = "Mining Satchel of Holding" - desc = "A mining satchel that can hold an infinite amount of ores." - id = "minerbag_holding" - build_type = PROTOLATHE - materials = list(MAT_GOLD = 250, MAT_URANIUM = 500) //quite cheap, for more convenience - build_path = /obj/item/storage/bag/ore/holding - category = list("Bluespace Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO + +///////////////////////////////////////// +//////////////Blue Space///////////////// +///////////////////////////////////////// + +/datum/design/beacon + name = "Tracking Beacon" + desc = "A blue space tracking beacon." + id = "beacon" + build_type = PROTOLATHE + materials = list(MAT_METAL = 150, MAT_GLASS = 100) + build_path = /obj/item/beacon + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_SECURITY + +/datum/design/bag_holding + name = "Bag of Holding" + desc = "A backpack that opens into a localized pocket of bluespace." + id = "bag_holding" + build_type = PROTOLATHE + materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000) + build_path = /obj/item/storage/backpack/holding + category = list("Bluespace Designs") + dangerous_construction = TRUE + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/satchel_holding + name = "Satchel of Holding" + desc = "A satchel that opens into a localized pocket of bluespace." + id = "satchel_holding" + build_type = PROTOLATHE + materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000) + build_path = /obj/item/storage/backpack/holding/satchel + category = list("Bluespace Designs") + dangerous_construction = TRUE + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/biobag_holding + name = "Bio Bag of Holding" + desc = "A chemical holding thingy. Mostly used for xenobiology." + id = "biobag_holding" + build_type = PROTOLATHE + materials = list(MAT_GOLD = 1500, MAT_DIAMOND = 750, MAT_URANIUM = 250, MAT_BLUESPACE = 1000) + build_path = /obj/item/storage/bag/bio/holding + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/bluespace_crystal + name = "Artificial Bluespace Crystal" + desc = "A small blue crystal with mystical properties." + id = "bluespace_crystal" + build_type = PROTOLATHE + materials = list(MAT_DIAMOND = 1500, MAT_PLASMA = 1500) + build_path = /obj/item/stack/ore/bluespace_crystal/artificial + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/telesci_gps + name = "GPS Device" + desc = "Little thingie that can track its position at all times." + id = "telesci_gps" + build_type = PROTOLATHE + materials = list(MAT_METAL = 500, MAT_GLASS = 1000) + build_path = /obj/item/gps + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO + +/datum/design/desynchronizer + name = "Desynchronizer" + desc = "A device that can desynchronize the user from spacetime." + id = "desynchronizer" + build_type = PROTOLATHE + materials = list(MAT_METAL = 1000, MAT_GLASS = 500, MAT_SILVER = 1500, MAT_BLUESPACE = 1000) + build_path = /obj/item/desynchronizer + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/miningsatchel_holding + name = "Mining Satchel of Holding" + desc = "A mining satchel that can hold an infinite amount of ores." + id = "minerbag_holding" + build_type = PROTOLATHE + materials = list(MAT_GOLD = 250, MAT_URANIUM = 500) //quite cheap, for more convenience + build_path = /obj/item/storage/bag/ore/holding + category = list("Bluespace Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO diff --git a/code/modules/research/designs/comp_board_designs/comp_board_designs_cargo .dm b/code/modules/research/designs/comp_board_designs/comp_board_designs_cargo.dm similarity index 97% rename from code/modules/research/designs/comp_board_designs/comp_board_designs_cargo .dm rename to code/modules/research/designs/comp_board_designs/comp_board_designs_cargo.dm index b11a5ee258..e5f7fdc004 100644 --- a/code/modules/research/designs/comp_board_designs/comp_board_designs_cargo .dm +++ b/code/modules/research/designs/comp_board_designs/comp_board_designs_cargo.dm @@ -40,4 +40,4 @@ id = "miningshuttle" build_path = /obj/item/circuitboard/computer/mining_shuttle category = list("Computer Boards") - departmental_flags = DEPARTMENTAL_FLAG_CARGO \ No newline at end of file + departmental_flags = DEPARTMENTAL_FLAG_CARGO diff --git a/code/modules/research/designs/electronics_designs.dm b/code/modules/research/designs/electronics_designs.dm index 818e80beea..f9af852eba 100644 --- a/code/modules/research/designs/electronics_designs.dm +++ b/code/modules/research/designs/electronics_designs.dm @@ -1,160 +1,160 @@ - -/////////////////////////////////// -/////Non-Board Computer Stuff////// -/////////////////////////////////// - -/datum/design/intellicard - name = "Intellicard AI Transportation System" - desc = "Allows for the construction of an intellicard." - id = "intellicard" - build_type = PROTOLATHE - materials = list(MAT_GLASS = 1000, MAT_GOLD = 200) - build_path = /obj/item/aicard - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/paicard - name = "Personal Artificial Intelligence Card" - desc = "Allows for the construction of a pAI Card." - id = "paicard" - build_type = PROTOLATHE - materials = list(MAT_GLASS = 500, MAT_METAL = 500) - build_path = /obj/item/paicard - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_ALL - -/////////////////////////////////// -//////////Nanite Devices/////////// -/////////////////////////////////// -/datum/design/nanite_remote - name = "Nanite Remote" - desc = "Allows for the construction of a nanite remote." - id = "nanite_remote" - build_type = PROTOLATHE - materials = list(MAT_GLASS = 500, MAT_METAL = 500) - build_path = /obj/item/nanite_remote - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/nanite_scanner - name = "Nanite Scanner" - desc = "Allows for the construction of a nanite scanner." - id = "nanite_scanner" - build_type = PROTOLATHE - materials = list(MAT_GLASS = 500, MAT_METAL = 500) - build_path = /obj/item/nanite_scanner - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -//////////////////////////////////////// -//////////Disk Construction Disks/////// -//////////////////////////////////////// -/datum/design/design_disk - name = "Design Storage Disk" - desc = "Produce additional disks for storing device designs." - id = "design_disk" - build_type = PROTOLATHE | AUTOLATHE - materials = list(MAT_METAL = 300, MAT_GLASS = 100) - build_path = /obj/item/disk/design_disk - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/design_disk_adv - name = "Advanced Design Storage Disk" - desc = "Produce additional disks for storing device designs." - id = "design_disk_adv" - build_type = PROTOLATHE - materials = list(MAT_METAL = 300, MAT_GLASS = 100, MAT_SILVER=50) - build_path = /obj/item/disk/design_disk/adv - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/tech_disk - name = "Technology Data Storage Disk" - desc = "Produce additional disks for storing technology data." - id = "tech_disk" - build_type = PROTOLATHE | AUTOLATHE - materials = list(MAT_METAL = 300, MAT_GLASS = 100) - build_path = /obj/item/disk/tech_disk - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/nanite_disk - name = "Nanite Program Disk" - desc = "Stores nanite programs." - id = "nanite_disk" - build_type = PROTOLATHE - materials = list(MAT_METAL = 300, MAT_GLASS = 100) - build_path = /obj/item/disk/nanite_program - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/integrated_printer - name = "Integrated circuit printer" - desc = "This machine provides all necessary things for circuitry." - id = "icprinter" - build_type = PROTOLATHE - materials = list(MAT_GLASS = 5000, MAT_METAL = 10000) - build_path = /obj/item/integrated_circuit_printer - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/IC_printer_upgrade_advanced - name = "Integrated circuit printer upgrade: Advanced Designs" - desc = "This disk allows for integrated circuit printers to print advanced circuitry designs." - id = "icupgadv" - build_type = PROTOLATHE - materials = list(MAT_GLASS = 10000, MAT_METAL = 10000) - build_path = /obj/item/disk/integrated_circuit/upgrade/advanced - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/IC_printer_upgrade_clone - name = "Integrated circuit printer upgrade: Instant Cloning" - desc = "This disk allows for integrated circuit printers to clone designs instantaneously." - id = "icupgclo" - build_type = PROTOLATHE - materials = list(MAT_GLASS = 10000, MAT_METAL = 10000) - build_path = /obj/item/disk/integrated_circuit/upgrade/clone - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -//CIT ADDITIONS -/datum/design/drone_shell - name = "Drone Shell" - desc = "A shell of a maintenance drone, an expendable robot built to perform station repairs." - id = "drone_shell" - build_type = MECHFAB | PROTOLATHE - materials = list(MAT_METAL = 800, MAT_GLASS = 350) - construction_time = 150 - build_path = /obj/item/drone_shell - category = list("Misc") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/xenobio_upgrade - name = "owo" - desc = "someone's bussin" - build_type = PROTOLATHE - materials = list(MAT_METAL = 300, MAT_GLASS = 100) - category = list("Electronics") - departmental_flags = DEPARTMENTAL_FLAG_SCIENCE - -/datum/design/xenobio_upgrade/xenobiomonkeys - name = "Xenobiology console monkey upgrade disk" - desc = "This disk will add the ability to remotely recycle monkeys via the Xenobiology console." - id = "xenobio_monkeys" - build_path = /obj/item/disk/xenobio_console_upgrade/monkey - -/datum/design/xenobio_upgrade/xenobioslimebasic - name = "Xenobiology console basic slime upgrade disk" - desc = "This disk will add the ability to remotely manipulate slimes via the Xenobiology console." - id = "xenobio_slimebasic" - build_path = /obj/item/disk/xenobio_console_upgrade/slimebasic - -/datum/design/xenobio_upgrade/xenobioslimeadv - name = "Xenobiology console advanced slime upgrade disk" - desc = "This disk will add the ability to remotely feed slimes potions via the Xenobiology console, and lift the restrictions on the number of slimes that can be stored inside the Xenobiology console. This includes the contents of the basic slime upgrade disk." - id = "xenobio_slimeadv" - build_path = /obj/item/disk/xenobio_console_upgrade/slimeadv - - + +/////////////////////////////////// +/////Non-Board Computer Stuff////// +/////////////////////////////////// + +/datum/design/intellicard + name = "Intellicard AI Transportation System" + desc = "Allows for the construction of an intellicard." + id = "intellicard" + build_type = PROTOLATHE + materials = list(MAT_GLASS = 1000, MAT_GOLD = 200) + build_path = /obj/item/aicard + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/paicard + name = "Personal Artificial Intelligence Card" + desc = "Allows for the construction of a pAI Card." + id = "paicard" + build_type = PROTOLATHE + materials = list(MAT_GLASS = 500, MAT_METAL = 500) + build_path = /obj/item/paicard + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_ALL + +/////////////////////////////////// +//////////Nanite Devices/////////// +/////////////////////////////////// +/datum/design/nanite_remote + name = "Nanite Remote" + desc = "Allows for the construction of a nanite remote." + id = "nanite_remote" + build_type = PROTOLATHE + materials = list(MAT_GLASS = 500, MAT_METAL = 500) + build_path = /obj/item/nanite_remote + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/nanite_scanner + name = "Nanite Scanner" + desc = "Allows for the construction of a nanite scanner." + id = "nanite_scanner" + build_type = PROTOLATHE + materials = list(MAT_GLASS = 500, MAT_METAL = 500) + build_path = /obj/item/nanite_scanner + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +//////////////////////////////////////// +//////////Disk Construction Disks/////// +//////////////////////////////////////// +/datum/design/design_disk + name = "Design Storage Disk" + desc = "Produce additional disks for storing device designs." + id = "design_disk" + build_type = PROTOLATHE | AUTOLATHE + materials = list(MAT_METAL = 300, MAT_GLASS = 100) + build_path = /obj/item/disk/design_disk + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/design_disk_adv + name = "Advanced Design Storage Disk" + desc = "Produce additional disks for storing device designs." + id = "design_disk_adv" + build_type = PROTOLATHE + materials = list(MAT_METAL = 300, MAT_GLASS = 100, MAT_SILVER=50) + build_path = /obj/item/disk/design_disk/adv + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/tech_disk + name = "Technology Data Storage Disk" + desc = "Produce additional disks for storing technology data." + id = "tech_disk" + build_type = PROTOLATHE | AUTOLATHE + materials = list(MAT_METAL = 300, MAT_GLASS = 100) + build_path = /obj/item/disk/tech_disk + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/nanite_disk + name = "Nanite Program Disk" + desc = "Stores nanite programs." + id = "nanite_disk" + build_type = PROTOLATHE + materials = list(MAT_METAL = 300, MAT_GLASS = 100) + build_path = /obj/item/disk/nanite_program + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/integrated_printer + name = "Integrated circuit printer" + desc = "This machine provides all necessary things for circuitry." + id = "icprinter" + build_type = PROTOLATHE + materials = list(MAT_GLASS = 5000, MAT_METAL = 10000) + build_path = /obj/item/integrated_circuit_printer + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/IC_printer_upgrade_advanced + name = "Integrated circuit printer upgrade: Advanced Designs" + desc = "This disk allows for integrated circuit printers to print advanced circuitry designs." + id = "icupgadv" + build_type = PROTOLATHE + materials = list(MAT_GLASS = 10000, MAT_METAL = 10000) + build_path = /obj/item/disk/integrated_circuit/upgrade/advanced + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/IC_printer_upgrade_clone + name = "Integrated circuit printer upgrade: Instant Cloning" + desc = "This disk allows for integrated circuit printers to clone designs instantaneously." + id = "icupgclo" + build_type = PROTOLATHE + materials = list(MAT_GLASS = 10000, MAT_METAL = 10000) + build_path = /obj/item/disk/integrated_circuit/upgrade/clone + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +//CIT ADDITIONS +/datum/design/drone_shell + name = "Drone Shell" + desc = "A shell of a maintenance drone, an expendable robot built to perform station repairs." + id = "drone_shell" + build_type = MECHFAB | PROTOLATHE + materials = list(MAT_METAL = 800, MAT_GLASS = 350) + construction_time = 150 + build_path = /obj/item/drone_shell + category = list("Misc") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/xenobio_upgrade + name = "owo" + desc = "someone's bussin" + build_type = PROTOLATHE + materials = list(MAT_METAL = 300, MAT_GLASS = 100) + category = list("Electronics") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/xenobio_upgrade/xenobiomonkeys + name = "Xenobiology console monkey upgrade disk" + desc = "This disk will add the ability to remotely recycle monkeys via the Xenobiology console." + id = "xenobio_monkeys" + build_path = /obj/item/disk/xenobio_console_upgrade/monkey + +/datum/design/xenobio_upgrade/xenobioslimebasic + name = "Xenobiology console basic slime upgrade disk" + desc = "This disk will add the ability to remotely manipulate slimes via the Xenobiology console." + id = "xenobio_slimebasic" + build_path = /obj/item/disk/xenobio_console_upgrade/slimebasic + +/datum/design/xenobio_upgrade/xenobioslimeadv + name = "Xenobiology console advanced slime upgrade disk" + desc = "This disk will add the ability to remotely feed slimes potions via the Xenobiology console, and lift the restrictions on the number of slimes that can be stored inside the Xenobiology console. This includes the contents of the basic slime upgrade disk." + id = "xenobio_slimeadv" + build_path = /obj/item/disk/xenobio_console_upgrade/slimeadv + + diff --git a/code/modules/research/designs/equipment_designs.dm b/code/modules/research/designs/equipment_designs.dm index 61a2854135..9f954adb33 100644 --- a/code/modules/research/designs/equipment_designs.dm +++ b/code/modules/research/designs/equipment_designs.dm @@ -1,55 +1,55 @@ -/* -/datum/design/flightsuit - name = "Flight Suit" - desc = "A specialized hardsuit that is able to attach a flightpack and accessories.." - id = "flightsuit" - build_type = PROTOLATHE - build_path = /obj/item/clothing/suit/space/hardsuit/flightsuit - materials = list(MAT_METAL=16000, MAT_GLASS = 8000, MAT_DIAMOND = 200, MAT_GOLD = 3000, MAT_SILVER = 3000, MAT_TITANIUM = 16000) //This expensive enough for you? - construction_time = 250 - category = list("Misc") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/flightpack - name = "Flight Pack" - desc = "An advanced back-worn system that has dual ion engines powerful enough to grant a humanoid flight. Contains an internal self-recharging high-current capacitor for short, powerful boosts." - id = "flightpack" - build_type = PROTOLATHE - build_path = /obj/item/flightpack - materials = list(MAT_METAL=16000, MAT_GLASS = 8000, MAT_DIAMOND = 4000, MAT_GOLD = 12000, MAT_SILVER = 12000, MAT_URANIUM = 20000, MAT_PLASMA = 16000, MAT_TITANIUM = 16000) //This expensive enough for you? - construction_time = 250 - category = list("Misc") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/flightshoes - name = "Flight Shoes" - desc = "Flight shoes, attachable to a flight suit to provide additional functions." - id = "flightshoes" - build_type = PROTOLATHE - build_path = /obj/item/clothing/shoes/flightshoes - materials = list(MAT_METAL = 5000, MAT_GLASS = 5000, MAT_GOLD = 1500, MAT_SILVER = 1500, MAT_PLASMA = 2000, MAT_TITANIUM = 2000) - construction_time = 100 - category = list("Misc") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING */ - -/datum/design/constructionhardsuit - name = "Construction Hardsuit" - desc = "A hardsuit, designed for EVA construction and hazardous material transportation" - id = "chardsuit" - build_type = PROTOLATHE - build_path = /obj/item/clothing/suit/space/hardsuit/engine - materials = list(MAT_METAL=16000, MAT_GLASS = 8000, MAT_DIAMOND = 200, MAT_GOLD = 3000, MAT_SILVER = 3000, MAT_TITANIUM = 16000) - construction_time = 100 - category = list("Misc") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING - -/datum/design/integratedjpack - name = "Hardsuit Jetpack Upgrade" - desc = "A modular upgrade for any hardsuit, giving it an integrated jetpack." - id = "hardsuitjpack" - build_type = PROTOLATHE - build_path = /obj/item/tank/jetpack/suit - materials = list(MAT_METAL=16000, MAT_GLASS = 8000, MAT_DIAMOND = 2000, MAT_GOLD = 6000, MAT_SILVER = 6000, MAT_URANIUM = 10000, MAT_PLASMA = 8000, MAT_TITANIUM = 16000) - construction_time = 100 - category = list("Misc") +/* +/datum/design/flightsuit + name = "Flight Suit" + desc = "A specialized hardsuit that is able to attach a flightpack and accessories.." + id = "flightsuit" + build_type = PROTOLATHE + build_path = /obj/item/clothing/suit/space/hardsuit/flightsuit + materials = list(MAT_METAL=16000, MAT_GLASS = 8000, MAT_DIAMOND = 200, MAT_GOLD = 3000, MAT_SILVER = 3000, MAT_TITANIUM = 16000) //This expensive enough for you? + construction_time = 250 + category = list("Misc") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/flightpack + name = "Flight Pack" + desc = "An advanced back-worn system that has dual ion engines powerful enough to grant a humanoid flight. Contains an internal self-recharging high-current capacitor for short, powerful boosts." + id = "flightpack" + build_type = PROTOLATHE + build_path = /obj/item/flightpack + materials = list(MAT_METAL=16000, MAT_GLASS = 8000, MAT_DIAMOND = 4000, MAT_GOLD = 12000, MAT_SILVER = 12000, MAT_URANIUM = 20000, MAT_PLASMA = 16000, MAT_TITANIUM = 16000) //This expensive enough for you? + construction_time = 250 + category = list("Misc") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/flightshoes + name = "Flight Shoes" + desc = "Flight shoes, attachable to a flight suit to provide additional functions." + id = "flightshoes" + build_type = PROTOLATHE + build_path = /obj/item/clothing/shoes/flightshoes + materials = list(MAT_METAL = 5000, MAT_GLASS = 5000, MAT_GOLD = 1500, MAT_SILVER = 1500, MAT_PLASMA = 2000, MAT_TITANIUM = 2000) + construction_time = 100 + category = list("Misc") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING */ + +/datum/design/constructionhardsuit + name = "Construction Hardsuit" + desc = "A hardsuit, designed for EVA construction and hazardous material transportation" + id = "chardsuit" + build_type = PROTOLATHE + build_path = /obj/item/clothing/suit/space/hardsuit/engine + materials = list(MAT_METAL=16000, MAT_GLASS = 8000, MAT_DIAMOND = 200, MAT_GOLD = 3000, MAT_SILVER = 3000, MAT_TITANIUM = 16000) + construction_time = 100 + category = list("Misc") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/integratedjpack + name = "Hardsuit Jetpack Upgrade" + desc = "A modular upgrade for any hardsuit, giving it an integrated jetpack." + id = "hardsuitjpack" + build_type = PROTOLATHE + build_path = /obj/item/tank/jetpack/suit + materials = list(MAT_METAL=16000, MAT_GLASS = 8000, MAT_DIAMOND = 2000, MAT_GOLD = 6000, MAT_SILVER = 6000, MAT_URANIUM = 10000, MAT_PLASMA = 8000, MAT_TITANIUM = 16000) + construction_time = 100 + category = list("Misc") departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING \ No newline at end of file diff --git a/code/modules/research/designs/mining_designs.dm b/code/modules/research/designs/mining_designs.dm index 18934d021c..b4ac5f109d 100644 --- a/code/modules/research/designs/mining_designs.dm +++ b/code/modules/research/designs/mining_designs.dm @@ -1,133 +1,133 @@ - -///////////////////////////////////////// -/////////////////Mining////////////////// -///////////////////////////////////////// -/datum/design/cargo_express - name = "Computer Design (Express Supply Console)"//shes beautiful - desc = "Allows for the construction of circuit boards used to build an Express Supply Console."//who? - id = "cargoexpress"//the coder reading this - build_type = IMPRINTER - materials = list(MAT_GLASS = 1000) - build_path = /obj/item/circuitboard/computer/cargo/express - category = list("Mining Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/bluespace_pod - name = "Supply Drop Pod Upgrade Disk" - desc = "Allows the Cargo Express Console to call down the Bluespace Drop Pod, greatly increasing user safety."//who? - id = "bluespace_pod"//the coder reading this - build_type = PROTOLATHE - materials = list(MAT_GLASS = 1000) - build_path = /obj/item/disk/cargo/bluespace_pod - category = list("Mining Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/drill - name = "Mining Drill" - desc = "Yours is the drill that will pierce through the rock walls." - id = "drill" - build_type = PROTOLATHE - materials = list(MAT_METAL = 6000, MAT_GLASS = 1000) //expensive, but no need for miners. - build_path = /obj/item/pickaxe/drill - category = list("Mining Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/drill_diamond - name = "Diamond-Tipped Mining Drill" - desc = "Yours is the drill that will pierce the heavens!" - id = "drill_diamond" - build_type = PROTOLATHE - materials = list(MAT_METAL = 6000, MAT_GLASS = 1000, MAT_DIAMOND = 2000) //Yes, a whole diamond is needed. - build_path = /obj/item/pickaxe/drill/diamonddrill - category = list("Mining Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/plasmacutter - name = "Plasma Cutter" - desc = "You could use it to cut limbs off of xenos! Or, you know, mine stuff." - id = "plasmacutter" - build_type = PROTOLATHE - materials = list(MAT_METAL = 1500, MAT_GLASS = 500, MAT_PLASMA = 400) - build_path = /obj/item/gun/energy/plasmacutter - category = list("Mining Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/plasmacutter_adv - name = "Advanced Plasma Cutter" - desc = "It's an advanced plasma cutter, oh my god." - id = "plasmacutter_adv" - build_type = PROTOLATHE - materials = list(MAT_METAL = 3000, MAT_GLASS = 1000, MAT_PLASMA = 2000, MAT_GOLD = 500) - build_path = /obj/item/gun/energy/plasmacutter/adv - category = list("Mining Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/jackhammer - name = "Sonic Jackhammer" - desc = "Essentially a handheld planet-cracker. Can drill through walls with ease as well." - id = "jackhammer" - build_type = PROTOLATHE - materials = list(MAT_METAL = 6000, MAT_GLASS = 2000, MAT_SILVER = 2000, MAT_DIAMOND = 6000) - build_path = /obj/item/pickaxe/drill/jackhammer - category = list("Mining Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/superresonator - name = "Upgraded Resonator" - desc = "An upgraded version of the resonator that allows more fields to be active at once." - id = "superresonator" - build_type = PROTOLATHE - materials = list(MAT_METAL = 4000, MAT_GLASS = 1500, MAT_SILVER = 1000, MAT_URANIUM = 1000) - build_path = /obj/item/resonator/upgraded - category = list("Mining Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/trigger_guard_mod - name = "Kinetic Accelerator Trigger Guard Mod" - desc = "A device which allows kinetic accelerators to be wielded by any organism." - id = "triggermod" - build_type = PROTOLATHE - materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) - build_path = /obj/item/borg/upgrade/modkit/trigger_guard - category = list("Mining Designs") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/damage_mod - name = "Kinetic Accelerator Damage Mod" - desc = "A device which allows kinetic accelerators to deal more damage." - id = "damagemod" - build_type = PROTOLATHE | MECHFAB - materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) - build_path = /obj/item/borg/upgrade/modkit/damage - category = list("Mining Designs", "Cyborg Upgrade Modules") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/cooldown_mod - name = "Kinetic Accelerator Cooldown Mod" - desc = "A device which decreases the cooldown of a Kinetic Accelerator." - id = "cooldownmod" - build_type = PROTOLATHE | MECHFAB - materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) - build_path = /obj/item/borg/upgrade/modkit/cooldown - category = list("Mining Designs", "Cyborg Upgrade Modules") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/range_mod - name = "Kinetic Accelerator Range Mod" - desc = "A device which allows kinetic accelerators to fire at a further range." - id = "rangemod" - build_type = PROTOLATHE | MECHFAB - materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) - build_path = /obj/item/borg/upgrade/modkit/range - category = list("Mining Designs", "Cyborg Upgrade Modules") - departmental_flags = DEPARTMENTAL_FLAG_CARGO - -/datum/design/hyperaccelerator - name = "Kinetic Accelerator Mining AoE Mod" - desc = "A modification kit for Kinetic Accelerators which causes it to fire AoE blasts that destroy rock." - id = "hypermod" - build_type = PROTOLATHE | MECHFAB - materials = list(MAT_METAL = 8000, MAT_GLASS = 1500, MAT_SILVER = 2000, MAT_GOLD = 2000, MAT_DIAMOND = 2000) - build_path = /obj/item/borg/upgrade/modkit/aoe/turfs - category = list("Mining Designs", "Cyborg Upgrade Modules") - departmental_flags = DEPARTMENTAL_FLAG_CARGO + +///////////////////////////////////////// +/////////////////Mining////////////////// +///////////////////////////////////////// +/datum/design/cargo_express + name = "Computer Design (Express Supply Console)"//shes beautiful + desc = "Allows for the construction of circuit boards used to build an Express Supply Console."//who? + id = "cargoexpress"//the coder reading this + build_type = IMPRINTER + materials = list(MAT_GLASS = 1000) + build_path = /obj/item/circuitboard/computer/cargo/express + category = list("Mining Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/bluespace_pod + name = "Supply Drop Pod Upgrade Disk" + desc = "Allows the Cargo Express Console to call down the Bluespace Drop Pod, greatly increasing user safety."//who? + id = "bluespace_pod"//the coder reading this + build_type = PROTOLATHE + materials = list(MAT_GLASS = 1000) + build_path = /obj/item/disk/cargo/bluespace_pod + category = list("Mining Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/drill + name = "Mining Drill" + desc = "Yours is the drill that will pierce through the rock walls." + id = "drill" + build_type = PROTOLATHE + materials = list(MAT_METAL = 6000, MAT_GLASS = 1000) //expensive, but no need for miners. + build_path = /obj/item/pickaxe/drill + category = list("Mining Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/drill_diamond + name = "Diamond-Tipped Mining Drill" + desc = "Yours is the drill that will pierce the heavens!" + id = "drill_diamond" + build_type = PROTOLATHE + materials = list(MAT_METAL = 6000, MAT_GLASS = 1000, MAT_DIAMOND = 2000) //Yes, a whole diamond is needed. + build_path = /obj/item/pickaxe/drill/diamonddrill + category = list("Mining Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/plasmacutter + name = "Plasma Cutter" + desc = "You could use it to cut limbs off of xenos! Or, you know, mine stuff." + id = "plasmacutter" + build_type = PROTOLATHE + materials = list(MAT_METAL = 1500, MAT_GLASS = 500, MAT_PLASMA = 400) + build_path = /obj/item/gun/energy/plasmacutter + category = list("Mining Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/plasmacutter_adv + name = "Advanced Plasma Cutter" + desc = "It's an advanced plasma cutter, oh my god." + id = "plasmacutter_adv" + build_type = PROTOLATHE + materials = list(MAT_METAL = 3000, MAT_GLASS = 1000, MAT_PLASMA = 2000, MAT_GOLD = 500) + build_path = /obj/item/gun/energy/plasmacutter/adv + category = list("Mining Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/jackhammer + name = "Sonic Jackhammer" + desc = "Essentially a handheld planet-cracker. Can drill through walls with ease as well." + id = "jackhammer" + build_type = PROTOLATHE + materials = list(MAT_METAL = 6000, MAT_GLASS = 2000, MAT_SILVER = 2000, MAT_DIAMOND = 6000) + build_path = /obj/item/pickaxe/drill/jackhammer + category = list("Mining Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/superresonator + name = "Upgraded Resonator" + desc = "An upgraded version of the resonator that allows more fields to be active at once." + id = "superresonator" + build_type = PROTOLATHE + materials = list(MAT_METAL = 4000, MAT_GLASS = 1500, MAT_SILVER = 1000, MAT_URANIUM = 1000) + build_path = /obj/item/resonator/upgraded + category = list("Mining Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/trigger_guard_mod + name = "Kinetic Accelerator Trigger Guard Mod" + desc = "A device which allows kinetic accelerators to be wielded by any organism." + id = "triggermod" + build_type = PROTOLATHE + materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) + build_path = /obj/item/borg/upgrade/modkit/trigger_guard + category = list("Mining Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/damage_mod + name = "Kinetic Accelerator Damage Mod" + desc = "A device which allows kinetic accelerators to deal more damage." + id = "damagemod" + build_type = PROTOLATHE | MECHFAB + materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) + build_path = /obj/item/borg/upgrade/modkit/damage + category = list("Mining Designs", "Cyborg Upgrade Modules") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/cooldown_mod + name = "Kinetic Accelerator Cooldown Mod" + desc = "A device which decreases the cooldown of a Kinetic Accelerator." + id = "cooldownmod" + build_type = PROTOLATHE | MECHFAB + materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) + build_path = /obj/item/borg/upgrade/modkit/cooldown + category = list("Mining Designs", "Cyborg Upgrade Modules") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/range_mod + name = "Kinetic Accelerator Range Mod" + desc = "A device which allows kinetic accelerators to fire at a further range." + id = "rangemod" + build_type = PROTOLATHE | MECHFAB + materials = list(MAT_METAL = 2000, MAT_GLASS = 1500, MAT_GOLD = 1500, MAT_URANIUM = 1000) + build_path = /obj/item/borg/upgrade/modkit/range + category = list("Mining Designs", "Cyborg Upgrade Modules") + departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/hyperaccelerator + name = "Kinetic Accelerator Mining AoE Mod" + desc = "A modification kit for Kinetic Accelerators which causes it to fire AoE blasts that destroy rock." + id = "hypermod" + build_type = PROTOLATHE | MECHFAB + materials = list(MAT_METAL = 8000, MAT_GLASS = 1500, MAT_SILVER = 2000, MAT_GOLD = 2000, MAT_DIAMOND = 2000) + build_path = /obj/item/borg/upgrade/modkit/aoe/turfs + category = list("Mining Designs", "Cyborg Upgrade Modules") + departmental_flags = DEPARTMENTAL_FLAG_CARGO diff --git a/code/modules/research/destructive_analyzer.dm b/code/modules/research/destructive_analyzer.dm index ac76e8b9fe..7dd7b76007 100644 --- a/code/modules/research/destructive_analyzer.dm +++ b/code/modules/research/destructive_analyzer.dm @@ -1,154 +1,154 @@ - - -/* -Destructive Analyzer - -It is used to destroy hand-held objects and advance technological research. Controls are in the linked R&D console. - -Note: Must be placed within 3 tiles of the R&D Console -*/ -/obj/machinery/rnd/destructive_analyzer - name = "destructive analyzer" - desc = "Learn science by destroying things!" - icon_state = "d_analyzer" - circuit = /obj/item/circuitboard/machine/destructive_analyzer - var/decon_mod = 0 - -/obj/machinery/rnd/destructive_analyzer/RefreshParts() - var/T = 0 - for(var/obj/item/stock_parts/S in component_parts) - T += S.rating - decon_mod = T - - -/obj/machinery/rnd/destructive_analyzer/proc/ConvertReqString2List(list/source_list) - var/list/temp_list = params2list(source_list) - for(var/O in temp_list) - temp_list[O] = text2num(temp_list[O]) - return temp_list - -/obj/machinery/rnd/destructive_analyzer/disconnect_console() - linked_console.linked_destroy = null - ..() - -/obj/machinery/rnd/destructive_analyzer/Insert_Item(obj/item/O, mob/user) - if(user.a_intent != INTENT_HARM) - . = 1 - if(!is_insertion_ready(user)) - return - if(!user.transferItemToLoc(O, src)) - to_chat(user, "\The [O] is stuck to your hand, you cannot put it in the [src.name]!") - return - busy = TRUE - loaded_item = O - to_chat(user, "You add the [O.name] to the [src.name]!") - flick("d_analyzer_la", src) - addtimer(CALLBACK(src, .proc/finish_loading), 10) - if (linked_console) - linked_console.updateUsrDialog() - -/obj/machinery/rnd/destructive_analyzer/proc/finish_loading() - update_icon() - reset_busy() - -/obj/machinery/rnd/destructive_analyzer/update_icon() - if(loaded_item) - icon_state = "d_analyzer_l" - else - icon_state = initial(icon_state) - -/obj/machinery/rnd/destructive_analyzer/proc/reclaim_materials_from(obj/item/thing) - . = 0 - var/datum/component/material_container/storage = linked_console?.linked_lathe?.materials.mat_container - if(storage) //Also sends salvaged materials to a linked protolathe, if any. - for(var/material in thing.materials) - var/can_insert = min((storage.max_amount - storage.total_amount), (max(thing.materials[material]*(decon_mod/10), thing.materials[material]))) - storage.insert_amount(can_insert, material) - . += can_insert - if (.) - linked_console.linked_lathe.materials.silo_log(src, "reclaimed", 1, "[thing.name]", thing.materials) - -/obj/machinery/rnd/destructive_analyzer/proc/destroy_item(obj/item/thing, innermode = FALSE) - if(QDELETED(thing) || QDELETED(src) || QDELETED(linked_console)) - return FALSE - if(!innermode) - flick("d_analyzer_process", src) - busy = TRUE - addtimer(CALLBACK(src, .proc/reset_busy), 24) - use_power(250) - if(thing == loaded_item) - loaded_item = null - var/list/food = thing.GetDeconstructableContents() - for(var/obj/item/innerthing in food) - destroy_item(innerthing, TRUE) - reclaim_materials_from(thing) - for(var/mob/M in thing) - M.death() - if(istype(thing, /obj/item/stack/sheet)) - var/obj/item/stack/sheet/S = thing - if(S.amount > 1 && !innermode) - S.amount-- - loaded_item = S - else - qdel(S) - else - qdel(thing) - if (!innermode) - update_icon() - return TRUE - -/obj/machinery/rnd/destructive_analyzer/proc/user_try_decon_id(id, mob/user) - if(!istype(loaded_item) || !istype(linked_console)) - return FALSE - - if (id && id != RESEARCH_MATERIAL_RECLAMATION_ID) - var/datum/techweb_node/TN = SSresearch.techweb_node_by_id(id) - if(!istype(TN)) - return FALSE - var/dpath = loaded_item.type - var/list/worths = TN.boost_item_paths[dpath] - var/list/differences = list() - var/list/already_boosted = linked_console.stored_research.boosted_nodes[TN.id] - for(var/i in worths) - var/used = already_boosted? already_boosted[i] : 0 - var/value = min(worths[i], TN.research_costs[i]) - used - if(value > 0) - differences[i] = value - if(length(worths) && !length(differences)) - return FALSE - var/choice = input("Are you sure you want to destroy [loaded_item] to [!length(worths) ? "reveal [TN.display_name]" : "boost [TN.display_name] by [json_encode(differences)] point\s"]?") in list("Proceed", "Cancel") - if(choice == "Cancel") - return FALSE - if(QDELETED(loaded_item) || QDELETED(linked_console) || !user.Adjacent(linked_console) || QDELETED(src)) - return FALSE - SSblackbox.record_feedback("nested tally", "item_deconstructed", 1, list("[TN.id]", "[loaded_item.type]")) - if(destroy_item(loaded_item)) - linked_console.stored_research.boost_with_path(SSresearch.techweb_node_by_id(TN.id), dpath) - - else - var/list/point_value = techweb_item_point_check(loaded_item) - if(linked_console.stored_research.deconstructed_items[loaded_item.type]) - point_value = list() - var/user_mode_string = "" - if(length(point_value)) - user_mode_string = " for [json_encode(point_value)] points" - else if(loaded_item.materials.len) - user_mode_string = " for material reclamation" - var/choice = input("Are you sure you want to destroy [loaded_item][user_mode_string]?") in list("Proceed", "Cancel") - if(choice == "Cancel") - return FALSE - if(QDELETED(loaded_item) || QDELETED(linked_console) || !user.Adjacent(linked_console) || QDELETED(src)) - return FALSE - var/loaded_type = loaded_item.type - if(destroy_item(loaded_item)) - linked_console.stored_research.add_point_list(point_value) - linked_console.stored_research.deconstructed_items[loaded_type] = point_value - return TRUE - -/obj/machinery/rnd/destructive_analyzer/proc/unload_item() - if(!loaded_item) - return FALSE - loaded_item.forceMove(get_turf(src)) - loaded_item = null - update_icon() - return TRUE + + +/* +Destructive Analyzer + +It is used to destroy hand-held objects and advance technological research. Controls are in the linked R&D console. + +Note: Must be placed within 3 tiles of the R&D Console +*/ +/obj/machinery/rnd/destructive_analyzer + name = "destructive analyzer" + desc = "Learn science by destroying things!" + icon_state = "d_analyzer" + circuit = /obj/item/circuitboard/machine/destructive_analyzer + var/decon_mod = 0 + +/obj/machinery/rnd/destructive_analyzer/RefreshParts() + var/T = 0 + for(var/obj/item/stock_parts/S in component_parts) + T += S.rating + decon_mod = T + + +/obj/machinery/rnd/destructive_analyzer/proc/ConvertReqString2List(list/source_list) + var/list/temp_list = params2list(source_list) + for(var/O in temp_list) + temp_list[O] = text2num(temp_list[O]) + return temp_list + +/obj/machinery/rnd/destructive_analyzer/disconnect_console() + linked_console.linked_destroy = null + ..() + +/obj/machinery/rnd/destructive_analyzer/Insert_Item(obj/item/O, mob/user) + if(user.a_intent != INTENT_HARM) + . = 1 + if(!is_insertion_ready(user)) + return + if(!user.transferItemToLoc(O, src)) + to_chat(user, "\The [O] is stuck to your hand, you cannot put it in the [src.name]!") + return + busy = TRUE + loaded_item = O + to_chat(user, "You add the [O.name] to the [src.name]!") + flick("d_analyzer_la", src) + addtimer(CALLBACK(src, .proc/finish_loading), 10) + if (linked_console) + linked_console.updateUsrDialog() + +/obj/machinery/rnd/destructive_analyzer/proc/finish_loading() + update_icon() + reset_busy() + +/obj/machinery/rnd/destructive_analyzer/update_icon() + if(loaded_item) + icon_state = "d_analyzer_l" + else + icon_state = initial(icon_state) + +/obj/machinery/rnd/destructive_analyzer/proc/reclaim_materials_from(obj/item/thing) + . = 0 + var/datum/component/material_container/storage = linked_console?.linked_lathe?.materials.mat_container + if(storage) //Also sends salvaged materials to a linked protolathe, if any. + for(var/material in thing.materials) + var/can_insert = min((storage.max_amount - storage.total_amount), (max(thing.materials[material]*(decon_mod/10), thing.materials[material]))) + storage.insert_amount(can_insert, material) + . += can_insert + if (.) + linked_console.linked_lathe.materials.silo_log(src, "reclaimed", 1, "[thing.name]", thing.materials) + +/obj/machinery/rnd/destructive_analyzer/proc/destroy_item(obj/item/thing, innermode = FALSE) + if(QDELETED(thing) || QDELETED(src) || QDELETED(linked_console)) + return FALSE + if(!innermode) + flick("d_analyzer_process", src) + busy = TRUE + addtimer(CALLBACK(src, .proc/reset_busy), 24) + use_power(250) + if(thing == loaded_item) + loaded_item = null + var/list/food = thing.GetDeconstructableContents() + for(var/obj/item/innerthing in food) + destroy_item(innerthing, TRUE) + reclaim_materials_from(thing) + for(var/mob/M in thing) + M.death() + if(istype(thing, /obj/item/stack/sheet)) + var/obj/item/stack/sheet/S = thing + if(S.amount > 1 && !innermode) + S.amount-- + loaded_item = S + else + qdel(S) + else + qdel(thing) + if (!innermode) + update_icon() + return TRUE + +/obj/machinery/rnd/destructive_analyzer/proc/user_try_decon_id(id, mob/user) + if(!istype(loaded_item) || !istype(linked_console)) + return FALSE + + if (id && id != RESEARCH_MATERIAL_RECLAMATION_ID) + var/datum/techweb_node/TN = SSresearch.techweb_node_by_id(id) + if(!istype(TN)) + return FALSE + var/dpath = loaded_item.type + var/list/worths = TN.boost_item_paths[dpath] + var/list/differences = list() + var/list/already_boosted = linked_console.stored_research.boosted_nodes[TN.id] + for(var/i in worths) + var/used = already_boosted? already_boosted[i] : 0 + var/value = min(worths[i], TN.research_costs[i]) - used + if(value > 0) + differences[i] = value + if(length(worths) && !length(differences)) + return FALSE + var/choice = input("Are you sure you want to destroy [loaded_item] to [!length(worths) ? "reveal [TN.display_name]" : "boost [TN.display_name] by [json_encode(differences)] point\s"]?") in list("Proceed", "Cancel") + if(choice == "Cancel") + return FALSE + if(QDELETED(loaded_item) || QDELETED(linked_console) || !user.Adjacent(linked_console) || QDELETED(src)) + return FALSE + SSblackbox.record_feedback("nested tally", "item_deconstructed", 1, list("[TN.id]", "[loaded_item.type]")) + if(destroy_item(loaded_item)) + linked_console.stored_research.boost_with_path(SSresearch.techweb_node_by_id(TN.id), dpath) + + else + var/list/point_value = techweb_item_point_check(loaded_item) + if(linked_console.stored_research.deconstructed_items[loaded_item.type]) + point_value = list() + var/user_mode_string = "" + if(length(point_value)) + user_mode_string = " for [json_encode(point_value)] points" + else if(loaded_item.materials.len) + user_mode_string = " for material reclamation" + var/choice = input("Are you sure you want to destroy [loaded_item][user_mode_string]?") in list("Proceed", "Cancel") + if(choice == "Cancel") + return FALSE + if(QDELETED(loaded_item) || QDELETED(linked_console) || !user.Adjacent(linked_console) || QDELETED(src)) + return FALSE + var/loaded_type = loaded_item.type + if(destroy_item(loaded_item)) + linked_console.stored_research.add_point_list(point_value) + linked_console.stored_research.deconstructed_items[loaded_type] = point_value + return TRUE + +/obj/machinery/rnd/destructive_analyzer/proc/unload_item() + if(!loaded_item) + return FALSE + loaded_item.forceMove(get_turf(src)) + loaded_item = null + update_icon() + return TRUE diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index b25ee9bb2d..bff8df8816 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -1,667 +1,667 @@ -//this is designed to replace the destructive analyzer - -//NEEDS MAJOR CODE CLEANUP - -#define SCANTYPE_POKE 1 -#define SCANTYPE_IRRADIATE 2 -#define SCANTYPE_GAS 3 -#define SCANTYPE_HEAT 4 -#define SCANTYPE_COLD 5 -#define SCANTYPE_OBLITERATE 6 -#define SCANTYPE_DISCOVER 7 - -#define EFFECT_PROB_VERYLOW 20 -#define EFFECT_PROB_LOW 35 -#define EFFECT_PROB_MEDIUM 50 -#define EFFECT_PROB_HIGH 75 -#define EFFECT_PROB_VERYHIGH 95 - -#define FAIL 8 -/obj/machinery/rnd/experimentor - name = "\improper E.X.P.E.R.I-MENTOR" - desc = "A \"replacement\" for the destructive analyzer with a slight tendency to catastrophically fail." - icon = 'icons/obj/machines/heavy_lathe.dmi' - icon_state = "h_lathe" - density = TRUE - use_power = IDLE_POWER_USE - circuit = /obj/item/circuitboard/machine/experimentor - var/recentlyExperimented = 0 - var/mob/trackedIan - var/mob/trackedRuntime - var/badThingCoeff = 0 - var/resetTime = 15 - var/cloneMode = FALSE - var/list/item_reactions = list() - var/list/valid_items = list() //valid items for special reactions like transforming - var/list/critical_items = list() //items that can cause critical reactions - -/obj/machinery/rnd/experimentor/proc/ConvertReqString2List(list/source_list) - var/list/temp_list = params2list(source_list) - for(var/O in temp_list) - temp_list[O] = text2num(temp_list[O]) - return temp_list - - -/obj/machinery/rnd/experimentor/proc/SetTypeReactions() - for(var/I in typesof(/obj/item)) - if(ispath(I, /obj/item/relic)) - item_reactions["[I]"] = SCANTYPE_DISCOVER - else - item_reactions["[I]"] = pick(SCANTYPE_POKE,SCANTYPE_IRRADIATE,SCANTYPE_GAS,SCANTYPE_HEAT,SCANTYPE_COLD,SCANTYPE_OBLITERATE) - - if(ispath(I, /obj/item/stock_parts) || ispath(I, /obj/item/grenade/chem_grenade) || ispath(I, /obj/item/kitchen)) - var/obj/item/tempCheck = I - if(initial(tempCheck.icon_state) != null) //check it's an actual usable item, in a hacky way - if(ispath(I, /obj/item/grenade/chem_grenade/tuberculosis)) - continue - valid_items["[I]"] += 15 - - if(ispath(I, /obj/item/reagent_containers/food)) - var/obj/item/tempCheck = I - if(initial(tempCheck.icon_state) != null) //check it's an actual usable item, in a hacky way - valid_items["[I]"] += rand(1,4) - - if(ispath(I, /obj/item/construction/rcd) || ispath(I, /obj/item/grenade) || ispath(I, /obj/item/aicard) || ispath(I, /obj/item/storage/backpack/holding) || ispath(I, /obj/item/slime_extract) || ispath(I, /obj/item/onetankbomb) || ispath(I, /obj/item/transfer_valve)) - var/obj/item/tempCheck = I - if(initial(tempCheck.icon_state) != null) - critical_items += I - -/obj/machinery/rnd/experimentor/Initialize() - . = ..() - - trackedIan = locate(/mob/living/simple_animal/pet/dog/corgi/Ian) in GLOB.mob_living_list - trackedRuntime = locate(/mob/living/simple_animal/pet/cat/Runtime) in GLOB.mob_living_list - SetTypeReactions() - -/obj/machinery/rnd/experimentor/RefreshParts() - for(var/obj/item/stock_parts/manipulator/M in component_parts) - if(resetTime > 0 && (resetTime - M.rating) >= 1) - resetTime -= M.rating - for(var/obj/item/stock_parts/scanning_module/M in component_parts) - badThingCoeff += M.rating*2 - for(var/obj/item/stock_parts/micro_laser/M in component_parts) - badThingCoeff += M.rating - -/obj/machinery/rnd/experimentor/proc/checkCircumstances(obj/item/O) - //snowflake check to only take "made" bombs - if(istype(O, /obj/item/transfer_valve)) - var/obj/item/transfer_valve/T = O - if(!T.tank_one || !T.tank_two || !T.attached_device) - return FALSE - return TRUE - -/obj/machinery/rnd/experimentor/Insert_Item(obj/item/O, mob/user) - if(user.a_intent != INTENT_HARM) - . = 1 - if(!is_insertion_ready(user)) - return - if(!user.transferItemToLoc(O, src)) - return - loaded_item = O - to_chat(user, "You add [O] to the machine.") - flick("h_lathe_load", src) - -/obj/machinery/rnd/experimentor/default_deconstruction_crowbar(obj/item/O) - ejectItem() - . = ..(O) - -/obj/machinery/rnd/experimentor/ui_interact(mob/user) - var/list/dat = list("
                ") - if(!linked_console) - dat += "Scan for R&D Console" - if(loaded_item) - dat += "Loaded Item: [loaded_item]" - - dat += "
                Available tests:" - dat += "Poke" - dat += "Irradiate" - dat += "Gas" - dat += "Burn" - dat += "Freeze" - dat += "Destroy
                " - if(istype(loaded_item,/obj/item/relic)) - dat += "Discover" - dat += "Eject" - var/list/listin = techweb_item_boost_check(src) - if(listin) - var/list/output = list("Research Boost Data:") - var/list/res = list("Already researched:") - var/list/boosted = list("Already boosted:") - for(var/node_id in listin) - var/datum/techweb_node/N = SSresearch.techweb_node_by_id(node_id) - var/str = "[N.display_name]: [listin[N]] points." - if(SSresearch.science_tech.researched_nodes[N.id]) - res += str - else if(SSresearch.science_tech.boosted_nodes[N.id]) - boosted += str - if(SSresearch.science_tech.visible_nodes[N.id]) //JOY OF DISCOVERY! - output += str - output += boosted + res - dat += output - else - dat += "Nothing loaded." - dat += "Refresh" - dat += "Close
                " - var/datum/browser/popup = new(user, "experimentor","Experimentor", 700, 400, src) - popup.set_content(dat.Join("
                ")) - popup.open() - onclose(user, "experimentor") - -/obj/machinery/rnd/experimentor/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - - var/scantype = href_list["function"] - var/obj/item/process = locate(href_list["item"]) in src - - if(href_list["close"]) - usr << browse(null, "window=experimentor") - return - if(scantype == "search") - var/obj/machinery/computer/rdconsole/D = locate(/obj/machinery/computer/rdconsole) in oview(3,src) - if(D) - linked_console = D - else if(scantype == "eject") - ejectItem() - else if(scantype == "refresh") - updateUsrDialog() - else - if(recentlyExperimented) - to_chat(usr, "[src] has been used too recently!") - else if(!loaded_item) - to_chat(usr, "[src] is not currently loaded!") - else if(!process || process != loaded_item) //Interface exploit protection (such as hrefs or swapping items with interface set to old item) - to_chat(usr, "Interface failure detected in [src]. Please try again.") - else - var/dotype - if(text2num(scantype) == SCANTYPE_DISCOVER) - dotype = SCANTYPE_DISCOVER - else - dotype = matchReaction(process,scantype) - experiment(dotype,process) - use_power(750) - if(dotype != FAIL) - var/list/nodes = techweb_item_boost_check(process) - var/picked = pickweight(nodes) //This should work. - if(linked_console) - linked_console.stored_research.boost_with_path(SSresearch.techweb_node_by_id(picked), process.type) - updateUsrDialog() - -/obj/machinery/rnd/experimentor/proc/matchReaction(matching,reaction) - var/obj/item/D = matching - if(D) - if(item_reactions.Find("[D.type]")) - var/tor = item_reactions["[D.type]"] - if(tor == text2num(reaction)) - return tor - else - return FAIL - else - return FAIL - else - return FAIL - -/obj/machinery/rnd/experimentor/proc/ejectItem(delete=FALSE) - if(loaded_item) - if(cloneMode) - visible_message("A duplicate [loaded_item] pops out!") - var/type_to_make = loaded_item.type - new type_to_make(get_turf(pick(oview(1,src)))) - cloneMode = FALSE - return - var/turf/dropturf = get_turf(pick(view(1,src))) - if(!dropturf) //Failsafe to prevent the object being lost in the void forever. - dropturf = drop_location() - loaded_item.forceMove(dropturf) - if(delete) - qdel(loaded_item) - loaded_item = null - -/obj/machinery/rnd/experimentor/proc/throwSmoke(turf/where) - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(0, where) - smoke.start() - - -/obj/machinery/rnd/experimentor/proc/experiment(exp,obj/item/exp_on) - recentlyExperimented = 1 - icon_state = "h_lathe_wloop" - var/chosenchem - var/criticalReaction = (exp_on.type in critical_items) ? TRUE : FALSE - //////////////////////////////////////////////////////////////////////////////////////////////// - if(exp == SCANTYPE_POKE) - visible_message("[src] prods at [exp_on] with mechanical arms.") - if(prob(EFFECT_PROB_LOW) && criticalReaction) - visible_message("[exp_on] is gripped in just the right way, enhancing its focus.") - badThingCoeff++ - else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) - visible_message("[src] malfunctions and destroys [exp_on], lashing its arms out at nearby people!") - for(var/mob/living/m in oview(1, src)) - m.apply_damage(15, BRUTE, pick(BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_PRECISE_GROIN)) - investigate_log("Experimentor dealt minor brute to [m].", INVESTIGATE_EXPERIMENTOR) - ejectItem(TRUE) - else if(prob(EFFECT_PROB_LOW-badThingCoeff)) - visible_message("[src] malfunctions!") - exp = SCANTYPE_OBLITERATE - else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) - visible_message("[src] malfunctions, throwing the [exp_on]!") - var/mob/living/target = locate(/mob/living) in oview(7,src) - if(target) - var/obj/item/throwing = loaded_item - investigate_log("Experimentor has thrown [loaded_item] at [key_name(target)]", INVESTIGATE_EXPERIMENTOR) - ejectItem() - if(throwing) - throwing.throw_at(target, 10, 1) - //////////////////////////////////////////////////////////////////////////////////////////////// - if(exp == SCANTYPE_IRRADIATE) - visible_message("[src] reflects radioactive rays at [exp_on]!") - if(prob(EFFECT_PROB_LOW) && criticalReaction) - visible_message("[exp_on] has activated an unknown subroutine!") - cloneMode = TRUE - investigate_log("Experimentor has made a clone of [exp_on]", INVESTIGATE_EXPERIMENTOR) - ejectItem() - else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) - visible_message("[src] malfunctions, melting [exp_on] and leaking radiation!") - radiation_pulse(src, 500) - ejectItem(TRUE) - else if(prob(EFFECT_PROB_LOW-badThingCoeff)) - visible_message("[src] malfunctions, spewing toxic waste!") - for(var/turf/T in oview(1, src)) - if(!T.density) - if(prob(EFFECT_PROB_VERYHIGH) && !(locate(/obj/effect/decal/cleanable/greenglow) in T)) - var/obj/effect/decal/cleanable/reagentdecal = new/obj/effect/decal/cleanable/greenglow(T) - reagentdecal.reagents.add_reagent(/datum/reagent/radium, 7) - else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) - var/savedName = "[exp_on]" - ejectItem(TRUE) - var/newPath = text2path(pickweight(valid_items)) - loaded_item = new newPath(src) - visible_message("[src] malfunctions, transforming [savedName] into [loaded_item]!") - investigate_log("Experimentor has transformed [savedName] into [loaded_item]", INVESTIGATE_EXPERIMENTOR) - if(istype(loaded_item, /obj/item/grenade/chem_grenade)) - var/obj/item/grenade/chem_grenade/CG = loaded_item - CG.prime() - ejectItem() - //////////////////////////////////////////////////////////////////////////////////////////////// - if(exp == SCANTYPE_GAS) - visible_message("[src] fills its chamber with gas, [exp_on] included.") - if(prob(EFFECT_PROB_LOW) && criticalReaction) - visible_message("[exp_on] achieves the perfect mix!") - new /obj/item/stack/sheet/mineral/plasma(get_turf(pick(oview(1,src)))) - else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) - visible_message("[src] destroys [exp_on], leaking dangerous gas!") - chosenchem = pick(/datum/reagent/carbon,/datum/reagent/radium,/datum/reagent/toxin, - /datum/reagent/consumable/condensedcapsaicin,/datum/reagent/drug/mushroomhallucinogen, - /datum/reagent/drug/space_drugs,/datum/reagent/consumable/ethanol,/datum/reagent/consumable/ethanol/beepsky_smash) - var/datum/reagents/R = new/datum/reagents(50) - R.my_atom = src - R.add_reagent(chosenchem , 50) - investigate_log("Experimentor has released [chosenchem] smoke.", INVESTIGATE_EXPERIMENTOR) - var/datum/effect_system/smoke_spread/chem/smoke = new - smoke.set_up(R, 0, src, silent = TRUE) - playsound(src, 'sound/effects/smoke.ogg', 50, 1, -3) - smoke.start() - qdel(R) - ejectItem(TRUE) - else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) - visible_message("[src]'s chemical chamber has sprung a leak!") - chosenchem = pick(/datum/reagent/mutationtoxin,/datum/reagent/nanomachines,/datum/reagent/toxin/acid) - var/datum/reagents/R = new/datum/reagents(50) - R.my_atom = src - R.add_reagent(chosenchem , 50) - var/datum/effect_system/smoke_spread/chem/smoke = new - smoke.set_up(R, 0, src, silent = TRUE) - playsound(src, 'sound/effects/smoke.ogg', 50, 1, -3) - smoke.start() - qdel(R) - ejectItem(TRUE) - warn_admins(usr, "[chosenchem] smoke") - investigate_log("Experimentor has released [chosenchem] smoke!", INVESTIGATE_EXPERIMENTOR) - else if(prob(EFFECT_PROB_LOW-badThingCoeff)) - visible_message("[src] malfunctions, spewing harmless gas.") - throwSmoke(loc) - else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) - visible_message("[src] melts [exp_on], ionizing the air around it!") - empulse(loc, 4, 6) - investigate_log("Experimentor has generated an Electromagnetic Pulse.", INVESTIGATE_EXPERIMENTOR) - ejectItem(TRUE) - //////////////////////////////////////////////////////////////////////////////////////////////// - if(exp == SCANTYPE_HEAT) - visible_message("[src] raises [exp_on]'s temperature.") - if(prob(EFFECT_PROB_LOW) && criticalReaction) - visible_message("[src]'s emergency coolant system gives off a small ding!") - playsound(src, 'sound/machines/ding.ogg', 50, 1) - var/obj/item/reagent_containers/food/drinks/coffee/C = new /obj/item/reagent_containers/food/drinks/coffee(get_turf(pick(oview(1,src)))) - chosenchem = pick(/datum/reagent/toxin/plasma,/datum/reagent/consumable/capsaicin,/datum/reagent/consumable/ethanol) - C.reagents.remove_any(25) - C.reagents.add_reagent(chosenchem , 50) - C.name = "Cup of Suspicious Liquid" - C.desc = "It has a large hazard symbol printed on the side in fading ink." - investigate_log("Experimentor has made a cup of [chosenchem] coffee.", INVESTIGATE_EXPERIMENTOR) - else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) - var/turf/start = get_turf(src) - var/mob/M = locate(/mob/living) in view(src, 3) - var/turf/MT = get_turf(M) - if(MT) - visible_message("[src] dangerously overheats, launching a flaming fuel orb!") - investigate_log("Experimentor has launched a fireball at [M]!", INVESTIGATE_EXPERIMENTOR) - var/obj/item/projectile/magic/aoe/fireball/FB = new /obj/item/projectile/magic/aoe/fireball(start) - FB.preparePixelProjectile(MT, start) - FB.fire() - else if(prob(EFFECT_PROB_LOW-badThingCoeff)) - visible_message("[src] malfunctions, melting [exp_on] and releasing a burst of flame!") - explosion(loc, -1, 0, 0, 0, 0, flame_range = 2) - investigate_log("Experimentor started a fire.", INVESTIGATE_EXPERIMENTOR) - ejectItem(TRUE) - else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) - visible_message("[src] malfunctions, melting [exp_on] and leaking hot air!") - var/datum/gas_mixture/env = loc.return_air() - var/transfer_moles = 0.25 * env.total_moles() - var/datum/gas_mixture/removed = env.remove(transfer_moles) - if(removed) - var/heat_capacity = removed.heat_capacity() - if(heat_capacity == 0 || heat_capacity == null) - heat_capacity = 1 - removed.temperature = min((removed.temperature*heat_capacity + 100000)/heat_capacity, 1000) - env.merge(removed) - air_update_turf() - investigate_log("Experimentor has released hot air.", INVESTIGATE_EXPERIMENTOR) - ejectItem(TRUE) - else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) - visible_message("[src] malfunctions, activating its emergency coolant systems!") - throwSmoke(loc) - for(var/mob/living/m in oview(1, src)) - m.apply_damage(5, BURN, pick(BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_PRECISE_GROIN)) - investigate_log("Experimentor has dealt minor burn damage to [key_name(m)]", INVESTIGATE_EXPERIMENTOR) - ejectItem() - //////////////////////////////////////////////////////////////////////////////////////////////// - if(exp == SCANTYPE_COLD) - visible_message("[src] lowers [exp_on]'s temperature.") - if(prob(EFFECT_PROB_LOW) && criticalReaction) - visible_message("[src]'s emergency coolant system gives off a small ding!") - var/obj/item/reagent_containers/food/drinks/coffee/C = new /obj/item/reagent_containers/food/drinks/coffee(get_turf(pick(oview(1,src)))) - playsound(src, 'sound/machines/ding.ogg', 50, 1) //Ding! Your death coffee is ready! - chosenchem = pick(/datum/reagent/uranium,/datum/reagent/consumable/frostoil,/datum/reagent/medicine/ephedrine) - C.reagents.remove_any(25) - C.reagents.add_reagent(chosenchem , 50) - C.name = "Cup of Suspicious Liquid" - C.desc = "It has a large hazard symbol printed on the side in fading ink." - investigate_log("Experimentor has made a cup of [chosenchem] coffee.", INVESTIGATE_EXPERIMENTOR) - else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) - visible_message("[src] malfunctions, shattering [exp_on] and releasing a dangerous cloud of coolant!") - var/datum/reagents/R = new/datum/reagents(50) - R.my_atom = src - R.add_reagent(/datum/reagent/consumable/frostoil, 50) - investigate_log("Experimentor has released frostoil gas.", INVESTIGATE_EXPERIMENTOR) - var/datum/effect_system/smoke_spread/chem/smoke = new - smoke.set_up(R, 0, src, silent = TRUE) - playsound(src, 'sound/effects/smoke.ogg', 50, 1, -3) - smoke.start() - qdel(R) - ejectItem(TRUE) - else if(prob(EFFECT_PROB_LOW-badThingCoeff)) - visible_message("[src] malfunctions, shattering [exp_on] and leaking cold air!") - var/datum/gas_mixture/env = loc.return_air() - var/transfer_moles = 0.25 * env.total_moles() - var/datum/gas_mixture/removed = env.remove(transfer_moles) - if(removed) - var/heat_capacity = removed.heat_capacity() - if(heat_capacity == 0 || heat_capacity == null) - heat_capacity = 1 - removed.temperature = (removed.temperature*heat_capacity - 75000)/heat_capacity - env.merge(removed) - air_update_turf() - investigate_log("Experimentor has released cold air.", INVESTIGATE_EXPERIMENTOR) - ejectItem(TRUE) - else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) - visible_message("[src] malfunctions, releasing a flurry of chilly air as [exp_on] pops out!") - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(0, loc) - smoke.start() - ejectItem() - //////////////////////////////////////////////////////////////////////////////////////////////// - if(exp == SCANTYPE_OBLITERATE) - visible_message("[exp_on] activates the crushing mechanism, [exp_on] is destroyed!") - if(linked_console.linked_lathe) - var/datum/component/material_container/linked_materials = linked_console.linked_lathe.GetComponent(/datum/component/material_container) - for(var/material in exp_on.materials) - linked_materials.insert_amount( min((linked_materials.max_amount - linked_materials.total_amount), (exp_on.materials[material])), material) - if(prob(EFFECT_PROB_LOW) && criticalReaction) - visible_message("[src]'s crushing mechanism slowly and smoothly descends, flattening the [exp_on]!") - new /obj/item/stack/sheet/plasteel(get_turf(pick(oview(1,src)))) - else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) - visible_message("[src]'s crusher goes way too many levels too high, crushing right through space-time!") - playsound(src, 'sound/effects/supermatter.ogg', 50, 1, -3) - investigate_log("Experimentor has triggered the 'throw things' reaction.", INVESTIGATE_EXPERIMENTOR) - for(var/atom/movable/AM in oview(7,src)) - if(!AM.anchored) - AM.throw_at(src,10,1) - else if(prob(EFFECT_PROB_LOW-badThingCoeff)) - visible_message("[src]'s crusher goes one level too high, crushing right into space-time!") - playsound(src, 'sound/effects/supermatter.ogg', 50, 1, -3) - investigate_log("Experimentor has triggered the 'minor throw things' reaction.", INVESTIGATE_EXPERIMENTOR) - var/list/throwAt = list() - for(var/atom/movable/AM in oview(7,src)) - if(!AM.anchored) - throwAt.Add(AM) - for(var/counter = 1, counter < throwAt.len, ++counter) - var/atom/movable/cast = throwAt[counter] - cast.throw_at(pick(throwAt),10,1) - ejectItem(TRUE) - //////////////////////////////////////////////////////////////////////////////////////////////// - if(exp == FAIL) - var/a = pick("rumbles","shakes","vibrates","shudders") - var/b = pick("crushes","spins","viscerates","smashes","insults") - visible_message("[exp_on] [a], and [b], the experiment was a failure.") - - if(exp == SCANTYPE_DISCOVER) - visible_message("[src] scans the [exp_on], revealing its true nature!") - playsound(src, 'sound/effects/supermatter.ogg', 50, 3, -1) - var/obj/item/relic/R = loaded_item - R.reveal() - investigate_log("Experimentor has revealed a relic with [R.realProc] effect.", INVESTIGATE_EXPERIMENTOR) - ejectItem() - - //Global reactions - if(prob(EFFECT_PROB_VERYLOW-badThingCoeff) && loaded_item) - var/globalMalf = rand(1,100) - if(globalMalf < 15) - visible_message("[src]'s onboard detection system has malfunctioned!") - item_reactions["[exp_on.type]"] = pick(SCANTYPE_POKE,SCANTYPE_IRRADIATE,SCANTYPE_GAS,SCANTYPE_HEAT,SCANTYPE_COLD,SCANTYPE_OBLITERATE) - ejectItem() - if(globalMalf > 16 && globalMalf < 35) - visible_message("[src] melts [exp_on], ian-izing the air around it!") - throwSmoke(loc) - if(trackedIan) - throwSmoke(trackedIan.loc) - trackedIan.forceMove(loc) - investigate_log("Experimentor has stolen Ian!", INVESTIGATE_EXPERIMENTOR) //...if anyone ever fixes it... - else - new /mob/living/simple_animal/pet/dog/corgi(loc) - investigate_log("Experimentor has spawned a new corgi.", INVESTIGATE_EXPERIMENTOR) - ejectItem(TRUE) - if(globalMalf > 36 && globalMalf < 50) - visible_message("Experimentor draws the life essence of those nearby!") - for(var/mob/living/m in view(4,src)) - to_chat(m, "You feel your flesh being torn from you, mists of blood drifting to [src]!") - m.apply_damage(50, BRUTE, BODY_ZONE_CHEST) - investigate_log("Experimentor has taken 50 brute a blood sacrifice from [m]", INVESTIGATE_EXPERIMENTOR) - if(globalMalf > 51 && globalMalf < 75) - visible_message("[src] encounters a run-time error!") - throwSmoke(loc) - if(trackedRuntime) - throwSmoke(trackedRuntime.loc) - trackedRuntime.forceMove(drop_location()) - investigate_log("Experimentor has stolen Runtime!", INVESTIGATE_EXPERIMENTOR) - else - new /mob/living/simple_animal/pet/cat(loc) - investigate_log("Experimentor failed to steal runtime, and instead spawned a new cat.", INVESTIGATE_EXPERIMENTOR) - ejectItem(TRUE) - if(globalMalf > 76) - visible_message("[src] begins to smoke and hiss, shaking violently!") - use_power(500000) - investigate_log("Experimentor has drained power from its APC", INVESTIGATE_EXPERIMENTOR) - - addtimer(CALLBACK(src, .proc/reset_exp), resetTime) - -/obj/machinery/rnd/experimentor/proc/reset_exp() - update_icon() - recentlyExperimented = FALSE - -/obj/machinery/rnd/experimentor/update_icon() - icon_state = "h_lathe" - -/obj/machinery/rnd/experimentor/proc/warn_admins(user, ReactionName) - var/turf/T = get_turf(user) - message_admins("Experimentor reaction: [ReactionName] generated by [ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(T)]") - log_game("Experimentor reaction: [ReactionName] generated by [key_name(user)] in [AREACOORD(T)]") - -#undef SCANTYPE_POKE -#undef SCANTYPE_IRRADIATE -#undef SCANTYPE_GAS -#undef SCANTYPE_HEAT -#undef SCANTYPE_COLD -#undef SCANTYPE_OBLITERATE -#undef SCANTYPE_DISCOVER - -#undef EFFECT_PROB_VERYLOW -#undef EFFECT_PROB_LOW -#undef EFFECT_PROB_MEDIUM -#undef EFFECT_PROB_HIGH -#undef EFFECT_PROB_VERYHIGH - -#undef FAIL - - -//////////////////////////////////SPECIAL ITEMS//////////////////////////////////////// - -/obj/item/relic - name = "strange object" - desc = "What mysteries could this hold?" - icon = 'icons/obj/assemblies.dmi' - var/realName = "defined object" - var/revealed = FALSE - var/realProc - var/cooldownMax = 60 - var/cooldown - -/obj/item/relic/Initialize() - . = ..() - icon_state = pick("shock_kit","armor-igniter-analyzer","infra-igniter0","infra-igniter1","radio-multitool","prox-radio1","radio-radio","timer-multitool0","radio-igniter-tank") - realName = "[pick("broken","twisted","spun","improved","silly","regular","badly made")] [pick("device","object","toy","illegal tech","weapon")]" - - -/obj/item/relic/proc/reveal() - if(revealed) //Re-rolling your relics seems a bit overpowered, yes? - return - revealed = TRUE - name = realName - cooldownMax = rand(60,300) - realProc = pick("teleport","explode","rapidDupe","petSpray","flash","clean","corgicannon") - -/obj/item/relic/attack_self(mob/user) - if(revealed) - if(cooldown) - to_chat(user, "[src] does not react!") - return - else if(loc == user) - cooldown = TRUE - call(src,realProc)(user) - addtimer(CALLBACK(src, .proc/cd), cooldownMax) - else - to_chat(user, "You aren't quite sure what to do with this yet.") - -/obj/item/relic/proc/cd() - cooldown = FALSE - -//////////////// RELIC PROCS ///////////////////////////// - -/obj/item/relic/proc/throwSmoke(turf/where) - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(0, get_turf(where)) - smoke.start() - -/obj/item/relic/proc/corgicannon(mob/user) - playsound(src, "sparks", rand(25,50), 1) - var/mob/living/simple_animal/pet/dog/corgi/C = new/mob/living/simple_animal/pet/dog/corgi(get_turf(user)) - C.throw_at(pick(oview(10,user)), 10, rand(3,8), callback = CALLBACK(src, .proc/throwSmoke, C)) - warn_admins(user, "Corgi Cannon", 0) - -/obj/item/relic/proc/clean(mob/user) - playsound(src, "sparks", rand(25,50), 1) - var/obj/item/grenade/chem_grenade/cleaner/CL = new/obj/item/grenade/chem_grenade/cleaner(get_turf(user)) - CL.prime() - warn_admins(user, "Smoke", 0) - -/obj/item/relic/proc/flash(mob/user) - playsound(src, "sparks", rand(25,50), 1) - var/obj/item/grenade/flashbang/CB = new/obj/item/grenade/flashbang(user.loc) - CB.prime() - warn_admins(user, "Flash") - -/obj/item/relic/proc/petSpray(mob/user) - var/message = "[src] begins to shake, and in the distance the sound of rampaging animals arises!" - visible_message(message) - to_chat(user, message) - var/animals = rand(1,25) - var/counter - var/list/valid_animals = list(/mob/living/simple_animal/parrot, /mob/living/simple_animal/butterfly, /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/crab, /mob/living/simple_animal/pet/fox, /mob/living/simple_animal/hostile/lizard, /mob/living/simple_animal/mouse, /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/poison/bees, /mob/living/simple_animal/hostile/carp) - for(counter = 1; counter < animals; counter++) - var/mobType = pick(valid_animals) - new mobType(get_turf(src)) - warn_admins(user, "Mass Mob Spawn") - if(prob(60)) - to_chat(user, "[src] falls apart!") - qdel(src) - -/obj/item/relic/proc/rapidDupe(mob/user) - audible_message("[src] emits a loud pop!") - var/list/dupes = list() - var/counter - var/max = rand(5,10) - for(counter = 1; counter < max; counter++) - var/obj/item/relic/R = new type(get_turf(src)) - R.name = name - R.desc = desc - R.realName = realName - R.realProc = realProc - R.revealed = TRUE - dupes |= R - R.throw_at(pick(oview(7,get_turf(src))),10,1) - counter = 0 - QDEL_LIST_IN(dupes, rand(10, 100)) - warn_admins(user, "Rapid duplicator", 0) - -/obj/item/relic/proc/explode(mob/user) - to_chat(user, "[src] begins to heat up!") - addtimer(CALLBACK(src, .proc/do_explode, user), rand(35, 100)) - -/obj/item/relic/proc/do_explode(mob/user) - if(loc == user) - visible_message("\The [src]'s top opens, releasing a powerful blast!") - explosion(user.loc, 0, rand(1,5), rand(1,5), rand(1,5), rand(1,5), flame_range = 2) - warn_admins(user, "Explosion") - qdel(src) //Comment this line to produce a light grenade (the bomb that keeps on exploding when used)!! - -/obj/item/relic/proc/teleport(mob/user) - to_chat(user, "[src] begins to vibrate!") - addtimer(CALLBACK(src, .proc/do_the_teleport, user), rand(10, 30)) - -/obj/item/relic/proc/do_the_teleport(mob/user) - var/turf/userturf = get_turf(user) - if(loc == user && !is_centcom_level(userturf.z)) //Because Nuke Ops bringing this back on their shuttle, then looting the ERT area is 2fun4you! - visible_message("[src] twists and bends, relocating itself!") - throwSmoke(userturf) - do_teleport(user, userturf, 8, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) - throwSmoke(get_turf(user)) - warn_admins(user, "Teleport", 0) - -//Admin Warning proc for relics -/obj/item/relic/proc/warn_admins(mob/user, RelicType, priority = 1) - var/turf/T = get_turf(src) - var/log_msg = "[RelicType] relic used by [key_name(user)] in [AREACOORD(T)]" - if(priority) //For truly dangerous relics that may need an admin's attention. BWOINK! - message_admins("[RelicType] relic activated by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]") - log_game(log_msg) - investigate_log(log_msg, "experimentor") +//this is designed to replace the destructive analyzer + +//NEEDS MAJOR CODE CLEANUP + +#define SCANTYPE_POKE 1 +#define SCANTYPE_IRRADIATE 2 +#define SCANTYPE_GAS 3 +#define SCANTYPE_HEAT 4 +#define SCANTYPE_COLD 5 +#define SCANTYPE_OBLITERATE 6 +#define SCANTYPE_DISCOVER 7 + +#define EFFECT_PROB_VERYLOW 20 +#define EFFECT_PROB_LOW 35 +#define EFFECT_PROB_MEDIUM 50 +#define EFFECT_PROB_HIGH 75 +#define EFFECT_PROB_VERYHIGH 95 + +#define FAIL 8 +/obj/machinery/rnd/experimentor + name = "\improper E.X.P.E.R.I-MENTOR" + desc = "A \"replacement\" for the destructive analyzer with a slight tendency to catastrophically fail." + icon = 'icons/obj/machines/heavy_lathe.dmi' + icon_state = "h_lathe" + density = TRUE + use_power = IDLE_POWER_USE + circuit = /obj/item/circuitboard/machine/experimentor + var/recentlyExperimented = 0 + var/mob/trackedIan + var/mob/trackedRuntime + var/badThingCoeff = 0 + var/resetTime = 15 + var/cloneMode = FALSE + var/list/item_reactions = list() + var/list/valid_items = list() //valid items for special reactions like transforming + var/list/critical_items = list() //items that can cause critical reactions + +/obj/machinery/rnd/experimentor/proc/ConvertReqString2List(list/source_list) + var/list/temp_list = params2list(source_list) + for(var/O in temp_list) + temp_list[O] = text2num(temp_list[O]) + return temp_list + + +/obj/machinery/rnd/experimentor/proc/SetTypeReactions() + for(var/I in typesof(/obj/item)) + if(ispath(I, /obj/item/relic)) + item_reactions["[I]"] = SCANTYPE_DISCOVER + else + item_reactions["[I]"] = pick(SCANTYPE_POKE,SCANTYPE_IRRADIATE,SCANTYPE_GAS,SCANTYPE_HEAT,SCANTYPE_COLD,SCANTYPE_OBLITERATE) + + if(ispath(I, /obj/item/stock_parts) || ispath(I, /obj/item/grenade/chem_grenade) || ispath(I, /obj/item/kitchen)) + var/obj/item/tempCheck = I + if(initial(tempCheck.icon_state) != null) //check it's an actual usable item, in a hacky way + if(ispath(I, /obj/item/grenade/chem_grenade/tuberculosis)) + continue + valid_items["[I]"] += 15 + + if(ispath(I, /obj/item/reagent_containers/food)) + var/obj/item/tempCheck = I + if(initial(tempCheck.icon_state) != null) //check it's an actual usable item, in a hacky way + valid_items["[I]"] += rand(1,4) + + if(ispath(I, /obj/item/construction/rcd) || ispath(I, /obj/item/grenade) || ispath(I, /obj/item/aicard) || ispath(I, /obj/item/storage/backpack/holding) || ispath(I, /obj/item/slime_extract) || ispath(I, /obj/item/onetankbomb) || ispath(I, /obj/item/transfer_valve)) + var/obj/item/tempCheck = I + if(initial(tempCheck.icon_state) != null) + critical_items += I + +/obj/machinery/rnd/experimentor/Initialize() + . = ..() + + trackedIan = locate(/mob/living/simple_animal/pet/dog/corgi/Ian) in GLOB.mob_living_list + trackedRuntime = locate(/mob/living/simple_animal/pet/cat/Runtime) in GLOB.mob_living_list + SetTypeReactions() + +/obj/machinery/rnd/experimentor/RefreshParts() + for(var/obj/item/stock_parts/manipulator/M in component_parts) + if(resetTime > 0 && (resetTime - M.rating) >= 1) + resetTime -= M.rating + for(var/obj/item/stock_parts/scanning_module/M in component_parts) + badThingCoeff += M.rating*2 + for(var/obj/item/stock_parts/micro_laser/M in component_parts) + badThingCoeff += M.rating + +/obj/machinery/rnd/experimentor/proc/checkCircumstances(obj/item/O) + //snowflake check to only take "made" bombs + if(istype(O, /obj/item/transfer_valve)) + var/obj/item/transfer_valve/T = O + if(!T.tank_one || !T.tank_two || !T.attached_device) + return FALSE + return TRUE + +/obj/machinery/rnd/experimentor/Insert_Item(obj/item/O, mob/user) + if(user.a_intent != INTENT_HARM) + . = 1 + if(!is_insertion_ready(user)) + return + if(!user.transferItemToLoc(O, src)) + return + loaded_item = O + to_chat(user, "You add [O] to the machine.") + flick("h_lathe_load", src) + +/obj/machinery/rnd/experimentor/default_deconstruction_crowbar(obj/item/O) + ejectItem() + . = ..(O) + +/obj/machinery/rnd/experimentor/ui_interact(mob/user) + var/list/dat = list("
                ") + if(!linked_console) + dat += "Scan for R&D Console" + if(loaded_item) + dat += "Loaded Item: [loaded_item]" + + dat += "
                Available tests:" + dat += "Poke" + dat += "Irradiate" + dat += "Gas" + dat += "Burn" + dat += "Freeze" + dat += "Destroy
                " + if(istype(loaded_item,/obj/item/relic)) + dat += "Discover" + dat += "Eject" + var/list/listin = techweb_item_boost_check(src) + if(listin) + var/list/output = list("Research Boost Data:") + var/list/res = list("Already researched:") + var/list/boosted = list("Already boosted:") + for(var/node_id in listin) + var/datum/techweb_node/N = SSresearch.techweb_node_by_id(node_id) + var/str = "[N.display_name]: [listin[N]] points." + if(SSresearch.science_tech.researched_nodes[N.id]) + res += str + else if(SSresearch.science_tech.boosted_nodes[N.id]) + boosted += str + if(SSresearch.science_tech.visible_nodes[N.id]) //JOY OF DISCOVERY! + output += str + output += boosted + res + dat += output + else + dat += "Nothing loaded." + dat += "Refresh" + dat += "Close
                " + var/datum/browser/popup = new(user, "experimentor","Experimentor", 700, 400, src) + popup.set_content(dat.Join("
                ")) + popup.open() + onclose(user, "experimentor") + +/obj/machinery/rnd/experimentor/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + + var/scantype = href_list["function"] + var/obj/item/process = locate(href_list["item"]) in src + + if(href_list["close"]) + usr << browse(null, "window=experimentor") + return + if(scantype == "search") + var/obj/machinery/computer/rdconsole/D = locate(/obj/machinery/computer/rdconsole) in oview(3,src) + if(D) + linked_console = D + else if(scantype == "eject") + ejectItem() + else if(scantype == "refresh") + updateUsrDialog() + else + if(recentlyExperimented) + to_chat(usr, "[src] has been used too recently!") + else if(!loaded_item) + to_chat(usr, "[src] is not currently loaded!") + else if(!process || process != loaded_item) //Interface exploit protection (such as hrefs or swapping items with interface set to old item) + to_chat(usr, "Interface failure detected in [src]. Please try again.") + else + var/dotype + if(text2num(scantype) == SCANTYPE_DISCOVER) + dotype = SCANTYPE_DISCOVER + else + dotype = matchReaction(process,scantype) + experiment(dotype,process) + use_power(750) + if(dotype != FAIL) + var/list/nodes = techweb_item_boost_check(process) + var/picked = pickweight(nodes) //This should work. + if(linked_console) + linked_console.stored_research.boost_with_path(SSresearch.techweb_node_by_id(picked), process.type) + updateUsrDialog() + +/obj/machinery/rnd/experimentor/proc/matchReaction(matching,reaction) + var/obj/item/D = matching + if(D) + if(item_reactions.Find("[D.type]")) + var/tor = item_reactions["[D.type]"] + if(tor == text2num(reaction)) + return tor + else + return FAIL + else + return FAIL + else + return FAIL + +/obj/machinery/rnd/experimentor/proc/ejectItem(delete=FALSE) + if(loaded_item) + if(cloneMode) + visible_message("A duplicate [loaded_item] pops out!") + var/type_to_make = loaded_item.type + new type_to_make(get_turf(pick(oview(1,src)))) + cloneMode = FALSE + return + var/turf/dropturf = get_turf(pick(view(1,src))) + if(!dropturf) //Failsafe to prevent the object being lost in the void forever. + dropturf = drop_location() + loaded_item.forceMove(dropturf) + if(delete) + qdel(loaded_item) + loaded_item = null + +/obj/machinery/rnd/experimentor/proc/throwSmoke(turf/where) + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(0, where) + smoke.start() + + +/obj/machinery/rnd/experimentor/proc/experiment(exp,obj/item/exp_on) + recentlyExperimented = 1 + icon_state = "h_lathe_wloop" + var/chosenchem + var/criticalReaction = (exp_on.type in critical_items) ? TRUE : FALSE + //////////////////////////////////////////////////////////////////////////////////////////////// + if(exp == SCANTYPE_POKE) + visible_message("[src] prods at [exp_on] with mechanical arms.") + if(prob(EFFECT_PROB_LOW) && criticalReaction) + visible_message("[exp_on] is gripped in just the right way, enhancing its focus.") + badThingCoeff++ + else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) + visible_message("[src] malfunctions and destroys [exp_on], lashing its arms out at nearby people!") + for(var/mob/living/m in oview(1, src)) + m.apply_damage(15, BRUTE, pick(BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_PRECISE_GROIN)) + investigate_log("Experimentor dealt minor brute to [m].", INVESTIGATE_EXPERIMENTOR) + ejectItem(TRUE) + else if(prob(EFFECT_PROB_LOW-badThingCoeff)) + visible_message("[src] malfunctions!") + exp = SCANTYPE_OBLITERATE + else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) + visible_message("[src] malfunctions, throwing the [exp_on]!") + var/mob/living/target = locate(/mob/living) in oview(7,src) + if(target) + var/obj/item/throwing = loaded_item + investigate_log("Experimentor has thrown [loaded_item] at [key_name(target)]", INVESTIGATE_EXPERIMENTOR) + ejectItem() + if(throwing) + throwing.throw_at(target, 10, 1) + //////////////////////////////////////////////////////////////////////////////////////////////// + if(exp == SCANTYPE_IRRADIATE) + visible_message("[src] reflects radioactive rays at [exp_on]!") + if(prob(EFFECT_PROB_LOW) && criticalReaction) + visible_message("[exp_on] has activated an unknown subroutine!") + cloneMode = TRUE + investigate_log("Experimentor has made a clone of [exp_on]", INVESTIGATE_EXPERIMENTOR) + ejectItem() + else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) + visible_message("[src] malfunctions, melting [exp_on] and leaking radiation!") + radiation_pulse(src, 500) + ejectItem(TRUE) + else if(prob(EFFECT_PROB_LOW-badThingCoeff)) + visible_message("[src] malfunctions, spewing toxic waste!") + for(var/turf/T in oview(1, src)) + if(!T.density) + if(prob(EFFECT_PROB_VERYHIGH) && !(locate(/obj/effect/decal/cleanable/greenglow) in T)) + var/obj/effect/decal/cleanable/reagentdecal = new/obj/effect/decal/cleanable/greenglow(T) + reagentdecal.reagents.add_reagent(/datum/reagent/radium, 7) + else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) + var/savedName = "[exp_on]" + ejectItem(TRUE) + var/newPath = text2path(pickweight(valid_items)) + loaded_item = new newPath(src) + visible_message("[src] malfunctions, transforming [savedName] into [loaded_item]!") + investigate_log("Experimentor has transformed [savedName] into [loaded_item]", INVESTIGATE_EXPERIMENTOR) + if(istype(loaded_item, /obj/item/grenade/chem_grenade)) + var/obj/item/grenade/chem_grenade/CG = loaded_item + CG.prime() + ejectItem() + //////////////////////////////////////////////////////////////////////////////////////////////// + if(exp == SCANTYPE_GAS) + visible_message("[src] fills its chamber with gas, [exp_on] included.") + if(prob(EFFECT_PROB_LOW) && criticalReaction) + visible_message("[exp_on] achieves the perfect mix!") + new /obj/item/stack/sheet/mineral/plasma(get_turf(pick(oview(1,src)))) + else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) + visible_message("[src] destroys [exp_on], leaking dangerous gas!") + chosenchem = pick(/datum/reagent/carbon,/datum/reagent/radium,/datum/reagent/toxin, + /datum/reagent/consumable/condensedcapsaicin,/datum/reagent/drug/mushroomhallucinogen, + /datum/reagent/drug/space_drugs,/datum/reagent/consumable/ethanol,/datum/reagent/consumable/ethanol/beepsky_smash) + var/datum/reagents/R = new/datum/reagents(50) + R.my_atom = src + R.add_reagent(chosenchem , 50) + investigate_log("Experimentor has released [chosenchem] smoke.", INVESTIGATE_EXPERIMENTOR) + var/datum/effect_system/smoke_spread/chem/smoke = new + smoke.set_up(R, 0, src, silent = TRUE) + playsound(src, 'sound/effects/smoke.ogg', 50, 1, -3) + smoke.start() + qdel(R) + ejectItem(TRUE) + else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) + visible_message("[src]'s chemical chamber has sprung a leak!") + chosenchem = pick(/datum/reagent/mutationtoxin,/datum/reagent/nanomachines,/datum/reagent/toxin/acid) + var/datum/reagents/R = new/datum/reagents(50) + R.my_atom = src + R.add_reagent(chosenchem , 50) + var/datum/effect_system/smoke_spread/chem/smoke = new + smoke.set_up(R, 0, src, silent = TRUE) + playsound(src, 'sound/effects/smoke.ogg', 50, 1, -3) + smoke.start() + qdel(R) + ejectItem(TRUE) + warn_admins(usr, "[chosenchem] smoke") + investigate_log("Experimentor has released [chosenchem] smoke!", INVESTIGATE_EXPERIMENTOR) + else if(prob(EFFECT_PROB_LOW-badThingCoeff)) + visible_message("[src] malfunctions, spewing harmless gas.") + throwSmoke(loc) + else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) + visible_message("[src] melts [exp_on], ionizing the air around it!") + empulse(loc, 4, 6) + investigate_log("Experimentor has generated an Electromagnetic Pulse.", INVESTIGATE_EXPERIMENTOR) + ejectItem(TRUE) + //////////////////////////////////////////////////////////////////////////////////////////////// + if(exp == SCANTYPE_HEAT) + visible_message("[src] raises [exp_on]'s temperature.") + if(prob(EFFECT_PROB_LOW) && criticalReaction) + visible_message("[src]'s emergency coolant system gives off a small ding!") + playsound(src, 'sound/machines/ding.ogg', 50, 1) + var/obj/item/reagent_containers/food/drinks/coffee/C = new /obj/item/reagent_containers/food/drinks/coffee(get_turf(pick(oview(1,src)))) + chosenchem = pick(/datum/reagent/toxin/plasma,/datum/reagent/consumable/capsaicin,/datum/reagent/consumable/ethanol) + C.reagents.remove_any(25) + C.reagents.add_reagent(chosenchem , 50) + C.name = "Cup of Suspicious Liquid" + C.desc = "It has a large hazard symbol printed on the side in fading ink." + investigate_log("Experimentor has made a cup of [chosenchem] coffee.", INVESTIGATE_EXPERIMENTOR) + else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) + var/turf/start = get_turf(src) + var/mob/M = locate(/mob/living) in view(src, 3) + var/turf/MT = get_turf(M) + if(MT) + visible_message("[src] dangerously overheats, launching a flaming fuel orb!") + investigate_log("Experimentor has launched a fireball at [M]!", INVESTIGATE_EXPERIMENTOR) + var/obj/item/projectile/magic/aoe/fireball/FB = new /obj/item/projectile/magic/aoe/fireball(start) + FB.preparePixelProjectile(MT, start) + FB.fire() + else if(prob(EFFECT_PROB_LOW-badThingCoeff)) + visible_message("[src] malfunctions, melting [exp_on] and releasing a burst of flame!") + explosion(loc, -1, 0, 0, 0, 0, flame_range = 2) + investigate_log("Experimentor started a fire.", INVESTIGATE_EXPERIMENTOR) + ejectItem(TRUE) + else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) + visible_message("[src] malfunctions, melting [exp_on] and leaking hot air!") + var/datum/gas_mixture/env = loc.return_air() + var/transfer_moles = 0.25 * env.total_moles() + var/datum/gas_mixture/removed = env.remove(transfer_moles) + if(removed) + var/heat_capacity = removed.heat_capacity() + if(heat_capacity == 0 || heat_capacity == null) + heat_capacity = 1 + removed.temperature = min((removed.temperature*heat_capacity + 100000)/heat_capacity, 1000) + env.merge(removed) + air_update_turf() + investigate_log("Experimentor has released hot air.", INVESTIGATE_EXPERIMENTOR) + ejectItem(TRUE) + else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) + visible_message("[src] malfunctions, activating its emergency coolant systems!") + throwSmoke(loc) + for(var/mob/living/m in oview(1, src)) + m.apply_damage(5, BURN, pick(BODY_ZONE_HEAD,BODY_ZONE_CHEST,BODY_ZONE_PRECISE_GROIN)) + investigate_log("Experimentor has dealt minor burn damage to [key_name(m)]", INVESTIGATE_EXPERIMENTOR) + ejectItem() + //////////////////////////////////////////////////////////////////////////////////////////////// + if(exp == SCANTYPE_COLD) + visible_message("[src] lowers [exp_on]'s temperature.") + if(prob(EFFECT_PROB_LOW) && criticalReaction) + visible_message("[src]'s emergency coolant system gives off a small ding!") + var/obj/item/reagent_containers/food/drinks/coffee/C = new /obj/item/reagent_containers/food/drinks/coffee(get_turf(pick(oview(1,src)))) + playsound(src, 'sound/machines/ding.ogg', 50, 1) //Ding! Your death coffee is ready! + chosenchem = pick(/datum/reagent/uranium,/datum/reagent/consumable/frostoil,/datum/reagent/medicine/ephedrine) + C.reagents.remove_any(25) + C.reagents.add_reagent(chosenchem , 50) + C.name = "Cup of Suspicious Liquid" + C.desc = "It has a large hazard symbol printed on the side in fading ink." + investigate_log("Experimentor has made a cup of [chosenchem] coffee.", INVESTIGATE_EXPERIMENTOR) + else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) + visible_message("[src] malfunctions, shattering [exp_on] and releasing a dangerous cloud of coolant!") + var/datum/reagents/R = new/datum/reagents(50) + R.my_atom = src + R.add_reagent(/datum/reagent/consumable/frostoil, 50) + investigate_log("Experimentor has released frostoil gas.", INVESTIGATE_EXPERIMENTOR) + var/datum/effect_system/smoke_spread/chem/smoke = new + smoke.set_up(R, 0, src, silent = TRUE) + playsound(src, 'sound/effects/smoke.ogg', 50, 1, -3) + smoke.start() + qdel(R) + ejectItem(TRUE) + else if(prob(EFFECT_PROB_LOW-badThingCoeff)) + visible_message("[src] malfunctions, shattering [exp_on] and leaking cold air!") + var/datum/gas_mixture/env = loc.return_air() + var/transfer_moles = 0.25 * env.total_moles() + var/datum/gas_mixture/removed = env.remove(transfer_moles) + if(removed) + var/heat_capacity = removed.heat_capacity() + if(heat_capacity == 0 || heat_capacity == null) + heat_capacity = 1 + removed.temperature = (removed.temperature*heat_capacity - 75000)/heat_capacity + env.merge(removed) + air_update_turf() + investigate_log("Experimentor has released cold air.", INVESTIGATE_EXPERIMENTOR) + ejectItem(TRUE) + else if(prob(EFFECT_PROB_MEDIUM-badThingCoeff)) + visible_message("[src] malfunctions, releasing a flurry of chilly air as [exp_on] pops out!") + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(0, loc) + smoke.start() + ejectItem() + //////////////////////////////////////////////////////////////////////////////////////////////// + if(exp == SCANTYPE_OBLITERATE) + visible_message("[exp_on] activates the crushing mechanism, [exp_on] is destroyed!") + if(linked_console.linked_lathe) + var/datum/component/material_container/linked_materials = linked_console.linked_lathe.GetComponent(/datum/component/material_container) + for(var/material in exp_on.materials) + linked_materials.insert_amount( min((linked_materials.max_amount - linked_materials.total_amount), (exp_on.materials[material])), material) + if(prob(EFFECT_PROB_LOW) && criticalReaction) + visible_message("[src]'s crushing mechanism slowly and smoothly descends, flattening the [exp_on]!") + new /obj/item/stack/sheet/plasteel(get_turf(pick(oview(1,src)))) + else if(prob(EFFECT_PROB_VERYLOW-badThingCoeff)) + visible_message("[src]'s crusher goes way too many levels too high, crushing right through space-time!") + playsound(src, 'sound/effects/supermatter.ogg', 50, 1, -3) + investigate_log("Experimentor has triggered the 'throw things' reaction.", INVESTIGATE_EXPERIMENTOR) + for(var/atom/movable/AM in oview(7,src)) + if(!AM.anchored) + AM.throw_at(src,10,1) + else if(prob(EFFECT_PROB_LOW-badThingCoeff)) + visible_message("[src]'s crusher goes one level too high, crushing right into space-time!") + playsound(src, 'sound/effects/supermatter.ogg', 50, 1, -3) + investigate_log("Experimentor has triggered the 'minor throw things' reaction.", INVESTIGATE_EXPERIMENTOR) + var/list/throwAt = list() + for(var/atom/movable/AM in oview(7,src)) + if(!AM.anchored) + throwAt.Add(AM) + for(var/counter = 1, counter < throwAt.len, ++counter) + var/atom/movable/cast = throwAt[counter] + cast.throw_at(pick(throwAt),10,1) + ejectItem(TRUE) + //////////////////////////////////////////////////////////////////////////////////////////////// + if(exp == FAIL) + var/a = pick("rumbles","shakes","vibrates","shudders") + var/b = pick("crushes","spins","viscerates","smashes","insults") + visible_message("[exp_on] [a], and [b], the experiment was a failure.") + + if(exp == SCANTYPE_DISCOVER) + visible_message("[src] scans the [exp_on], revealing its true nature!") + playsound(src, 'sound/effects/supermatter.ogg', 50, 3, -1) + var/obj/item/relic/R = loaded_item + R.reveal() + investigate_log("Experimentor has revealed a relic with [R.realProc] effect.", INVESTIGATE_EXPERIMENTOR) + ejectItem() + + //Global reactions + if(prob(EFFECT_PROB_VERYLOW-badThingCoeff) && loaded_item) + var/globalMalf = rand(1,100) + if(globalMalf < 15) + visible_message("[src]'s onboard detection system has malfunctioned!") + item_reactions["[exp_on.type]"] = pick(SCANTYPE_POKE,SCANTYPE_IRRADIATE,SCANTYPE_GAS,SCANTYPE_HEAT,SCANTYPE_COLD,SCANTYPE_OBLITERATE) + ejectItem() + if(globalMalf > 16 && globalMalf < 35) + visible_message("[src] melts [exp_on], ian-izing the air around it!") + throwSmoke(loc) + if(trackedIan) + throwSmoke(trackedIan.loc) + trackedIan.forceMove(loc) + investigate_log("Experimentor has stolen Ian!", INVESTIGATE_EXPERIMENTOR) //...if anyone ever fixes it... + else + new /mob/living/simple_animal/pet/dog/corgi(loc) + investigate_log("Experimentor has spawned a new corgi.", INVESTIGATE_EXPERIMENTOR) + ejectItem(TRUE) + if(globalMalf > 36 && globalMalf < 50) + visible_message("Experimentor draws the life essence of those nearby!") + for(var/mob/living/m in view(4,src)) + to_chat(m, "You feel your flesh being torn from you, mists of blood drifting to [src]!") + m.apply_damage(50, BRUTE, BODY_ZONE_CHEST) + investigate_log("Experimentor has taken 50 brute a blood sacrifice from [m]", INVESTIGATE_EXPERIMENTOR) + if(globalMalf > 51 && globalMalf < 75) + visible_message("[src] encounters a run-time error!") + throwSmoke(loc) + if(trackedRuntime) + throwSmoke(trackedRuntime.loc) + trackedRuntime.forceMove(drop_location()) + investigate_log("Experimentor has stolen Runtime!", INVESTIGATE_EXPERIMENTOR) + else + new /mob/living/simple_animal/pet/cat(loc) + investigate_log("Experimentor failed to steal runtime, and instead spawned a new cat.", INVESTIGATE_EXPERIMENTOR) + ejectItem(TRUE) + if(globalMalf > 76) + visible_message("[src] begins to smoke and hiss, shaking violently!") + use_power(500000) + investigate_log("Experimentor has drained power from its APC", INVESTIGATE_EXPERIMENTOR) + + addtimer(CALLBACK(src, .proc/reset_exp), resetTime) + +/obj/machinery/rnd/experimentor/proc/reset_exp() + update_icon() + recentlyExperimented = FALSE + +/obj/machinery/rnd/experimentor/update_icon() + icon_state = "h_lathe" + +/obj/machinery/rnd/experimentor/proc/warn_admins(user, ReactionName) + var/turf/T = get_turf(user) + message_admins("Experimentor reaction: [ReactionName] generated by [ADMIN_LOOKUPFLW(user)] at [ADMIN_VERBOSEJMP(T)]") + log_game("Experimentor reaction: [ReactionName] generated by [key_name(user)] in [AREACOORD(T)]") + +#undef SCANTYPE_POKE +#undef SCANTYPE_IRRADIATE +#undef SCANTYPE_GAS +#undef SCANTYPE_HEAT +#undef SCANTYPE_COLD +#undef SCANTYPE_OBLITERATE +#undef SCANTYPE_DISCOVER + +#undef EFFECT_PROB_VERYLOW +#undef EFFECT_PROB_LOW +#undef EFFECT_PROB_MEDIUM +#undef EFFECT_PROB_HIGH +#undef EFFECT_PROB_VERYHIGH + +#undef FAIL + + +//////////////////////////////////SPECIAL ITEMS//////////////////////////////////////// + +/obj/item/relic + name = "strange object" + desc = "What mysteries could this hold?" + icon = 'icons/obj/assemblies.dmi' + var/realName = "defined object" + var/revealed = FALSE + var/realProc + var/cooldownMax = 60 + var/cooldown + +/obj/item/relic/Initialize() + . = ..() + icon_state = pick("shock_kit","armor-igniter-analyzer","infra-igniter0","infra-igniter1","radio-multitool","prox-radio1","radio-radio","timer-multitool0","radio-igniter-tank") + realName = "[pick("broken","twisted","spun","improved","silly","regular","badly made")] [pick("device","object","toy","illegal tech","weapon")]" + + +/obj/item/relic/proc/reveal() + if(revealed) //Re-rolling your relics seems a bit overpowered, yes? + return + revealed = TRUE + name = realName + cooldownMax = rand(60,300) + realProc = pick("teleport","explode","rapidDupe","petSpray","flash","clean","corgicannon") + +/obj/item/relic/attack_self(mob/user) + if(revealed) + if(cooldown) + to_chat(user, "[src] does not react!") + return + else if(loc == user) + cooldown = TRUE + call(src,realProc)(user) + addtimer(CALLBACK(src, .proc/cd), cooldownMax) + else + to_chat(user, "You aren't quite sure what to do with this yet.") + +/obj/item/relic/proc/cd() + cooldown = FALSE + +//////////////// RELIC PROCS ///////////////////////////// + +/obj/item/relic/proc/throwSmoke(turf/where) + var/datum/effect_system/smoke_spread/smoke = new + smoke.set_up(0, get_turf(where)) + smoke.start() + +/obj/item/relic/proc/corgicannon(mob/user) + playsound(src, "sparks", rand(25,50), 1) + var/mob/living/simple_animal/pet/dog/corgi/C = new/mob/living/simple_animal/pet/dog/corgi(get_turf(user)) + C.throw_at(pick(oview(10,user)), 10, rand(3,8), callback = CALLBACK(src, .proc/throwSmoke, C)) + warn_admins(user, "Corgi Cannon", 0) + +/obj/item/relic/proc/clean(mob/user) + playsound(src, "sparks", rand(25,50), 1) + var/obj/item/grenade/chem_grenade/cleaner/CL = new/obj/item/grenade/chem_grenade/cleaner(get_turf(user)) + CL.prime() + warn_admins(user, "Smoke", 0) + +/obj/item/relic/proc/flash(mob/user) + playsound(src, "sparks", rand(25,50), 1) + var/obj/item/grenade/flashbang/CB = new/obj/item/grenade/flashbang(user.loc) + CB.prime() + warn_admins(user, "Flash") + +/obj/item/relic/proc/petSpray(mob/user) + var/message = "[src] begins to shake, and in the distance the sound of rampaging animals arises!" + visible_message(message) + to_chat(user, message) + var/animals = rand(1,25) + var/counter + var/list/valid_animals = list(/mob/living/simple_animal/parrot, /mob/living/simple_animal/butterfly, /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/crab, /mob/living/simple_animal/pet/fox, /mob/living/simple_animal/hostile/lizard, /mob/living/simple_animal/mouse, /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/hostile/bear, /mob/living/simple_animal/hostile/poison/bees, /mob/living/simple_animal/hostile/carp) + for(counter = 1; counter < animals; counter++) + var/mobType = pick(valid_animals) + new mobType(get_turf(src)) + warn_admins(user, "Mass Mob Spawn") + if(prob(60)) + to_chat(user, "[src] falls apart!") + qdel(src) + +/obj/item/relic/proc/rapidDupe(mob/user) + audible_message("[src] emits a loud pop!") + var/list/dupes = list() + var/counter + var/max = rand(5,10) + for(counter = 1; counter < max; counter++) + var/obj/item/relic/R = new type(get_turf(src)) + R.name = name + R.desc = desc + R.realName = realName + R.realProc = realProc + R.revealed = TRUE + dupes |= R + R.throw_at(pick(oview(7,get_turf(src))),10,1) + counter = 0 + QDEL_LIST_IN(dupes, rand(10, 100)) + warn_admins(user, "Rapid duplicator", 0) + +/obj/item/relic/proc/explode(mob/user) + to_chat(user, "[src] begins to heat up!") + addtimer(CALLBACK(src, .proc/do_explode, user), rand(35, 100)) + +/obj/item/relic/proc/do_explode(mob/user) + if(loc == user) + visible_message("\The [src]'s top opens, releasing a powerful blast!") + explosion(user.loc, 0, rand(1,5), rand(1,5), rand(1,5), rand(1,5), flame_range = 2) + warn_admins(user, "Explosion") + qdel(src) //Comment this line to produce a light grenade (the bomb that keeps on exploding when used)!! + +/obj/item/relic/proc/teleport(mob/user) + to_chat(user, "[src] begins to vibrate!") + addtimer(CALLBACK(src, .proc/do_the_teleport, user), rand(10, 30)) + +/obj/item/relic/proc/do_the_teleport(mob/user) + var/turf/userturf = get_turf(user) + if(loc == user && !is_centcom_level(userturf.z)) //Because Nuke Ops bringing this back on their shuttle, then looting the ERT area is 2fun4you! + visible_message("[src] twists and bends, relocating itself!") + throwSmoke(userturf) + do_teleport(user, userturf, 8, asoundin = 'sound/effects/phasein.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) + throwSmoke(get_turf(user)) + warn_admins(user, "Teleport", 0) + +//Admin Warning proc for relics +/obj/item/relic/proc/warn_admins(mob/user, RelicType, priority = 1) + var/turf/T = get_turf(src) + var/log_msg = "[RelicType] relic used by [key_name(user)] in [AREACOORD(T)]" + if(priority) //For truly dangerous relics that may need an admin's attention. BWOINK! + message_admins("[RelicType] relic activated by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(T)]") + log_game(log_msg) + investigate_log(log_msg, "experimentor") diff --git a/code/modules/research/machinery/_production.dm b/code/modules/research/machinery/_production.dm index 9ba45fb1a3..d93355b36c 100644 --- a/code/modules/research/machinery/_production.dm +++ b/code/modules/research/machinery/_production.dm @@ -1,365 +1,365 @@ -/obj/machinery/rnd/production - name = "technology fabricator" - desc = "Makes researched and prototype items with materials and energy." - layer = BELOW_OBJ_LAYER - var/consoleless_interface = FALSE //Whether it can be used without a console. - var/efficiency_coeff = 1 //Materials needed / coeff = actual. - var/list/categories = list() - var/datum/component/remote_materials/materials - var/allowed_department_flags = ALL - var/production_animation //What's flick()'d on print. - var/allowed_buildtypes = NONE - var/list/datum/design/cached_designs - var/list/datum/design/matching_designs - var/department_tag = "Unidentified" //used for material distribution among other things. - var/datum/techweb/stored_research - var/datum/techweb/host_research - - var/screen = RESEARCH_FABRICATOR_SCREEN_MAIN - var/selected_category - -/obj/machinery/rnd/production/Initialize(mapload) - . = ..() - create_reagents(0, OPENCONTAINER) - matching_designs = list() - cached_designs = list() - stored_research = new - host_research = SSresearch.science_tech - update_research() - materials = AddComponent(/datum/component/remote_materials, "lathe", mapload) - RefreshParts() - -/obj/machinery/rnd/production/proc/update_research() - host_research.copy_research_to(stored_research, TRUE) - update_designs() - -/obj/machinery/rnd/production/proc/update_designs() - cached_designs.Cut() - for(var/i in stored_research.researched_designs) - var/datum/design/d = SSresearch.techweb_design_by_id(i) - if((isnull(allowed_department_flags) || (d.departmental_flags & allowed_department_flags)) && (d.build_type & allowed_buildtypes)) - cached_designs |= d - -/obj/machinery/rnd/production/RefreshParts() - calculate_efficiency() - -/obj/machinery/rnd/production/ui_interact(mob/user) - if(!consoleless_interface) - return ..() - user.set_machine(src) - var/datum/browser/popup = new(user, "rndconsole", name, 460, 550) - popup.set_content(generate_ui()) - popup.open() - -/obj/machinery/rnd/production/Destroy() - QDEL_NULL(stored_research) - return ..() - -/obj/machinery/rnd/production/proc/calculate_efficiency() - efficiency_coeff = 1 - if(reagents) //If reagents/materials aren't initialized, don't bother, we'll be doing this again after reagents init anyways. - reagents.maximum_volume = 0 - for(var/obj/item/reagent_containers/glass/G in component_parts) - reagents.maximum_volume += G.volume - G.reagents.trans_to(src, G.reagents.total_volume) - if(materials) - var/total_storage = 0 - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - total_storage += M.rating * 75000 - materials.set_local_size(total_storage) - var/total_rating = 0 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - total_rating += M.rating - total_rating = max(1, total_rating) - efficiency_coeff = total_rating - -//we eject the materials upon deconstruction. -/obj/machinery/rnd/production/on_deconstruction() - for(var/obj/item/reagent_containers/glass/G in component_parts) - reagents.trans_to(G, G.reagents.maximum_volume) - return ..() - -/obj/machinery/rnd/production/proc/do_print(path, amount, list/matlist, notify_admins, mob/user) - if(notify_admins) - message_admins("[ADMIN_LOOKUPFLW(user)] has built [amount] of [path] at a [src]([type]).") - for(var/i in 1 to amount) - var/obj/O = new path(get_turf(src)) - if(efficient_with(O.type) && isitem(O)) - var/obj/item/I = O - I.materials = matlist.Copy() - SSblackbox.record_feedback("nested tally", "item_printed", amount, list("[type]", "[path]")) - investigate_log("[key_name(user)] built [amount] of [path] at [src]([type]).", INVESTIGATE_RESEARCH) - -/obj/machinery/rnd/production/proc/check_mat(datum/design/being_built, M) // now returns how many times the item can be built with the material - if (!materials.mat_container) // no connected silo - return 0 - var/list/all_materials = being_built.reagents_list + being_built.materials - - var/A = materials.mat_container.amount(M) - if(!A) - A = reagents.get_reagent_amount(M) - - // these types don't have their .materials set in do_print, so don't allow - // them to be constructed efficiently - var/ef = efficient_with(being_built.build_path) ? efficiency_coeff : 1 - return round(A / max(1, all_materials[M] / ef)) - -/obj/machinery/rnd/production/proc/efficient_with(path) - return !ispath(path, /obj/item/stack/sheet) && !ispath(path, /obj/item/stack/ore/bluespace_crystal) - -/obj/machinery/rnd/production/proc/user_try_print_id(id, amount) - if((!istype(linked_console) && requires_console) || !id) - return FALSE - if(istext(amount)) - amount = text2num(amount) - if(isnull(amount)) - amount = 1 - var/datum/design/D = (linked_console || requires_console)? (linked_console.stored_research.researched_designs[id]? SSresearch.techweb_design_by_id(id) : null) : SSresearch.techweb_design_by_id(id) - if(!istype(D)) - return FALSE - if(!(isnull(allowed_department_flags) || (D.departmental_flags & allowed_department_flags))) - say("Warning: Printing failed: This fabricator does not have the necessary keys to decrypt design schematics. Please update the research data with the on-screen button and contact Nanotrasen Support!") - return FALSE - if(D.build_type && !(D.build_type & allowed_buildtypes)) - say("This machine does not have the necessary manipulation systems for this design. Please contact Nanotrasen Support!") - return FALSE - if(!materials.mat_container) - say("No connection to material storage, please contact the quartermaster.") - return FALSE - if(materials.on_hold()) - say("Mineral access is on hold, please contact the quartermaster.") - return FALSE - var/power = 1000 - amount = CLAMP(amount, 1, 50) - for(var/M in D.materials) - power += round(D.materials[M] * amount / 35) - power = min(3000, power) - use_power(power) - var/coeff = efficient_with(D.build_path) ? efficiency_coeff : 1 - var/list/efficient_mats = list() - for(var/MAT in D.materials) - efficient_mats[MAT] = D.materials[MAT]/coeff - if(!materials.mat_container.has_materials(efficient_mats, amount)) - say("Not enough materials to complete prototype[amount > 1? "s" : ""].") - return FALSE - for(var/R in D.reagents_list) - if(!reagents.has_reagent(R, D.reagents_list[R]*amount/coeff)) - say("Not enough reagents to complete prototype[amount > 1? "s" : ""].") - return FALSE - materials.mat_container.use_amount(efficient_mats, amount) - materials.silo_log(src, "built", -amount, "[D.name]", efficient_mats) - for(var/R in D.reagents_list) - reagents.remove_reagent(R, D.reagents_list[R]*amount/coeff) - busy = TRUE - if(production_animation) - flick(production_animation, src) - var/timecoeff = D.lathe_time_factor / efficiency_coeff - addtimer(CALLBACK(src, .proc/reset_busy), (30 * timecoeff * amount) ** 0.5) - addtimer(CALLBACK(src, .proc/do_print, D.build_path, amount, efficient_mats, D.dangerous_construction, usr), (32 * timecoeff * amount) ** 0.8) - return TRUE - -/obj/machinery/rnd/production/proc/search(string) - matching_designs.Cut() - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(D.build_type & allowed_buildtypes) || !(isnull(allowed_department_flags) || (D.departmental_flags & allowed_department_flags))) - continue - if(findtext(D.name,string)) - matching_designs.Add(D) - -/obj/machinery/rnd/production/proc/generate_ui() - var/list/ui = list() - ui += ui_header() - switch(screen) - if(RESEARCH_FABRICATOR_SCREEN_MATERIALS) - ui += ui_screen_materials() - if(RESEARCH_FABRICATOR_SCREEN_CHEMICALS) - ui += ui_screen_chemicals() - if(RESEARCH_FABRICATOR_SCREEN_SEARCH) - ui += ui_screen_search() - if(RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW) - ui += ui_screen_category_view() - else - ui += ui_screen_main() - for(var/i in 1 to length(ui)) - if(!findtextEx(ui[i], RDSCREEN_NOBREAK)) - ui[i] += "
                " - ui[i] = replacetextEx(ui[i], RDSCREEN_NOBREAK, "") - return ui.Join("") - -/obj/machinery/rnd/production/proc/ui_header() - var/list/l = list() - l += "
                [host_research.organization] [department_tag] Department Lathe" - l += "Security protocols: [(obj_flags & EMAGGED)? "Disabled" : "Enabled"]" - if (materials.mat_container) - l += "Material Amount: [materials.format_amount()]" - else - l += "No material storage connected, please contact the quartermaster." - l += "Chemical volume: [reagents.total_volume] / [reagents.maximum_volume]" - l += "Synchronize Research" - l += "Main Screen
                [RDSCREEN_NOBREAK]" - return l - -/obj/machinery/rnd/production/proc/ui_screen_materials() - if (!materials.mat_container) - screen = RESEARCH_FABRICATOR_SCREEN_MAIN - return ui_screen_main() - var/list/l = list() - l += "

                Material Storage:

                " - for(var/mat_id in materials.mat_container.materials) - var/datum/material/M = materials.mat_container.materials[mat_id] - l += "* [M.amount] of [M.name]: " - if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" - if(M.amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" - if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]" - l += "" - l += "
                [RDSCREEN_NOBREAK]" - return l - -/obj/machinery/rnd/production/proc/ui_screen_chemicals() - var/list/l = list() - l += "
                Disposal All Chemicals in Storage" - l += "

                Chemical Storage:

                " - for(var/datum/reagent/R in reagents.reagent_list) - l += "[R.name]: [R.volume]" - l += "Purge" - l += "
                " - return l - -/obj/machinery/rnd/production/proc/ui_screen_search() - var/list/l = list() - var/coeff = efficiency_coeff - l += "

                Search Results:

                " - l += "
                \ - \ - \ - \ - \ -

                " - for(var/datum/design/D in matching_designs) - l += design_menu_entry(D, coeff) - l += "
                " - return l - -/obj/machinery/rnd/production/proc/design_menu_entry(datum/design/D, coeff) - if(!istype(D)) - return - if(!coeff) - coeff = efficiency_coeff - if(!efficient_with(D.build_path)) - coeff = 1 - var/list/l = list() - var/temp_material - var/c = 50 - var/t - var/all_materials = D.materials + D.reagents_list - for(var/M in all_materials) - t = check_mat(D, M) - temp_material += " | " - if (t < 1) - temp_material += "[all_materials[M]/coeff] [CallMaterialName(M)]" - else - temp_material += " [all_materials[M]/coeff] [CallMaterialName(M)]" - c = min(c,t) - - if (c >= 1) - l += "[D.name][RDSCREEN_NOBREAK]" - if(c >= 5) - l += "x5[RDSCREEN_NOBREAK]" - if(c >= 10) - l += "x10[RDSCREEN_NOBREAK]" - l += "[temp_material][RDSCREEN_NOBREAK]" - else - l += "[D.name][temp_material][RDSCREEN_NOBREAK]" - l += "" - return l - -/obj/machinery/rnd/production/Topic(raw, ls) - if(..()) - return - add_fingerprint(usr) - usr.set_machine(src) - if(ls["switch_screen"]) - screen = text2num(ls["switch_screen"]) - if(ls["build"]) //Causes the Protolathe to build something. - if(busy) - say("Warning: Fabricators busy!") - else - user_try_print_id(ls["build"], ls["amount"]) - if(ls["search"]) //Search for designs with name matching pattern - search(ls["to_search"]) - screen = RESEARCH_FABRICATOR_SCREEN_SEARCH - if(ls["sync_research"]) - update_research() - say("Synchronizing research with host technology database.") - if(ls["category"]) - selected_category = ls["category"] - if(ls["dispose"]) //Causes the protolathe to dispose of a single reagent (all of it) - reagents.del_reagent(ls["dispose"]) - if(ls["disposeall"]) //Causes the protolathe to dispose of all it's reagents. - reagents.clear_reagents() - if(ls["ejectsheet"]) //Causes the protolathe to eject a sheet of material - eject_sheets(ls["ejectsheet"], ls["eject_amt"]) - updateUsrDialog() - -/obj/machinery/rnd/production/proc/eject_sheets(eject_sheet, eject_amt) - var/datum/component/material_container/mat_container = materials.mat_container - if (!mat_container) - say("No access to material storage, please contact the quartermaster.") - return 0 - if (materials.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] = MINERAL_MATERIAL_AMOUNT - materials.silo_log(src, "ejected", -count, "sheets", matlist) - return count - -/obj/machinery/rnd/production/proc/ui_screen_main() - var/list/l = list() - l += "
                \ - \ - \ - \ - \ - \ -

                " - - l += list_categories(categories, RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW) - - return l - -/obj/machinery/rnd/production/proc/ui_screen_category_view() - if(!selected_category) - return ui_screen_main() - var/list/l = list() - l += "

                Browsing [selected_category]:

                " - var/coeff = efficiency_coeff - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(selected_category in D.category)|| !(D.build_type & allowed_buildtypes)) - continue - if(!(isnull(allowed_department_flags) || (D.departmental_flags & allowed_department_flags))) - continue - l += design_menu_entry(D, coeff) - l += "
                " - return l - -/obj/machinery/rnd/production/proc/list_categories(list/categories, menu_num) - if(!categories) - return - - var/line_length = 1 - var/list/l = "" - - for(var/C in categories) - if(line_length > 2) - l += "" - line_length = 1 - - l += "" - line_length++ - - l += "
                [C]
                " - return l +/obj/machinery/rnd/production + name = "technology fabricator" + desc = "Makes researched and prototype items with materials and energy." + layer = BELOW_OBJ_LAYER + var/consoleless_interface = FALSE //Whether it can be used without a console. + var/efficiency_coeff = 1 //Materials needed / coeff = actual. + var/list/categories = list() + var/datum/component/remote_materials/materials + var/allowed_department_flags = ALL + var/production_animation //What's flick()'d on print. + var/allowed_buildtypes = NONE + var/list/datum/design/cached_designs + var/list/datum/design/matching_designs + var/department_tag = "Unidentified" //used for material distribution among other things. + var/datum/techweb/stored_research + var/datum/techweb/host_research + + var/screen = RESEARCH_FABRICATOR_SCREEN_MAIN + var/selected_category + +/obj/machinery/rnd/production/Initialize(mapload) + . = ..() + create_reagents(0, OPENCONTAINER) + matching_designs = list() + cached_designs = list() + stored_research = new + host_research = SSresearch.science_tech + update_research() + materials = AddComponent(/datum/component/remote_materials, "lathe", mapload) + RefreshParts() + +/obj/machinery/rnd/production/proc/update_research() + host_research.copy_research_to(stored_research, TRUE) + update_designs() + +/obj/machinery/rnd/production/proc/update_designs() + cached_designs.Cut() + for(var/i in stored_research.researched_designs) + var/datum/design/d = SSresearch.techweb_design_by_id(i) + if((isnull(allowed_department_flags) || (d.departmental_flags & allowed_department_flags)) && (d.build_type & allowed_buildtypes)) + cached_designs |= d + +/obj/machinery/rnd/production/RefreshParts() + calculate_efficiency() + +/obj/machinery/rnd/production/ui_interact(mob/user) + if(!consoleless_interface) + return ..() + user.set_machine(src) + var/datum/browser/popup = new(user, "rndconsole", name, 460, 550) + popup.set_content(generate_ui()) + popup.open() + +/obj/machinery/rnd/production/Destroy() + QDEL_NULL(stored_research) + return ..() + +/obj/machinery/rnd/production/proc/calculate_efficiency() + efficiency_coeff = 1 + if(reagents) //If reagents/materials aren't initialized, don't bother, we'll be doing this again after reagents init anyways. + reagents.maximum_volume = 0 + for(var/obj/item/reagent_containers/glass/G in component_parts) + reagents.maximum_volume += G.volume + G.reagents.trans_to(src, G.reagents.total_volume) + if(materials) + var/total_storage = 0 + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + total_storage += M.rating * 75000 + materials.set_local_size(total_storage) + var/total_rating = 0 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + total_rating += M.rating + total_rating = max(1, total_rating) + efficiency_coeff = total_rating + +//we eject the materials upon deconstruction. +/obj/machinery/rnd/production/on_deconstruction() + for(var/obj/item/reagent_containers/glass/G in component_parts) + reagents.trans_to(G, G.reagents.maximum_volume) + return ..() + +/obj/machinery/rnd/production/proc/do_print(path, amount, list/matlist, notify_admins, mob/user) + if(notify_admins) + message_admins("[ADMIN_LOOKUPFLW(user)] has built [amount] of [path] at a [src]([type]).") + for(var/i in 1 to amount) + var/obj/O = new path(get_turf(src)) + if(efficient_with(O.type) && isitem(O)) + var/obj/item/I = O + I.materials = matlist.Copy() + SSblackbox.record_feedback("nested tally", "item_printed", amount, list("[type]", "[path]")) + investigate_log("[key_name(user)] built [amount] of [path] at [src]([type]).", INVESTIGATE_RESEARCH) + +/obj/machinery/rnd/production/proc/check_mat(datum/design/being_built, M) // now returns how many times the item can be built with the material + if (!materials.mat_container) // no connected silo + return 0 + var/list/all_materials = being_built.reagents_list + being_built.materials + + var/A = materials.mat_container.amount(M) + if(!A) + A = reagents.get_reagent_amount(M) + + // these types don't have their .materials set in do_print, so don't allow + // them to be constructed efficiently + var/ef = efficient_with(being_built.build_path) ? efficiency_coeff : 1 + return round(A / max(1, all_materials[M] / ef)) + +/obj/machinery/rnd/production/proc/efficient_with(path) + return !ispath(path, /obj/item/stack/sheet) && !ispath(path, /obj/item/stack/ore/bluespace_crystal) + +/obj/machinery/rnd/production/proc/user_try_print_id(id, amount) + if((!istype(linked_console) && requires_console) || !id) + return FALSE + if(istext(amount)) + amount = text2num(amount) + if(isnull(amount)) + amount = 1 + var/datum/design/D = (linked_console || requires_console)? (linked_console.stored_research.researched_designs[id]? SSresearch.techweb_design_by_id(id) : null) : SSresearch.techweb_design_by_id(id) + if(!istype(D)) + return FALSE + if(!(isnull(allowed_department_flags) || (D.departmental_flags & allowed_department_flags))) + say("Warning: Printing failed: This fabricator does not have the necessary keys to decrypt design schematics. Please update the research data with the on-screen button and contact Nanotrasen Support!") + return FALSE + if(D.build_type && !(D.build_type & allowed_buildtypes)) + say("This machine does not have the necessary manipulation systems for this design. Please contact Nanotrasen Support!") + return FALSE + if(!materials.mat_container) + say("No connection to material storage, please contact the quartermaster.") + return FALSE + if(materials.on_hold()) + say("Mineral access is on hold, please contact the quartermaster.") + return FALSE + var/power = 1000 + amount = CLAMP(amount, 1, 50) + for(var/M in D.materials) + power += round(D.materials[M] * amount / 35) + power = min(3000, power) + use_power(power) + var/coeff = efficient_with(D.build_path) ? efficiency_coeff : 1 + var/list/efficient_mats = list() + for(var/MAT in D.materials) + efficient_mats[MAT] = D.materials[MAT]/coeff + if(!materials.mat_container.has_materials(efficient_mats, amount)) + say("Not enough materials to complete prototype[amount > 1? "s" : ""].") + return FALSE + for(var/R in D.reagents_list) + if(!reagents.has_reagent(R, D.reagents_list[R]*amount/coeff)) + say("Not enough reagents to complete prototype[amount > 1? "s" : ""].") + return FALSE + materials.mat_container.use_amount(efficient_mats, amount) + materials.silo_log(src, "built", -amount, "[D.name]", efficient_mats) + for(var/R in D.reagents_list) + reagents.remove_reagent(R, D.reagents_list[R]*amount/coeff) + busy = TRUE + if(production_animation) + flick(production_animation, src) + var/timecoeff = D.lathe_time_factor / efficiency_coeff + addtimer(CALLBACK(src, .proc/reset_busy), (30 * timecoeff * amount) ** 0.5) + addtimer(CALLBACK(src, .proc/do_print, D.build_path, amount, efficient_mats, D.dangerous_construction, usr), (32 * timecoeff * amount) ** 0.8) + return TRUE + +/obj/machinery/rnd/production/proc/search(string) + matching_designs.Cut() + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(!(D.build_type & allowed_buildtypes) || !(isnull(allowed_department_flags) || (D.departmental_flags & allowed_department_flags))) + continue + if(findtext(D.name,string)) + matching_designs.Add(D) + +/obj/machinery/rnd/production/proc/generate_ui() + var/list/ui = list() + ui += ui_header() + switch(screen) + if(RESEARCH_FABRICATOR_SCREEN_MATERIALS) + ui += ui_screen_materials() + if(RESEARCH_FABRICATOR_SCREEN_CHEMICALS) + ui += ui_screen_chemicals() + if(RESEARCH_FABRICATOR_SCREEN_SEARCH) + ui += ui_screen_search() + if(RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW) + ui += ui_screen_category_view() + else + ui += ui_screen_main() + for(var/i in 1 to length(ui)) + if(!findtextEx(ui[i], RDSCREEN_NOBREAK)) + ui[i] += "
                " + ui[i] = replacetextEx(ui[i], RDSCREEN_NOBREAK, "") + return ui.Join("") + +/obj/machinery/rnd/production/proc/ui_header() + var/list/l = list() + l += "
                [host_research.organization] [department_tag] Department Lathe" + l += "Security protocols: [(obj_flags & EMAGGED)? "Disabled" : "Enabled"]" + if (materials.mat_container) + l += "Material Amount: [materials.format_amount()]" + else + l += "No material storage connected, please contact the quartermaster." + l += "Chemical volume: [reagents.total_volume] / [reagents.maximum_volume]" + l += "Synchronize Research" + l += "Main Screen
                [RDSCREEN_NOBREAK]" + return l + +/obj/machinery/rnd/production/proc/ui_screen_materials() + if (!materials.mat_container) + screen = RESEARCH_FABRICATOR_SCREEN_MAIN + return ui_screen_main() + var/list/l = list() + l += "

                Material Storage:

                " + for(var/mat_id in materials.mat_container.materials) + var/datum/material/M = materials.mat_container.materials[mat_id] + l += "* [M.amount] of [M.name]: " + if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" + if(M.amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" + if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]" + l += "" + l += "
                [RDSCREEN_NOBREAK]" + return l + +/obj/machinery/rnd/production/proc/ui_screen_chemicals() + var/list/l = list() + l += "
                Disposal All Chemicals in Storage" + l += "

                Chemical Storage:

                " + for(var/datum/reagent/R in reagents.reagent_list) + l += "[R.name]: [R.volume]" + l += "Purge" + l += "
                " + return l + +/obj/machinery/rnd/production/proc/ui_screen_search() + var/list/l = list() + var/coeff = efficiency_coeff + l += "

                Search Results:

                " + l += "
                \ + \ + \ + \ + \ +

                " + for(var/datum/design/D in matching_designs) + l += design_menu_entry(D, coeff) + l += "
                " + return l + +/obj/machinery/rnd/production/proc/design_menu_entry(datum/design/D, coeff) + if(!istype(D)) + return + if(!coeff) + coeff = efficiency_coeff + if(!efficient_with(D.build_path)) + coeff = 1 + var/list/l = list() + var/temp_material + var/c = 50 + var/t + var/all_materials = D.materials + D.reagents_list + for(var/M in all_materials) + t = check_mat(D, M) + temp_material += " | " + if (t < 1) + temp_material += "[all_materials[M]/coeff] [CallMaterialName(M)]" + else + temp_material += " [all_materials[M]/coeff] [CallMaterialName(M)]" + c = min(c,t) + + if (c >= 1) + l += "[D.name][RDSCREEN_NOBREAK]" + if(c >= 5) + l += "x5[RDSCREEN_NOBREAK]" + if(c >= 10) + l += "x10[RDSCREEN_NOBREAK]" + l += "[temp_material][RDSCREEN_NOBREAK]" + else + l += "[D.name][temp_material][RDSCREEN_NOBREAK]" + l += "" + return l + +/obj/machinery/rnd/production/Topic(raw, ls) + if(..()) + return + add_fingerprint(usr) + usr.set_machine(src) + if(ls["switch_screen"]) + screen = text2num(ls["switch_screen"]) + if(ls["build"]) //Causes the Protolathe to build something. + if(busy) + say("Warning: Fabricators busy!") + else + user_try_print_id(ls["build"], ls["amount"]) + if(ls["search"]) //Search for designs with name matching pattern + search(ls["to_search"]) + screen = RESEARCH_FABRICATOR_SCREEN_SEARCH + if(ls["sync_research"]) + update_research() + say("Synchronizing research with host technology database.") + if(ls["category"]) + selected_category = ls["category"] + if(ls["dispose"]) //Causes the protolathe to dispose of a single reagent (all of it) + reagents.del_reagent(ls["dispose"]) + if(ls["disposeall"]) //Causes the protolathe to dispose of all it's reagents. + reagents.clear_reagents() + if(ls["ejectsheet"]) //Causes the protolathe to eject a sheet of material + eject_sheets(ls["ejectsheet"], ls["eject_amt"]) + updateUsrDialog() + +/obj/machinery/rnd/production/proc/eject_sheets(eject_sheet, eject_amt) + var/datum/component/material_container/mat_container = materials.mat_container + if (!mat_container) + say("No access to material storage, please contact the quartermaster.") + return 0 + if (materials.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] = MINERAL_MATERIAL_AMOUNT + materials.silo_log(src, "ejected", -count, "sheets", matlist) + return count + +/obj/machinery/rnd/production/proc/ui_screen_main() + var/list/l = list() + l += "
                \ + \ + \ + \ + \ + \ +

                " + + l += list_categories(categories, RESEARCH_FABRICATOR_SCREEN_CATEGORYVIEW) + + return l + +/obj/machinery/rnd/production/proc/ui_screen_category_view() + if(!selected_category) + return ui_screen_main() + var/list/l = list() + l += "

                Browsing [selected_category]:

                " + var/coeff = efficiency_coeff + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(!(selected_category in D.category)|| !(D.build_type & allowed_buildtypes)) + continue + if(!(isnull(allowed_department_flags) || (D.departmental_flags & allowed_department_flags))) + continue + l += design_menu_entry(D, coeff) + l += "
                " + return l + +/obj/machinery/rnd/production/proc/list_categories(list/categories, menu_num) + if(!categories) + return + + var/line_length = 1 + var/list/l = "" + + for(var/C in categories) + if(line_length > 2) + l += "" + line_length = 1 + + l += "" + line_length++ + + l += "
                [C]
                " + return l diff --git a/code/modules/research/machinery/circuit_imprinter.dm b/code/modules/research/machinery/circuit_imprinter.dm index 09cf9cda87..948dad61db 100644 --- a/code/modules/research/machinery/circuit_imprinter.dm +++ b/code/modules/research/machinery/circuit_imprinter.dm @@ -1,32 +1,32 @@ -/obj/machinery/rnd/production/circuit_imprinter - name = "circuit imprinter" - desc = "Manufactures circuit boards for the construction of machines." - icon_state = "circuit_imprinter" - circuit = /obj/item/circuitboard/machine/circuit_imprinter - categories = list( - "AI Modules", - "Computer Boards", - "Teleportation Machinery", - "Medical Machinery", - "Engineering Machinery", - "Exosuit Modules", - "Hydroponics Machinery", - "Subspace Telecomms", - "Research Machinery", - "Misc. Machinery", - "Computer Parts" - ) - production_animation = "circuit_imprinter_ani" - allowed_buildtypes = IMPRINTER - -/obj/machinery/rnd/production/circuit_imprinter/disconnect_console() - linked_console.linked_imprinter = null - ..() - -/obj/machinery/rnd/production/circuit_imprinter/calculate_efficiency() - . = ..() - var/total_rating = 0 - for(var/obj/item/stock_parts/manipulator/M in component_parts) - total_rating += M.rating * 2 //There is only one. - total_rating = max(1, total_rating) - efficiency_coeff = total_rating +/obj/machinery/rnd/production/circuit_imprinter + name = "circuit imprinter" + desc = "Manufactures circuit boards for the construction of machines." + icon_state = "circuit_imprinter" + circuit = /obj/item/circuitboard/machine/circuit_imprinter + categories = list( + "AI Modules", + "Computer Boards", + "Teleportation Machinery", + "Medical Machinery", + "Engineering Machinery", + "Exosuit Modules", + "Hydroponics Machinery", + "Subspace Telecomms", + "Research Machinery", + "Misc. Machinery", + "Computer Parts" + ) + production_animation = "circuit_imprinter_ani" + allowed_buildtypes = IMPRINTER + +/obj/machinery/rnd/production/circuit_imprinter/disconnect_console() + linked_console.linked_imprinter = null + ..() + +/obj/machinery/rnd/production/circuit_imprinter/calculate_efficiency() + . = ..() + var/total_rating = 0 + for(var/obj/item/stock_parts/manipulator/M in component_parts) + total_rating += M.rating * 2 //There is only one. + total_rating = max(1, total_rating) + efficiency_coeff = total_rating diff --git a/code/modules/research/machinery/departmental_circuit_imprinter.dm b/code/modules/research/machinery/departmental_circuit_imprinter.dm index 53d4a21a9d..623c40462a 100644 --- a/code/modules/research/machinery/departmental_circuit_imprinter.dm +++ b/code/modules/research/machinery/departmental_circuit_imprinter.dm @@ -1,13 +1,13 @@ -/obj/machinery/rnd/production/circuit_imprinter/department - name = "department circuit imprinter" - desc = "A special circuit imprinter with a built in interface meant for departmental usage, with built in ExoSync receivers allowing it to print designs researched that match its ROM-encoded department type." - icon_state = "circuit_imprinter" - circuit = /obj/item/circuitboard/machine/circuit_imprinter/department - requires_console = FALSE - consoleless_interface = TRUE - -/obj/machinery/rnd/production/circuit_imprinter/department/science - name = "department circuit imprinter (Science)" - circuit = /obj/item/circuitboard/machine/circuit_imprinter/department/science - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SCIENCE +/obj/machinery/rnd/production/circuit_imprinter/department + name = "department circuit imprinter" + desc = "A special circuit imprinter with a built in interface meant for departmental usage, with built in ExoSync receivers allowing it to print designs researched that match its ROM-encoded department type." + icon_state = "circuit_imprinter" + circuit = /obj/item/circuitboard/machine/circuit_imprinter/department + requires_console = FALSE + consoleless_interface = TRUE + +/obj/machinery/rnd/production/circuit_imprinter/department/science + name = "department circuit imprinter (Science)" + circuit = /obj/item/circuitboard/machine/circuit_imprinter/department/science + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SCIENCE department_tag = "Science" \ No newline at end of file diff --git a/code/modules/research/machinery/departmental_protolathe.dm b/code/modules/research/machinery/departmental_protolathe.dm index 7fad6825fe..0f9257cbeb 100644 --- a/code/modules/research/machinery/departmental_protolathe.dm +++ b/code/modules/research/machinery/departmental_protolathe.dm @@ -1,43 +1,43 @@ -/obj/machinery/rnd/production/protolathe/department - name = "department protolathe" - desc = "A special protolathe with a built in interface meant for departmental usage, with built in ExoSync receivers allowing it to print designs researched that match its ROM-encoded department type." - icon_state = "protolathe" - circuit = /obj/item/circuitboard/machine/protolathe/department - requires_console = FALSE - consoleless_interface = TRUE - -/obj/machinery/rnd/production/protolathe/department/engineering - name = "department protolathe (Engineering)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_ENGINEERING - department_tag = "Engineering" - circuit = /obj/item/circuitboard/machine/protolathe/department/engineering - -/obj/machinery/rnd/production/protolathe/department/service - name = "department protolathe (Service)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SERVICE - department_tag = "Service" - circuit = /obj/item/circuitboard/machine/protolathe/department/service - -/obj/machinery/rnd/production/protolathe/department/medical - name = "department protolathe (Medical)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_MEDICAL - department_tag = "Medical" - circuit = /obj/item/circuitboard/machine/protolathe/department/medical - -/obj/machinery/rnd/production/protolathe/department/cargo - name = "department protolathe (Cargo)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_CARGO - department_tag = "Cargo" - circuit = /obj/item/circuitboard/machine/protolathe/department/cargo - -/obj/machinery/rnd/production/protolathe/department/science - name = "department protolathe (Science)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SCIENCE - department_tag = "Science" - circuit = /obj/item/circuitboard/machine/protolathe/department/science - -/obj/machinery/rnd/production/protolathe/department/security - name = "department protolathe (Security)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SECURITY - department_tag = "Security" +/obj/machinery/rnd/production/protolathe/department + name = "department protolathe" + desc = "A special protolathe with a built in interface meant for departmental usage, with built in ExoSync receivers allowing it to print designs researched that match its ROM-encoded department type." + icon_state = "protolathe" + circuit = /obj/item/circuitboard/machine/protolathe/department + requires_console = FALSE + consoleless_interface = TRUE + +/obj/machinery/rnd/production/protolathe/department/engineering + name = "department protolathe (Engineering)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_ENGINEERING + department_tag = "Engineering" + circuit = /obj/item/circuitboard/machine/protolathe/department/engineering + +/obj/machinery/rnd/production/protolathe/department/service + name = "department protolathe (Service)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SERVICE + department_tag = "Service" + circuit = /obj/item/circuitboard/machine/protolathe/department/service + +/obj/machinery/rnd/production/protolathe/department/medical + name = "department protolathe (Medical)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_MEDICAL + department_tag = "Medical" + circuit = /obj/item/circuitboard/machine/protolathe/department/medical + +/obj/machinery/rnd/production/protolathe/department/cargo + name = "department protolathe (Cargo)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_CARGO + department_tag = "Cargo" + circuit = /obj/item/circuitboard/machine/protolathe/department/cargo + +/obj/machinery/rnd/production/protolathe/department/science + name = "department protolathe (Science)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SCIENCE + department_tag = "Science" + circuit = /obj/item/circuitboard/machine/protolathe/department/science + +/obj/machinery/rnd/production/protolathe/department/security + name = "department protolathe (Security)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SECURITY + department_tag = "Security" circuit = /obj/item/circuitboard/machine/protolathe/department/security \ No newline at end of file diff --git a/code/modules/research/machinery/departmental_techfab.dm b/code/modules/research/machinery/departmental_techfab.dm index 8b82fd2b37..5aa0eb942b 100644 --- a/code/modules/research/machinery/departmental_techfab.dm +++ b/code/modules/research/machinery/departmental_techfab.dm @@ -1,41 +1,41 @@ -/obj/machinery/rnd/production/techfab/department - name = "department techfab" - desc = "An advanced fabricator designed to print out the latest prototypes and circuits researched from Science. Contains hardware to sync to research networks. This one is department-locked and only possesses a limited set of decryption keys." - icon_state = "protolathe" - circuit = /obj/item/circuitboard/machine/techfab/department - -/obj/machinery/rnd/production/techfab/department/engineering - name = "department techfab (Engineering)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_ENGINEERING - department_tag = "Engineering" - circuit = /obj/item/circuitboard/machine/techfab/department/engineering - -/obj/machinery/rnd/production/techfab/department/service - name = "department techfab (Service)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SERVICE - department_tag = "Service" - circuit = /obj/item/circuitboard/machine/techfab/department/service - -/obj/machinery/rnd/production/techfab/department/medical - name = "department techfab (Medical)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_MEDICAL - department_tag = "Medical" - circuit = /obj/item/circuitboard/machine/techfab/department/medical - -/obj/machinery/rnd/production/techfab/department/cargo - name = "department techfab (Cargo)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_CARGO - department_tag = "Cargo" - circuit = /obj/item/circuitboard/machine/techfab/department/cargo - -/obj/machinery/rnd/production/techfab/department/science - name = "department techfab (Science)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SCIENCE - department_tag = "Science" - circuit = /obj/item/circuitboard/machine/techfab/department/science - -/obj/machinery/rnd/production/techfab/department/security - name = "department techfab (Security)" - allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SECURITY - department_tag = "Security" +/obj/machinery/rnd/production/techfab/department + name = "department techfab" + desc = "An advanced fabricator designed to print out the latest prototypes and circuits researched from Science. Contains hardware to sync to research networks. This one is department-locked and only possesses a limited set of decryption keys." + icon_state = "protolathe" + circuit = /obj/item/circuitboard/machine/techfab/department + +/obj/machinery/rnd/production/techfab/department/engineering + name = "department techfab (Engineering)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_ENGINEERING + department_tag = "Engineering" + circuit = /obj/item/circuitboard/machine/techfab/department/engineering + +/obj/machinery/rnd/production/techfab/department/service + name = "department techfab (Service)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SERVICE + department_tag = "Service" + circuit = /obj/item/circuitboard/machine/techfab/department/service + +/obj/machinery/rnd/production/techfab/department/medical + name = "department techfab (Medical)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_MEDICAL + department_tag = "Medical" + circuit = /obj/item/circuitboard/machine/techfab/department/medical + +/obj/machinery/rnd/production/techfab/department/cargo + name = "department techfab (Cargo)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_CARGO + department_tag = "Cargo" + circuit = /obj/item/circuitboard/machine/techfab/department/cargo + +/obj/machinery/rnd/production/techfab/department/science + name = "department techfab (Science)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SCIENCE + department_tag = "Science" + circuit = /obj/item/circuitboard/machine/techfab/department/science + +/obj/machinery/rnd/production/techfab/department/security + name = "department techfab (Security)" + allowed_department_flags = DEPARTMENTAL_FLAG_ALL|DEPARTMENTAL_FLAG_SECURITY + department_tag = "Security" circuit = /obj/item/circuitboard/machine/techfab/department/security \ No newline at end of file diff --git a/code/modules/research/machinery/protolathe.dm b/code/modules/research/machinery/protolathe.dm index 6acfc9ec42..2467def64e 100644 --- a/code/modules/research/machinery/protolathe.dm +++ b/code/modules/research/machinery/protolathe.dm @@ -1,24 +1,24 @@ -/obj/machinery/rnd/production/protolathe - name = "protolathe" - desc = "Converts raw materials into useful objects." - icon_state = "protolathe" - circuit = /obj/item/circuitboard/machine/protolathe - categories = list( - "Power Designs", - "Medical Designs", - "Bluespace Designs", - "Stock Parts", - "Equipment", - "Mining Designs", - "Electronics", - "Weapons", - "Ammo", - "Firing Pins", - "Computer Parts" - ) - production_animation = "protolathe_n" - allowed_buildtypes = PROTOLATHE - -/obj/machinery/rnd/production/protolathe/disconnect_console() - linked_console.linked_lathe = null +/obj/machinery/rnd/production/protolathe + name = "protolathe" + desc = "Converts raw materials into useful objects." + icon_state = "protolathe" + circuit = /obj/item/circuitboard/machine/protolathe + categories = list( + "Power Designs", + "Medical Designs", + "Bluespace Designs", + "Stock Parts", + "Equipment", + "Mining Designs", + "Electronics", + "Weapons", + "Ammo", + "Firing Pins", + "Computer Parts" + ) + production_animation = "protolathe_n" + allowed_buildtypes = PROTOLATHE + +/obj/machinery/rnd/production/protolathe/disconnect_console() + linked_console.linked_lathe = null ..() \ No newline at end of file diff --git a/code/modules/research/machinery/techfab.dm b/code/modules/research/machinery/techfab.dm index 332a1ccf88..0e809fa0e1 100644 --- a/code/modules/research/machinery/techfab.dm +++ b/code/modules/research/machinery/techfab.dm @@ -1,34 +1,34 @@ -/obj/machinery/rnd/production/techfab - name = "technology fabricator" - desc = "Produces researched prototypes with raw materials and energy." - icon_state = "protolathe" - circuit = /obj/item/circuitboard/machine/techfab - categories = list( - "Power Designs", - "Medical Designs", - "Bluespace Designs", - "Stock Parts", - "Equipment", - "Mining Designs", - "Electronics", - "Weapons", - "Ammo", - "Firing Pins", - "Computer Parts", - "AI Modules", - "Computer Boards", - "Teleportation Machinery", - "Medical Machinery", - "Engineering Machinery", - "Exosuit Modules", - "Hydroponics Machinery", - "Subspace Telecomms", - "Research Machinery", - "Misc. Machinery", - "Computer Parts" - ) - console_link = FALSE - production_animation = "protolathe_n" - requires_console = FALSE - consoleless_interface = TRUE +/obj/machinery/rnd/production/techfab + name = "technology fabricator" + desc = "Produces researched prototypes with raw materials and energy." + icon_state = "protolathe" + circuit = /obj/item/circuitboard/machine/techfab + categories = list( + "Power Designs", + "Medical Designs", + "Bluespace Designs", + "Stock Parts", + "Equipment", + "Mining Designs", + "Electronics", + "Weapons", + "Ammo", + "Firing Pins", + "Computer Parts", + "AI Modules", + "Computer Boards", + "Teleportation Machinery", + "Medical Machinery", + "Engineering Machinery", + "Exosuit Modules", + "Hydroponics Machinery", + "Subspace Telecomms", + "Research Machinery", + "Misc. Machinery", + "Computer Parts" + ) + console_link = FALSE + production_animation = "protolathe_n" + requires_console = FALSE + consoleless_interface = TRUE allowed_buildtypes = PROTOLATHE | IMPRINTER \ No newline at end of file diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm index 6cd54dd941..c40299d7a8 100644 --- a/code/modules/research/rdconsole.dm +++ b/code/modules/research/rdconsole.dm @@ -1,1135 +1,1135 @@ - -/* -Research and Development (R&D) Console - -This is the main work horse of the R&D system. It contains the menus/controls for the Destructive Analyzer, Protolathe, and Circuit -imprinter. - -Basic use: When it first is created, it will attempt to link up to related devices within 3 squares. It'll only link up if they -aren't already linked to another console. Any consoles it cannot link up with (either because all of a certain type are already -linked or there aren't any in range), you'll just not have access to that menu. In the settings menu, there are menu options that -allow a player to attempt to re-sync with nearby consoles. You can also force it to disconnect from a specific console. - -The only thing that requires toxins access is locking and unlocking the console on the settings menu. -Nothing else in the console has ID requirements. - -*/ -/obj/machinery/computer/rdconsole - name = "R&D Console" - desc = "A console used to interface with R&D tools." - icon_screen = "rdcomp" - icon_keyboard = "rd_key" - var/datum/techweb/stored_research //Reference to global science techweb. - var/obj/item/disk/tech_disk/t_disk //Stores the technology disk. - var/obj/item/disk/design_disk/d_disk //Stores the design disk. - circuit = /obj/item/circuitboard/computer/rdconsole - - var/obj/machinery/rnd/destructive_analyzer/linked_destroy //Linked Destructive Analyzer - var/obj/machinery/rnd/production/protolathe/linked_lathe //Linked Protolathe - var/obj/machinery/rnd/production/circuit_imprinter/linked_imprinter //Linked Circuit Imprinter - - req_access = list(ACCESS_TOX) //lA AND SETTING MANIPULATION REQUIRES SCIENTIST ACCESS. - - //UI VARS - var/screen = RDSCREEN_MENU - var/back = RDSCREEN_MENU - var/locked = FALSE - var/tdisk_uple = FALSE - var/ddisk_uple = FALSE - var/datum/selected_node_id - var/datum/selected_design_id - var/selected_category - var/list/matching_design_ids - var/disk_slot_selected - var/searchstring = "" - var/searchtype = "" - var/ui_mode = RDCONSOLE_UI_MODE_NORMAL - - var/research_control = TRUE - -/obj/machinery/computer/rdconsole/production - circuit = /obj/item/circuitboard/computer/rdconsole/production - research_control = FALSE - -/proc/CallMaterialName(ID) - if (copytext(ID, 1, 2) == "$" && GLOB.materials_list[ID]) - var/datum/material/material = GLOB.materials_list[ID] - return material.name - - else if(GLOB.chemical_reagents_list[ID]) - var/datum/reagent/reagent = GLOB.chemical_reagents_list[ID] - return reagent.name - return "ERROR: Report This" - -/obj/machinery/computer/rdconsole/proc/SyncRDevices() //Makes sure it is properly sync'ed up with the devices attached to it (if any). - for(var/obj/machinery/rnd/D in oview(3,src)) - if(D.linked_console != null || D.disabled || D.panel_open) - continue - if(istype(D, /obj/machinery/rnd/destructive_analyzer)) - if(linked_destroy == null) - linked_destroy = D - D.linked_console = src - else if(istype(D, /obj/machinery/rnd/production/protolathe)) - if(linked_lathe == null) - var/obj/machinery/rnd/production/protolathe/P = D - if(!P.console_link) - continue - linked_lathe = D - D.linked_console = src - else if(istype(D, /obj/machinery/rnd/production/circuit_imprinter)) - if(linked_imprinter == null) - var/obj/machinery/rnd/production/circuit_imprinter/C = D - if(!C.console_link) - continue - linked_imprinter = D - D.linked_console = src - -/obj/machinery/computer/rdconsole/Initialize() - . = ..() - stored_research = SSresearch.science_tech - stored_research.consoles_accessing[src] = TRUE - matching_design_ids = list() - SyncRDevices() - -/obj/machinery/computer/rdconsole/Destroy() - if(stored_research) - stored_research.consoles_accessing -= src - if(linked_destroy) - linked_destroy.linked_console = null - linked_destroy = null - if(linked_lathe) - linked_lathe.linked_console = null - linked_lathe = null - if(linked_imprinter) - linked_imprinter.linked_console = null - linked_imprinter = null - if(t_disk) - t_disk.forceMove(get_turf(src)) - t_disk = null - if(d_disk) - d_disk.forceMove(get_turf(src)) - d_disk = null - matching_design_ids = null - return ..() - -/obj/machinery/computer/rdconsole/attackby(obj/item/D, mob/user, params) - //Loading a disk into it. - if(istype(D, /obj/item/disk)) - if(istype(D, /obj/item/disk/tech_disk)) - if(t_disk) - to_chat(user, "A technology disk is already loaded!") - return - if(!user.transferItemToLoc(D, src)) - to_chat(user, "[D] is stuck to your hand!") - return - t_disk = D - else if (istype(D, /obj/item/disk/design_disk)) - if(d_disk) - to_chat(user, "A design disk is already loaded!") - return - if(!user.transferItemToLoc(D, src)) - to_chat(user, "[D] is stuck to your hand!") - return - d_disk = D - else - to_chat(user, "Machine cannot accept disks in that format.") - return - to_chat(user, "You insert [D] into \the [src]!") - else if(!(linked_destroy && linked_destroy.busy) && !(linked_lathe && linked_lathe.busy) && !(linked_imprinter && linked_imprinter.busy)) - . = ..() - -/obj/machinery/computer/rdconsole/proc/research_node(id, mob/user) - if(!stored_research.available_nodes[id] || stored_research.researched_nodes[id]) - say("Node unlock failed: Either already researched or not available!") - return FALSE - var/datum/techweb_node/TN = SSresearch.techweb_node_by_id(id) - if(!istype(TN)) - say("Node unlock failed: Unknown error.") - return FALSE - var/list/price = TN.get_price(stored_research) - if(stored_research.can_afford(price)) - investigate_log("[key_name(user)] researched [id]([json_encode(price)]) on techweb id [stored_research.id].", INVESTIGATE_RESEARCH) - if(stored_research == SSresearch.science_tech) - SSblackbox.record_feedback("associative", "science_techweb_unlock", 1, list("id" = "[id]", "name" = TN.display_name, "price" = "[json_encode(price)]", "time" = SQLtime())) - if(stored_research.research_node_id(id)) - say("Successfully researched [TN.display_name].") - var/logname = "Unknown" - if(isAI(user)) - logname = "AI: [user.name]" - else if(iscyborg(user)) - logname = "Cyborg: [user.name]" - else if(isliving(user)) - var/mob/living/L = user - logname = L.get_visible_name() - stored_research.research_logs += "[logname] researched node id [id] with cost [json_encode(price)] at [COORD(src)]." - return TRUE - else - say("Failed to research node: Internal database error!") - return FALSE - say("Not enough research points...") - return FALSE - -/obj/machinery/computer/rdconsole/on_deconstruction() - if(linked_destroy) - linked_destroy.linked_console = null - linked_destroy = null - if(linked_lathe) - linked_lathe.linked_console = null - linked_lathe = null - if(linked_imprinter) - linked_imprinter.linked_console = null - linked_imprinter = null - ..() - -/obj/machinery/computer/rdconsole/emag_act(mob/user) - . = ..() - if(obj_flags & EMAGGED) - return - to_chat(user, "You disable the security protocols[locked? " and unlock the console":""].") - playsound(src, "sparks", 75, 1) - obj_flags |= EMAGGED - locked = FALSE - return TRUE - -/obj/machinery/computer/rdconsole/multitool_act(mob/user, obj/item/multitool/I) - var/lathe = linked_lathe && linked_lathe.multitool_act(user, I) - var/print = linked_imprinter && linked_imprinter.multitool_act(user, I) - return lathe || print - -/obj/machinery/computer/rdconsole/proc/list_categories(list/categories, menu_num as num) - if(!categories) - return - - var/line_length = 1 - var/list/l = "" - - for(var/C in categories) - if(line_length > 2) - l += "" - line_length = 1 - - l += "" - line_length++ - - l += "
                [C]
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_header() - var/list/l = list() - var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/research_designs) - l += "[sheet.css_tag()][RDSCREEN_NOBREAK]" - l += "
                [stored_research.organization] Research and Development Network" - l += "Available points:
                [techweb_point_display_rdconsole(stored_research.research_points, stored_research.last_bitcoins)]" - l += "Security protocols: [obj_flags & EMAGGED ? "Disabled" : "Enabled"]" - l += "Main Menu | Back
                [RDSCREEN_NOBREAK]" - l += "[ui_mode == 1? "Normal View" : "Normal View"] | [ui_mode == 2? "Expert View" : "Expert View"] | [ui_mode == 3? "List View" : "List View"]" - return l - -/obj/machinery/computer/rdconsole/proc/ui_main_menu() - var/list/l = list() - if(research_control) - l += "

                Technology" - if(d_disk) - l += "
                Design Disk" - if(t_disk) - l += "
                Tech Disk" - if(linked_destroy) - l += "
                Destructive Analyzer" - if(linked_lathe) - l += "
                Protolathe" - if(linked_imprinter) - l += "
                Circuit Imprinter" - l += "
                Settings

                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_locked() - return list("

                SYSTEM LOCKED


                ") - -/obj/machinery/computer/rdconsole/proc/ui_settings() - var/list/l = list() - l += "

                R&D Console Settings:

                " - l += "Device Linkage Menu" - l += "Lock Console
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_device_linking() - var/list/l = list() - l += "Settings Menu
                " - l += "

                R&D Console Device Linkage Menu:

                " - l += "Re-sync with Nearby Devices" - l += "

                Linked Devices:

                " - l += linked_destroy? "* Destructive Analyzer Disconnect" : "* No Destructive Analyzer Linked" - l += linked_lathe? "* Protolathe Disconnect" : "* No Protolathe Linked" - l += linked_imprinter? "* Circuit Imprinter Disconnect" : "* No Circuit Imprinter Linked" - l += "
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_protolathe_header() - var/list/l = list() - l += "
                Protolathe Menu" - if(linked_lathe.materials.mat_container) - l += "Material Amount: [linked_lathe.materials.format_amount()]" - else - l += "No material storage connected, please contact the quartermaster." - l += "Chemical volume: [linked_lathe.reagents.total_volume] / [linked_lathe.reagents.maximum_volume]
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_protolathe_category_view() //Legacy code - RDSCREEN_UI_LATHE_CHECK - var/list/l = list() - l += ui_protolathe_header() - l += "

                Browsing [selected_category]:

                " - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(selected_category in D.category)|| !(D.build_type & PROTOLATHE)) - continue - if(!(isnull(linked_lathe.allowed_department_flags) || (D.departmental_flags & linked_lathe.allowed_department_flags))) - continue - var/temp_material - var/c = 50 - var/coeff = linked_lathe.efficiency_coeff - if(!linked_lathe.efficient_with(D.build_path)) - coeff = 1 - - var/all_materials = D.materials + D.reagents_list - for(var/M in all_materials) - var/t = linked_lathe.check_mat(D, M) - temp_material += " | " - if (t < 1) - temp_material += "[all_materials[M]/coeff] [CallMaterialName(M)]" - else - temp_material += " [all_materials[M]/coeff] [CallMaterialName(M)]" - c = min(c,t) - - if (c >= 1) - l += "[D.name][RDSCREEN_NOBREAK]" - if(c >= 5) - l += "x5[RDSCREEN_NOBREAK]" - if(c >= 10) - l += "x10[RDSCREEN_NOBREAK]" - l += "[temp_material][RDSCREEN_NOBREAK]" - else - l += "[D.name][temp_material][RDSCREEN_NOBREAK]" - l += "" - l += "
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_protolathe() //Legacy code - RDSCREEN_UI_LATHE_CHECK - var/list/l = list() - l += ui_protolathe_header() - - l += "
                \ - \ - \ - \ - \ - \ -

                " - - l += list_categories(linked_lathe.categories, RDSCREEN_PROTOLATHE_CATEGORY_VIEW) - - return l - -/obj/machinery/computer/rdconsole/proc/ui_protolathe_search() //Legacy code - RDSCREEN_UI_LATHE_CHECK - var/list/l = list() - l += ui_protolathe_header() - for(var/id in matching_design_ids) - var/datum/design/D = SSresearch.techweb_design_by_id(id) - if(!(isnull(linked_lathe.allowed_department_flags) || (D.departmental_flags & linked_lathe.allowed_department_flags))) - continue - var/temp_material - var/c = 50 - var/all_materials = D.materials + D.reagents_list - var/coeff = linked_lathe.efficiency_coeff - if(!linked_lathe.efficient_with(D.build_path)) - coeff = 1 - for(var/M in all_materials) - var/t = linked_lathe.check_mat(D, M) - temp_material += " | " - if (t < 1) - temp_material += "[all_materials[M]/coeff] [CallMaterialName(M)]" - else - temp_material += " [all_materials[M]/coeff] [CallMaterialName(M)]" - c = min(c,t) - - if (c >= 1) - l += "[D.name][RDSCREEN_NOBREAK]" - if(c >= 5) - l += "x5[RDSCREEN_NOBREAK]" - if(c >= 10) - l += "x10[RDSCREEN_NOBREAK]" - l += "[temp_material][RDSCREEN_NOBREAK]" - else - l += "[D.name][temp_material][RDSCREEN_NOBREAK]" - l += "" - l += "
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_protolathe_materials() //Legacy code - RDSCREEN_UI_LATHE_CHECK - var/datum/component/material_container/mat_container = linked_lathe.materials.mat_container - if (!mat_container) - screen = RDSCREEN_PROTOLATHE - return ui_protolathe() - var/list/l = list() - l += ui_protolathe_header() - l += "

                Material Storage:

                " - for(var/mat_id in mat_container.materials) - var/datum/material/M = mat_container.materials[mat_id] - l += "* [M.amount] of [M.name]: " - if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" - if(M.amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" - if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]" - l += "" - l += "
                [RDSCREEN_NOBREAK]" - return l - -/obj/machinery/computer/rdconsole/proc/ui_protolathe_chemicals() //Legacy code - RDSCREEN_UI_LATHE_CHECK - var/list/l = list() - l += ui_protolathe_header() - l += "
                Disposal All Chemicals in Storage" - l += "

                Chemical Storage:

                " - for(var/datum/reagent/R in linked_lathe.reagents.reagent_list) - l += "[R.name]: [R.volume]" - l += "Purge" - l += "
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_circuit_header() //Legacy Code - var/list/l = list() - l += "
                Circuit Imprinter Menu" - if (linked_imprinter.materials.mat_container) - l += "Material Amount: [linked_imprinter.materials.format_amount()]" - else - l += "No material storage connected, please contact the quartermaster." - l += "Chemical volume: [linked_imprinter.reagents.total_volume] / [linked_imprinter.reagents.maximum_volume]
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_circuit() //Legacy code - RDSCREEN_UI_IMPRINTER_CHECK - var/list/l = list() - l += ui_circuit_header() - l += "

                Circuit Imprinter Menu:

                " - - l += "
                \ - \ - \ - \ - \ - \ -

                " - - l += list_categories(linked_imprinter.categories, RDSCREEN_IMPRINTER_CATEGORY_VIEW) - return l - -/obj/machinery/computer/rdconsole/proc/ui_circuit_category_view() //Legacy code - RDSCREEN_UI_IMPRINTER_CHECK - var/list/l = list() - l += ui_circuit_header() - l += "

                Browsing [selected_category]:

                " - - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(selected_category in D.category) || !(D.build_type & IMPRINTER)) - continue - if(!(isnull(linked_imprinter.allowed_department_flags) || (D.departmental_flags & linked_imprinter.allowed_department_flags))) - continue - var/temp_materials - var/check_materials = TRUE - - var/all_materials = D.materials + D.reagents_list - var/coeff = linked_imprinter.efficiency_coeff - if(!linked_imprinter.efficient_with(D.build_path)) - coeff = 1 - - for(var/M in all_materials) - temp_materials += " | " - if (!linked_imprinter.check_mat(D, M)) - check_materials = FALSE - temp_materials += " [all_materials[M]/coeff] [CallMaterialName(M)]" - else - temp_materials += " [all_materials[M]/coeff] [CallMaterialName(M)]" - if (check_materials) - l += "[D.name][temp_materials]" - else - l += "[D.name][temp_materials]" - l += "
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_circuit_search() //Legacy code - RDSCREEN_UI_IMPRINTER_CHECK - var/list/l = list() - l += ui_circuit_header() - l += "

                Search results:

                " - - for(var/id in matching_design_ids) - var/datum/design/D = SSresearch.techweb_design_by_id(id) - if(!(isnull(linked_imprinter.allowed_department_flags) || (D.departmental_flags & linked_imprinter.allowed_department_flags))) - continue - var/temp_materials - var/check_materials = TRUE - var/all_materials = D.materials + D.reagents_list - var/coeff = linked_imprinter.efficiency_coeff - if(!linked_imprinter.efficient_with(D.build_path)) - coeff = 1 - for(var/M in all_materials) - temp_materials += " | " - if (!linked_imprinter.check_mat(D, M)) - check_materials = FALSE - temp_materials += " [all_materials[M]/coeff] [CallMaterialName(M)]" - else - temp_materials += " [all_materials[M]/coeff] [CallMaterialName(M)]" - if (check_materials) - l += "[D.name][temp_materials]" - else - l += "[D.name][temp_materials]" - l += "
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_circuit_chemicals() //legacy code - RDSCREEN_UI_IMPRINTER_CHECK - var/list/l = list() - l += ui_circuit_header() - l += "Disposal All Chemicals in Storage
                " - l += "

                Chemical Storage:

                " - for(var/datum/reagent/R in linked_imprinter.reagents.reagent_list) - l += "[R.name]: [R.volume]" - l += "Purge" - return l - -/obj/machinery/computer/rdconsole/proc/ui_circuit_materials() //Legacy code! - RDSCREEN_UI_IMPRINTER_CHECK - var/datum/component/material_container/mat_container = linked_imprinter.materials.mat_container - if (!mat_container) - screen = RDSCREEN_IMPRINTER - return ui_circuit() - var/list/l = list() - l += ui_circuit_header() - l += "

                Material Storage:

                " - for(var/mat_id in mat_container.materials) - var/datum/material/M = mat_container.materials[mat_id] - l += "* [M.amount] of [M.name]: " - if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" - if(M.amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" - if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_techdisk() //Legacy code - RDSCREEN_UI_TDISK_CHECK - var/list/l = list() - l += "
                Disk Operations: Clear Disk" - l += "Eject Disk" - l += "Upload All" - l += "Load Technology to Disk
                " - l += "

                Stored Technology Nodes:

                " - for(var/i in t_disk.stored_research.researched_nodes) - var/datum/techweb_node/N = SSresearch.techweb_node_by_id(i) - l += "[N.display_name]" - l += "
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_designdisk() //Legacy code - RDSCREEN_UI_DDISK_CHECK - var/list/l = list() - l += "Disk Operations: Clear DiskUpload AllEject Disk" - for(var/i in 1 to d_disk.max_blueprints) - l += "
                " - if(d_disk.blueprints[i]) - var/datum/design/D = d_disk.blueprints[i] - l += "[D.name]" - l += "Operations: Upload to database Clear Slot" - else - l += "Empty Slot Operations: Load Design to Slot" - l += "
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_designdisk_upload() //Legacy code - RDSCREEN_UI_DDISK_CHECK - var/list/l = list() - l += "Return to Disk Operations
                " - l += "

                Load Design to Disk:

                " - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - l += "[D.name] " - l += "Copy to Disk" - l += "
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_deconstruct() //Legacy code - RDSCREEN_UI_DECONSTRUCT_CHECK - var/list/l = list() - if(!linked_destroy.loaded_item) - l += "
                No item loaded. Standing-by...
                " - else - l += "
                [RDSCREEN_NOBREAK]" - l += "
                [icon2html(linked_destroy.loaded_item, usr)][linked_destroy.loaded_item.name] Eject
                [RDSCREEN_NOBREAK]" - l += "Select a node to boost by deconstructing this item. This item can boost:" - - var/anything = FALSE - var/list/boostable_nodes = techweb_item_boost_check(linked_destroy.loaded_item) - for(var/id in boostable_nodes) - anything = TRUE - var/list/worth = boostable_nodes[id] - var/datum/techweb_node/N = SSresearch.techweb_node_by_id(id) - - l += "
                [RDSCREEN_NOBREAK]" - if (stored_research.researched_nodes[N.id]) // already researched - l += "[N.display_name]" - l += "This node has already been researched." - else if(!length(worth)) // reveal only - if (stored_research.hidden_nodes[N.id]) - l += "[N.display_name]" - l += "This node will be revealed." - else - l += "[N.display_name]" - l += "This node has already been revealed." - else // boost by the difference - var/list/differences = list() - var/list/already_boosted = stored_research.boosted_nodes[N.id] - for(var/i in worth) - var/already_boosted_amount = already_boosted? stored_research.boosted_nodes[N.id][i] : 0 - var/amt = min(worth[i], N.research_costs[i]) - already_boosted_amount - if(amt > 0) - differences[i] = amt - if (length(differences)) - l += "[N.display_name]" - l += "This node will be boosted with the following:
                [techweb_point_display_generic(differences)]" - else - l += "[N.display_name]" - l += "This node has already been boosted." - l += "
                [RDSCREEN_NOBREAK]" - - // point deconstruction and material reclamation use the same ID to prevent accidentally missing the points - var/list/point_values = techweb_item_point_check(linked_destroy.loaded_item) - if(point_values) - anything = TRUE - l += "
                [RDSCREEN_NOBREAK]" - if (stored_research.deconstructed_items[linked_destroy.loaded_item.type]) - l += "Point Deconstruction" - l += "This item's points have already been claimed." - else - l += "Point Deconstruction" - l += "This item is worth:
                [techweb_point_display_generic(point_values)]!" - l += "
                [RDSCREEN_NOBREAK]" - - if(!(linked_destroy.loaded_item.resistance_flags & INDESTRUCTIBLE)) - var/list/materials = linked_destroy.loaded_item.materials - l += "
                [materials.len? "Material Reclamation" : "Destroy Item"]" - for (var/M in materials) - l += "* [CallMaterialName(M)] x [materials[M]]" - l += "
                [RDSCREEN_NOBREAK]" - anything = TRUE - - if (!anything) - l += "Nothing!" - - l += "
                " - return l - -/obj/machinery/computer/rdconsole/proc/ui_techweb() - var/list/l = list() - if(islist(stored_research.research_logs) && stored_research.research_logs.len) - l += "Last action: [stored_research.research_logs[stored_research.research_logs.len]]" - if(ui_mode != RDCONSOLE_UI_MODE_LIST) - var/list/columns = list() - var/max_tier = 0 - for (var/node_ in stored_research.tiers) - var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_) - var/tier = stored_research.tiers[node.id] - LAZYINITLIST(columns["[tier]"]) // String hackery to make the numbers associative - columns["[tier]"] += ui_techweb_single_node(node, minimal=(tier != 1)) - max_tier = max(max_tier, tier) - - l += "[RDSCREEN_NOBREAK]" - if(max_tier) - for(var/tier in 0 to max_tier) - l += "[RDSCREEN_NOBREAK]" - l += "
                ResearchedAvailableFuture
                [RDSCREEN_NOBREAK]" - l += columns["[tier]"] - l += "
                [RDSCREEN_NOBREAK]" - else - var/list/avail = list() //This could probably be optimized a bit later. - var/list/unavail = list() - var/list/res = list() - for(var/v in stored_research.researched_nodes) - res += SSresearch.techweb_node_by_id(v) - for(var/v in stored_research.available_nodes) - if(stored_research.researched_nodes[v]) - continue - avail += SSresearch.techweb_node_by_id(v) - for(var/v in stored_research.visible_nodes) - if(stored_research.available_nodes[v]) - continue - unavail += SSresearch.techweb_node_by_id(v) - l += "

                Technology Nodes:

                [RDSCREEN_NOBREAK]" - l += "

                Available for Research:

                " - for(var/datum/techweb_node/N in avail) - var/not_unlocked = (stored_research.available_nodes[N.id] && !stored_research.researched_nodes[N.id]) - var/has_points = (stored_research.can_afford(N.get_price(stored_research))) - var/research_href = not_unlocked? (has_points? "Research" : "Not Enough Points") : null - l += "[N.display_name][research_href]" - l += "

                Locked Nodes:

                " - for(var/datum/techweb_node/N in unavail) - l += "[N.display_name]" - l += "

                Researched Nodes:

                " - for(var/datum/techweb_node/N in res) - l += "[N.display_name]" - l += "
                [RDSCREEN_NOBREAK]" - return l - -/obj/machinery/computer/rdconsole/proc/machine_icon(atom/item) - return icon2html(initial(item.icon), usr, initial(item.icon_state), SOUTH) - -/obj/machinery/computer/rdconsole/proc/ui_techweb_single_node(datum/techweb_node/node, selflink=TRUE, minimal=FALSE) - var/list/l = list() - if (stored_research.hidden_nodes[node.id]) - return l - var/display_name = node.display_name - if (selflink) - display_name = "[display_name]" - l += "
                [display_name] [RDSCREEN_NOBREAK]" - if(minimal) - l += "
                [node.description]" - else - if(stored_research.researched_nodes[node.id]) - l += "Researched" - else if(stored_research.available_nodes[node.id]) - if(stored_research.can_afford(node.get_price(stored_research))) - l += "
                [node.price_display(stored_research)]" - else - l += "
                [node.price_display(stored_research)]" // gray - too expensive - else - l += "
                [node.price_display(stored_research)]" // red - missing prereqs - if(ui_mode == RDCONSOLE_UI_MODE_NORMAL) - l += "[node.description]" - for(var/i in node.design_ids) - var/datum/design/D = SSresearch.techweb_design_by_id(i) - l += "[D.icon_html(usr)][RDSCREEN_NOBREAK]" - l += "
                [RDSCREEN_NOBREAK]" - return l - -/obj/machinery/computer/rdconsole/proc/ui_techweb_nodeview() - var/datum/techweb_node/selected_node = SSresearch.techweb_node_by_id(selected_node_id) - RDSCREEN_UI_SNODE_CHECK - var/list/l = list() - if(stored_research.hidden_nodes[selected_node.id]) - l += "

                ERROR: RESEARCH NODE UNKNOWN.

                " - return - - l += "[RDSCREEN_NOBREAK]" - if (length(selected_node.prereq_ids)) - l += "[RDSCREEN_NOBREAK]" - l += "[RDSCREEN_NOBREAK]" - if (length(selected_node.unlock_ids)) - l += "[RDSCREEN_NOBREAK]" - - l += "[RDSCREEN_NOBREAK]" - if (length(selected_node.prereq_ids)) - l += "[RDSCREEN_NOBREAK]" - l += "[RDSCREEN_NOBREAK]" - if (length(selected_node.unlock_ids)) - l += "[RDSCREEN_NOBREAK]" - - l += "
                RequiresCurrent NodeUnlocks
                [RDSCREEN_NOBREAK]" - for (var/i in selected_node.prereq_ids) - l += ui_techweb_single_node(SSresearch.techweb_node_by_id(i)) - l += "[RDSCREEN_NOBREAK]" - l += ui_techweb_single_node(selected_node, selflink=FALSE) - l += "[RDSCREEN_NOBREAK]" - for (var/i in selected_node.unlock_ids) - l += ui_techweb_single_node(SSresearch.techweb_node_by_id(i)) - l += "
                [RDSCREEN_NOBREAK]" - return l - -/obj/machinery/computer/rdconsole/proc/ui_techweb_designview() //Legacy code - var/datum/design/selected_design = SSresearch.techweb_design_by_id(selected_design_id) - RDSCREEN_UI_SDESIGN_CHECK - var/list/l = list() - l += "
                [selected_design.icon_html(usr)][selected_design.name]
                [RDSCREEN_NOBREAK]" - if(selected_design.build_type) - var/lathes = list() - if(selected_design.build_type & IMPRINTER) - lathes += "[machine_icon(/obj/machinery/rnd/production/circuit_imprinter)][RDSCREEN_NOBREAK]" - if (linked_imprinter && stored_research.researched_designs[selected_design.id]) - l += "Imprint" - if(selected_design.build_type & PROTOLATHE) - lathes += "[machine_icon(/obj/machinery/rnd/production/protolathe)][RDSCREEN_NOBREAK]" - if (linked_lathe && stored_research.researched_designs[selected_design.id]) - l += "Construct" - if(selected_design.build_type & AUTOLATHE) - lathes += "[machine_icon(/obj/machinery/autolathe)][RDSCREEN_NOBREAK]" - if(selected_design.build_type & MECHFAB) - lathes += "[machine_icon(/obj/machinery/mecha_part_fabricator)][RDSCREEN_NOBREAK]" - if(selected_design.build_type & BIOGENERATOR) - lathes += "[machine_icon(/obj/machinery/biogenerator)][RDSCREEN_NOBREAK]" - if(selected_design.build_type & LIMBGROWER) - lathes += "[machine_icon(/obj/machinery/limbgrower)][RDSCREEN_NOBREAK]" - if(selected_design.build_type & SMELTER) - lathes += "[machine_icon(/obj/machinery/mineral/processing_unit)][RDSCREEN_NOBREAK]" - l += "Construction types:" - l += lathes - l += "" - l += "Required materials:" - var/all_mats = selected_design.materials + selected_design.reagents_list - for(var/M in all_mats) - l += "* [CallMaterialName(M)] x [all_mats[M]]" - l += "Unlocked by:" - for (var/i in selected_design.unlocked_by) - l += ui_techweb_single_node(SSresearch.techweb_node_by_id(i)) - l += "[RDSCREEN_NOBREAK]
                " - return l - -//Fuck TGUI. -/obj/machinery/computer/rdconsole/proc/generate_ui() - var/list/ui = list() - ui += ui_header() - if(locked) - ui += ui_locked() - else - switch(screen) - if(RDSCREEN_MENU) - ui += ui_main_menu() - if(RDSCREEN_TECHWEB) - ui += ui_techweb() - if(RDSCREEN_TECHWEB_NODEVIEW) - ui += ui_techweb_nodeview() - if(RDSCREEN_TECHWEB_DESIGNVIEW) - ui += ui_techweb_designview() - if(RDSCREEN_DESIGNDISK) - ui += ui_designdisk() - if(RDSCREEN_DESIGNDISK_UPLOAD) - ui += ui_designdisk_upload() - if(RDSCREEN_TECHDISK) - ui += ui_techdisk() - if(RDSCREEN_DECONSTRUCT) - ui += ui_deconstruct() - if(RDSCREEN_PROTOLATHE) - ui += ui_protolathe() - if(RDSCREEN_PROTOLATHE_CATEGORY_VIEW) - ui += ui_protolathe_category_view() - if(RDSCREEN_PROTOLATHE_MATERIALS) - ui += ui_protolathe_materials() - if(RDSCREEN_PROTOLATHE_CHEMICALS) - ui += ui_protolathe_chemicals() - if(RDSCREEN_PROTOLATHE_SEARCH) - ui += ui_protolathe_search() - if(RDSCREEN_IMPRINTER) - ui += ui_circuit() - if(RDSCREEN_IMPRINTER_CATEGORY_VIEW) - ui += ui_circuit_category_view() - if(RDSCREEN_IMPRINTER_MATERIALS) - ui += ui_circuit_materials() - if(RDSCREEN_IMPRINTER_CHEMICALS) - ui += ui_circuit_chemicals() - if(RDSCREEN_IMPRINTER_SEARCH) - ui += ui_circuit_search() - if(RDSCREEN_SETTINGS) - ui += ui_settings() - if(RDSCREEN_DEVICE_LINKING) - ui += ui_device_linking() - for(var/i in 1 to length(ui)) - if(!findtextEx(ui[i], RDSCREEN_NOBREAK)) - ui[i] += "
                " - ui[i] = replacetextEx(ui[i], RDSCREEN_NOBREAK, "") - return ui.Join("") - -/obj/machinery/computer/rdconsole/Topic(raw, ls) - if(..()) - return - add_fingerprint(usr) - usr.set_machine(src) - if(ls["switch_screen"]) - back = screen - screen = text2num(ls["switch_screen"]) - if(ls["ui_mode"]) - ui_mode = text2num(ls["ui_mode"]) - if(ls["lock_console"]) - if(obj_flags & EMAGGED) - to_chat(usr, "Security protocol error: Unable to lock.") - return - if(allowed(usr)) - lock_console(usr) - else - to_chat(usr, "Unauthorized Access.") - if(ls["unlock_console"]) - if(allowed(usr)) - unlock_console(usr) - else - to_chat(usr, "Unauthorized Access.") - if(ls["find_device"]) - SyncRDevices() - say("Resynced with nearby devices.") - if(ls["back_screen"]) - back = text2num(ls["back_screen"]) - if(ls["build"]) //Causes the Protolathe to build something. - if(QDELETED(linked_lathe)) - say("No Protolathe Linked!") - return - if(linked_lathe.busy) - say("Warning: Protolathe busy!") - else - linked_lathe.user_try_print_id(ls["build"], ls["amount"]) - if(ls["imprint"]) - if(QDELETED(linked_imprinter)) - say("No Circuit Imprinter Linked!") - return - if(linked_imprinter.busy) - say("Warning: Imprinter busy!") - else - linked_imprinter.user_try_print_id(ls["imprint"]) - if(ls["category"]) - selected_category = ls["category"] - if(ls["disconnect"]) //The R&D console disconnects with a specific device. - switch(ls["disconnect"]) - if("destroy") - if(QDELETED(linked_destroy)) - say("No Destructive Analyzer Linked!") - return - linked_destroy.linked_console = null - linked_destroy = null - if("lathe") - if(QDELETED(linked_lathe)) - say("No Protolathe Linked!") - return - linked_lathe.linked_console = null - linked_lathe = null - if("imprinter") - if(QDELETED(linked_imprinter)) - say("No Circuit Imprinter Linked!") - return - linked_imprinter.linked_console = null - linked_imprinter = null - if(ls["eject_design"]) //Eject the design disk. - eject_disk("design") - screen = RDSCREEN_MENU - say("Ejecting Design Disk") - if(ls["eject_tech"]) //Eject the technology disk. - eject_disk("tech") - screen = RDSCREEN_MENU - say("Ejecting Technology Disk") - if(ls["deconstruct"]) - if(QDELETED(linked_destroy)) - say("No Destructive Analyzer Linked!") - return - if(!linked_destroy.user_try_decon_id(ls["deconstruct"], usr)) - say("Destructive analysis failed!") - //Protolathe Materials - if(ls["disposeP"]) //Causes the protolathe to dispose of a single reagent (all of it) - if(QDELETED(linked_lathe)) - say("No Protolathe Linked!") - return - linked_lathe.reagents.del_reagent(ls["disposeP"]) - if(ls["disposeallP"]) //Causes the protolathe to dispose of all it's reagents. - if(QDELETED(linked_lathe)) - say("No Protolathe Linked!") - return - linked_lathe.reagents.clear_reagents() - if(ls["ejectsheet"]) //Causes the protolathe to eject a sheet of material - if(QDELETED(linked_lathe)) - say("No Protolathe Linked!") - return - if(!linked_lathe.materials.mat_container) - say("No material storage linked to protolathe!") - return - linked_lathe.eject_sheets(ls["ejectsheet"], ls["eject_amt"]) - //Circuit Imprinter Materials - if(ls["disposeI"]) //Causes the circuit imprinter to dispose of a single reagent (all of it) - if(QDELETED(linked_imprinter)) - say("No Circuit Imprinter Linked!") - return - linked_imprinter.reagents.del_reagent(ls["disposeI"]) - if(ls["disposeallI"]) //Causes the circuit imprinter to dispose of all it's reagents. - if(QDELETED(linked_imprinter)) - say("No Circuit Imprinter Linked!") - return - linked_imprinter.reagents.clear_reagents() - if(ls["imprinter_ejectsheet"]) //Causes the imprinter to eject a sheet of material - if(QDELETED(linked_imprinter)) - say("No Circuit Imprinter Linked!") - return - if(!linked_imprinter.materials.mat_container) - say("No material storage linked to circuit imprinter!") - return - linked_imprinter.eject_sheets(ls["imprinter_ejectsheet"], ls["eject_amt"]) - if(ls["disk_slot"]) - disk_slot_selected = text2num(ls["disk_slot"]) - if(ls["research_node"]) - if(!research_control) - return //honestly should call them out for href exploiting :^) - if(!SSresearch.science_tech.available_nodes[ls["research_node"]]) - return //Nope! - research_node(ls["research_node"], usr) - if(ls["clear_tech"]) //Erase la on the technology disk. - if(QDELETED(t_disk)) - say("No Technology Disk Inserted!") - return - qdel(t_disk.stored_research) - t_disk.stored_research = new - say("Wiping technology disk.") - if(ls["copy_tech"]) //Copy some technology from the research holder to the disk. - if(QDELETED(t_disk)) - say("No Technology Disk Inserted!") - return - stored_research.copy_research_to(t_disk.stored_research) - screen = RDSCREEN_TECHDISK - say("Downloading to technology disk.") - if(ls["clear_design"]) //Erases la on the design disk. - if(QDELETED(d_disk)) - say("No Design Disk Inserted!") - return - var/n = text2num(ls["clear_design"]) - if(!n) - for(var/i in 1 to d_disk.max_blueprints) - d_disk.blueprints[i] = null - say("Wiping design disk.") - else - var/datum/design/D = d_disk.blueprints[n] - say("Wiping design [D.name] from design disk.") - d_disk.blueprints[n] = null - if(ls["search"]) //Search for designs with name matching pattern - searchstring = ls["to_search"] - searchtype = ls["type"] - rescan_views() - if(searchtype == "proto") - screen = RDSCREEN_PROTOLATHE_SEARCH - else - screen = RDSCREEN_IMPRINTER_SEARCH - if(ls["updt_tech"]) //Uple the research holder with information from the technology disk. - if(QDELETED(t_disk)) - say("No Technology Disk Inserted!") - return - say("Uploading technology disk.") - t_disk.stored_research.copy_research_to(stored_research) - if(ls["copy_design"]) //Copy design from the research holder to the design disk. - if(QDELETED(d_disk)) - say("No Design Disk Inserted!") - return - var/slot = text2num(ls["copy_design"]) - var/datum/design/D = SSresearch.techweb_design_by_id(ls["copy_design_ID"]) - if(D) - var/autolathe_friendly = TRUE - if(D.reagents_list.len) - autolathe_friendly = FALSE - D.category -= "Imported" - else - for(var/x in D.materials) - if( !(x in list(MAT_METAL, MAT_GLASS))) - autolathe_friendly = FALSE - D.category -= "Imported" - - if(D.build_type & (AUTOLATHE|PROTOLATHE|CRAFTLATHE)) // Specifically excludes circuit imprinter and mechfab - D.build_type = autolathe_friendly ? (D.build_type | AUTOLATHE) : D.build_type - D.category |= "Imported" - d_disk.blueprints[slot] = D - screen = RDSCREEN_DESIGNDISK - if(ls["eject_item"]) //Eject the item inside the destructive analyzer. - if(QDELETED(linked_destroy)) - say("No Destructive Analyzer Linked!") - return - if(linked_destroy.busy) - to_chat(usr, "The destructive analyzer is busy at the moment.") - else if(linked_destroy.loaded_item) - linked_destroy.unload_item() - screen = RDSCREEN_MENU - if(ls["view_node"]) - selected_node_id = ls["view_node"] - screen = RDSCREEN_TECHWEB_NODEVIEW - if(ls["view_design"]) - selected_design_id = ls["view_design"] - screen = RDSCREEN_TECHWEB_DESIGNVIEW - if(ls["updt_design"]) //Uploads a design from disk to the techweb. - if(QDELETED(d_disk)) - say("No design disk found.") - return - var/n = text2num(ls["updt_design"]) - if(!n) - for(var/D in d_disk.blueprints) - if(D) - stored_research.add_design(D, TRUE) - else - stored_research.add_design(d_disk.blueprints[n], TRUE) - - updateUsrDialog() - -/obj/machinery/computer/rdconsole/ui_interact(mob/user) - . = ..() - var/datum/browser/popup = new(user, "rndconsole", name, 900, 600) - popup.add_stylesheet("techwebs", 'html/browser/techwebs.css') - popup.set_content(generate_ui()) - popup.open() - -/obj/machinery/computer/rdconsole/proc/tdisk_uple_complete() - tdisk_uple = FALSE - updateUsrDialog() - -/obj/machinery/computer/rdconsole/proc/ddisk_uple_complete() - ddisk_uple = FALSE - updateUsrDialog() - -/obj/machinery/computer/rdconsole/proc/eject_disk(type) - if(type == "design") - d_disk.forceMove(get_turf(src)) - d_disk = null - if(type == "tech") - t_disk.forceMove(get_turf(src)) - t_disk = null - -/obj/machinery/computer/rdconsole/proc/rescan_views() - var/compare - matching_design_ids.Cut() - if(searchtype == "proto") - compare = PROTOLATHE - else if(searchtype == "imprint") - compare = IMPRINTER - for(var/v in stored_research.researched_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(v) - if(!(D.build_type & compare)) - continue - if(findtext(D.name,searchstring)) - matching_design_ids.Add(D.id) - -/obj/machinery/computer/rdconsole/proc/check_canprint(datum/design/D, buildtype) - var/amount = 50 - if(buildtype == IMPRINTER) - if(QDELETED(linked_imprinter)) - return FALSE - for(var/M in D.materials + D.reagents_list) - amount = min(amount, linked_imprinter.check_mat(D, M)) - if(amount < 1) - return FALSE - else if(buildtype == PROTOLATHE) - if(QDELETED(linked_lathe)) - return FALSE - for(var/M in D.materials + D.reagents_list) - amount = min(amount, linked_lathe.check_mat(D, M)) - if(amount < 1) - return FALSE - else - return FALSE - return amount - -/obj/machinery/computer/rdconsole/proc/lock_console(mob/user) - locked = TRUE - -/obj/machinery/computer/rdconsole/proc/unlock_console(mob/user) - locked = FALSE - -/obj/machinery/computer/rdconsole/robotics - name = "Robotics R&D Console" - req_access = null - req_access_txt = "29" - -/obj/machinery/computer/rdconsole/robotics/Initialize() - . = ..() - if(circuit) - circuit.name = "R&D Console - Robotics (Computer Board)" - circuit.build_path = /obj/machinery/computer/rdconsole/robotics - -/obj/machinery/computer/rdconsole/core - name = "Core R&D Console" - -/obj/machinery/computer/rdconsole/experiment - name = "E.X.P.E.R.I-MENTOR R&D Console" + +/* +Research and Development (R&D) Console + +This is the main work horse of the R&D system. It contains the menus/controls for the Destructive Analyzer, Protolathe, and Circuit +imprinter. + +Basic use: When it first is created, it will attempt to link up to related devices within 3 squares. It'll only link up if they +aren't already linked to another console. Any consoles it cannot link up with (either because all of a certain type are already +linked or there aren't any in range), you'll just not have access to that menu. In the settings menu, there are menu options that +allow a player to attempt to re-sync with nearby consoles. You can also force it to disconnect from a specific console. + +The only thing that requires toxins access is locking and unlocking the console on the settings menu. +Nothing else in the console has ID requirements. + +*/ +/obj/machinery/computer/rdconsole + name = "R&D Console" + desc = "A console used to interface with R&D tools." + icon_screen = "rdcomp" + icon_keyboard = "rd_key" + var/datum/techweb/stored_research //Reference to global science techweb. + var/obj/item/disk/tech_disk/t_disk //Stores the technology disk. + var/obj/item/disk/design_disk/d_disk //Stores the design disk. + circuit = /obj/item/circuitboard/computer/rdconsole + + var/obj/machinery/rnd/destructive_analyzer/linked_destroy //Linked Destructive Analyzer + var/obj/machinery/rnd/production/protolathe/linked_lathe //Linked Protolathe + var/obj/machinery/rnd/production/circuit_imprinter/linked_imprinter //Linked Circuit Imprinter + + req_access = list(ACCESS_TOX) //lA AND SETTING MANIPULATION REQUIRES SCIENTIST ACCESS. + + //UI VARS + var/screen = RDSCREEN_MENU + var/back = RDSCREEN_MENU + var/locked = FALSE + var/tdisk_uple = FALSE + var/ddisk_uple = FALSE + var/datum/selected_node_id + var/datum/selected_design_id + var/selected_category + var/list/matching_design_ids + var/disk_slot_selected + var/searchstring = "" + var/searchtype = "" + var/ui_mode = RDCONSOLE_UI_MODE_NORMAL + + var/research_control = TRUE + +/obj/machinery/computer/rdconsole/production + circuit = /obj/item/circuitboard/computer/rdconsole/production + research_control = FALSE + +/proc/CallMaterialName(ID) + if (copytext(ID, 1, 2) == "$" && GLOB.materials_list[ID]) + var/datum/material/material = GLOB.materials_list[ID] + return material.name + + else if(GLOB.chemical_reagents_list[ID]) + var/datum/reagent/reagent = GLOB.chemical_reagents_list[ID] + return reagent.name + return "ERROR: Report This" + +/obj/machinery/computer/rdconsole/proc/SyncRDevices() //Makes sure it is properly sync'ed up with the devices attached to it (if any). + for(var/obj/machinery/rnd/D in oview(3,src)) + if(D.linked_console != null || D.disabled || D.panel_open) + continue + if(istype(D, /obj/machinery/rnd/destructive_analyzer)) + if(linked_destroy == null) + linked_destroy = D + D.linked_console = src + else if(istype(D, /obj/machinery/rnd/production/protolathe)) + if(linked_lathe == null) + var/obj/machinery/rnd/production/protolathe/P = D + if(!P.console_link) + continue + linked_lathe = D + D.linked_console = src + else if(istype(D, /obj/machinery/rnd/production/circuit_imprinter)) + if(linked_imprinter == null) + var/obj/machinery/rnd/production/circuit_imprinter/C = D + if(!C.console_link) + continue + linked_imprinter = D + D.linked_console = src + +/obj/machinery/computer/rdconsole/Initialize() + . = ..() + stored_research = SSresearch.science_tech + stored_research.consoles_accessing[src] = TRUE + matching_design_ids = list() + SyncRDevices() + +/obj/machinery/computer/rdconsole/Destroy() + if(stored_research) + stored_research.consoles_accessing -= src + if(linked_destroy) + linked_destroy.linked_console = null + linked_destroy = null + if(linked_lathe) + linked_lathe.linked_console = null + linked_lathe = null + if(linked_imprinter) + linked_imprinter.linked_console = null + linked_imprinter = null + if(t_disk) + t_disk.forceMove(get_turf(src)) + t_disk = null + if(d_disk) + d_disk.forceMove(get_turf(src)) + d_disk = null + matching_design_ids = null + return ..() + +/obj/machinery/computer/rdconsole/attackby(obj/item/D, mob/user, params) + //Loading a disk into it. + if(istype(D, /obj/item/disk)) + if(istype(D, /obj/item/disk/tech_disk)) + if(t_disk) + to_chat(user, "A technology disk is already loaded!") + return + if(!user.transferItemToLoc(D, src)) + to_chat(user, "[D] is stuck to your hand!") + return + t_disk = D + else if (istype(D, /obj/item/disk/design_disk)) + if(d_disk) + to_chat(user, "A design disk is already loaded!") + return + if(!user.transferItemToLoc(D, src)) + to_chat(user, "[D] is stuck to your hand!") + return + d_disk = D + else + to_chat(user, "Machine cannot accept disks in that format.") + return + to_chat(user, "You insert [D] into \the [src]!") + else if(!(linked_destroy && linked_destroy.busy) && !(linked_lathe && linked_lathe.busy) && !(linked_imprinter && linked_imprinter.busy)) + . = ..() + +/obj/machinery/computer/rdconsole/proc/research_node(id, mob/user) + if(!stored_research.available_nodes[id] || stored_research.researched_nodes[id]) + say("Node unlock failed: Either already researched or not available!") + return FALSE + var/datum/techweb_node/TN = SSresearch.techweb_node_by_id(id) + if(!istype(TN)) + say("Node unlock failed: Unknown error.") + return FALSE + var/list/price = TN.get_price(stored_research) + if(stored_research.can_afford(price)) + investigate_log("[key_name(user)] researched [id]([json_encode(price)]) on techweb id [stored_research.id].", INVESTIGATE_RESEARCH) + if(stored_research == SSresearch.science_tech) + SSblackbox.record_feedback("associative", "science_techweb_unlock", 1, list("id" = "[id]", "name" = TN.display_name, "price" = "[json_encode(price)]", "time" = SQLtime())) + if(stored_research.research_node_id(id)) + say("Successfully researched [TN.display_name].") + var/logname = "Unknown" + if(isAI(user)) + logname = "AI: [user.name]" + else if(iscyborg(user)) + logname = "Cyborg: [user.name]" + else if(isliving(user)) + var/mob/living/L = user + logname = L.get_visible_name() + stored_research.research_logs += "[logname] researched node id [id] with cost [json_encode(price)] at [COORD(src)]." + return TRUE + else + say("Failed to research node: Internal database error!") + return FALSE + say("Not enough research points...") + return FALSE + +/obj/machinery/computer/rdconsole/on_deconstruction() + if(linked_destroy) + linked_destroy.linked_console = null + linked_destroy = null + if(linked_lathe) + linked_lathe.linked_console = null + linked_lathe = null + if(linked_imprinter) + linked_imprinter.linked_console = null + linked_imprinter = null + ..() + +/obj/machinery/computer/rdconsole/emag_act(mob/user) + . = ..() + if(obj_flags & EMAGGED) + return + to_chat(user, "You disable the security protocols[locked? " and unlock the console":""].") + playsound(src, "sparks", 75, 1) + obj_flags |= EMAGGED + locked = FALSE + return TRUE + +/obj/machinery/computer/rdconsole/multitool_act(mob/user, obj/item/multitool/I) + var/lathe = linked_lathe && linked_lathe.multitool_act(user, I) + var/print = linked_imprinter && linked_imprinter.multitool_act(user, I) + return lathe || print + +/obj/machinery/computer/rdconsole/proc/list_categories(list/categories, menu_num as num) + if(!categories) + return + + var/line_length = 1 + var/list/l = "" + + for(var/C in categories) + if(line_length > 2) + l += "" + line_length = 1 + + l += "" + line_length++ + + l += "
                [C]
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_header() + var/list/l = list() + var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/research_designs) + l += "[sheet.css_tag()][RDSCREEN_NOBREAK]" + l += "
                [stored_research.organization] Research and Development Network" + l += "Available points:
                [techweb_point_display_rdconsole(stored_research.research_points, stored_research.last_bitcoins)]" + l += "Security protocols: [obj_flags & EMAGGED ? "Disabled" : "Enabled"]" + l += "Main Menu | Back
                [RDSCREEN_NOBREAK]" + l += "[ui_mode == 1? "Normal View" : "Normal View"] | [ui_mode == 2? "Expert View" : "Expert View"] | [ui_mode == 3? "List View" : "List View"]" + return l + +/obj/machinery/computer/rdconsole/proc/ui_main_menu() + var/list/l = list() + if(research_control) + l += "

                Technology" + if(d_disk) + l += "
                Design Disk" + if(t_disk) + l += "
                Tech Disk" + if(linked_destroy) + l += "
                Destructive Analyzer" + if(linked_lathe) + l += "
                Protolathe" + if(linked_imprinter) + l += "
                Circuit Imprinter" + l += "
                Settings

                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_locked() + return list("

                SYSTEM LOCKED


                ") + +/obj/machinery/computer/rdconsole/proc/ui_settings() + var/list/l = list() + l += "

                R&D Console Settings:

                " + l += "Device Linkage Menu" + l += "Lock Console
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_device_linking() + var/list/l = list() + l += "Settings Menu
                " + l += "

                R&D Console Device Linkage Menu:

                " + l += "Re-sync with Nearby Devices" + l += "

                Linked Devices:

                " + l += linked_destroy? "* Destructive Analyzer Disconnect" : "* No Destructive Analyzer Linked" + l += linked_lathe? "* Protolathe Disconnect" : "* No Protolathe Linked" + l += linked_imprinter? "* Circuit Imprinter Disconnect" : "* No Circuit Imprinter Linked" + l += "
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_protolathe_header() + var/list/l = list() + l += "
                Protolathe Menu" + if(linked_lathe.materials.mat_container) + l += "Material Amount: [linked_lathe.materials.format_amount()]" + else + l += "No material storage connected, please contact the quartermaster." + l += "Chemical volume: [linked_lathe.reagents.total_volume] / [linked_lathe.reagents.maximum_volume]
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_protolathe_category_view() //Legacy code + RDSCREEN_UI_LATHE_CHECK + var/list/l = list() + l += ui_protolathe_header() + l += "

                Browsing [selected_category]:

                " + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(!(selected_category in D.category)|| !(D.build_type & PROTOLATHE)) + continue + if(!(isnull(linked_lathe.allowed_department_flags) || (D.departmental_flags & linked_lathe.allowed_department_flags))) + continue + var/temp_material + var/c = 50 + var/coeff = linked_lathe.efficiency_coeff + if(!linked_lathe.efficient_with(D.build_path)) + coeff = 1 + + var/all_materials = D.materials + D.reagents_list + for(var/M in all_materials) + var/t = linked_lathe.check_mat(D, M) + temp_material += " | " + if (t < 1) + temp_material += "[all_materials[M]/coeff] [CallMaterialName(M)]" + else + temp_material += " [all_materials[M]/coeff] [CallMaterialName(M)]" + c = min(c,t) + + if (c >= 1) + l += "[D.name][RDSCREEN_NOBREAK]" + if(c >= 5) + l += "x5[RDSCREEN_NOBREAK]" + if(c >= 10) + l += "x10[RDSCREEN_NOBREAK]" + l += "[temp_material][RDSCREEN_NOBREAK]" + else + l += "[D.name][temp_material][RDSCREEN_NOBREAK]" + l += "" + l += "
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_protolathe() //Legacy code + RDSCREEN_UI_LATHE_CHECK + var/list/l = list() + l += ui_protolathe_header() + + l += "
                \ + \ + \ + \ + \ + \ +

                " + + l += list_categories(linked_lathe.categories, RDSCREEN_PROTOLATHE_CATEGORY_VIEW) + + return l + +/obj/machinery/computer/rdconsole/proc/ui_protolathe_search() //Legacy code + RDSCREEN_UI_LATHE_CHECK + var/list/l = list() + l += ui_protolathe_header() + for(var/id in matching_design_ids) + var/datum/design/D = SSresearch.techweb_design_by_id(id) + if(!(isnull(linked_lathe.allowed_department_flags) || (D.departmental_flags & linked_lathe.allowed_department_flags))) + continue + var/temp_material + var/c = 50 + var/all_materials = D.materials + D.reagents_list + var/coeff = linked_lathe.efficiency_coeff + if(!linked_lathe.efficient_with(D.build_path)) + coeff = 1 + for(var/M in all_materials) + var/t = linked_lathe.check_mat(D, M) + temp_material += " | " + if (t < 1) + temp_material += "[all_materials[M]/coeff] [CallMaterialName(M)]" + else + temp_material += " [all_materials[M]/coeff] [CallMaterialName(M)]" + c = min(c,t) + + if (c >= 1) + l += "[D.name][RDSCREEN_NOBREAK]" + if(c >= 5) + l += "x5[RDSCREEN_NOBREAK]" + if(c >= 10) + l += "x10[RDSCREEN_NOBREAK]" + l += "[temp_material][RDSCREEN_NOBREAK]" + else + l += "[D.name][temp_material][RDSCREEN_NOBREAK]" + l += "" + l += "" + return l + +/obj/machinery/computer/rdconsole/proc/ui_protolathe_materials() //Legacy code + RDSCREEN_UI_LATHE_CHECK + var/datum/component/material_container/mat_container = linked_lathe.materials.mat_container + if (!mat_container) + screen = RDSCREEN_PROTOLATHE + return ui_protolathe() + var/list/l = list() + l += ui_protolathe_header() + l += "

                Material Storage:

                " + for(var/mat_id in mat_container.materials) + var/datum/material/M = mat_container.materials[mat_id] + l += "* [M.amount] of [M.name]: " + if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" + if(M.amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" + if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]" + l += "" + l += "
                [RDSCREEN_NOBREAK]" + return l + +/obj/machinery/computer/rdconsole/proc/ui_protolathe_chemicals() //Legacy code + RDSCREEN_UI_LATHE_CHECK + var/list/l = list() + l += ui_protolathe_header() + l += "
                Disposal All Chemicals in Storage" + l += "

                Chemical Storage:

                " + for(var/datum/reagent/R in linked_lathe.reagents.reagent_list) + l += "[R.name]: [R.volume]" + l += "Purge" + l += "
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_circuit_header() //Legacy Code + var/list/l = list() + l += "
                Circuit Imprinter Menu" + if (linked_imprinter.materials.mat_container) + l += "Material Amount: [linked_imprinter.materials.format_amount()]" + else + l += "No material storage connected, please contact the quartermaster." + l += "Chemical volume: [linked_imprinter.reagents.total_volume] / [linked_imprinter.reagents.maximum_volume]
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_circuit() //Legacy code + RDSCREEN_UI_IMPRINTER_CHECK + var/list/l = list() + l += ui_circuit_header() + l += "

                Circuit Imprinter Menu:

                " + + l += "
                \ + \ + \ + \ + \ + \ +

                " + + l += list_categories(linked_imprinter.categories, RDSCREEN_IMPRINTER_CATEGORY_VIEW) + return l + +/obj/machinery/computer/rdconsole/proc/ui_circuit_category_view() //Legacy code + RDSCREEN_UI_IMPRINTER_CHECK + var/list/l = list() + l += ui_circuit_header() + l += "

                Browsing [selected_category]:

                " + + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(!(selected_category in D.category) || !(D.build_type & IMPRINTER)) + continue + if(!(isnull(linked_imprinter.allowed_department_flags) || (D.departmental_flags & linked_imprinter.allowed_department_flags))) + continue + var/temp_materials + var/check_materials = TRUE + + var/all_materials = D.materials + D.reagents_list + var/coeff = linked_imprinter.efficiency_coeff + if(!linked_imprinter.efficient_with(D.build_path)) + coeff = 1 + + for(var/M in all_materials) + temp_materials += " | " + if (!linked_imprinter.check_mat(D, M)) + check_materials = FALSE + temp_materials += " [all_materials[M]/coeff] [CallMaterialName(M)]" + else + temp_materials += " [all_materials[M]/coeff] [CallMaterialName(M)]" + if (check_materials) + l += "[D.name][temp_materials]" + else + l += "[D.name][temp_materials]" + l += "
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_circuit_search() //Legacy code + RDSCREEN_UI_IMPRINTER_CHECK + var/list/l = list() + l += ui_circuit_header() + l += "

                Search results:

                " + + for(var/id in matching_design_ids) + var/datum/design/D = SSresearch.techweb_design_by_id(id) + if(!(isnull(linked_imprinter.allowed_department_flags) || (D.departmental_flags & linked_imprinter.allowed_department_flags))) + continue + var/temp_materials + var/check_materials = TRUE + var/all_materials = D.materials + D.reagents_list + var/coeff = linked_imprinter.efficiency_coeff + if(!linked_imprinter.efficient_with(D.build_path)) + coeff = 1 + for(var/M in all_materials) + temp_materials += " | " + if (!linked_imprinter.check_mat(D, M)) + check_materials = FALSE + temp_materials += " [all_materials[M]/coeff] [CallMaterialName(M)]" + else + temp_materials += " [all_materials[M]/coeff] [CallMaterialName(M)]" + if (check_materials) + l += "[D.name][temp_materials]" + else + l += "[D.name][temp_materials]" + l += "
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_circuit_chemicals() //legacy code + RDSCREEN_UI_IMPRINTER_CHECK + var/list/l = list() + l += ui_circuit_header() + l += "Disposal All Chemicals in Storage
                " + l += "

                Chemical Storage:

                " + for(var/datum/reagent/R in linked_imprinter.reagents.reagent_list) + l += "[R.name]: [R.volume]" + l += "Purge" + return l + +/obj/machinery/computer/rdconsole/proc/ui_circuit_materials() //Legacy code! + RDSCREEN_UI_IMPRINTER_CHECK + var/datum/component/material_container/mat_container = linked_imprinter.materials.mat_container + if (!mat_container) + screen = RDSCREEN_IMPRINTER + return ui_circuit() + var/list/l = list() + l += ui_circuit_header() + l += "

                Material Storage:

                " + for(var/mat_id in mat_container.materials) + var/datum/material/M = mat_container.materials[mat_id] + l += "* [M.amount] of [M.name]: " + if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "Eject [RDSCREEN_NOBREAK]" + if(M.amount >= MINERAL_MATERIAL_AMOUNT*5) l += "5x [RDSCREEN_NOBREAK]" + if(M.amount >= MINERAL_MATERIAL_AMOUNT) l += "All[RDSCREEN_NOBREAK]
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_techdisk() //Legacy code + RDSCREEN_UI_TDISK_CHECK + var/list/l = list() + l += "
                Disk Operations: Clear Disk" + l += "Eject Disk" + l += "Upload All" + l += "Load Technology to Disk
                " + l += "

                Stored Technology Nodes:

                " + for(var/i in t_disk.stored_research.researched_nodes) + var/datum/techweb_node/N = SSresearch.techweb_node_by_id(i) + l += "[N.display_name]" + l += "
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_designdisk() //Legacy code + RDSCREEN_UI_DDISK_CHECK + var/list/l = list() + l += "Disk Operations: Clear DiskUpload AllEject Disk" + for(var/i in 1 to d_disk.max_blueprints) + l += "
                " + if(d_disk.blueprints[i]) + var/datum/design/D = d_disk.blueprints[i] + l += "[D.name]" + l += "Operations: Upload to database Clear Slot" + else + l += "Empty Slot Operations: Load Design to Slot" + l += "
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_designdisk_upload() //Legacy code + RDSCREEN_UI_DDISK_CHECK + var/list/l = list() + l += "Return to Disk Operations
                " + l += "

                Load Design to Disk:

                " + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + l += "[D.name] " + l += "Copy to Disk" + l += "
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_deconstruct() //Legacy code + RDSCREEN_UI_DECONSTRUCT_CHECK + var/list/l = list() + if(!linked_destroy.loaded_item) + l += "
                No item loaded. Standing-by...
                " + else + l += "
                [RDSCREEN_NOBREAK]" + l += "
                [icon2html(linked_destroy.loaded_item, usr)][linked_destroy.loaded_item.name] Eject
                [RDSCREEN_NOBREAK]" + l += "Select a node to boost by deconstructing this item. This item can boost:" + + var/anything = FALSE + var/list/boostable_nodes = techweb_item_boost_check(linked_destroy.loaded_item) + for(var/id in boostable_nodes) + anything = TRUE + var/list/worth = boostable_nodes[id] + var/datum/techweb_node/N = SSresearch.techweb_node_by_id(id) + + l += "
                [RDSCREEN_NOBREAK]" + if (stored_research.researched_nodes[N.id]) // already researched + l += "[N.display_name]" + l += "This node has already been researched." + else if(!length(worth)) // reveal only + if (stored_research.hidden_nodes[N.id]) + l += "[N.display_name]" + l += "This node will be revealed." + else + l += "[N.display_name]" + l += "This node has already been revealed." + else // boost by the difference + var/list/differences = list() + var/list/already_boosted = stored_research.boosted_nodes[N.id] + for(var/i in worth) + var/already_boosted_amount = already_boosted? stored_research.boosted_nodes[N.id][i] : 0 + var/amt = min(worth[i], N.research_costs[i]) - already_boosted_amount + if(amt > 0) + differences[i] = amt + if (length(differences)) + l += "[N.display_name]" + l += "This node will be boosted with the following:
                [techweb_point_display_generic(differences)]" + else + l += "[N.display_name]" + l += "This node has already been boosted." + l += "
                [RDSCREEN_NOBREAK]" + + // point deconstruction and material reclamation use the same ID to prevent accidentally missing the points + var/list/point_values = techweb_item_point_check(linked_destroy.loaded_item) + if(point_values) + anything = TRUE + l += "
                [RDSCREEN_NOBREAK]" + if (stored_research.deconstructed_items[linked_destroy.loaded_item.type]) + l += "Point Deconstruction" + l += "This item's points have already been claimed." + else + l += "Point Deconstruction" + l += "This item is worth:
                [techweb_point_display_generic(point_values)]!" + l += "
                [RDSCREEN_NOBREAK]" + + if(!(linked_destroy.loaded_item.resistance_flags & INDESTRUCTIBLE)) + var/list/materials = linked_destroy.loaded_item.materials + l += "
                [materials.len? "Material Reclamation" : "Destroy Item"]" + for (var/M in materials) + l += "* [CallMaterialName(M)] x [materials[M]]" + l += "
                [RDSCREEN_NOBREAK]" + anything = TRUE + + if (!anything) + l += "Nothing!" + + l += "
                " + return l + +/obj/machinery/computer/rdconsole/proc/ui_techweb() + var/list/l = list() + if(islist(stored_research.research_logs) && stored_research.research_logs.len) + l += "Last action: [stored_research.research_logs[stored_research.research_logs.len]]" + if(ui_mode != RDCONSOLE_UI_MODE_LIST) + var/list/columns = list() + var/max_tier = 0 + for (var/node_ in stored_research.tiers) + var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_) + var/tier = stored_research.tiers[node.id] + LAZYINITLIST(columns["[tier]"]) // String hackery to make the numbers associative + columns["[tier]"] += ui_techweb_single_node(node, minimal=(tier != 1)) + max_tier = max(max_tier, tier) + + l += "[RDSCREEN_NOBREAK]" + if(max_tier) + for(var/tier in 0 to max_tier) + l += "[RDSCREEN_NOBREAK]" + l += "
                ResearchedAvailableFuture
                [RDSCREEN_NOBREAK]" + l += columns["[tier]"] + l += "
                [RDSCREEN_NOBREAK]" + else + var/list/avail = list() //This could probably be optimized a bit later. + var/list/unavail = list() + var/list/res = list() + for(var/v in stored_research.researched_nodes) + res += SSresearch.techweb_node_by_id(v) + for(var/v in stored_research.available_nodes) + if(stored_research.researched_nodes[v]) + continue + avail += SSresearch.techweb_node_by_id(v) + for(var/v in stored_research.visible_nodes) + if(stored_research.available_nodes[v]) + continue + unavail += SSresearch.techweb_node_by_id(v) + l += "

                Technology Nodes:

                [RDSCREEN_NOBREAK]" + l += "

                Available for Research:

                " + for(var/datum/techweb_node/N in avail) + var/not_unlocked = (stored_research.available_nodes[N.id] && !stored_research.researched_nodes[N.id]) + var/has_points = (stored_research.can_afford(N.get_price(stored_research))) + var/research_href = not_unlocked? (has_points? "Research" : "Not Enough Points") : null + l += "[N.display_name][research_href]" + l += "

                Locked Nodes:

                " + for(var/datum/techweb_node/N in unavail) + l += "[N.display_name]" + l += "

                Researched Nodes:

                " + for(var/datum/techweb_node/N in res) + l += "[N.display_name]" + l += "
                [RDSCREEN_NOBREAK]" + return l + +/obj/machinery/computer/rdconsole/proc/machine_icon(atom/item) + return icon2html(initial(item.icon), usr, initial(item.icon_state), SOUTH) + +/obj/machinery/computer/rdconsole/proc/ui_techweb_single_node(datum/techweb_node/node, selflink=TRUE, minimal=FALSE) + var/list/l = list() + if (stored_research.hidden_nodes[node.id]) + return l + var/display_name = node.display_name + if (selflink) + display_name = "[display_name]" + l += "
                [display_name] [RDSCREEN_NOBREAK]" + if(minimal) + l += "
                [node.description]" + else + if(stored_research.researched_nodes[node.id]) + l += "Researched" + else if(stored_research.available_nodes[node.id]) + if(stored_research.can_afford(node.get_price(stored_research))) + l += "
                [node.price_display(stored_research)]" + else + l += "
                [node.price_display(stored_research)]" // gray - too expensive + else + l += "
                [node.price_display(stored_research)]" // red - missing prereqs + if(ui_mode == RDCONSOLE_UI_MODE_NORMAL) + l += "[node.description]" + for(var/i in node.design_ids) + var/datum/design/D = SSresearch.techweb_design_by_id(i) + l += "[D.icon_html(usr)][RDSCREEN_NOBREAK]" + l += "
                [RDSCREEN_NOBREAK]" + return l + +/obj/machinery/computer/rdconsole/proc/ui_techweb_nodeview() + var/datum/techweb_node/selected_node = SSresearch.techweb_node_by_id(selected_node_id) + RDSCREEN_UI_SNODE_CHECK + var/list/l = list() + if(stored_research.hidden_nodes[selected_node.id]) + l += "

                ERROR: RESEARCH NODE UNKNOWN.

                " + return + + l += "[RDSCREEN_NOBREAK]" + if (length(selected_node.prereq_ids)) + l += "[RDSCREEN_NOBREAK]" + l += "[RDSCREEN_NOBREAK]" + if (length(selected_node.unlock_ids)) + l += "[RDSCREEN_NOBREAK]" + + l += "[RDSCREEN_NOBREAK]" + if (length(selected_node.prereq_ids)) + l += "[RDSCREEN_NOBREAK]" + l += "[RDSCREEN_NOBREAK]" + if (length(selected_node.unlock_ids)) + l += "[RDSCREEN_NOBREAK]" + + l += "
                RequiresCurrent NodeUnlocks
                [RDSCREEN_NOBREAK]" + for (var/i in selected_node.prereq_ids) + l += ui_techweb_single_node(SSresearch.techweb_node_by_id(i)) + l += "[RDSCREEN_NOBREAK]" + l += ui_techweb_single_node(selected_node, selflink=FALSE) + l += "[RDSCREEN_NOBREAK]" + for (var/i in selected_node.unlock_ids) + l += ui_techweb_single_node(SSresearch.techweb_node_by_id(i)) + l += "
                [RDSCREEN_NOBREAK]" + return l + +/obj/machinery/computer/rdconsole/proc/ui_techweb_designview() //Legacy code + var/datum/design/selected_design = SSresearch.techweb_design_by_id(selected_design_id) + RDSCREEN_UI_SDESIGN_CHECK + var/list/l = list() + l += "
                [selected_design.icon_html(usr)][selected_design.name]
                [RDSCREEN_NOBREAK]" + if(selected_design.build_type) + var/lathes = list() + if(selected_design.build_type & IMPRINTER) + lathes += "[machine_icon(/obj/machinery/rnd/production/circuit_imprinter)][RDSCREEN_NOBREAK]" + if (linked_imprinter && stored_research.researched_designs[selected_design.id]) + l += "Imprint" + if(selected_design.build_type & PROTOLATHE) + lathes += "[machine_icon(/obj/machinery/rnd/production/protolathe)][RDSCREEN_NOBREAK]" + if (linked_lathe && stored_research.researched_designs[selected_design.id]) + l += "Construct" + if(selected_design.build_type & AUTOLATHE) + lathes += "[machine_icon(/obj/machinery/autolathe)][RDSCREEN_NOBREAK]" + if(selected_design.build_type & MECHFAB) + lathes += "[machine_icon(/obj/machinery/mecha_part_fabricator)][RDSCREEN_NOBREAK]" + if(selected_design.build_type & BIOGENERATOR) + lathes += "[machine_icon(/obj/machinery/biogenerator)][RDSCREEN_NOBREAK]" + if(selected_design.build_type & LIMBGROWER) + lathes += "[machine_icon(/obj/machinery/limbgrower)][RDSCREEN_NOBREAK]" + if(selected_design.build_type & SMELTER) + lathes += "[machine_icon(/obj/machinery/mineral/processing_unit)][RDSCREEN_NOBREAK]" + l += "Construction types:" + l += lathes + l += "" + l += "Required materials:" + var/all_mats = selected_design.materials + selected_design.reagents_list + for(var/M in all_mats) + l += "* [CallMaterialName(M)] x [all_mats[M]]" + l += "Unlocked by:" + for (var/i in selected_design.unlocked_by) + l += ui_techweb_single_node(SSresearch.techweb_node_by_id(i)) + l += "[RDSCREEN_NOBREAK]
                " + return l + +//Fuck TGUI. +/obj/machinery/computer/rdconsole/proc/generate_ui() + var/list/ui = list() + ui += ui_header() + if(locked) + ui += ui_locked() + else + switch(screen) + if(RDSCREEN_MENU) + ui += ui_main_menu() + if(RDSCREEN_TECHWEB) + ui += ui_techweb() + if(RDSCREEN_TECHWEB_NODEVIEW) + ui += ui_techweb_nodeview() + if(RDSCREEN_TECHWEB_DESIGNVIEW) + ui += ui_techweb_designview() + if(RDSCREEN_DESIGNDISK) + ui += ui_designdisk() + if(RDSCREEN_DESIGNDISK_UPLOAD) + ui += ui_designdisk_upload() + if(RDSCREEN_TECHDISK) + ui += ui_techdisk() + if(RDSCREEN_DECONSTRUCT) + ui += ui_deconstruct() + if(RDSCREEN_PROTOLATHE) + ui += ui_protolathe() + if(RDSCREEN_PROTOLATHE_CATEGORY_VIEW) + ui += ui_protolathe_category_view() + if(RDSCREEN_PROTOLATHE_MATERIALS) + ui += ui_protolathe_materials() + if(RDSCREEN_PROTOLATHE_CHEMICALS) + ui += ui_protolathe_chemicals() + if(RDSCREEN_PROTOLATHE_SEARCH) + ui += ui_protolathe_search() + if(RDSCREEN_IMPRINTER) + ui += ui_circuit() + if(RDSCREEN_IMPRINTER_CATEGORY_VIEW) + ui += ui_circuit_category_view() + if(RDSCREEN_IMPRINTER_MATERIALS) + ui += ui_circuit_materials() + if(RDSCREEN_IMPRINTER_CHEMICALS) + ui += ui_circuit_chemicals() + if(RDSCREEN_IMPRINTER_SEARCH) + ui += ui_circuit_search() + if(RDSCREEN_SETTINGS) + ui += ui_settings() + if(RDSCREEN_DEVICE_LINKING) + ui += ui_device_linking() + for(var/i in 1 to length(ui)) + if(!findtextEx(ui[i], RDSCREEN_NOBREAK)) + ui[i] += "
                " + ui[i] = replacetextEx(ui[i], RDSCREEN_NOBREAK, "") + return ui.Join("") + +/obj/machinery/computer/rdconsole/Topic(raw, ls) + if(..()) + return + add_fingerprint(usr) + usr.set_machine(src) + if(ls["switch_screen"]) + back = screen + screen = text2num(ls["switch_screen"]) + if(ls["ui_mode"]) + ui_mode = text2num(ls["ui_mode"]) + if(ls["lock_console"]) + if(obj_flags & EMAGGED) + to_chat(usr, "Security protocol error: Unable to lock.") + return + if(allowed(usr)) + lock_console(usr) + else + to_chat(usr, "Unauthorized Access.") + if(ls["unlock_console"]) + if(allowed(usr)) + unlock_console(usr) + else + to_chat(usr, "Unauthorized Access.") + if(ls["find_device"]) + SyncRDevices() + say("Resynced with nearby devices.") + if(ls["back_screen"]) + back = text2num(ls["back_screen"]) + if(ls["build"]) //Causes the Protolathe to build something. + if(QDELETED(linked_lathe)) + say("No Protolathe Linked!") + return + if(linked_lathe.busy) + say("Warning: Protolathe busy!") + else + linked_lathe.user_try_print_id(ls["build"], ls["amount"]) + if(ls["imprint"]) + if(QDELETED(linked_imprinter)) + say("No Circuit Imprinter Linked!") + return + if(linked_imprinter.busy) + say("Warning: Imprinter busy!") + else + linked_imprinter.user_try_print_id(ls["imprint"]) + if(ls["category"]) + selected_category = ls["category"] + if(ls["disconnect"]) //The R&D console disconnects with a specific device. + switch(ls["disconnect"]) + if("destroy") + if(QDELETED(linked_destroy)) + say("No Destructive Analyzer Linked!") + return + linked_destroy.linked_console = null + linked_destroy = null + if("lathe") + if(QDELETED(linked_lathe)) + say("No Protolathe Linked!") + return + linked_lathe.linked_console = null + linked_lathe = null + if("imprinter") + if(QDELETED(linked_imprinter)) + say("No Circuit Imprinter Linked!") + return + linked_imprinter.linked_console = null + linked_imprinter = null + if(ls["eject_design"]) //Eject the design disk. + eject_disk("design") + screen = RDSCREEN_MENU + say("Ejecting Design Disk") + if(ls["eject_tech"]) //Eject the technology disk. + eject_disk("tech") + screen = RDSCREEN_MENU + say("Ejecting Technology Disk") + if(ls["deconstruct"]) + if(QDELETED(linked_destroy)) + say("No Destructive Analyzer Linked!") + return + if(!linked_destroy.user_try_decon_id(ls["deconstruct"], usr)) + say("Destructive analysis failed!") + //Protolathe Materials + if(ls["disposeP"]) //Causes the protolathe to dispose of a single reagent (all of it) + if(QDELETED(linked_lathe)) + say("No Protolathe Linked!") + return + linked_lathe.reagents.del_reagent(ls["disposeP"]) + if(ls["disposeallP"]) //Causes the protolathe to dispose of all it's reagents. + if(QDELETED(linked_lathe)) + say("No Protolathe Linked!") + return + linked_lathe.reagents.clear_reagents() + if(ls["ejectsheet"]) //Causes the protolathe to eject a sheet of material + if(QDELETED(linked_lathe)) + say("No Protolathe Linked!") + return + if(!linked_lathe.materials.mat_container) + say("No material storage linked to protolathe!") + return + linked_lathe.eject_sheets(ls["ejectsheet"], ls["eject_amt"]) + //Circuit Imprinter Materials + if(ls["disposeI"]) //Causes the circuit imprinter to dispose of a single reagent (all of it) + if(QDELETED(linked_imprinter)) + say("No Circuit Imprinter Linked!") + return + linked_imprinter.reagents.del_reagent(ls["disposeI"]) + if(ls["disposeallI"]) //Causes the circuit imprinter to dispose of all it's reagents. + if(QDELETED(linked_imprinter)) + say("No Circuit Imprinter Linked!") + return + linked_imprinter.reagents.clear_reagents() + if(ls["imprinter_ejectsheet"]) //Causes the imprinter to eject a sheet of material + if(QDELETED(linked_imprinter)) + say("No Circuit Imprinter Linked!") + return + if(!linked_imprinter.materials.mat_container) + say("No material storage linked to circuit imprinter!") + return + linked_imprinter.eject_sheets(ls["imprinter_ejectsheet"], ls["eject_amt"]) + if(ls["disk_slot"]) + disk_slot_selected = text2num(ls["disk_slot"]) + if(ls["research_node"]) + if(!research_control) + return //honestly should call them out for href exploiting :^) + if(!SSresearch.science_tech.available_nodes[ls["research_node"]]) + return //Nope! + research_node(ls["research_node"], usr) + if(ls["clear_tech"]) //Erase la on the technology disk. + if(QDELETED(t_disk)) + say("No Technology Disk Inserted!") + return + qdel(t_disk.stored_research) + t_disk.stored_research = new + say("Wiping technology disk.") + if(ls["copy_tech"]) //Copy some technology from the research holder to the disk. + if(QDELETED(t_disk)) + say("No Technology Disk Inserted!") + return + stored_research.copy_research_to(t_disk.stored_research) + screen = RDSCREEN_TECHDISK + say("Downloading to technology disk.") + if(ls["clear_design"]) //Erases la on the design disk. + if(QDELETED(d_disk)) + say("No Design Disk Inserted!") + return + var/n = text2num(ls["clear_design"]) + if(!n) + for(var/i in 1 to d_disk.max_blueprints) + d_disk.blueprints[i] = null + say("Wiping design disk.") + else + var/datum/design/D = d_disk.blueprints[n] + say("Wiping design [D.name] from design disk.") + d_disk.blueprints[n] = null + if(ls["search"]) //Search for designs with name matching pattern + searchstring = ls["to_search"] + searchtype = ls["type"] + rescan_views() + if(searchtype == "proto") + screen = RDSCREEN_PROTOLATHE_SEARCH + else + screen = RDSCREEN_IMPRINTER_SEARCH + if(ls["updt_tech"]) //Uple the research holder with information from the technology disk. + if(QDELETED(t_disk)) + say("No Technology Disk Inserted!") + return + say("Uploading technology disk.") + t_disk.stored_research.copy_research_to(stored_research) + if(ls["copy_design"]) //Copy design from the research holder to the design disk. + if(QDELETED(d_disk)) + say("No Design Disk Inserted!") + return + var/slot = text2num(ls["copy_design"]) + var/datum/design/D = SSresearch.techweb_design_by_id(ls["copy_design_ID"]) + if(D) + var/autolathe_friendly = TRUE + if(D.reagents_list.len) + autolathe_friendly = FALSE + D.category -= "Imported" + else + for(var/x in D.materials) + if( !(x in list(MAT_METAL, MAT_GLASS))) + autolathe_friendly = FALSE + D.category -= "Imported" + + if(D.build_type & (AUTOLATHE|PROTOLATHE|CRAFTLATHE)) // Specifically excludes circuit imprinter and mechfab + D.build_type = autolathe_friendly ? (D.build_type | AUTOLATHE) : D.build_type + D.category |= "Imported" + d_disk.blueprints[slot] = D + screen = RDSCREEN_DESIGNDISK + if(ls["eject_item"]) //Eject the item inside the destructive analyzer. + if(QDELETED(linked_destroy)) + say("No Destructive Analyzer Linked!") + return + if(linked_destroy.busy) + to_chat(usr, "The destructive analyzer is busy at the moment.") + else if(linked_destroy.loaded_item) + linked_destroy.unload_item() + screen = RDSCREEN_MENU + if(ls["view_node"]) + selected_node_id = ls["view_node"] + screen = RDSCREEN_TECHWEB_NODEVIEW + if(ls["view_design"]) + selected_design_id = ls["view_design"] + screen = RDSCREEN_TECHWEB_DESIGNVIEW + if(ls["updt_design"]) //Uploads a design from disk to the techweb. + if(QDELETED(d_disk)) + say("No design disk found.") + return + var/n = text2num(ls["updt_design"]) + if(!n) + for(var/D in d_disk.blueprints) + if(D) + stored_research.add_design(D, TRUE) + else + stored_research.add_design(d_disk.blueprints[n], TRUE) + + updateUsrDialog() + +/obj/machinery/computer/rdconsole/ui_interact(mob/user) + . = ..() + var/datum/browser/popup = new(user, "rndconsole", name, 900, 600) + popup.add_stylesheet("techwebs", 'html/browser/techwebs.css') + popup.set_content(generate_ui()) + popup.open() + +/obj/machinery/computer/rdconsole/proc/tdisk_uple_complete() + tdisk_uple = FALSE + updateUsrDialog() + +/obj/machinery/computer/rdconsole/proc/ddisk_uple_complete() + ddisk_uple = FALSE + updateUsrDialog() + +/obj/machinery/computer/rdconsole/proc/eject_disk(type) + if(type == "design") + d_disk.forceMove(get_turf(src)) + d_disk = null + if(type == "tech") + t_disk.forceMove(get_turf(src)) + t_disk = null + +/obj/machinery/computer/rdconsole/proc/rescan_views() + var/compare + matching_design_ids.Cut() + if(searchtype == "proto") + compare = PROTOLATHE + else if(searchtype == "imprint") + compare = IMPRINTER + for(var/v in stored_research.researched_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(v) + if(!(D.build_type & compare)) + continue + if(findtext(D.name,searchstring)) + matching_design_ids.Add(D.id) + +/obj/machinery/computer/rdconsole/proc/check_canprint(datum/design/D, buildtype) + var/amount = 50 + if(buildtype == IMPRINTER) + if(QDELETED(linked_imprinter)) + return FALSE + for(var/M in D.materials + D.reagents_list) + amount = min(amount, linked_imprinter.check_mat(D, M)) + if(amount < 1) + return FALSE + else if(buildtype == PROTOLATHE) + if(QDELETED(linked_lathe)) + return FALSE + for(var/M in D.materials + D.reagents_list) + amount = min(amount, linked_lathe.check_mat(D, M)) + if(amount < 1) + return FALSE + else + return FALSE + return amount + +/obj/machinery/computer/rdconsole/proc/lock_console(mob/user) + locked = TRUE + +/obj/machinery/computer/rdconsole/proc/unlock_console(mob/user) + locked = FALSE + +/obj/machinery/computer/rdconsole/robotics + name = "Robotics R&D Console" + req_access = null + req_access_txt = "29" + +/obj/machinery/computer/rdconsole/robotics/Initialize() + . = ..() + if(circuit) + circuit.name = "R&D Console - Robotics (Computer Board)" + circuit.build_path = /obj/machinery/computer/rdconsole/robotics + +/obj/machinery/computer/rdconsole/core + name = "Core R&D Console" + +/obj/machinery/computer/rdconsole/experiment + name = "E.X.P.E.R.I-MENTOR R&D Console" diff --git a/code/modules/research/rdmachines.dm b/code/modules/research/rdmachines.dm index 23d515cf88..18bf8033b9 100644 --- a/code/modules/research/rdmachines.dm +++ b/code/modules/research/rdmachines.dm @@ -1,106 +1,106 @@ - -//All devices that link into the R&D console fall into thise type for easy identification and some shared procs. - - -/obj/machinery/rnd - name = "R&D Device" - icon = 'icons/obj/machines/research.dmi' - density = TRUE - use_power = IDLE_POWER_USE - var/busy = FALSE - var/hacked = FALSE - var/console_link = TRUE //allow console link. - var/requires_console = TRUE - var/disabled = FALSE - var/obj/machinery/computer/rdconsole/linked_console - var/obj/item/loaded_item = null //the item loaded inside the machine (currently only used by experimentor and destructive analyzer) - -/obj/machinery/rnd/proc/reset_busy() - busy = FALSE - -/obj/machinery/rnd/Initialize() - . = ..() - wires = new /datum/wires/rnd(src) - -/obj/machinery/rnd/Destroy() - QDEL_NULL(wires) - return ..() - -/obj/machinery/rnd/proc/shock(mob/user, prb) - if(stat & (BROKEN|NOPOWER)) // unpowered, no shock - return FALSE - if(!prob(prb)) - return FALSE - do_sparks(5, TRUE, src) - if (electrocute_mob(user, get_area(src), src, 0.7, TRUE)) - return TRUE - else - return FALSE - -/obj/machinery/rnd/attackby(obj/item/O, mob/user, params) - if (default_deconstruction_screwdriver(user, "[initial(icon_state)]_t", initial(icon_state), O)) - if(linked_console) - disconnect_console() - return - if(default_deconstruction_crowbar(O)) - return - if(panel_open && is_wire_tool(O)) - wires.interact(user) - return TRUE - if(is_refillable() && O.is_drainable()) - return FALSE //inserting reagents into the machine - if(Insert_Item(O, user)) - return TRUE - else - return ..() - -//to disconnect the machine from the r&d console it's linked to -/obj/machinery/rnd/proc/disconnect_console() - linked_console = null - -//proc used to handle inserting items or reagents into rnd machines -/obj/machinery/rnd/proc/Insert_Item(obj/item/I, mob/user) - return - -//whether the machine can have an item inserted in its current state. -/obj/machinery/rnd/proc/is_insertion_ready(mob/user) - if(panel_open) - to_chat(user, "You can't load [src] while it's opened!") - return FALSE - if(disabled) - to_chat(user, "The insertion belts of [src] won't engage!") - return FALSE - if(requires_console && !linked_console) - to_chat(user, "[src] must be linked to an R&D console first!") - return FALSE - if(busy) - to_chat(user, "[src] is busy right now.") - return FALSE - if(stat & BROKEN) - to_chat(user, "[src] is broken.") - return FALSE - if(stat & NOPOWER) - to_chat(user, "[src] has no power.") - return FALSE - if(loaded_item) - to_chat(user, "[src] is already loaded.") - return FALSE - return TRUE - -//we eject the loaded item when deconstructing the machine -/obj/machinery/rnd/on_deconstruction() - if(loaded_item) - loaded_item.forceMove(loc) - ..() - -/obj/machinery/rnd/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted) - var/stack_name - if(ispath(type_inserted, /obj/item/stack/ore/bluespace_crystal)) - stack_name = "bluespace" - use_power(MINERAL_MATERIAL_AMOUNT / 10) - else - var/obj/item/stack/S = type_inserted - stack_name = initial(S.name) - use_power(min(1000, (amount_inserted / 100))) - add_overlay("protolathe_[stack_name]") - addtimer(CALLBACK(src, /atom/proc/cut_overlay, "protolathe_[stack_name]"), 10) + +//All devices that link into the R&D console fall into thise type for easy identification and some shared procs. + + +/obj/machinery/rnd + name = "R&D Device" + icon = 'icons/obj/machines/research.dmi' + density = TRUE + use_power = IDLE_POWER_USE + var/busy = FALSE + var/hacked = FALSE + var/console_link = TRUE //allow console link. + var/requires_console = TRUE + var/disabled = FALSE + var/obj/machinery/computer/rdconsole/linked_console + var/obj/item/loaded_item = null //the item loaded inside the machine (currently only used by experimentor and destructive analyzer) + +/obj/machinery/rnd/proc/reset_busy() + busy = FALSE + +/obj/machinery/rnd/Initialize() + . = ..() + wires = new /datum/wires/rnd(src) + +/obj/machinery/rnd/Destroy() + QDEL_NULL(wires) + return ..() + +/obj/machinery/rnd/proc/shock(mob/user, prb) + if(stat & (BROKEN|NOPOWER)) // unpowered, no shock + return FALSE + if(!prob(prb)) + return FALSE + do_sparks(5, TRUE, src) + if (electrocute_mob(user, get_area(src), src, 0.7, TRUE)) + return TRUE + else + return FALSE + +/obj/machinery/rnd/attackby(obj/item/O, mob/user, params) + if (default_deconstruction_screwdriver(user, "[initial(icon_state)]_t", initial(icon_state), O)) + if(linked_console) + disconnect_console() + return + if(default_deconstruction_crowbar(O)) + return + if(panel_open && is_wire_tool(O)) + wires.interact(user) + return TRUE + if(is_refillable() && O.is_drainable()) + return FALSE //inserting reagents into the machine + if(Insert_Item(O, user)) + return TRUE + else + return ..() + +//to disconnect the machine from the r&d console it's linked to +/obj/machinery/rnd/proc/disconnect_console() + linked_console = null + +//proc used to handle inserting items or reagents into rnd machines +/obj/machinery/rnd/proc/Insert_Item(obj/item/I, mob/user) + return + +//whether the machine can have an item inserted in its current state. +/obj/machinery/rnd/proc/is_insertion_ready(mob/user) + if(panel_open) + to_chat(user, "You can't load [src] while it's opened!") + return FALSE + if(disabled) + to_chat(user, "The insertion belts of [src] won't engage!") + return FALSE + if(requires_console && !linked_console) + to_chat(user, "[src] must be linked to an R&D console first!") + return FALSE + if(busy) + to_chat(user, "[src] is busy right now.") + return FALSE + if(stat & BROKEN) + to_chat(user, "[src] is broken.") + return FALSE + if(stat & NOPOWER) + to_chat(user, "[src] has no power.") + return FALSE + if(loaded_item) + to_chat(user, "[src] is already loaded.") + return FALSE + return TRUE + +//we eject the loaded item when deconstructing the machine +/obj/machinery/rnd/on_deconstruction() + if(loaded_item) + loaded_item.forceMove(loc) + ..() + +/obj/machinery/rnd/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted) + var/stack_name + if(ispath(type_inserted, /obj/item/stack/ore/bluespace_crystal)) + stack_name = "bluespace" + use_power(MINERAL_MATERIAL_AMOUNT / 10) + else + var/obj/item/stack/S = type_inserted + stack_name = initial(S.name) + use_power(min(1000, (amount_inserted / 100))) + add_overlay("protolathe_[stack_name]") + addtimer(CALLBACK(src, /atom/proc/cut_overlay, "protolathe_[stack_name]"), 10) diff --git a/code/modules/research/research_disk.dm b/code/modules/research/research_disk.dm index 02865dc5bb..e4c036f7ff 100644 --- a/code/modules/research/research_disk.dm +++ b/code/modules/research/research_disk.dm @@ -1,40 +1,40 @@ - -/obj/item/disk/tech_disk - name = "technology disk" - desc = "A disk for storing technology data for further research." - icon_state = "datadisk0" - materials = list(MAT_METAL=300, MAT_GLASS=100) - var/datum/techweb/stored_research - -/obj/item/disk/tech_disk/Initialize() - . = ..() - pixel_x = rand(-5, 5) - pixel_y = rand(-5, 5) - stored_research = new /datum/techweb - -/obj/item/disk/tech_disk/debug - name = "\improper CentCom technology disk" - desc = "A debug item for research" - materials = list() - -/obj/item/disk/tech_disk/debug/Initialize() - . = ..() - stored_research = new /datum/techweb/admin - -/obj/item/disk/tech_disk/illegal - name = "Illegal technology disk" - desc = "A technology disk containing schematics for syndicate inspired equipment." - materials = list() - -/obj/item/disk/tech_disk/illegal/Initialize() - . = ..() - stored_research = new /datum/techweb/syndicate - -/obj/item/disk/tech_disk/abductor - name = "Gray technology disk" - desc = "You feel like it's not Gray because of its color." - materials = list() - -/obj/item/disk/tech_disk/abductor/Initialize() - . = ..() - stored_research = new /datum/techweb/abductor + +/obj/item/disk/tech_disk + name = "technology disk" + desc = "A disk for storing technology data for further research." + icon_state = "datadisk0" + materials = list(MAT_METAL=300, MAT_GLASS=100) + var/datum/techweb/stored_research + +/obj/item/disk/tech_disk/Initialize() + . = ..() + pixel_x = rand(-5, 5) + pixel_y = rand(-5, 5) + stored_research = new /datum/techweb + +/obj/item/disk/tech_disk/debug + name = "\improper CentCom technology disk" + desc = "A debug item for research" + materials = list() + +/obj/item/disk/tech_disk/debug/Initialize() + . = ..() + stored_research = new /datum/techweb/admin + +/obj/item/disk/tech_disk/illegal + name = "Illegal technology disk" + desc = "A technology disk containing schematics for syndicate inspired equipment." + materials = list() + +/obj/item/disk/tech_disk/illegal/Initialize() + . = ..() + stored_research = new /datum/techweb/syndicate + +/obj/item/disk/tech_disk/abductor + name = "Gray technology disk" + desc = "You feel like it's not Gray because of its color." + materials = list() + +/obj/item/disk/tech_disk/abductor/Initialize() + . = ..() + stored_research = new /datum/techweb/abductor diff --git a/code/modules/research/techweb/__techweb_helpers.dm b/code/modules/research/techweb/__techweb_helpers.dm index 6c967c0924..2fd89378a5 100644 --- a/code/modules/research/techweb/__techweb_helpers.dm +++ b/code/modules/research/techweb/__techweb_helpers.dm @@ -1,34 +1,34 @@ -/proc/count_unique_techweb_nodes() - var/static/list/L = typesof(/datum/techweb_node) - return L.len - -/proc/count_unique_techweb_designs() - var/static/list/L = typesof(/datum/design) - return L.len - -/proc/node_boost_error(id, message) - WARNING("Invalid boost information for node \[[id]\]: [message]") - SSresearch.invalid_node_boost[id] = message - -/proc/techweb_item_boost_check(obj/item/I) //Returns an associative list of techweb node datums with values of the boost it gives. var/list/returned = list() - if(SSresearch.techweb_boost_items[I.type]) - return SSresearch.techweb_boost_items[I.type] //It should already be formatted in node datum = list(point type = value) - -/proc/techweb_item_point_check(obj/item/I) - if(SSresearch.techweb_point_items[I.type]) - return SSresearch.techweb_point_items[I.type] - -/proc/techweb_point_display_generic(pointlist) - var/list/ret = list() - for(var/i in pointlist) - if(SSresearch.point_types[i]) - ret += "[SSresearch.point_types[i]]: [pointlist[i]]" - else - ret += "ERRORED POINT TYPE: [pointlist[i]]" - return ret.Join("
                ") - -/proc/techweb_point_display_rdconsole(pointlist, last_pointlist) - var/list/ret = list() - for(var/i in pointlist) - ret += "[SSresearch.point_types[i] || "ERRORED POINT TYPE"]: [pointlist[i]] (+[(last_pointlist[i]) * ((SSresearch.flags & SS_TICKER)? (600 / (world.tick_lag * SSresearch.wait)) : (600 / SSresearch.wait))]/ minute)" - return ret.Join("
                ") +/proc/count_unique_techweb_nodes() + var/static/list/L = typesof(/datum/techweb_node) + return L.len + +/proc/count_unique_techweb_designs() + var/static/list/L = typesof(/datum/design) + return L.len + +/proc/node_boost_error(id, message) + WARNING("Invalid boost information for node \[[id]\]: [message]") + SSresearch.invalid_node_boost[id] = message + +/proc/techweb_item_boost_check(obj/item/I) //Returns an associative list of techweb node datums with values of the boost it gives. var/list/returned = list() + if(SSresearch.techweb_boost_items[I.type]) + return SSresearch.techweb_boost_items[I.type] //It should already be formatted in node datum = list(point type = value) + +/proc/techweb_item_point_check(obj/item/I) + if(SSresearch.techweb_point_items[I.type]) + return SSresearch.techweb_point_items[I.type] + +/proc/techweb_point_display_generic(pointlist) + var/list/ret = list() + for(var/i in pointlist) + if(SSresearch.point_types[i]) + ret += "[SSresearch.point_types[i]]: [pointlist[i]]" + else + ret += "ERRORED POINT TYPE: [pointlist[i]]" + return ret.Join("
                ") + +/proc/techweb_point_display_rdconsole(pointlist, last_pointlist) + var/list/ret = list() + for(var/i in pointlist) + ret += "[SSresearch.point_types[i] || "ERRORED POINT TYPE"]: [pointlist[i]] (+[(last_pointlist[i]) * ((SSresearch.flags & SS_TICKER)? (600 / (world.tick_lag * SSresearch.wait)) : (600 / SSresearch.wait))]/ minute)" + return ret.Join("
                ") diff --git a/code/modules/research/techweb/_techweb.dm b/code/modules/research/techweb/_techweb.dm index 17294fe108..9a55e53659 100644 --- a/code/modules/research/techweb/_techweb.dm +++ b/code/modules/research/techweb/_techweb.dm @@ -1,389 +1,389 @@ - -//Used \n[\s]*origin_tech[\s]*=[\s]*"[\S]+" to delete all origin techs. -//Or \n[\s]*origin_tech[\s]*=[\s]list\([A-Z_\s=0-9,]*\) -//Used \n[\s]*req_tech[\s]*=[\s]*list\(["a-z\s=0-9,]*\) to delete all req_techs. - -//Techweb datums are meant to store unlocked research, being able to be stored on research consoles, servers, and disks. They are NOT global. -/datum/techweb - var/list/researched_nodes = list() //Already unlocked and all designs are now available. Assoc list, id = TRUE - var/list/visible_nodes = list() //Visible nodes, doesn't mean it can be researched. Assoc list, id = TRUE - var/list/available_nodes = list() //Nodes that can immediately be researched, all reqs met. assoc list, id = TRUE - var/list/researched_designs = list() //Designs that are available for use. Assoc list, id = TRUE - var/list/custom_designs = list() //Custom inserted designs like from disks that should survive recalculation. - var/list/boosted_nodes = list() //Already boosted nodes that can't be boosted again. node id = path of boost object. - var/list/hidden_nodes = list() //Hidden nodes. id = TRUE. Used for unhiding nodes when requirements are met by removing the entry of the node. - var/list/deconstructed_items = list() //items already deconstructed for a generic point boost. path = list(point_type = points) - var/list/research_points = list() //Available research points. type = number - var/list/obj/machinery/computer/rdconsole/consoles_accessing = list() - var/id = "generic" - var/list/research_logs = list() //IC logs. - var/largest_bomb_value = 0 - var/organization = "Third-Party" //Organization name, used for display. - var/list/last_bitcoins = list() //Current per-second production, used for display only. - var/list/tiers = list() //Assoc list, id = number, 1 is available, 2 is all reqs are 1, so on - -/datum/techweb/New() - for(var/i in SSresearch.techweb_nodes_starting) - var/datum/techweb_node/DN = SSresearch.techweb_node_by_id(i) - research_node(DN, TRUE, FALSE) - hidden_nodes = SSresearch.techweb_nodes_hidden.Copy() - return ..() - -/datum/techweb/admin - id = "ADMIN" - organization = "CentCom" - -/datum/techweb/admin/New() //All unlocked. - . = ..() - for(var/i in SSresearch.techweb_nodes) - var/datum/techweb_node/TN = SSresearch.techweb_nodes[i] - research_node(TN, TRUE) - for(var/i in SSresearch.point_types) - research_points[i] = INFINITY - hidden_nodes = list() - -/datum/techweb/syndicate - id = "SYNDICATE" - organization = "Syndicate" - -/datum/techweb/syndicate/New() - var/datum/techweb_node/syndicate_basic/Node = new() - research_node(Node, TRUE) - -/datum/techweb/abductor - id = "ABDUCTOR" - organization = "Aliens" - -/datum/techweb/abductor/New() - var/datum/techweb_node/alientech/Node = new() - research_node(Node, TRUE) - -/datum/techweb/science //Global science techweb for RND consoles. - id = "SCIENCE" - organization = "Nanotrasen" - -/datum/techweb/Destroy() - researched_nodes = null - researched_designs = null - available_nodes = null - visible_nodes = null - custom_designs = null - SSresearch.techwebs -= src - return ..() - -/datum/techweb/proc/recalculate_nodes(recalculate_designs = FALSE, wipe_custom_designs = FALSE) - var/list/datum/techweb_node/processing = list() - for(var/id in researched_nodes) - processing[id] = TRUE - for(var/id in visible_nodes) - processing[id] = TRUE - for(var/id in available_nodes) - processing[id] = TRUE - if(recalculate_designs) - researched_designs = custom_designs.Copy() - if(wipe_custom_designs) - custom_designs = list() - for(var/id in processing) - update_node_status(SSresearch.techweb_node_by_id(id), FALSE) - CHECK_TICK - for(var/v in consoles_accessing) - var/obj/machinery/computer/rdconsole/V = v - V.rescan_views() - V.updateUsrDialog() - -/datum/techweb/proc/add_point_list(list/pointlist) - for(var/i in pointlist) - if(SSresearch.point_types[i] && pointlist[i] > 0) - research_points[i] += pointlist[i] - -/datum/techweb/proc/add_points_all(amount) - var/list/l = SSresearch.point_types.Copy() - for(var/i in l) - l[i] = amount - add_point_list(l) - -/datum/techweb/proc/remove_point_list(list/pointlist) - for(var/i in pointlist) - if(SSresearch.point_types[i] && pointlist[i] > 0) - research_points[i] = max(0, research_points[i] - pointlist[i]) - -/datum/techweb/proc/remove_points_all(amount) - var/list/l = SSresearch.point_types.Copy() - for(var/i in l) - l[i] = amount - remove_point_list(l) - -/datum/techweb/proc/modify_point_list(list/pointlist) - for(var/i in pointlist) - if(SSresearch.point_types[i] && pointlist[i] != 0) - research_points[i] = max(0, research_points[i] + pointlist[i]) - -/datum/techweb/proc/modify_points_all(amount) - var/list/l = SSresearch.point_types.Copy() - for(var/i in l) - l[i] = amount - modify_point_list(l) - -/datum/techweb/proc/copy_research_to(datum/techweb/receiver, unlock_hidden = TRUE) //Adds any missing research to theirs. - for(var/i in researched_nodes) - CHECK_TICK - receiver.research_node_id(i, TRUE, FALSE) - for(var/i in researched_designs) - CHECK_TICK - receiver.add_design_by_id(i) - if(unlock_hidden) - for(var/i in receiver.hidden_nodes) - CHECK_TICK - if(!hidden_nodes[i]) - receiver.hidden_nodes -= i //We can see it so let them see it too. - receiver.recalculate_nodes() - -/datum/techweb/proc/copy() - var/datum/techweb/returned = new() - returned.researched_nodes = researched_nodes.Copy() - returned.visible_nodes = visible_nodes.Copy() - returned.available_nodes = available_nodes.Copy() - returned.researched_designs = researched_designs.Copy() - returned.hidden_nodes = hidden_nodes.Copy() - return returned - -/datum/techweb/proc/get_visible_nodes() //The way this is set up is shit but whatever. - return visible_nodes - hidden_nodes - -/datum/techweb/proc/get_available_nodes() - return available_nodes - hidden_nodes - -/datum/techweb/proc/get_researched_nodes() - return researched_nodes - hidden_nodes - -/datum/techweb/proc/add_point_type(type, amount) - if(!SSresearch.point_types[type] || (amount <= 0)) - return FALSE - research_points[type] += amount - return TRUE - -/datum/techweb/proc/modify_point_type(type, amount) - if(!SSresearch.point_types[type]) - return FALSE - research_points[type] = max(0, research_points[type] + amount) - return TRUE - -/datum/techweb/proc/remove_point_type(type, amount) - if(!SSresearch.point_types[type] || (amount <= 0)) - return FALSE - research_points[type] = max(0, research_points[type] - amount) - return TRUE - -/datum/techweb/proc/add_design_by_id(id, custom = FALSE) - return add_design(SSresearch.techweb_design_by_id(id), custom) - -/datum/techweb/proc/add_design(datum/design/design, custom = FALSE) - if(!istype(design)) - return FALSE - researched_designs[design.id] = design - researched_designs[design.id] = TRUE - if(custom) - custom_designs[design.id] = TRUE - return TRUE - -/datum/techweb/proc/remove_design_by_id(id, custom = FALSE) - return remove_design(SSresearch.techweb_design_by_id(id), custom) - -/datum/techweb/proc/remove_design(datum/design/design, custom = FALSE) - if(!istype(design)) - return FALSE - if(custom_designs[design.id] && !custom) - return FALSE - custom_designs -= design.id - researched_designs -= design.id - return TRUE - -/datum/techweb/proc/can_afford(list/pointlist) - for(var/i in pointlist) - if(research_points[i] < pointlist[i]) - return FALSE - return TRUE - -/datum/techweb/proc/printout_points() - return techweb_point_display_generic(research_points) - -/datum/techweb/proc/research_node_id(id, force, auto_update_points) - return research_node(SSresearch.techweb_node_by_id(id), force, auto_update_points) - -/datum/techweb/proc/research_node(datum/techweb_node/node, force = FALSE, auto_adjust_cost = TRUE) - if(!istype(node)) - return FALSE - update_node_status(node) - if(!force) - if(!available_nodes[node.id] || (auto_adjust_cost && (!can_afford(node.get_price(src))))) - return FALSE - if(auto_adjust_cost) - remove_point_list(node.get_price(src)) - researched_nodes[node.id] = TRUE //Add to our researched list - for(var/id in node.unlock_ids) - visible_nodes[id] = TRUE - update_node_status(SSresearch.techweb_node_by_id(id)) - for(var/id in node.design_ids) - add_design_by_id(id) - update_node_status(node) - return TRUE - -/datum/techweb/proc/unresearch_node_id(id) - return unresearch_node(SSresearch.techweb_node_by_id(id)) - -/datum/techweb/proc/unresearch_node(datum/techweb_node/node) - if(!istype(node)) - return FALSE - researched_nodes -= node.id - recalculate_nodes(TRUE) //Fully rebuild the tree. - -/datum/techweb/proc/boost_with_path(datum/techweb_node/N, itempath) - if(!istype(N) || !ispath(itempath)) - return FALSE - LAZYINITLIST(boosted_nodes[N.id]) - for(var/i in N.boost_item_paths[itempath]) - boosted_nodes[N.id][i] = max(boosted_nodes[N.id][i], N.boost_item_paths[itempath][i]) - if(N.autounlock_by_boost) - hidden_nodes -= N.id - update_node_status(N) - return TRUE - -/datum/techweb/proc/update_tiers(datum/techweb_node/base) - var/list/current = list(base) - while (current.len) - var/list/next = list() - for (var/node_ in current) - var/datum/techweb_node/node = node_ - var/tier = 0 - if (!researched_nodes[node.id]) // researched is tier 0 - for (var/id in node.prereq_ids) - var/prereq_tier = tiers[id] - tier = max(tier, prereq_tier + 1) - - if (tier != tiers[node.id]) - tiers[node.id] = tier - for (var/id in node.unlock_ids) - next += SSresearch.techweb_node_by_id(id) - current = next - -/datum/techweb/proc/update_node_status(datum/techweb_node/node, autoupdate_consoles = TRUE) - var/researched = FALSE - var/available = FALSE - var/visible = FALSE - if(researched_nodes[node.id]) - researched = TRUE - var/needed = node.prereq_ids.len - for(var/id in node.prereq_ids) - if(researched_nodes[id]) - visible = TRUE - needed-- - if(!needed) - available = TRUE - researched_nodes -= node.id - available_nodes -= node.id - visible_nodes -= node.id - if(hidden_nodes[node.id]) //Hidden. - return - if(researched) - researched_nodes[node.id] = TRUE - for(var/id in node.design_ids) - add_design(SSresearch.techweb_design_by_id(id)) - else - if(available) - available_nodes[node.id] = TRUE - else - if(visible) - visible_nodes[node.id] = TRUE - update_tiers(node) - if(autoupdate_consoles) - for(var/v in consoles_accessing) - var/obj/machinery/computer/rdconsole/V = v - V.rescan_views() - V.updateUsrDialog() - -//Laggy procs to do specific checks, just in case. Don't use them if you can just use the vars that already store all this! -/datum/techweb/proc/designHasReqs(datum/design/D) - for(var/i in researched_nodes) - var/datum/techweb_node/N = SSresearch.techweb_node_by_id(i) - if(N.design_ids[D.id]) - return TRUE - return FALSE - -/datum/techweb/proc/isDesignResearched(datum/design/D) - return isDesignResearchedID(D.id) - -/datum/techweb/proc/isDesignResearchedID(id) - return researched_designs[id]? SSresearch.techweb_design_by_id(id) : FALSE - -/datum/techweb/proc/isNodeResearched(datum/techweb_node/N) - return isNodeResearchedID(N.id) - -/datum/techweb/proc/isNodeResearchedID(id) - return researched_nodes[id]? SSresearch.techweb_node_by_id(id) : FALSE - -/datum/techweb/proc/isNodeVisible(datum/techweb_node/N) - return isNodeResearchedID(N.id) - -/datum/techweb/proc/isNodeVisibleID(id) - return visible_nodes[id]? SSresearch.techweb_node_by_id(id) : FALSE - -/datum/techweb/proc/isNodeAvailable(datum/techweb_node/N) - return isNodeAvailableID(N.id) - -/datum/techweb/proc/isNodeAvailableID(id) - return available_nodes[id]? SSresearch.techweb_node_by_id(id) : FALSE - -/datum/techweb/specialized - var/allowed_buildtypes = ALL - -/datum/techweb/specialized/add_design(datum/design/D) - if(!(D.build_type & allowed_buildtypes)) - return FALSE - return ..() - -/datum/techweb/specialized/autounlocking - var/design_autounlock_buildtypes = NONE - var/design_autounlock_categories = list("initial") //if a design has a buildtype that matches the abovea and either has a category in this or this is null, unlock it. - var/node_autounlock_ids = list() //autounlock nodes of this type. - -/datum/techweb/specialized/autounlocking/New() - ..() - autounlock() - -/datum/techweb/specialized/autounlocking/proc/autounlock() - for(var/id in node_autounlock_ids) - research_node_id(id, TRUE, FALSE) - for(var/id in SSresearch.techweb_designs) - var/datum/design/D = SSresearch.techweb_design_by_id(id) - if(D.build_type & design_autounlock_buildtypes) - for(var/i in D.category) - if(i in design_autounlock_categories) - add_design_by_id(D.id) - break - -/datum/techweb/specialized/autounlocking/autolathe - design_autounlock_buildtypes = AUTOLATHE - allowed_buildtypes = AUTOLATHE - -/datum/techweb/specialized/autounlocking/limbgrower - design_autounlock_buildtypes = LIMBGROWER - allowed_buildtypes = LIMBGROWER - -/datum/techweb/specialized/autounlocking/biogenerator - design_autounlock_buildtypes = BIOGENERATOR - allowed_buildtypes = BIOGENERATOR - -/datum/techweb/specialized/autounlocking/smelter - design_autounlock_buildtypes = SMELTER - allowed_buildtypes = SMELTER - -/datum/techweb/specialized/autounlocking/exofab - allowed_buildtypes = MECHFAB - -/datum/techweb/specialized/autounlocking/autoylathe - design_autounlock_buildtypes = AUTOYLATHE - allowed_buildtypes = AUTOYLATHE - -/datum/techweb/specialized/autounlocking/autobottler - design_autounlock_buildtypes = AUTOBOTTLER + +//Used \n[\s]*origin_tech[\s]*=[\s]*"[\S]+" to delete all origin techs. +//Or \n[\s]*origin_tech[\s]*=[\s]list\([A-Z_\s=0-9,]*\) +//Used \n[\s]*req_tech[\s]*=[\s]*list\(["a-z\s=0-9,]*\) to delete all req_techs. + +//Techweb datums are meant to store unlocked research, being able to be stored on research consoles, servers, and disks. They are NOT global. +/datum/techweb + var/list/researched_nodes = list() //Already unlocked and all designs are now available. Assoc list, id = TRUE + var/list/visible_nodes = list() //Visible nodes, doesn't mean it can be researched. Assoc list, id = TRUE + var/list/available_nodes = list() //Nodes that can immediately be researched, all reqs met. assoc list, id = TRUE + var/list/researched_designs = list() //Designs that are available for use. Assoc list, id = TRUE + var/list/custom_designs = list() //Custom inserted designs like from disks that should survive recalculation. + var/list/boosted_nodes = list() //Already boosted nodes that can't be boosted again. node id = path of boost object. + var/list/hidden_nodes = list() //Hidden nodes. id = TRUE. Used for unhiding nodes when requirements are met by removing the entry of the node. + var/list/deconstructed_items = list() //items already deconstructed for a generic point boost. path = list(point_type = points) + var/list/research_points = list() //Available research points. type = number + var/list/obj/machinery/computer/rdconsole/consoles_accessing = list() + var/id = "generic" + var/list/research_logs = list() //IC logs. + var/largest_bomb_value = 0 + var/organization = "Third-Party" //Organization name, used for display. + var/list/last_bitcoins = list() //Current per-second production, used for display only. + var/list/tiers = list() //Assoc list, id = number, 1 is available, 2 is all reqs are 1, so on + +/datum/techweb/New() + for(var/i in SSresearch.techweb_nodes_starting) + var/datum/techweb_node/DN = SSresearch.techweb_node_by_id(i) + research_node(DN, TRUE, FALSE) + hidden_nodes = SSresearch.techweb_nodes_hidden.Copy() + return ..() + +/datum/techweb/admin + id = "ADMIN" + organization = "CentCom" + +/datum/techweb/admin/New() //All unlocked. + . = ..() + for(var/i in SSresearch.techweb_nodes) + var/datum/techweb_node/TN = SSresearch.techweb_nodes[i] + research_node(TN, TRUE) + for(var/i in SSresearch.point_types) + research_points[i] = INFINITY + hidden_nodes = list() + +/datum/techweb/syndicate + id = "SYNDICATE" + organization = "Syndicate" + +/datum/techweb/syndicate/New() + var/datum/techweb_node/syndicate_basic/Node = new() + research_node(Node, TRUE) + +/datum/techweb/abductor + id = "ABDUCTOR" + organization = "Aliens" + +/datum/techweb/abductor/New() + var/datum/techweb_node/alientech/Node = new() + research_node(Node, TRUE) + +/datum/techweb/science //Global science techweb for RND consoles. + id = "SCIENCE" + organization = "Nanotrasen" + +/datum/techweb/Destroy() + researched_nodes = null + researched_designs = null + available_nodes = null + visible_nodes = null + custom_designs = null + SSresearch.techwebs -= src + return ..() + +/datum/techweb/proc/recalculate_nodes(recalculate_designs = FALSE, wipe_custom_designs = FALSE) + var/list/datum/techweb_node/processing = list() + for(var/id in researched_nodes) + processing[id] = TRUE + for(var/id in visible_nodes) + processing[id] = TRUE + for(var/id in available_nodes) + processing[id] = TRUE + if(recalculate_designs) + researched_designs = custom_designs.Copy() + if(wipe_custom_designs) + custom_designs = list() + for(var/id in processing) + update_node_status(SSresearch.techweb_node_by_id(id), FALSE) + CHECK_TICK + for(var/v in consoles_accessing) + var/obj/machinery/computer/rdconsole/V = v + V.rescan_views() + V.updateUsrDialog() + +/datum/techweb/proc/add_point_list(list/pointlist) + for(var/i in pointlist) + if(SSresearch.point_types[i] && pointlist[i] > 0) + research_points[i] += pointlist[i] + +/datum/techweb/proc/add_points_all(amount) + var/list/l = SSresearch.point_types.Copy() + for(var/i in l) + l[i] = amount + add_point_list(l) + +/datum/techweb/proc/remove_point_list(list/pointlist) + for(var/i in pointlist) + if(SSresearch.point_types[i] && pointlist[i] > 0) + research_points[i] = max(0, research_points[i] - pointlist[i]) + +/datum/techweb/proc/remove_points_all(amount) + var/list/l = SSresearch.point_types.Copy() + for(var/i in l) + l[i] = amount + remove_point_list(l) + +/datum/techweb/proc/modify_point_list(list/pointlist) + for(var/i in pointlist) + if(SSresearch.point_types[i] && pointlist[i] != 0) + research_points[i] = max(0, research_points[i] + pointlist[i]) + +/datum/techweb/proc/modify_points_all(amount) + var/list/l = SSresearch.point_types.Copy() + for(var/i in l) + l[i] = amount + modify_point_list(l) + +/datum/techweb/proc/copy_research_to(datum/techweb/receiver, unlock_hidden = TRUE) //Adds any missing research to theirs. + for(var/i in researched_nodes) + CHECK_TICK + receiver.research_node_id(i, TRUE, FALSE) + for(var/i in researched_designs) + CHECK_TICK + receiver.add_design_by_id(i) + if(unlock_hidden) + for(var/i in receiver.hidden_nodes) + CHECK_TICK + if(!hidden_nodes[i]) + receiver.hidden_nodes -= i //We can see it so let them see it too. + receiver.recalculate_nodes() + +/datum/techweb/proc/copy() + var/datum/techweb/returned = new() + returned.researched_nodes = researched_nodes.Copy() + returned.visible_nodes = visible_nodes.Copy() + returned.available_nodes = available_nodes.Copy() + returned.researched_designs = researched_designs.Copy() + returned.hidden_nodes = hidden_nodes.Copy() + return returned + +/datum/techweb/proc/get_visible_nodes() //The way this is set up is shit but whatever. + return visible_nodes - hidden_nodes + +/datum/techweb/proc/get_available_nodes() + return available_nodes - hidden_nodes + +/datum/techweb/proc/get_researched_nodes() + return researched_nodes - hidden_nodes + +/datum/techweb/proc/add_point_type(type, amount) + if(!SSresearch.point_types[type] || (amount <= 0)) + return FALSE + research_points[type] += amount + return TRUE + +/datum/techweb/proc/modify_point_type(type, amount) + if(!SSresearch.point_types[type]) + return FALSE + research_points[type] = max(0, research_points[type] + amount) + return TRUE + +/datum/techweb/proc/remove_point_type(type, amount) + if(!SSresearch.point_types[type] || (amount <= 0)) + return FALSE + research_points[type] = max(0, research_points[type] - amount) + return TRUE + +/datum/techweb/proc/add_design_by_id(id, custom = FALSE) + return add_design(SSresearch.techweb_design_by_id(id), custom) + +/datum/techweb/proc/add_design(datum/design/design, custom = FALSE) + if(!istype(design)) + return FALSE + researched_designs[design.id] = design + researched_designs[design.id] = TRUE + if(custom) + custom_designs[design.id] = TRUE + return TRUE + +/datum/techweb/proc/remove_design_by_id(id, custom = FALSE) + return remove_design(SSresearch.techweb_design_by_id(id), custom) + +/datum/techweb/proc/remove_design(datum/design/design, custom = FALSE) + if(!istype(design)) + return FALSE + if(custom_designs[design.id] && !custom) + return FALSE + custom_designs -= design.id + researched_designs -= design.id + return TRUE + +/datum/techweb/proc/can_afford(list/pointlist) + for(var/i in pointlist) + if(research_points[i] < pointlist[i]) + return FALSE + return TRUE + +/datum/techweb/proc/printout_points() + return techweb_point_display_generic(research_points) + +/datum/techweb/proc/research_node_id(id, force, auto_update_points) + return research_node(SSresearch.techweb_node_by_id(id), force, auto_update_points) + +/datum/techweb/proc/research_node(datum/techweb_node/node, force = FALSE, auto_adjust_cost = TRUE) + if(!istype(node)) + return FALSE + update_node_status(node) + if(!force) + if(!available_nodes[node.id] || (auto_adjust_cost && (!can_afford(node.get_price(src))))) + return FALSE + if(auto_adjust_cost) + remove_point_list(node.get_price(src)) + researched_nodes[node.id] = TRUE //Add to our researched list + for(var/id in node.unlock_ids) + visible_nodes[id] = TRUE + update_node_status(SSresearch.techweb_node_by_id(id)) + for(var/id in node.design_ids) + add_design_by_id(id) + update_node_status(node) + return TRUE + +/datum/techweb/proc/unresearch_node_id(id) + return unresearch_node(SSresearch.techweb_node_by_id(id)) + +/datum/techweb/proc/unresearch_node(datum/techweb_node/node) + if(!istype(node)) + return FALSE + researched_nodes -= node.id + recalculate_nodes(TRUE) //Fully rebuild the tree. + +/datum/techweb/proc/boost_with_path(datum/techweb_node/N, itempath) + if(!istype(N) || !ispath(itempath)) + return FALSE + LAZYINITLIST(boosted_nodes[N.id]) + for(var/i in N.boost_item_paths[itempath]) + boosted_nodes[N.id][i] = max(boosted_nodes[N.id][i], N.boost_item_paths[itempath][i]) + if(N.autounlock_by_boost) + hidden_nodes -= N.id + update_node_status(N) + return TRUE + +/datum/techweb/proc/update_tiers(datum/techweb_node/base) + var/list/current = list(base) + while (current.len) + var/list/next = list() + for (var/node_ in current) + var/datum/techweb_node/node = node_ + var/tier = 0 + if (!researched_nodes[node.id]) // researched is tier 0 + for (var/id in node.prereq_ids) + var/prereq_tier = tiers[id] + tier = max(tier, prereq_tier + 1) + + if (tier != tiers[node.id]) + tiers[node.id] = tier + for (var/id in node.unlock_ids) + next += SSresearch.techweb_node_by_id(id) + current = next + +/datum/techweb/proc/update_node_status(datum/techweb_node/node, autoupdate_consoles = TRUE) + var/researched = FALSE + var/available = FALSE + var/visible = FALSE + if(researched_nodes[node.id]) + researched = TRUE + var/needed = node.prereq_ids.len + for(var/id in node.prereq_ids) + if(researched_nodes[id]) + visible = TRUE + needed-- + if(!needed) + available = TRUE + researched_nodes -= node.id + available_nodes -= node.id + visible_nodes -= node.id + if(hidden_nodes[node.id]) //Hidden. + return + if(researched) + researched_nodes[node.id] = TRUE + for(var/id in node.design_ids) + add_design(SSresearch.techweb_design_by_id(id)) + else + if(available) + available_nodes[node.id] = TRUE + else + if(visible) + visible_nodes[node.id] = TRUE + update_tiers(node) + if(autoupdate_consoles) + for(var/v in consoles_accessing) + var/obj/machinery/computer/rdconsole/V = v + V.rescan_views() + V.updateUsrDialog() + +//Laggy procs to do specific checks, just in case. Don't use them if you can just use the vars that already store all this! +/datum/techweb/proc/designHasReqs(datum/design/D) + for(var/i in researched_nodes) + var/datum/techweb_node/N = SSresearch.techweb_node_by_id(i) + if(N.design_ids[D.id]) + return TRUE + return FALSE + +/datum/techweb/proc/isDesignResearched(datum/design/D) + return isDesignResearchedID(D.id) + +/datum/techweb/proc/isDesignResearchedID(id) + return researched_designs[id]? SSresearch.techweb_design_by_id(id) : FALSE + +/datum/techweb/proc/isNodeResearched(datum/techweb_node/N) + return isNodeResearchedID(N.id) + +/datum/techweb/proc/isNodeResearchedID(id) + return researched_nodes[id]? SSresearch.techweb_node_by_id(id) : FALSE + +/datum/techweb/proc/isNodeVisible(datum/techweb_node/N) + return isNodeResearchedID(N.id) + +/datum/techweb/proc/isNodeVisibleID(id) + return visible_nodes[id]? SSresearch.techweb_node_by_id(id) : FALSE + +/datum/techweb/proc/isNodeAvailable(datum/techweb_node/N) + return isNodeAvailableID(N.id) + +/datum/techweb/proc/isNodeAvailableID(id) + return available_nodes[id]? SSresearch.techweb_node_by_id(id) : FALSE + +/datum/techweb/specialized + var/allowed_buildtypes = ALL + +/datum/techweb/specialized/add_design(datum/design/D) + if(!(D.build_type & allowed_buildtypes)) + return FALSE + return ..() + +/datum/techweb/specialized/autounlocking + var/design_autounlock_buildtypes = NONE + var/design_autounlock_categories = list("initial") //if a design has a buildtype that matches the abovea and either has a category in this or this is null, unlock it. + var/node_autounlock_ids = list() //autounlock nodes of this type. + +/datum/techweb/specialized/autounlocking/New() + ..() + autounlock() + +/datum/techweb/specialized/autounlocking/proc/autounlock() + for(var/id in node_autounlock_ids) + research_node_id(id, TRUE, FALSE) + for(var/id in SSresearch.techweb_designs) + var/datum/design/D = SSresearch.techweb_design_by_id(id) + if(D.build_type & design_autounlock_buildtypes) + for(var/i in D.category) + if(i in design_autounlock_categories) + add_design_by_id(D.id) + break + +/datum/techweb/specialized/autounlocking/autolathe + design_autounlock_buildtypes = AUTOLATHE + allowed_buildtypes = AUTOLATHE + +/datum/techweb/specialized/autounlocking/limbgrower + design_autounlock_buildtypes = LIMBGROWER + allowed_buildtypes = LIMBGROWER + +/datum/techweb/specialized/autounlocking/biogenerator + design_autounlock_buildtypes = BIOGENERATOR + allowed_buildtypes = BIOGENERATOR + +/datum/techweb/specialized/autounlocking/smelter + design_autounlock_buildtypes = SMELTER + allowed_buildtypes = SMELTER + +/datum/techweb/specialized/autounlocking/exofab + allowed_buildtypes = MECHFAB + +/datum/techweb/specialized/autounlocking/autoylathe + design_autounlock_buildtypes = AUTOYLATHE + allowed_buildtypes = AUTOYLATHE + +/datum/techweb/specialized/autounlocking/autobottler + design_autounlock_buildtypes = AUTOBOTTLER allowed_buildtypes = AUTOBOTTLER \ No newline at end of file diff --git a/code/modules/research/techweb/_techweb_node.dm b/code/modules/research/techweb/_techweb_node.dm index e850158d89..b510abbedd 100644 --- a/code/modules/research/techweb/_techweb_node.dm +++ b/code/modules/research/techweb/_techweb_node.dm @@ -1,97 +1,97 @@ - -//Techweb nodes are GLOBAL, there should only be one instance of them in the game. Persistant changes should never be made to them in-game. -//USE SSRESEARCH PROCS TO OBTAIN REFERENCES. DO NOT REFERENCE OUTSIDE OF SSRESEARCH OR YOU WILL FUCK UP GC. - -/datum/techweb_node - var/id - var/display_name = "Errored Node" - var/description = "Why are you seeing this?" - var/hidden = FALSE //Whether it starts off hidden. - var/starting_node = FALSE //Whether it's available without any research. - var/list/prereq_ids = list() - var/list/design_ids = list() - var/list/unlock_ids = list() //CALCULATED FROM OTHER NODE'S PREREQUISITES. Assoc list id = TRUE. - var/list/boost_item_paths = list() //Associative list, path = list(point type = point_value). - var/autounlock_by_boost = TRUE //boosting this will autounlock this node. - var/export_price = 0 //Cargo export price. - var/list/research_costs = list() //Point cost to research. type = amount - var/category = "Misc" //Category - -/datum/techweb_node/error_node - id = "ERROR" - display_name = "ERROR" - description = "This usually means something in the database has corrupted. If it doesn't go away automatically, inform Central Command for their techs to fix it ASAP(tm)" - -/datum/techweb_node/proc/Initialize() - //Make lists associative for lookup - for(var/id in prereq_ids) - prereq_ids[id] = TRUE - for(var/id in design_ids) - design_ids[id] = TRUE - for(var/id in unlock_ids) - unlock_ids[id] = TRUE - -/datum/techweb_node/Destroy() - SSresearch.techweb_nodes -= id - return ..() - -/datum/techweb_node/serialize_list(list/options) - . = list() - VARSET_TO_LIST(., id) - VARSET_TO_LIST(., display_name) - VARSET_TO_LIST(., hidden) - VARSET_TO_LIST(., starting_node) - VARSET_TO_LIST(., assoc_list_strip_value(prereq_ids)) - VARSET_TO_LIST(., assoc_list_strip_value(design_ids)) - VARSET_TO_LIST(., assoc_list_strip_value(unlock_ids)) - VARSET_TO_LIST(., boost_item_paths) - VARSET_TO_LIST(., autounlock_by_boost) - VARSET_TO_LIST(., export_price) - VARSET_TO_LIST(., research_costs) - VARSET_TO_LIST(., category) - -/datum/techweb_node/deserialize_list(list/input, list/options) - if(!input["id"]) - return - VARSET_FROM_LIST(input, id) - VARSET_FROM_LIST(input, display_name) - VARSET_FROM_LIST(input, hidden) - VARSET_FROM_LIST(input, starting_node) - VARSET_FROM_LIST(input, prereq_ids) - VARSET_FROM_LIST(input, design_ids) - VARSET_FROM_LIST(input, unlock_ids) - VARSET_FROM_LIST(input, boost_item_paths) - VARSET_FROM_LIST(input, autounlock_by_boost) - VARSET_FROM_LIST(input, export_price) - VARSET_FROM_LIST(input, research_costs) - VARSET_FROM_LIST(input, category) - Initialize() - return src - -/datum/techweb_node/proc/on_design_deletion(datum/design/D) - prune_design_id(D.id) - -/datum/techweb_node/proc/on_node_deletion(datum/techweb_node/TN) - prune_node_id(TN.id) - -/datum/techweb_node/proc/prune_design_id(design_id) - design_ids -= design_id - -/datum/techweb_node/proc/prune_node_id(node_id) - prereq_ids -= node_id - unlock_ids -= node_id - -/datum/techweb_node/proc/get_price(datum/techweb/host) - if(host) - var/list/actual_costs = research_costs - if(host.boosted_nodes[id]) - var/list/L = host.boosted_nodes[id] - for(var/i in L) - if(actual_costs[i]) - actual_costs[i] -= L[i] - return actual_costs - else - return research_costs - -/datum/techweb_node/proc/price_display(datum/techweb/TN) - return techweb_point_display_generic(get_price(TN)) + +//Techweb nodes are GLOBAL, there should only be one instance of them in the game. Persistant changes should never be made to them in-game. +//USE SSRESEARCH PROCS TO OBTAIN REFERENCES. DO NOT REFERENCE OUTSIDE OF SSRESEARCH OR YOU WILL FUCK UP GC. + +/datum/techweb_node + var/id + var/display_name = "Errored Node" + var/description = "Why are you seeing this?" + var/hidden = FALSE //Whether it starts off hidden. + var/starting_node = FALSE //Whether it's available without any research. + var/list/prereq_ids = list() + var/list/design_ids = list() + var/list/unlock_ids = list() //CALCULATED FROM OTHER NODE'S PREREQUISITES. Assoc list id = TRUE. + var/list/boost_item_paths = list() //Associative list, path = list(point type = point_value). + var/autounlock_by_boost = TRUE //boosting this will autounlock this node. + var/export_price = 0 //Cargo export price. + var/list/research_costs = list() //Point cost to research. type = amount + var/category = "Misc" //Category + +/datum/techweb_node/error_node + id = "ERROR" + display_name = "ERROR" + description = "This usually means something in the database has corrupted. If it doesn't go away automatically, inform Central Command for their techs to fix it ASAP(tm)" + +/datum/techweb_node/proc/Initialize() + //Make lists associative for lookup + for(var/id in prereq_ids) + prereq_ids[id] = TRUE + for(var/id in design_ids) + design_ids[id] = TRUE + for(var/id in unlock_ids) + unlock_ids[id] = TRUE + +/datum/techweb_node/Destroy() + SSresearch.techweb_nodes -= id + return ..() + +/datum/techweb_node/serialize_list(list/options) + . = list() + VARSET_TO_LIST(., id) + VARSET_TO_LIST(., display_name) + VARSET_TO_LIST(., hidden) + VARSET_TO_LIST(., starting_node) + VARSET_TO_LIST(., assoc_list_strip_value(prereq_ids)) + VARSET_TO_LIST(., assoc_list_strip_value(design_ids)) + VARSET_TO_LIST(., assoc_list_strip_value(unlock_ids)) + VARSET_TO_LIST(., boost_item_paths) + VARSET_TO_LIST(., autounlock_by_boost) + VARSET_TO_LIST(., export_price) + VARSET_TO_LIST(., research_costs) + VARSET_TO_LIST(., category) + +/datum/techweb_node/deserialize_list(list/input, list/options) + if(!input["id"]) + return + VARSET_FROM_LIST(input, id) + VARSET_FROM_LIST(input, display_name) + VARSET_FROM_LIST(input, hidden) + VARSET_FROM_LIST(input, starting_node) + VARSET_FROM_LIST(input, prereq_ids) + VARSET_FROM_LIST(input, design_ids) + VARSET_FROM_LIST(input, unlock_ids) + VARSET_FROM_LIST(input, boost_item_paths) + VARSET_FROM_LIST(input, autounlock_by_boost) + VARSET_FROM_LIST(input, export_price) + VARSET_FROM_LIST(input, research_costs) + VARSET_FROM_LIST(input, category) + Initialize() + return src + +/datum/techweb_node/proc/on_design_deletion(datum/design/D) + prune_design_id(D.id) + +/datum/techweb_node/proc/on_node_deletion(datum/techweb_node/TN) + prune_node_id(TN.id) + +/datum/techweb_node/proc/prune_design_id(design_id) + design_ids -= design_id + +/datum/techweb_node/proc/prune_node_id(node_id) + prereq_ids -= node_id + unlock_ids -= node_id + +/datum/techweb_node/proc/get_price(datum/techweb/host) + if(host) + var/list/actual_costs = research_costs + if(host.boosted_nodes[id]) + var/list/L = host.boosted_nodes[id] + for(var/i in L) + if(actual_costs[i]) + actual_costs[i] -= L[i] + return actual_costs + else + return research_costs + +/datum/techweb_node/proc/price_display(datum/techweb/TN) + return techweb_point_display_generic(get_price(TN)) diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm index 162942d1d8..8a58563407 100644 --- a/code/modules/security_levels/keycard_authentication.dm +++ b/code/modules/security_levels/keycard_authentication.dm @@ -1,143 +1,143 @@ -GLOBAL_DATUM_INIT(keycard_events, /datum/events, new) - -#define KEYCARD_RED_ALERT "Red Alert" -#define KEYCARD_EMERGENCY_MAINTENANCE_ACCESS "Emergency Maintenance Access" -#define KEYCARD_BSA_UNLOCK "Bluespace Artillery Unlock" - -/obj/machinery/keycard_auth - name = "Keycard Authentication Device" - desc = "This device is used to trigger station functions, which require more than one ID card to authenticate." - icon = 'icons/obj/monitors.dmi' - icon_state = "auth_off" - use_power = IDLE_POWER_USE - idle_power_usage = 2 - active_power_usage = 6 - power_channel = ENVIRON - req_access = list(ACCESS_KEYCARD_AUTH) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/datum/callback/ev - var/event = "" - var/obj/machinery/keycard_auth/event_source - var/mob/triggerer = null - var/waiting = 0 - -/obj/machinery/keycard_auth/Initialize() - . = ..() - ev = GLOB.keycard_events.addEvent("triggerEvent", CALLBACK(src, .proc/triggerEvent)) - -/obj/machinery/keycard_auth/Destroy() - GLOB.keycard_events.clearEvent("triggerEvent", ev) - QDEL_NULL(ev) - return ..() - -/obj/machinery/keycard_auth/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, "keycard_auth", name, 375, 125, master_ui, state) - ui.open() - -/obj/machinery/keycard_auth/ui_data() - var/list/data = list() - data["waiting"] = waiting - data["auth_required"] = event_source ? event_source.event : 0 - data["red_alert"] = (seclevel2num(get_security_level()) >= SEC_LEVEL_RED) ? 1 : 0 - data["emergency_maint"] = GLOB.emergency_access - data["bsa_unlock"] = GLOB.bsa_unlock - return data - -/obj/machinery/keycard_auth/ui_status(mob/user) - if(isanimal(user)) - var/mob/living/simple_animal/A = user - if(!A.dextrous) - to_chat(user, "You are too primitive to use this device!") - return UI_CLOSE - return ..() - -/obj/machinery/keycard_auth/ui_act(action, params) - if(..() || waiting || !allowed(usr)) - return - switch(action) - if("red_alert") - if(!event_source) - sendEvent(KEYCARD_RED_ALERT) - . = TRUE - if("emergency_maint") - if(!event_source) - sendEvent(KEYCARD_EMERGENCY_MAINTENANCE_ACCESS) - . = TRUE - if("auth_swipe") - if(event_source) - event_source.trigger_event(usr) - event_source = null - . = TRUE - if("bsa_unlock") - if(!event_source) - sendEvent(KEYCARD_BSA_UNLOCK) - . = TRUE - -/obj/machinery/keycard_auth/proc/sendEvent(event_type) - triggerer = usr - event = event_type - waiting = 1 - GLOB.keycard_events.fireEvent("triggerEvent", src) - addtimer(CALLBACK(src, .proc/eventSent), 20) - -/obj/machinery/keycard_auth/proc/eventSent() - triggerer = null - event = "" - waiting = 0 - -/obj/machinery/keycard_auth/proc/triggerEvent(source) - icon_state = "auth_on" - event_source = source - addtimer(CALLBACK(src, .proc/eventTriggered), 20) - -/obj/machinery/keycard_auth/proc/eventTriggered() - icon_state = "auth_off" - event_source = null - -/obj/machinery/keycard_auth/proc/trigger_event(confirmer) - log_game("[key_name(triggerer)] triggered and [key_name(confirmer)] confirmed event [event]") - message_admins("[key_name(triggerer)] triggered and [key_name(confirmer)] confirmed event [event]") - - var/area/A1 = get_area(triggerer) - deadchat_broadcast("[triggerer] triggered [event] at [A1.name].", triggerer) - - var/area/A2 = get_area(confirmer) - deadchat_broadcast("[confirmer] confirmed [event] at [A2.name].", confirmer) - switch(event) - if(KEYCARD_RED_ALERT) - set_security_level(SEC_LEVEL_RED) - if(KEYCARD_EMERGENCY_MAINTENANCE_ACCESS) - make_maint_all_access() - if(KEYCARD_BSA_UNLOCK) - toggle_bluespace_artillery() - -GLOBAL_VAR_INIT(emergency_access, FALSE) -/proc/make_maint_all_access() - for(var/area/maintenance/A in world) - for(var/obj/machinery/door/airlock/D in A) - D.emergency = TRUE - D.update_icon(0) - minor_announce("Access restrictions on maintenance and external airlocks have been lifted.", "Attention! Station-wide emergency declared!",1) - GLOB.emergency_access = TRUE - SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "enabled")) - -/proc/revoke_maint_all_access() - for(var/area/maintenance/A in world) - for(var/obj/machinery/door/airlock/D in A) - D.emergency = FALSE - D.update_icon(0) - minor_announce("Access restrictions in maintenance areas have been restored.", "Attention! Station-wide emergency rescinded:") - GLOB.emergency_access = FALSE - SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "disabled")) - -/proc/toggle_bluespace_artillery() - GLOB.bsa_unlock = !GLOB.bsa_unlock - minor_announce("Bluespace Artillery firing protocols have been [GLOB.bsa_unlock? "unlocked" : "locked"]", "Weapons Systems Update:") - SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("bluespace artillery", GLOB.bsa_unlock? "unlocked" : "locked")) - -#undef KEYCARD_RED_ALERT -#undef KEYCARD_EMERGENCY_MAINTENANCE_ACCESS -#undef KEYCARD_BSA_UNLOCK +GLOBAL_DATUM_INIT(keycard_events, /datum/events, new) + +#define KEYCARD_RED_ALERT "Red Alert" +#define KEYCARD_EMERGENCY_MAINTENANCE_ACCESS "Emergency Maintenance Access" +#define KEYCARD_BSA_UNLOCK "Bluespace Artillery Unlock" + +/obj/machinery/keycard_auth + name = "Keycard Authentication Device" + desc = "This device is used to trigger station functions, which require more than one ID card to authenticate." + icon = 'icons/obj/monitors.dmi' + icon_state = "auth_off" + use_power = IDLE_POWER_USE + idle_power_usage = 2 + active_power_usage = 6 + power_channel = ENVIRON + req_access = list(ACCESS_KEYCARD_AUTH) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + var/datum/callback/ev + var/event = "" + var/obj/machinery/keycard_auth/event_source + var/mob/triggerer = null + var/waiting = 0 + +/obj/machinery/keycard_auth/Initialize() + . = ..() + ev = GLOB.keycard_events.addEvent("triggerEvent", CALLBACK(src, .proc/triggerEvent)) + +/obj/machinery/keycard_auth/Destroy() + GLOB.keycard_events.clearEvent("triggerEvent", ev) + QDEL_NULL(ev) + return ..() + +/obj/machinery/keycard_auth/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, "keycard_auth", name, 375, 125, master_ui, state) + ui.open() + +/obj/machinery/keycard_auth/ui_data() + var/list/data = list() + data["waiting"] = waiting + data["auth_required"] = event_source ? event_source.event : 0 + data["red_alert"] = (seclevel2num(get_security_level()) >= SEC_LEVEL_RED) ? 1 : 0 + data["emergency_maint"] = GLOB.emergency_access + data["bsa_unlock"] = GLOB.bsa_unlock + return data + +/obj/machinery/keycard_auth/ui_status(mob/user) + if(isanimal(user)) + var/mob/living/simple_animal/A = user + if(!A.dextrous) + to_chat(user, "You are too primitive to use this device!") + return UI_CLOSE + return ..() + +/obj/machinery/keycard_auth/ui_act(action, params) + if(..() || waiting || !allowed(usr)) + return + switch(action) + if("red_alert") + if(!event_source) + sendEvent(KEYCARD_RED_ALERT) + . = TRUE + if("emergency_maint") + if(!event_source) + sendEvent(KEYCARD_EMERGENCY_MAINTENANCE_ACCESS) + . = TRUE + if("auth_swipe") + if(event_source) + event_source.trigger_event(usr) + event_source = null + . = TRUE + if("bsa_unlock") + if(!event_source) + sendEvent(KEYCARD_BSA_UNLOCK) + . = TRUE + +/obj/machinery/keycard_auth/proc/sendEvent(event_type) + triggerer = usr + event = event_type + waiting = 1 + GLOB.keycard_events.fireEvent("triggerEvent", src) + addtimer(CALLBACK(src, .proc/eventSent), 20) + +/obj/machinery/keycard_auth/proc/eventSent() + triggerer = null + event = "" + waiting = 0 + +/obj/machinery/keycard_auth/proc/triggerEvent(source) + icon_state = "auth_on" + event_source = source + addtimer(CALLBACK(src, .proc/eventTriggered), 20) + +/obj/machinery/keycard_auth/proc/eventTriggered() + icon_state = "auth_off" + event_source = null + +/obj/machinery/keycard_auth/proc/trigger_event(confirmer) + log_game("[key_name(triggerer)] triggered and [key_name(confirmer)] confirmed event [event]") + message_admins("[key_name(triggerer)] triggered and [key_name(confirmer)] confirmed event [event]") + + var/area/A1 = get_area(triggerer) + deadchat_broadcast("[triggerer] triggered [event] at [A1.name].", triggerer) + + var/area/A2 = get_area(confirmer) + deadchat_broadcast("[confirmer] confirmed [event] at [A2.name].", confirmer) + switch(event) + if(KEYCARD_RED_ALERT) + set_security_level(SEC_LEVEL_RED) + if(KEYCARD_EMERGENCY_MAINTENANCE_ACCESS) + make_maint_all_access() + if(KEYCARD_BSA_UNLOCK) + toggle_bluespace_artillery() + +GLOBAL_VAR_INIT(emergency_access, FALSE) +/proc/make_maint_all_access() + for(var/area/maintenance/A in world) + for(var/obj/machinery/door/airlock/D in A) + D.emergency = TRUE + D.update_icon(0) + minor_announce("Access restrictions on maintenance and external airlocks have been lifted.", "Attention! Station-wide emergency declared!",1) + GLOB.emergency_access = TRUE + SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "enabled")) + +/proc/revoke_maint_all_access() + for(var/area/maintenance/A in world) + for(var/obj/machinery/door/airlock/D in A) + D.emergency = FALSE + D.update_icon(0) + minor_announce("Access restrictions in maintenance areas have been restored.", "Attention! Station-wide emergency rescinded:") + GLOB.emergency_access = FALSE + SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("emergency maintenance access", "disabled")) + +/proc/toggle_bluespace_artillery() + GLOB.bsa_unlock = !GLOB.bsa_unlock + minor_announce("Bluespace Artillery firing protocols have been [GLOB.bsa_unlock? "unlocked" : "locked"]", "Weapons Systems Update:") + SSblackbox.record_feedback("nested tally", "keycard_auths", 1, list("bluespace artillery", GLOB.bsa_unlock? "unlocked" : "locked")) + +#undef KEYCARD_RED_ALERT +#undef KEYCARD_EMERGENCY_MAINTENANCE_ACCESS +#undef KEYCARD_BSA_UNLOCK diff --git a/code/modules/security_levels/security_levels.dm b/code/modules/security_levels/security_levels.dm index 13d1543c92..887891ca16 100644 --- a/code/modules/security_levels/security_levels.dm +++ b/code/modules/security_levels/security_levels.dm @@ -1,156 +1,156 @@ -GLOBAL_VAR_INIT(security_level, SEC_LEVEL_GREEN) -//SEC_LEVEL_GREEN = code green -//SEC_LEVEL_BLUE = code blue -//SEC_LEVEL_AMBER = code amber -//SEC_LEVEL_RED = code red -//SEC_LEVEL_DELTA = code delta - -//config.alert_desc_blue_downto - -/proc/set_security_level(level) - switch(level) - if("green") - level = SEC_LEVEL_GREEN - if("blue") - level = SEC_LEVEL_BLUE - if("amber") - level = SEC_LEVEL_AMBER - if("red") - level = SEC_LEVEL_RED - if("delta") - level = SEC_LEVEL_DELTA - - //Will not be announced if you try to set to the same level as it already is - if(level >= SEC_LEVEL_GREEN && level <= SEC_LEVEL_DELTA && level != GLOB.security_level) - switch(level) - if(SEC_LEVEL_GREEN) - minor_announce(CONFIG_GET(string/alert_green), "Attention! Security level lowered to green:") - if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) - if(GLOB.security_level >= SEC_LEVEL_RED) - SSshuttle.emergency.modTimer(4) - else if(GLOB.security_level == SEC_LEVEL_AMBER) - SSshuttle.emergency.modTimer(2.5) - else - SSshuttle.emergency.modTimer(1.66) - GLOB.security_level = SEC_LEVEL_GREEN - for(var/obj/machinery/firealarm/FA in GLOB.machines) - if(is_station_level(FA.z)) - FA.update_icon() - if(SEC_LEVEL_BLUE) - if(GLOB.security_level < SEC_LEVEL_BLUE) - minor_announce(CONFIG_GET(string/alert_blue_upto), "Attention! Security level elevated to blue:",1) - if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) - SSshuttle.emergency.modTimer(0.6) - else - minor_announce(CONFIG_GET(string/alert_blue_downto), "Attention! Security level lowered to blue:") - if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) - if(GLOB.security_level >= SEC_LEVEL_RED) - SSshuttle.emergency.modTimer(2.4) - else - SSshuttle.emergency.modTimer(1.5) - GLOB.security_level = SEC_LEVEL_BLUE - sound_to_playing_players('sound/misc/voybluealert.ogg', volume = 50) // Citadel change - Makes alerts play a sound - for(var/obj/machinery/firealarm/FA in GLOB.machines) - if(is_station_level(FA.z)) - FA.update_icon() - if(SEC_LEVEL_AMBER) - if(GLOB.security_level < SEC_LEVEL_AMBER) - minor_announce(CONFIG_GET(string/alert_amber_upto), "Attention! Security level elevated to amber:",1) - if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) - if(GLOB.security_level == SEC_LEVEL_GREEN) - SSshuttle.emergency.modTimer(0.4) - else - SSshuttle.emergency.modTimer(0.66) - else - minor_announce(CONFIG_GET(string/alert_amber_downto), "Attention! Security level lowered to amber:") - if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) - SSshuttle.emergency.modTimer(1.6) - GLOB.security_level = SEC_LEVEL_AMBER - sound_to_playing_players('sound/effects/alert.ogg', volume = 50) // Citadel change - Makes alerts play a sound - for(var/obj/machinery/firealarm/FA in GLOB.machines) - if(is_station_level(FA.z)) - FA.update_icon() - if(SEC_LEVEL_RED) - if(GLOB.security_level < SEC_LEVEL_RED) - minor_announce(CONFIG_GET(string/alert_red_upto), "Attention! Code red!",1) - if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) - if(GLOB.security_level == SEC_LEVEL_GREEN) - SSshuttle.emergency.modTimer(0.25) - else if(GLOB.security_level == SEC_LEVEL_BLUE) - SSshuttle.emergency.modTimer(0.416) - else - SSshuttle.emergency.modTimer(0.625) - else - minor_announce(CONFIG_GET(string/alert_red_downto), "Attention! Code red!") - GLOB.security_level = SEC_LEVEL_RED - sound_to_playing_players('sound/misc/voyalert.ogg', volume = 50) // Citadel change - Makes alerts play a sound - - for(var/obj/machinery/firealarm/FA in GLOB.machines) - if(is_station_level(FA.z)) - FA.update_icon() - for(var/obj/machinery/computer/shuttle/pod/pod in GLOB.machines) - pod.admin_controlled = 0 - if(SEC_LEVEL_DELTA) - minor_announce(CONFIG_GET(string/alert_delta), "Attention! Delta security level reached!",1) - if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) - if(GLOB.security_level < SEC_LEVEL_BLUE) - SSshuttle.emergency.modTimer(0.25) - else if(GLOB.security_level == SEC_LEVEL_BLUE) - SSshuttle.emergency.modTimer(0.416) - else - SSshuttle.emergency.modTimer(0.625) - GLOB.security_level = SEC_LEVEL_DELTA - sound_to_playing_players('sound/misc/deltakalaxon.ogg') // Citadel change - Makes alerts play a sound - for(var/obj/machinery/firealarm/FA in GLOB.machines) - if(is_station_level(FA.z)) - FA.update_icon() - for(var/obj/machinery/computer/shuttle/pod/pod in GLOB.machines) - pod.admin_controlled = 0 - if(level >= SEC_LEVEL_RED) - for(var/obj/machinery/door/D in GLOB.machines) - if(D.red_alert_access) - D.visible_message("[D] whirrs as it automatically lifts access requirements!") - playsound(D, 'sound/machines/boltsup.ogg', 50, TRUE) - SSblackbox.record_feedback("tally", "security_level_changes", 1, get_security_level()) - SSnightshift.check_nightshift() - else - return - -/proc/get_security_level() - switch(GLOB.security_level) - if(SEC_LEVEL_GREEN) - return "green" - if(SEC_LEVEL_BLUE) - return "blue" - if(SEC_LEVEL_AMBER) - return "amber" - if(SEC_LEVEL_RED) - return "red" - if(SEC_LEVEL_DELTA) - return "delta" - -/proc/num2seclevel(num) - switch(num) - if(SEC_LEVEL_GREEN) - return "green" - if(SEC_LEVEL_BLUE) - return "blue" - if(SEC_LEVEL_AMBER) - return "amber" - if(SEC_LEVEL_RED) - return "red" - if(SEC_LEVEL_DELTA) - return "delta" - -/proc/seclevel2num(seclevel) - switch( lowertext(seclevel) ) - if("green") - return SEC_LEVEL_GREEN - if("blue") - return SEC_LEVEL_BLUE - if("amber") - return SEC_LEVEL_AMBER - if("red") - return SEC_LEVEL_RED - if("delta") - return SEC_LEVEL_DELTA +GLOBAL_VAR_INIT(security_level, SEC_LEVEL_GREEN) +//SEC_LEVEL_GREEN = code green +//SEC_LEVEL_BLUE = code blue +//SEC_LEVEL_AMBER = code amber +//SEC_LEVEL_RED = code red +//SEC_LEVEL_DELTA = code delta + +//config.alert_desc_blue_downto + +/proc/set_security_level(level) + switch(level) + if("green") + level = SEC_LEVEL_GREEN + if("blue") + level = SEC_LEVEL_BLUE + if("amber") + level = SEC_LEVEL_AMBER + if("red") + level = SEC_LEVEL_RED + if("delta") + level = SEC_LEVEL_DELTA + + //Will not be announced if you try to set to the same level as it already is + if(level >= SEC_LEVEL_GREEN && level <= SEC_LEVEL_DELTA && level != GLOB.security_level) + switch(level) + if(SEC_LEVEL_GREEN) + minor_announce(CONFIG_GET(string/alert_green), "Attention! Security level lowered to green:") + if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) + if(GLOB.security_level >= SEC_LEVEL_RED) + SSshuttle.emergency.modTimer(4) + else if(GLOB.security_level == SEC_LEVEL_AMBER) + SSshuttle.emergency.modTimer(2.5) + else + SSshuttle.emergency.modTimer(1.66) + GLOB.security_level = SEC_LEVEL_GREEN + for(var/obj/machinery/firealarm/FA in GLOB.machines) + if(is_station_level(FA.z)) + FA.update_icon() + if(SEC_LEVEL_BLUE) + if(GLOB.security_level < SEC_LEVEL_BLUE) + minor_announce(CONFIG_GET(string/alert_blue_upto), "Attention! Security level elevated to blue:",1) + if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) + SSshuttle.emergency.modTimer(0.6) + else + minor_announce(CONFIG_GET(string/alert_blue_downto), "Attention! Security level lowered to blue:") + if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) + if(GLOB.security_level >= SEC_LEVEL_RED) + SSshuttle.emergency.modTimer(2.4) + else + SSshuttle.emergency.modTimer(1.5) + GLOB.security_level = SEC_LEVEL_BLUE + sound_to_playing_players('sound/misc/voybluealert.ogg', volume = 50) // Citadel change - Makes alerts play a sound + for(var/obj/machinery/firealarm/FA in GLOB.machines) + if(is_station_level(FA.z)) + FA.update_icon() + if(SEC_LEVEL_AMBER) + if(GLOB.security_level < SEC_LEVEL_AMBER) + minor_announce(CONFIG_GET(string/alert_amber_upto), "Attention! Security level elevated to amber:",1) + if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) + if(GLOB.security_level == SEC_LEVEL_GREEN) + SSshuttle.emergency.modTimer(0.4) + else + SSshuttle.emergency.modTimer(0.66) + else + minor_announce(CONFIG_GET(string/alert_amber_downto), "Attention! Security level lowered to amber:") + if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) + SSshuttle.emergency.modTimer(1.6) + GLOB.security_level = SEC_LEVEL_AMBER + sound_to_playing_players('sound/effects/alert.ogg', volume = 50) // Citadel change - Makes alerts play a sound + for(var/obj/machinery/firealarm/FA in GLOB.machines) + if(is_station_level(FA.z)) + FA.update_icon() + if(SEC_LEVEL_RED) + if(GLOB.security_level < SEC_LEVEL_RED) + minor_announce(CONFIG_GET(string/alert_red_upto), "Attention! Code red!",1) + if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) + if(GLOB.security_level == SEC_LEVEL_GREEN) + SSshuttle.emergency.modTimer(0.25) + else if(GLOB.security_level == SEC_LEVEL_BLUE) + SSshuttle.emergency.modTimer(0.416) + else + SSshuttle.emergency.modTimer(0.625) + else + minor_announce(CONFIG_GET(string/alert_red_downto), "Attention! Code red!") + GLOB.security_level = SEC_LEVEL_RED + sound_to_playing_players('sound/misc/voyalert.ogg', volume = 50) // Citadel change - Makes alerts play a sound + + for(var/obj/machinery/firealarm/FA in GLOB.machines) + if(is_station_level(FA.z)) + FA.update_icon() + for(var/obj/machinery/computer/shuttle/pod/pod in GLOB.machines) + pod.admin_controlled = 0 + if(SEC_LEVEL_DELTA) + minor_announce(CONFIG_GET(string/alert_delta), "Attention! Delta security level reached!",1) + if(SSshuttle.emergency.mode == SHUTTLE_CALL || SSshuttle.emergency.mode == SHUTTLE_RECALL) + if(GLOB.security_level < SEC_LEVEL_BLUE) + SSshuttle.emergency.modTimer(0.25) + else if(GLOB.security_level == SEC_LEVEL_BLUE) + SSshuttle.emergency.modTimer(0.416) + else + SSshuttle.emergency.modTimer(0.625) + GLOB.security_level = SEC_LEVEL_DELTA + sound_to_playing_players('sound/misc/deltakalaxon.ogg') // Citadel change - Makes alerts play a sound + for(var/obj/machinery/firealarm/FA in GLOB.machines) + if(is_station_level(FA.z)) + FA.update_icon() + for(var/obj/machinery/computer/shuttle/pod/pod in GLOB.machines) + pod.admin_controlled = 0 + if(level >= SEC_LEVEL_RED) + for(var/obj/machinery/door/D in GLOB.machines) + if(D.red_alert_access) + D.visible_message("[D] whirrs as it automatically lifts access requirements!") + playsound(D, 'sound/machines/boltsup.ogg', 50, TRUE) + SSblackbox.record_feedback("tally", "security_level_changes", 1, get_security_level()) + SSnightshift.check_nightshift() + else + return + +/proc/get_security_level() + switch(GLOB.security_level) + if(SEC_LEVEL_GREEN) + return "green" + if(SEC_LEVEL_BLUE) + return "blue" + if(SEC_LEVEL_AMBER) + return "amber" + if(SEC_LEVEL_RED) + return "red" + if(SEC_LEVEL_DELTA) + return "delta" + +/proc/num2seclevel(num) + switch(num) + if(SEC_LEVEL_GREEN) + return "green" + if(SEC_LEVEL_BLUE) + return "blue" + if(SEC_LEVEL_AMBER) + return "amber" + if(SEC_LEVEL_RED) + return "red" + if(SEC_LEVEL_DELTA) + return "delta" + +/proc/seclevel2num(seclevel) + switch( lowertext(seclevel) ) + if("green") + return SEC_LEVEL_GREEN + if("blue") + return SEC_LEVEL_BLUE + if("amber") + return SEC_LEVEL_AMBER + if("red") + return SEC_LEVEL_RED + if("delta") + return SEC_LEVEL_DELTA diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/arrivals.dm index 12e82e4212..190db2c362 100644 --- a/code/modules/shuttle/arrivals.dm +++ b/code/modules/shuttle/arrivals.dm @@ -1,206 +1,206 @@ -/obj/docking_port/mobile/arrivals - name = "arrivals shuttle" - id = "arrivals" - - dwidth = 3 - width = 7 - height = 15 - dir = WEST - port_direction = SOUTH - - callTime = INFINITY - ignitionTime = 50 - - movement_force = list("KNOCKDOWN" = 3, "THROW" = 0) - - var/sound_played - var/damaged //too damaged to undock? - var/list/areas //areas in our shuttle - var/list/queued_announces //people coming in that we have to announce - var/obj/machinery/requests_console/console - var/force_depart = FALSE - var/perma_docked = FALSE //highlander with RESPAWN??? OH GOD!!! - var/obj/docking_port/stationary/target_dock // for badminry - -/obj/docking_port/mobile/arrivals/Initialize(mapload) - . = ..() - preferred_direction = dir - return INITIALIZE_HINT_LATELOAD //for latejoin list - -/obj/docking_port/mobile/arrivals/register() - ..() - if(SSshuttle.arrivals) - WARNING("More than one arrivals docking_port placed on map! Ignoring duplicates.") - SSshuttle.arrivals = src - -/obj/docking_port/mobile/arrivals/LateInitialize() - areas = list() - - var/list/new_latejoin = list() - for(var/area/shuttle/arrival/A in GLOB.sortedAreas) - for(var/obj/structure/chair/C in A) - new_latejoin += C - if(!console) - console = locate(/obj/machinery/requests_console) in A - areas += A - - if(SSjob.latejoin_trackers.len) - WARNING("Map contains predefined latejoin spawn points and an arrivals shuttle. Using the arrivals shuttle.") - - if(!new_latejoin.len) - WARNING("Arrivals shuttle contains no chairs for spawn points. Reverting to latejoin landmarks.") - if(!SSjob.latejoin_trackers.len) - WARNING("No latejoin landmarks exist. Players will spawn unbuckled on the shuttle.") - return - - SSjob.latejoin_trackers = new_latejoin - -/obj/docking_port/mobile/arrivals/check() - . = ..() - - if(perma_docked) - if(mode != SHUTTLE_CALL) - sound_played = FALSE - mode = SHUTTLE_IDLE - else - SendToStation() - return - - if(damaged) - if(!CheckTurfsPressure()) - damaged = FALSE - if(console) - console.say("Repairs complete, launching soon.") - return - -//If this proc is high on the profiler add a cooldown to the stuff after this line - - else if(CheckTurfsPressure()) - damaged = TRUE - if(console) - console.say("Alert, hull breach detected!") - var/obj/machinery/announcement_system/announcer = safepick(GLOB.announcement_systems) - if(!QDELETED(announcer)) - announcer.announce("ARRIVALS_BROKEN", channels = list()) - if(mode != SHUTTLE_CALL) - sound_played = FALSE - mode = SHUTTLE_IDLE - else - SendToStation() - return - - var/found_awake = PersonCheck() || NukeDiskCheck() - if(mode == SHUTTLE_CALL) - if(found_awake) - SendToStation() - else if(mode == SHUTTLE_IGNITING) - if(found_awake && !force_depart) - mode = SHUTTLE_IDLE - sound_played = FALSE - else if(!sound_played) - hyperspace_sound(HYPERSPACE_WARMUP, areas) - sound_played = TRUE - else if(!found_awake) - Launch(FALSE) - -/obj/docking_port/mobile/arrivals/proc/CheckTurfsPressure() - for(var/I in SSjob.latejoin_trackers) - var/turf/open/T = get_turf(I) - var/pressure = T.air.return_pressure() - if(pressure < HAZARD_LOW_PRESSURE || pressure > HAZARD_HIGH_PRESSURE) //simple safety check - return TRUE - return FALSE - -/obj/docking_port/mobile/arrivals/proc/PersonCheck() - for(var/V in GLOB.player_list) - var/mob/M = V - if((get_area(M) in areas) && M.stat != DEAD) - if(!iscameramob(M)) - return TRUE - var/mob/camera/C = M - if(C.move_on_shuttle) - return TRUE - return FALSE - -/obj/docking_port/mobile/arrivals/proc/NukeDiskCheck() - for (var/obj/item/disk/nuclear/N in GLOB.poi_list) - if (get_area(N) in areas) - return TRUE - return FALSE - -/obj/docking_port/mobile/arrivals/proc/SendToStation() - var/dockTime = CONFIG_GET(number/arrivals_shuttle_dock_window) - if(mode == SHUTTLE_CALL && timeLeft(1) > dockTime) - if(console) - console.say(damaged ? "Initiating emergency docking for repairs!" : "Now approaching: [station_name()].") - hyperspace_sound(HYPERSPACE_LAUNCH, areas) //for the new guy - setTimer(dockTime) - -/obj/docking_port/mobile/arrivals/initiate_docking(obj/docking_port/stationary/S1, force=FALSE) - var/docked = S1 == assigned_transit - sound_played = FALSE - if(docked) //about to launch - if(!force_depart) - var/cancel_reason - if(PersonCheck()) - cancel_reason = "lifeform dectected on board" - else if(NukeDiskCheck()) - cancel_reason = "critical station device detected on board" - if(cancel_reason) - mode = SHUTTLE_IDLE - if(console) - console.say("Launch cancelled, [cancel_reason].") - return - force_depart = FALSE - . = ..() - if(!. && !docked && !damaged) - if(console) - console.say("Welcome to your new life, employees!") - for(var/L in queued_announces) - var/datum/callback/C = L - C.Invoke() - LAZYCLEARLIST(queued_announces) - -/obj/docking_port/mobile/arrivals/check_effects() - ..() - if(mode == SHUTTLE_CALL && !sound_played && timeLeft(1) <= HYPERSPACE_END_TIME) - sound_played = TRUE - hyperspace_sound(HYPERSPACE_END, areas) - -/obj/docking_port/mobile/arrivals/canDock(obj/docking_port/stationary/S) - . = ..() - if(. == SHUTTLE_ALREADY_DOCKED) - . = SHUTTLE_CAN_DOCK - -/obj/docking_port/mobile/arrivals/proc/Launch(pickingup) - if(pickingup) - force_depart = TRUE - if(mode == SHUTTLE_IDLE) - if(console) - console.say(pickingup ? "Departing immediately for new employee pickup." : "Shuttle departing.") - var/obj/docking_port/stationary/target = target_dock - if(QDELETED(target)) - target = SSshuttle.getDock("arrivals_stationary") - request(target) //we will intentionally never return SHUTTLE_ALREADY_DOCKED - -/obj/docking_port/mobile/arrivals/proc/RequireUndocked(mob/user) - if(mode == SHUTTLE_CALL || damaged) - return - - Launch(TRUE) - - to_chat(user, "Calling your shuttle. One moment...") - while(mode != SHUTTLE_CALL && !damaged) - stoplag() - -/obj/docking_port/mobile/arrivals/proc/QueueAnnounce(mob, rank) - if(mode != SHUTTLE_CALL) - AnnounceArrival(mob, rank) - else - LAZYADD(queued_announces, CALLBACK(GLOBAL_PROC, .proc/AnnounceArrival, mob, rank)) - -/obj/docking_port/mobile/arrivals/vv_edit_var(var_name, var_value) - switch(var_name) - if("perma_docked") - SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("arrivals shuttle", "[var_value ? "stopped" : "started"]")) - return ..() +/obj/docking_port/mobile/arrivals + name = "arrivals shuttle" + id = "arrivals" + + dwidth = 3 + width = 7 + height = 15 + dir = WEST + port_direction = SOUTH + + callTime = INFINITY + ignitionTime = 50 + + movement_force = list("KNOCKDOWN" = 3, "THROW" = 0) + + var/sound_played + var/damaged //too damaged to undock? + var/list/areas //areas in our shuttle + var/list/queued_announces //people coming in that we have to announce + var/obj/machinery/requests_console/console + var/force_depart = FALSE + var/perma_docked = FALSE //highlander with RESPAWN??? OH GOD!!! + var/obj/docking_port/stationary/target_dock // for badminry + +/obj/docking_port/mobile/arrivals/Initialize(mapload) + . = ..() + preferred_direction = dir + return INITIALIZE_HINT_LATELOAD //for latejoin list + +/obj/docking_port/mobile/arrivals/register() + ..() + if(SSshuttle.arrivals) + WARNING("More than one arrivals docking_port placed on map! Ignoring duplicates.") + SSshuttle.arrivals = src + +/obj/docking_port/mobile/arrivals/LateInitialize() + areas = list() + + var/list/new_latejoin = list() + for(var/area/shuttle/arrival/A in GLOB.sortedAreas) + for(var/obj/structure/chair/C in A) + new_latejoin += C + if(!console) + console = locate(/obj/machinery/requests_console) in A + areas += A + + if(SSjob.latejoin_trackers.len) + WARNING("Map contains predefined latejoin spawn points and an arrivals shuttle. Using the arrivals shuttle.") + + if(!new_latejoin.len) + WARNING("Arrivals shuttle contains no chairs for spawn points. Reverting to latejoin landmarks.") + if(!SSjob.latejoin_trackers.len) + WARNING("No latejoin landmarks exist. Players will spawn unbuckled on the shuttle.") + return + + SSjob.latejoin_trackers = new_latejoin + +/obj/docking_port/mobile/arrivals/check() + . = ..() + + if(perma_docked) + if(mode != SHUTTLE_CALL) + sound_played = FALSE + mode = SHUTTLE_IDLE + else + SendToStation() + return + + if(damaged) + if(!CheckTurfsPressure()) + damaged = FALSE + if(console) + console.say("Repairs complete, launching soon.") + return + +//If this proc is high on the profiler add a cooldown to the stuff after this line + + else if(CheckTurfsPressure()) + damaged = TRUE + if(console) + console.say("Alert, hull breach detected!") + var/obj/machinery/announcement_system/announcer = safepick(GLOB.announcement_systems) + if(!QDELETED(announcer)) + announcer.announce("ARRIVALS_BROKEN", channels = list()) + if(mode != SHUTTLE_CALL) + sound_played = FALSE + mode = SHUTTLE_IDLE + else + SendToStation() + return + + var/found_awake = PersonCheck() || NukeDiskCheck() + if(mode == SHUTTLE_CALL) + if(found_awake) + SendToStation() + else if(mode == SHUTTLE_IGNITING) + if(found_awake && !force_depart) + mode = SHUTTLE_IDLE + sound_played = FALSE + else if(!sound_played) + hyperspace_sound(HYPERSPACE_WARMUP, areas) + sound_played = TRUE + else if(!found_awake) + Launch(FALSE) + +/obj/docking_port/mobile/arrivals/proc/CheckTurfsPressure() + for(var/I in SSjob.latejoin_trackers) + var/turf/open/T = get_turf(I) + var/pressure = T.air.return_pressure() + if(pressure < HAZARD_LOW_PRESSURE || pressure > HAZARD_HIGH_PRESSURE) //simple safety check + return TRUE + return FALSE + +/obj/docking_port/mobile/arrivals/proc/PersonCheck() + for(var/V in GLOB.player_list) + var/mob/M = V + if((get_area(M) in areas) && M.stat != DEAD) + if(!iscameramob(M)) + return TRUE + var/mob/camera/C = M + if(C.move_on_shuttle) + return TRUE + return FALSE + +/obj/docking_port/mobile/arrivals/proc/NukeDiskCheck() + for (var/obj/item/disk/nuclear/N in GLOB.poi_list) + if (get_area(N) in areas) + return TRUE + return FALSE + +/obj/docking_port/mobile/arrivals/proc/SendToStation() + var/dockTime = CONFIG_GET(number/arrivals_shuttle_dock_window) + if(mode == SHUTTLE_CALL && timeLeft(1) > dockTime) + if(console) + console.say(damaged ? "Initiating emergency docking for repairs!" : "Now approaching: [station_name()].") + hyperspace_sound(HYPERSPACE_LAUNCH, areas) //for the new guy + setTimer(dockTime) + +/obj/docking_port/mobile/arrivals/initiate_docking(obj/docking_port/stationary/S1, force=FALSE) + var/docked = S1 == assigned_transit + sound_played = FALSE + if(docked) //about to launch + if(!force_depart) + var/cancel_reason + if(PersonCheck()) + cancel_reason = "lifeform dectected on board" + else if(NukeDiskCheck()) + cancel_reason = "critical station device detected on board" + if(cancel_reason) + mode = SHUTTLE_IDLE + if(console) + console.say("Launch cancelled, [cancel_reason].") + return + force_depart = FALSE + . = ..() + if(!. && !docked && !damaged) + if(console) + console.say("Welcome to your new life, employees!") + for(var/L in queued_announces) + var/datum/callback/C = L + C.Invoke() + LAZYCLEARLIST(queued_announces) + +/obj/docking_port/mobile/arrivals/check_effects() + ..() + if(mode == SHUTTLE_CALL && !sound_played && timeLeft(1) <= HYPERSPACE_END_TIME) + sound_played = TRUE + hyperspace_sound(HYPERSPACE_END, areas) + +/obj/docking_port/mobile/arrivals/canDock(obj/docking_port/stationary/S) + . = ..() + if(. == SHUTTLE_ALREADY_DOCKED) + . = SHUTTLE_CAN_DOCK + +/obj/docking_port/mobile/arrivals/proc/Launch(pickingup) + if(pickingup) + force_depart = TRUE + if(mode == SHUTTLE_IDLE) + if(console) + console.say(pickingup ? "Departing immediately for new employee pickup." : "Shuttle departing.") + var/obj/docking_port/stationary/target = target_dock + if(QDELETED(target)) + target = SSshuttle.getDock("arrivals_stationary") + request(target) //we will intentionally never return SHUTTLE_ALREADY_DOCKED + +/obj/docking_port/mobile/arrivals/proc/RequireUndocked(mob/user) + if(mode == SHUTTLE_CALL || damaged) + return + + Launch(TRUE) + + to_chat(user, "Calling your shuttle. One moment...") + while(mode != SHUTTLE_CALL && !damaged) + stoplag() + +/obj/docking_port/mobile/arrivals/proc/QueueAnnounce(mob, rank) + if(mode != SHUTTLE_CALL) + AnnounceArrival(mob, rank) + else + LAZYADD(queued_announces, CALLBACK(GLOBAL_PROC, .proc/AnnounceArrival, mob, rank)) + +/obj/docking_port/mobile/arrivals/vv_edit_var(var_name, var_value) + switch(var_name) + if("perma_docked") + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("arrivals shuttle", "[var_value ? "stopped" : "started"]")) + return ..() diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/computer.dm index 946f0fb9a4..d2b13fc71a 100644 --- a/code/modules/shuttle/computer.dm +++ b/code/modules/shuttle/computer.dm @@ -1,76 +1,76 @@ -/obj/machinery/computer/shuttle - name = "shuttle console" - desc = "A shuttle control computer." - icon_screen = "shuttle" - icon_keyboard = "tech_key" - light_color = LIGHT_COLOR_CYAN - req_access = list( ) - var/shuttleId - var/possible_destinations = "" - var/admin_controlled - var/no_destination_swap = 0 - -/obj/machinery/computer/shuttle/ui_interact(mob/user) - . = ..() - var/list/options = params2list(possible_destinations) - var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) - var/dat = "Status: [M ? M.getStatusText() : "*Missing*"]

                " - if(M) - var/destination_found - for(var/obj/docking_port/stationary/S in SSshuttle.stationary) - if(!options.Find(S.id)) - continue - if(!M.check_dock(S, silent=TRUE)) - continue - destination_found = 1 - dat += "Send to [S.name]
                " - if(!destination_found) - dat += "Shuttle Locked
                " - if(admin_controlled) - dat += "Authorized personnel only
                " - dat += "Request Authorization
                " - dat += "Close" - - var/datum/browser/popup = new(user, "computer", M ? M.name : "shuttle", 300, 200) - popup.set_content("
                [dat]
                ") - popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/shuttle/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - src.add_fingerprint(usr) - if(!allowed(usr)) - to_chat(usr, "Access denied.") - return - - if(href_list["move"]) - var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) - if(M.launch_status == ENDGAME_LAUNCHED) - to_chat(usr, "You've already escaped. Never going back to that place again!") - return - if(no_destination_swap) - if(M.mode != SHUTTLE_IDLE) - to_chat(usr, "Shuttle already in transit.") - return - switch(SSshuttle.moveShuttle(shuttleId, href_list["move"], 1)) - if(0) - say("Shuttle departing. Please stand away from the doors.") - if(1) - to_chat(usr, "Invalid shuttle requested.") - else - to_chat(usr, "Unable to comply.") - -/obj/machinery/computer/shuttle/emag_act(mob/user) - . = ..() - if(obj_flags & EMAGGED) - return - req_access = list() - obj_flags |= EMAGGED - to_chat(user, "You fried the consoles ID checking system.") - return TRUE - -/obj/machinery/computer/shuttle/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - if(port && (shuttleId == initial(shuttleId) || override)) +/obj/machinery/computer/shuttle + name = "shuttle console" + desc = "A shuttle control computer." + icon_screen = "shuttle" + icon_keyboard = "tech_key" + light_color = LIGHT_COLOR_CYAN + req_access = list( ) + var/shuttleId + var/possible_destinations = "" + var/admin_controlled + var/no_destination_swap = 0 + +/obj/machinery/computer/shuttle/ui_interact(mob/user) + . = ..() + var/list/options = params2list(possible_destinations) + var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) + var/dat = "Status: [M ? M.getStatusText() : "*Missing*"]

                " + if(M) + var/destination_found + for(var/obj/docking_port/stationary/S in SSshuttle.stationary) + if(!options.Find(S.id)) + continue + if(!M.check_dock(S, silent=TRUE)) + continue + destination_found = 1 + dat += "Send to [S.name]
                " + if(!destination_found) + dat += "Shuttle Locked
                " + if(admin_controlled) + dat += "Authorized personnel only
                " + dat += "Request Authorization
                " + dat += "Close" + + var/datum/browser/popup = new(user, "computer", M ? M.name : "shuttle", 300, 200) + popup.set_content("
                [dat]
                ") + popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/shuttle/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + src.add_fingerprint(usr) + if(!allowed(usr)) + to_chat(usr, "Access denied.") + return + + if(href_list["move"]) + var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) + if(M.launch_status == ENDGAME_LAUNCHED) + to_chat(usr, "You've already escaped. Never going back to that place again!") + return + if(no_destination_swap) + if(M.mode != SHUTTLE_IDLE) + to_chat(usr, "Shuttle already in transit.") + return + switch(SSshuttle.moveShuttle(shuttleId, href_list["move"], 1)) + if(0) + say("Shuttle departing. Please stand away from the doors.") + if(1) + to_chat(usr, "Invalid shuttle requested.") + else + to_chat(usr, "Unable to comply.") + +/obj/machinery/computer/shuttle/emag_act(mob/user) + . = ..() + if(obj_flags & EMAGGED) + return + req_access = list() + obj_flags |= EMAGGED + to_chat(user, "You fried the consoles ID checking system.") + return TRUE + +/obj/machinery/computer/shuttle/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + if(port && (shuttleId == initial(shuttleId) || override)) shuttleId = port.id \ No newline at end of file diff --git a/code/modules/shuttle/ferry.dm b/code/modules/shuttle/ferry.dm index 39ab087f9c..39bde5e541 100644 --- a/code/modules/shuttle/ferry.dm +++ b/code/modules/shuttle/ferry.dm @@ -1,40 +1,40 @@ -/obj/machinery/computer/shuttle/ferry - name = "transport ferry console" - desc = "A console that controls the transport ferry." - circuit = /obj/item/circuitboard/computer/ferry - shuttleId = "ferry" - possible_destinations = "ferry_home;ferry_away" - req_access = list(ACCESS_CENT_GENERAL) - - var/allow_silicons = FALSE - var/allow_emag = FALSE - -/obj/machinery/computer/shuttle/ferry/emag_act(mob/user) - if(!allow_emag) - to_chat(user, "[src]'s security firewall is far too powerful for you to bypass.") - return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) - return ..() - -/obj/machinery/computer/shuttle/ferry/attack_ai() - return allow_silicons ? ..() : FALSE - -/obj/machinery/computer/shuttle/ferry/attack_robot() - return allow_silicons ? ..() : FALSE - -/obj/machinery/computer/shuttle/ferry/request - name = "ferry console" - circuit = /obj/item/circuitboard/computer/ferry/request - var/last_request //prevents spamming admins - var/cooldown = 600 - possible_destinations = "ferry_home;ferry_away" - req_access = list(ACCESS_CENT_GENERAL) - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/machinery/computer/shuttle/ferry/request/Topic(href, href_list) - ..() - if(href_list["request"]) - if(last_request && (last_request + cooldown > world.time)) - return - last_request = world.time - to_chat(usr, "Your request has been received by CentCom.") - to_chat(GLOB.admins, "FERRY: [ADMIN_LOOKUPFLW(usr)] (Move Ferry) is requesting to move the transport ferry to CentCom.") +/obj/machinery/computer/shuttle/ferry + name = "transport ferry console" + desc = "A console that controls the transport ferry." + circuit = /obj/item/circuitboard/computer/ferry + shuttleId = "ferry" + possible_destinations = "ferry_home;ferry_away" + req_access = list(ACCESS_CENT_GENERAL) + + var/allow_silicons = FALSE + var/allow_emag = FALSE + +/obj/machinery/computer/shuttle/ferry/emag_act(mob/user) + if(!allow_emag) + to_chat(user, "[src]'s security firewall is far too powerful for you to bypass.") + return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) + return ..() + +/obj/machinery/computer/shuttle/ferry/attack_ai() + return allow_silicons ? ..() : FALSE + +/obj/machinery/computer/shuttle/ferry/attack_robot() + return allow_silicons ? ..() : FALSE + +/obj/machinery/computer/shuttle/ferry/request + name = "ferry console" + circuit = /obj/item/circuitboard/computer/ferry/request + var/last_request //prevents spamming admins + var/cooldown = 600 + possible_destinations = "ferry_home;ferry_away" + req_access = list(ACCESS_CENT_GENERAL) + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/machinery/computer/shuttle/ferry/request/Topic(href, href_list) + ..() + if(href_list["request"]) + if(last_request && (last_request + cooldown > world.time)) + return + last_request = world.time + to_chat(usr, "Your request has been received by CentCom.") + to_chat(GLOB.admins, "FERRY: [ADMIN_LOOKUPFLW(usr)] (Move Ferry) is requesting to move the transport ferry to CentCom.") diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/syndicate.dm index bffce9e7ab..4ebefc7e3c 100644 --- a/code/modules/shuttle/syndicate.dm +++ b/code/modules/shuttle/syndicate.dm @@ -1,67 +1,67 @@ -#define SYNDICATE_CHALLENGE_TIMER 12000 //20 minutes - -/obj/machinery/computer/shuttle/syndicate - name = "syndicate shuttle terminal" - desc = "The terminal used to control the syndicate transport shuttle." - circuit = /obj/item/circuitboard/computer/syndicate_shuttle - icon_screen = "syndishuttle" - icon_keyboard = "syndie_key" - light_color = LIGHT_COLOR_RED - req_access = list(ACCESS_SYNDICATE) - shuttleId = "syndicate" - possible_destinations = "syndicate_away;syndicate_z5;syndicate_ne;syndicate_nw;syndicate_n;syndicate_se;syndicate_sw;syndicate_s;syndicate_custom" - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - -/obj/machinery/computer/shuttle/syndicate/recall - name = "syndicate shuttle recall terminal" - desc = "Use this if your friends left you behind." - possible_destinations = "syndicate_away" - - -/obj/machinery/computer/shuttle/syndicate/Topic(href, href_list) - if(href_list["move"]) - var/obj/item/circuitboard/computer/syndicate_shuttle/board = circuit - if(board.challenge && world.time < SYNDICATE_CHALLENGE_TIMER) - to_chat(usr, "You've issued a combat challenge to the station! You've got to give them at least [DisplayTimeText(SYNDICATE_CHALLENGE_TIMER - world.time)] more to allow them to prepare.") - return 0 - board.moved = TRUE - ..() - -/obj/machinery/computer/shuttle/syndicate/allowed(mob/M) - if(issilicon(M) && !(ROLE_SYNDICATE in M.faction)) - return FALSE - return ..() - -/obj/machinery/computer/shuttle/syndicate/drop_pod - name = "syndicate assault pod control" - desc = "Controls the drop pod's launch system." - icon = 'icons/obj/terminals.dmi' - icon_state = "dorm_available" - light_color = LIGHT_COLOR_BLUE - req_access = list(ACCESS_SYNDICATE) - shuttleId = "steel_rain" - possible_destinations = null - clockwork = TRUE //it'd look weird - -/obj/machinery/computer/shuttle/syndicate/drop_pod/Topic(href, href_list) - if(href_list["move"]) - if(!is_centcom_level(z)) - to_chat(usr, "Pods are one way!") - return 0 - ..() - -/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate - name = "syndicate shuttle navigation computer" - desc = "Used to designate a precise transit location for the syndicate shuttle." - icon_screen = "syndishuttle" - icon_keyboard = "syndie_key" - shuttleId = "syndicate" - lock_override = CAMERA_LOCK_STATION - shuttlePortId = "syndicate_custom" - jumpto_ports = list("syndicate_ne" = 1, "syndicate_nw" = 1, "syndicate_n" = 1, "syndicate_se" = 1, "syndicate_sw" = 1, "syndicate_s" = 1) - view_range = 13 - x_offset = -7 - y_offset = -1 - see_hidden = TRUE - +#define SYNDICATE_CHALLENGE_TIMER 12000 //20 minutes + +/obj/machinery/computer/shuttle/syndicate + name = "syndicate shuttle terminal" + desc = "The terminal used to control the syndicate transport shuttle." + circuit = /obj/item/circuitboard/computer/syndicate_shuttle + icon_screen = "syndishuttle" + icon_keyboard = "syndie_key" + light_color = LIGHT_COLOR_RED + req_access = list(ACCESS_SYNDICATE) + shuttleId = "syndicate" + possible_destinations = "syndicate_away;syndicate_z5;syndicate_ne;syndicate_nw;syndicate_n;syndicate_se;syndicate_sw;syndicate_s;syndicate_custom" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF + +/obj/machinery/computer/shuttle/syndicate/recall + name = "syndicate shuttle recall terminal" + desc = "Use this if your friends left you behind." + possible_destinations = "syndicate_away" + + +/obj/machinery/computer/shuttle/syndicate/Topic(href, href_list) + if(href_list["move"]) + var/obj/item/circuitboard/computer/syndicate_shuttle/board = circuit + if(board.challenge && world.time < SYNDICATE_CHALLENGE_TIMER) + to_chat(usr, "You've issued a combat challenge to the station! You've got to give them at least [DisplayTimeText(SYNDICATE_CHALLENGE_TIMER - world.time)] more to allow them to prepare.") + return 0 + board.moved = TRUE + ..() + +/obj/machinery/computer/shuttle/syndicate/allowed(mob/M) + if(issilicon(M) && !(ROLE_SYNDICATE in M.faction)) + return FALSE + return ..() + +/obj/machinery/computer/shuttle/syndicate/drop_pod + name = "syndicate assault pod control" + desc = "Controls the drop pod's launch system." + icon = 'icons/obj/terminals.dmi' + icon_state = "dorm_available" + light_color = LIGHT_COLOR_BLUE + req_access = list(ACCESS_SYNDICATE) + shuttleId = "steel_rain" + possible_destinations = null + clockwork = TRUE //it'd look weird + +/obj/machinery/computer/shuttle/syndicate/drop_pod/Topic(href, href_list) + if(href_list["move"]) + if(!is_centcom_level(z)) + to_chat(usr, "Pods are one way!") + return 0 + ..() + +/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate + name = "syndicate shuttle navigation computer" + desc = "Used to designate a precise transit location for the syndicate shuttle." + icon_screen = "syndishuttle" + icon_keyboard = "syndie_key" + shuttleId = "syndicate" + lock_override = CAMERA_LOCK_STATION + shuttlePortId = "syndicate_custom" + jumpto_ports = list("syndicate_ne" = 1, "syndicate_nw" = 1, "syndicate_n" = 1, "syndicate_se" = 1, "syndicate_sw" = 1, "syndicate_s" = 1) + view_range = 13 + x_offset = -7 + y_offset = -1 + see_hidden = TRUE + #undef SYNDICATE_CHALLENGE_TIMER \ No newline at end of file diff --git a/code/modules/shuttle/white_ship.dm b/code/modules/shuttle/white_ship.dm index 537872ed0c..f25fe30f36 100644 --- a/code/modules/shuttle/white_ship.dm +++ b/code/modules/shuttle/white_ship.dm @@ -1,56 +1,56 @@ -/obj/machinery/computer/shuttle/white_ship - name = "White Ship Console" - desc = "Used to control the White Ship." - circuit = /obj/item/circuitboard/computer/white_ship - shuttleId = "whiteship" - possible_destinations = "whiteship_away;whiteship_home;whiteship_z4;whiteship_lavaland;whiteship_custom" - -/obj/machinery/computer/shuttle/white_ship/pod - name = "Salvage Pod Console" - desc = "Used to control the Salvage Pod." - circuit = /obj/item/circuitboard/computer/white_ship/pod - shuttleId = "whiteship_pod" - possible_destinations = "whiteship_pod_home;whiteship_pod_custom" - -/obj/machinery/computer/shuttle/white_ship/pod/recall - name = "Salvage Pod Recall Console" - desc = "Used to recall the Salvage Pod." - circuit = /obj/item/circuitboard/computer/white_ship/pod/recall - possible_destinations = "whiteship_pod_home" - -/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship - name = "White Ship Navigation Computer" - desc = "Used to designate a precise transit location for the White Ship." - shuttleId = "whiteship" - lock_override = NONE - shuttlePortId = "whiteship_custom" - jumpto_ports = list("whiteship_away" = 1, "whiteship_home" = 1, "whiteship_z4" = 1) - view_range = 18 - x_offset = -6 - y_offset = -10 - designate_time = 100 - -/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/pod - name = "Salvage Pod Navigation Computer" - desc = "Used to designate a precise transit location for the Salvage Pod." - shuttleId = "whiteship_pod" - shuttlePortId = "whiteship_pod_custom" - jumpto_ports = list("whiteship_pod_home" = 1) - view_range = 7 - x_offset = -2 - y_offset = 0 - designate_time = 0 - -/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/Initialize() - . = ..() - GLOB.jam_on_wardec += src - -/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/Destroy() - GLOB.jam_on_wardec -= src - return ..() - -/obj/effect/spawner/lootdrop/whiteship_cere_ripley - name = "25% mech 75% wreckage ripley spawner" - loot = list(/obj/mecha/working/ripley/mining = 1, - /obj/structure/mecha_wreckage/ripley = 5) - lootdoubles = FALSE +/obj/machinery/computer/shuttle/white_ship + name = "White Ship Console" + desc = "Used to control the White Ship." + circuit = /obj/item/circuitboard/computer/white_ship + shuttleId = "whiteship" + possible_destinations = "whiteship_away;whiteship_home;whiteship_z4;whiteship_lavaland;whiteship_custom" + +/obj/machinery/computer/shuttle/white_ship/pod + name = "Salvage Pod Console" + desc = "Used to control the Salvage Pod." + circuit = /obj/item/circuitboard/computer/white_ship/pod + shuttleId = "whiteship_pod" + possible_destinations = "whiteship_pod_home;whiteship_pod_custom" + +/obj/machinery/computer/shuttle/white_ship/pod/recall + name = "Salvage Pod Recall Console" + desc = "Used to recall the Salvage Pod." + circuit = /obj/item/circuitboard/computer/white_ship/pod/recall + possible_destinations = "whiteship_pod_home" + +/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship + name = "White Ship Navigation Computer" + desc = "Used to designate a precise transit location for the White Ship." + shuttleId = "whiteship" + lock_override = NONE + shuttlePortId = "whiteship_custom" + jumpto_ports = list("whiteship_away" = 1, "whiteship_home" = 1, "whiteship_z4" = 1) + view_range = 18 + x_offset = -6 + y_offset = -10 + designate_time = 100 + +/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/pod + name = "Salvage Pod Navigation Computer" + desc = "Used to designate a precise transit location for the Salvage Pod." + shuttleId = "whiteship_pod" + shuttlePortId = "whiteship_pod_custom" + jumpto_ports = list("whiteship_pod_home" = 1) + view_range = 7 + x_offset = -2 + y_offset = 0 + designate_time = 0 + +/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/Initialize() + . = ..() + GLOB.jam_on_wardec += src + +/obj/machinery/computer/camera_advanced/shuttle_docker/whiteship/Destroy() + GLOB.jam_on_wardec -= src + return ..() + +/obj/effect/spawner/lootdrop/whiteship_cere_ripley + name = "25% mech 75% wreckage ripley spawner" + loot = list(/obj/mecha/working/ripley/mining = 1, + /obj/structure/mecha_wreckage/ripley = 5) + lootdoubles = FALSE diff --git a/code/modules/spells/spell_types/area_teleport.dm b/code/modules/spells/spell_types/area_teleport.dm index 762c376748..cd3d471efc 100644 --- a/code/modules/spells/spell_types/area_teleport.dm +++ b/code/modules/spells/spell_types/area_teleport.dm @@ -1,92 +1,92 @@ -/obj/effect/proc_holder/spell/targeted/area_teleport - name = "Area teleport" - desc = "This spell teleports you to a type of area of your selection." - nonabstract_req = 1 - - var/randomise_selection = 0 //if it lets the usr choose the teleport loc or picks it from the list - var/invocation_area = 1 //if the invocation appends the selected area - var/sound1 = 'sound/weapons/zapbang.ogg' - var/sound2 = 'sound/weapons/zapbang.ogg' - -/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1,mob/living/user = usr) - var/thearea = before_cast(targets) - if(!thearea || !cast_check(1)) - revert_cast() - return - invocation(thearea,user) - if(charge_type == "recharge" && recharge) - INVOKE_ASYNC(src, .proc/start_recharge) - cast(targets,thearea,user) - after_cast(targets) - -/obj/effect/proc_holder/spell/targeted/area_teleport/before_cast(list/targets) - var/area/U = get_area(usr) - if(U.noteleport && !istype(U, /area/wizard_station)) // Wizard den special check for those complaining about being unable to tele on station. - to_chat(usr, "Unseen forces prevent you from casting this spell in this area") - return - var/A - if(!randomise_selection) - A = input("Area to teleport to", "Teleport", A) as null|anything in GLOB.teleportlocs - else - A = pick(GLOB.teleportlocs) - if(!A) - return - var/area/thearea = GLOB.teleportlocs[A] - - return thearea - -/obj/effect/proc_holder/spell/targeted/area_teleport/cast(list/targets,area/thearea,mob/user = usr) - playsound(get_turf(user), sound1, 50,1) - for(var/mob/living/target in targets) - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - if(!T.density) - var/clear = 1 - for(var/obj/O in T) - if(O.density) - clear = 0 - break - if(clear) - L+=T - - if(!L.len) - to_chat(usr, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") - return - - if(target && target.buckled) - target.buckled.unbuckle_mob(target, force=1) - - var/forcecheck = istype(get_area(target), /area/wizard_station) - var/list/tempL = L - var/attempt = null - var/success = 0 - while(tempL.len) - attempt = pick(tempL) - do_teleport(target, attempt, channel = TELEPORT_CHANNEL_MAGIC, forced = forcecheck) - if(get_turf(target) == attempt) - success = 1 - break - else - tempL.Remove(attempt) - - if(!success) - do_teleport(target, L, forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = forcecheck) - playsound(get_turf(user), sound2, 50,1) - - return - -/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null,mob/user = usr) - if(!invocation_area || !chosenarea) - ..() - else - switch(invocation_type) - if("shout") - user.say("[invocation] [uppertext(chosenarea.name)]", forced = "spell") - if(user.gender==MALE) - playsound(user.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1) - else - playsound(user.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1) - if("whisper") - user.whisper("[invocation] [uppertext(chosenarea.name)]") - - return +/obj/effect/proc_holder/spell/targeted/area_teleport + name = "Area teleport" + desc = "This spell teleports you to a type of area of your selection." + nonabstract_req = 1 + + var/randomise_selection = 0 //if it lets the usr choose the teleport loc or picks it from the list + var/invocation_area = 1 //if the invocation appends the selected area + var/sound1 = 'sound/weapons/zapbang.ogg' + var/sound2 = 'sound/weapons/zapbang.ogg' + +/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1,mob/living/user = usr) + var/thearea = before_cast(targets) + if(!thearea || !cast_check(1)) + revert_cast() + return + invocation(thearea,user) + if(charge_type == "recharge" && recharge) + INVOKE_ASYNC(src, .proc/start_recharge) + cast(targets,thearea,user) + after_cast(targets) + +/obj/effect/proc_holder/spell/targeted/area_teleport/before_cast(list/targets) + var/area/U = get_area(usr) + if(U.noteleport && !istype(U, /area/wizard_station)) // Wizard den special check for those complaining about being unable to tele on station. + to_chat(usr, "Unseen forces prevent you from casting this spell in this area") + return + var/A + if(!randomise_selection) + A = input("Area to teleport to", "Teleport", A) as null|anything in GLOB.teleportlocs + else + A = pick(GLOB.teleportlocs) + if(!A) + return + var/area/thearea = GLOB.teleportlocs[A] + + return thearea + +/obj/effect/proc_holder/spell/targeted/area_teleport/cast(list/targets,area/thearea,mob/user = usr) + playsound(get_turf(user), sound1, 50,1) + for(var/mob/living/target in targets) + var/list/L = list() + for(var/turf/T in get_area_turfs(thearea.type)) + if(!T.density) + var/clear = 1 + for(var/obj/O in T) + if(O.density) + clear = 0 + break + if(clear) + L+=T + + if(!L.len) + to_chat(usr, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") + return + + if(target && target.buckled) + target.buckled.unbuckle_mob(target, force=1) + + var/forcecheck = istype(get_area(target), /area/wizard_station) + var/list/tempL = L + var/attempt = null + var/success = 0 + while(tempL.len) + attempt = pick(tempL) + do_teleport(target, attempt, channel = TELEPORT_CHANNEL_MAGIC, forced = forcecheck) + if(get_turf(target) == attempt) + success = 1 + break + else + tempL.Remove(attempt) + + if(!success) + do_teleport(target, L, forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = forcecheck) + playsound(get_turf(user), sound2, 50,1) + + return + +/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null,mob/user = usr) + if(!invocation_area || !chosenarea) + ..() + else + switch(invocation_type) + if("shout") + user.say("[invocation] [uppertext(chosenarea.name)]", forced = "spell") + if(user.gender==MALE) + playsound(user.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1) + else + playsound(user.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1) + if("whisper") + user.whisper("[invocation] [uppertext(chosenarea.name)]") + + return diff --git a/code/modules/spells/spell_types/conjure.dm b/code/modules/spells/spell_types/conjure.dm index 38a2d5bddd..a04a86b928 100644 --- a/code/modules/spells/spell_types/conjure.dm +++ b/code/modules/spells/spell_types/conjure.dm @@ -1,100 +1,100 @@ -/obj/effect/proc_holder/spell/aoe_turf/conjure - name = "Conjure" - desc = "This spell conjures objs of the specified types in range." - - var/list/summon_type = list() //determines what exactly will be summoned - //should be text, like list("/mob/living/simple_animal/bot/ed209") - - var/summon_lifespan = 0 // 0=permanent, any other time in deciseconds - var/summon_amt = 1 //amount of objects summoned - var/summon_ignore_density = FALSE //if set to 1, adds dense tiles to possible spawn places - var/summon_ignore_prev_spawn_points = 0 //if set to 1, each new object is summoned on a new spawn point - - var/list/newVars = list() //vars of the summoned objects will be replaced with those where they meet - //should have format of list("emagged" = 1,"name" = "Wizard's Justicebot"), for example - - var/cast_sound = 'sound/items/welder.ogg' - -/obj/effect/proc_holder/spell/aoe_turf/conjure/cast(list/targets,mob/user = usr) - playsound(get_turf(user), cast_sound, 50,1) - for(var/turf/T in targets) - if(T.density && !summon_ignore_density) - targets -= T - - for(var/i=0,i world.time) || reappearing || !direction) - return - var/turf/newLoc = get_step(src,direction) - setDir(direction) - - movedelay = world.time + movespeed - - if(newLoc.flags_1 & NOJAUNT_1) - to_chat(user, "Some strange aura is blocking the way.") - return - if (locate(/obj/effect/blessing, newLoc)) - to_chat(user, "Holy energies block your path!") - return - - forceMove(newLoc) - -/obj/effect/dummy/phased_mob/spell_jaunt/ex_act(blah) - return -/obj/effect/dummy/phased_mob/spell_jaunt/bullet_act(blah) +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt + name = "Ethereal Jaunt" + desc = "This spell creates your ethereal form, temporarily making you invisible and able to pass through walls." + + school = "transmutation" + charge_max = 300 + clothes_req = 1 + invocation = "none" + invocation_type = "none" + range = -1 + cooldown_min = 100 //50 deciseconds reduction per rank + include_user = 1 + nonabstract_req = 1 + var/jaunt_duration = 50 //in deciseconds + var/jaunt_in_time = 5 + var/jaunt_in_type = /obj/effect/temp_visual/wizard + var/jaunt_out_type = /obj/effect/temp_visual/wizard/out + action_icon_state = "jaunt" + +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/cast(list/targets,mob/user = usr) //magnets, so mostly hardcoded + playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, 1, -1) + for(var/mob/living/target in targets) + INVOKE_ASYNC(src, .proc/do_jaunt, target) + +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/do_jaunt(mob/living/target) + target.notransform = 1 + var/turf/mobloc = get_turf(target) + var/obj/effect/dummy/phased_mob/spell_jaunt/holder = new /obj/effect/dummy/phased_mob/spell_jaunt(mobloc) + new jaunt_out_type(mobloc, target.dir) + target.ExtinguishMob() + target.forceMove(holder) + target.reset_perspective(holder) + target.notransform=0 //mob is safely inside holder now, no need for protection. + jaunt_steam(mobloc) + + sleep(jaunt_duration) + + if(target.loc != holder) //mob warped out of the warp + qdel(holder) + return + mobloc = get_turf(target.loc) + jaunt_steam(mobloc) + target.canmove = 0 + holder.reappearing = 1 + playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, 1, -1) + sleep(25 - jaunt_in_time) + new jaunt_in_type(mobloc, holder.dir) + target.setDir(holder.dir) + sleep(jaunt_in_time) + qdel(holder) + if(!QDELETED(target)) + if(mobloc.density) + for(var/direction in GLOB.alldirs) + var/turf/T = get_step(mobloc, direction) + if(T) + if(target.Move(T)) + break + target.canmove = 1 + +/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/jaunt_steam(mobloc) + var/datum/effect_system/steam_spread/steam = new /datum/effect_system/steam_spread() + steam.set_up(10, 0, mobloc) + steam.start() + +/obj/effect/dummy/phased_mob/spell_jaunt + name = "water" + icon = 'icons/effects/effects.dmi' + icon_state = "nothing" + var/reappearing = 0 + var/movedelay = 0 + var/movespeed = 2 + density = FALSE + anchored = TRUE + invisibility = 60 + resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + +/obj/effect/dummy/phased_mob/spell_jaunt/Destroy() + // Eject contents if deleted somehow + for(var/atom/movable/AM in src) + AM.forceMove(get_turf(src)) + return ..() + +/obj/effect/dummy/phased_mob/spell_jaunt/relaymove(var/mob/user, direction) + if ((movedelay > world.time) || reappearing || !direction) + return + var/turf/newLoc = get_step(src,direction) + setDir(direction) + + movedelay = world.time + movespeed + + if(newLoc.flags_1 & NOJAUNT_1) + to_chat(user, "Some strange aura is blocking the way.") + return + if (locate(/obj/effect/blessing, newLoc)) + to_chat(user, "Holy energies block your path!") + return + + forceMove(newLoc) + +/obj/effect/dummy/phased_mob/spell_jaunt/ex_act(blah) + return +/obj/effect/dummy/phased_mob/spell_jaunt/bullet_act(blah) return \ No newline at end of file diff --git a/code/modules/spells/spell_types/explosion.dm b/code/modules/spells/spell_types/explosion.dm index bd9d5d005a..0ef21ab786 100644 --- a/code/modules/spells/spell_types/explosion.dm +++ b/code/modules/spells/spell_types/explosion.dm @@ -1,16 +1,16 @@ -/obj/effect/proc_holder/spell/targeted/explosion - name = "Explosion" - desc = "This spell explodes an area." - - var/ex_severe = 1 - var/ex_heavy = 2 - var/ex_light = 3 - var/ex_flash = 4 - -/obj/effect/proc_holder/spell/targeted/explosion/cast(list/targets,mob/user = usr) - for(var/mob/living/target in targets) - if(target.anti_magic_check()) - continue - explosion(target.loc,ex_severe,ex_heavy,ex_light,ex_flash) - +/obj/effect/proc_holder/spell/targeted/explosion + name = "Explosion" + desc = "This spell explodes an area." + + var/ex_severe = 1 + var/ex_heavy = 2 + var/ex_light = 3 + var/ex_flash = 4 + +/obj/effect/proc_holder/spell/targeted/explosion/cast(list/targets,mob/user = usr) + for(var/mob/living/target in targets) + if(target.anti_magic_check()) + continue + explosion(target.loc,ex_severe,ex_heavy,ex_light,ex_flash) + return \ No newline at end of file diff --git a/code/modules/spells/spell_types/genetic.dm b/code/modules/spells/spell_types/genetic.dm index 02d3c087f6..b670d93f06 100644 --- a/code/modules/spells/spell_types/genetic.dm +++ b/code/modules/spells/spell_types/genetic.dm @@ -1,44 +1,44 @@ -/obj/effect/proc_holder/spell/targeted/genetic - name = "Genetic" - desc = "This spell inflicts a set of mutations and disabilities upon the target." - - var/list/active_on = list() - var/list/traits = list() //disabilities - var/list/mutations = list() //mutation strings - var/duration = 100 //deciseconds - /* - Disabilities - 1st bit - ? - 2nd bit - ? - 3rd bit - ? - 4th bit - ? - 5th bit - ? - 6th bit - ? - */ - -/obj/effect/proc_holder/spell/targeted/genetic/cast(list/targets,mob/user = usr) - playMagSound() - for(var/mob/living/carbon/target in targets) - if(target.anti_magic_check()) - continue - if(!target.dna) - continue - for(var/A in mutations) - target.dna.add_mutation(A) - for(var/A in traits) - ADD_TRAIT(target, A, GENETICS_SPELL) - active_on += target - addtimer(CALLBACK(src, .proc/remove, target), duration) - -/obj/effect/proc_holder/spell/targeted/genetic/Destroy() - . = ..() - for(var/V in active_on) - remove(V) - -/obj/effect/proc_holder/spell/targeted/genetic/proc/remove(mob/living/carbon/target) - active_on -= target - if(!QDELETED(target)) - for(var/A in mutations) - target.dna.remove_mutation(A) - for(var/A in traits) +/obj/effect/proc_holder/spell/targeted/genetic + name = "Genetic" + desc = "This spell inflicts a set of mutations and disabilities upon the target." + + var/list/active_on = list() + var/list/traits = list() //disabilities + var/list/mutations = list() //mutation strings + var/duration = 100 //deciseconds + /* + Disabilities + 1st bit - ? + 2nd bit - ? + 3rd bit - ? + 4th bit - ? + 5th bit - ? + 6th bit - ? + */ + +/obj/effect/proc_holder/spell/targeted/genetic/cast(list/targets,mob/user = usr) + playMagSound() + for(var/mob/living/carbon/target in targets) + if(target.anti_magic_check()) + continue + if(!target.dna) + continue + for(var/A in mutations) + target.dna.add_mutation(A) + for(var/A in traits) + ADD_TRAIT(target, A, GENETICS_SPELL) + active_on += target + addtimer(CALLBACK(src, .proc/remove, target), duration) + +/obj/effect/proc_holder/spell/targeted/genetic/Destroy() + . = ..() + for(var/V in active_on) + remove(V) + +/obj/effect/proc_holder/spell/targeted/genetic/proc/remove(mob/living/carbon/target) + active_on -= target + if(!QDELETED(target)) + for(var/A in mutations) + target.dna.remove_mutation(A) + for(var/A in traits) REMOVE_TRAIT(target, A, GENETICS_SPELL) \ No newline at end of file diff --git a/code/modules/spells/spell_types/inflict_handler.dm b/code/modules/spells/spell_types/inflict_handler.dm index 3ccc039285..7bc5ce34d3 100644 --- a/code/modules/spells/spell_types/inflict_handler.dm +++ b/code/modules/spells/spell_types/inflict_handler.dm @@ -1,57 +1,57 @@ -/obj/effect/proc_holder/spell/targeted/inflict_handler - name = "Inflict Handler" - desc = "This spell blinds and/or destroys/damages/heals and/or knockdowns/stuns the target." - - var/amt_knockdown = 0 - var/amt_hardstun - var/amt_unconscious = 0 - var/amt_stun = 0 - - //set to negatives for healing - var/amt_dam_stam - var/amt_dam_fire = 0 - var/amt_dam_brute = 0 - var/amt_dam_oxy = 0 - var/amt_dam_tox = 0 - - var/amt_eye_blind = 0 - var/amt_eye_blurry = 0 - - var/destroys = "none" //can be "none", "gib" or "disintegrate" - - var/summon_type = null //this will put an obj at the target's location - - var/check_anti_magic = TRUE - var/check_holy = FALSE - -/obj/effect/proc_holder/spell/targeted/inflict_handler/cast(list/targets,mob/user = usr) - for(var/mob/living/target in targets) - playsound(target,sound, 50,1) - if(target.anti_magic_check(check_anti_magic, check_holy)) - return - switch(destroys) - if("gib") - target.gib() - if("disintegrate") - target.dust() - - if(!target) - continue - //damage/healing - target.adjustBruteLoss(amt_dam_brute) - target.adjustFireLoss(amt_dam_fire) - target.adjustToxLoss(amt_dam_tox) - target.adjustOxyLoss(amt_dam_oxy) - //disabling - if(!amt_knockdown && amt_dam_stam) - target.adjustStaminaLoss(amt_dam_stam) - else - target.Knockdown(amt_knockdown, override_hardstun = amt_hardstun, override_stamdmg = amt_dam_stam) - target.Unconscious(amt_unconscious) - target.Stun(amt_stun) - - target.blind_eyes(amt_eye_blind) - target.blur_eyes(amt_eye_blurry) - //summoning - if(summon_type) +/obj/effect/proc_holder/spell/targeted/inflict_handler + name = "Inflict Handler" + desc = "This spell blinds and/or destroys/damages/heals and/or knockdowns/stuns the target." + + var/amt_knockdown = 0 + var/amt_hardstun + var/amt_unconscious = 0 + var/amt_stun = 0 + + //set to negatives for healing + var/amt_dam_stam + var/amt_dam_fire = 0 + var/amt_dam_brute = 0 + var/amt_dam_oxy = 0 + var/amt_dam_tox = 0 + + var/amt_eye_blind = 0 + var/amt_eye_blurry = 0 + + var/destroys = "none" //can be "none", "gib" or "disintegrate" + + var/summon_type = null //this will put an obj at the target's location + + var/check_anti_magic = TRUE + var/check_holy = FALSE + +/obj/effect/proc_holder/spell/targeted/inflict_handler/cast(list/targets,mob/user = usr) + for(var/mob/living/target in targets) + playsound(target,sound, 50,1) + if(target.anti_magic_check(check_anti_magic, check_holy)) + return + switch(destroys) + if("gib") + target.gib() + if("disintegrate") + target.dust() + + if(!target) + continue + //damage/healing + target.adjustBruteLoss(amt_dam_brute) + target.adjustFireLoss(amt_dam_fire) + target.adjustToxLoss(amt_dam_tox) + target.adjustOxyLoss(amt_dam_oxy) + //disabling + if(!amt_knockdown && amt_dam_stam) + target.adjustStaminaLoss(amt_dam_stam) + else + target.Knockdown(amt_knockdown, override_hardstun = amt_hardstun, override_stamdmg = amt_dam_stam) + target.Unconscious(amt_unconscious) + target.Stun(amt_stun) + + target.blind_eyes(amt_eye_blind) + target.blur_eyes(amt_eye_blurry) + //summoning + if(summon_type) new summon_type(target.loc, target) \ No newline at end of file diff --git a/code/modules/spells/spell_types/knock.dm b/code/modules/spells/spell_types/knock.dm index 7179bed031..be5f516680 100644 --- a/code/modules/spells/spell_types/knock.dm +++ b/code/modules/spells/spell_types/knock.dm @@ -1,31 +1,31 @@ -/obj/effect/proc_holder/spell/aoe_turf/knock - name = "Knock" - desc = "This spell opens nearby doors and does not require wizard garb." - - school = "transmutation" - charge_max = 100 - clothes_req = 0 - invocation = "AULIE OXIN FIERA" - invocation_type = "whisper" - range = 3 - cooldown_min = 20 //20 deciseconds reduction per rank - - action_icon_state = "knock" - -/obj/effect/proc_holder/spell/aoe_turf/knock/cast(list/targets,mob/user = usr) - SEND_SOUND(user, sound('sound/magic/knock.ogg')) - for(var/turf/T in targets) - for(var/obj/machinery/door/door in T.contents) - INVOKE_ASYNC(src, .proc/open_door, door) - for(var/obj/structure/closet/C in T.contents) - INVOKE_ASYNC(src, .proc/open_closet, C) - -/obj/effect/proc_holder/spell/aoe_turf/knock/proc/open_door(var/obj/machinery/door/door) - if(istype(door, /obj/machinery/door/airlock)) - var/obj/machinery/door/airlock/A = door - A.locked = FALSE - door.open() - -/obj/effect/proc_holder/spell/aoe_turf/knock/proc/open_closet(var/obj/structure/closet/C) - C.locked = FALSE - C.open() +/obj/effect/proc_holder/spell/aoe_turf/knock + name = "Knock" + desc = "This spell opens nearby doors and does not require wizard garb." + + school = "transmutation" + charge_max = 100 + clothes_req = 0 + invocation = "AULIE OXIN FIERA" + invocation_type = "whisper" + range = 3 + cooldown_min = 20 //20 deciseconds reduction per rank + + action_icon_state = "knock" + +/obj/effect/proc_holder/spell/aoe_turf/knock/cast(list/targets,mob/user = usr) + SEND_SOUND(user, sound('sound/magic/knock.ogg')) + for(var/turf/T in targets) + for(var/obj/machinery/door/door in T.contents) + INVOKE_ASYNC(src, .proc/open_door, door) + for(var/obj/structure/closet/C in T.contents) + INVOKE_ASYNC(src, .proc/open_closet, C) + +/obj/effect/proc_holder/spell/aoe_turf/knock/proc/open_door(var/obj/machinery/door/door) + if(istype(door, /obj/machinery/door/airlock)) + var/obj/machinery/door/airlock/A = door + A.locked = FALSE + door.open() + +/obj/effect/proc_holder/spell/aoe_turf/knock/proc/open_closet(var/obj/structure/closet/C) + C.locked = FALSE + C.open() diff --git a/code/modules/spells/spell_types/mime.dm b/code/modules/spells/spell_types/mime.dm index e3177dbb4f..d44c6a3e3d 100644 --- a/code/modules/spells/spell_types/mime.dm +++ b/code/modules/spells/spell_types/mime.dm @@ -1,156 +1,156 @@ -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall - name = "Invisible Wall" - desc = "The mime's performance transmutates into physical reality." - school = "mime" - panel = "Mime" - summon_type = list(/obj/effect/forcefield/mime) - invocation_type = "emote" - invocation_emote_self = "You form a wall in front of yourself." - summon_lifespan = 300 - charge_max = 300 - clothes_req = 0 - range = 0 - cast_sound = null - human_req = 1 - - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first.") - return - invocation = "[usr.real_name] looks as if a wall is in front of [usr.p_them()]." - else - invocation_type ="none" - ..() - - -/obj/effect/proc_holder/spell/targeted/mime/speak - name = "Speech" - desc = "Make or break a vow of silence." - school = "mime" - panel = "Mime" - clothes_req = 0 - human_req = 1 - charge_max = 3000 - range = -1 - include_user = 1 - - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/targeted/mime/speak/Click() - if(!usr) - return - if(!ishuman(usr)) - return - var/mob/living/carbon/human/H = usr - if(H.mind.miming) - still_recharging_msg = "You can't break your vow of silence that fast!" - else - still_recharging_msg = "You'll have to wait before you can give your vow of silence again!" - ..() - -/obj/effect/proc_holder/spell/targeted/mime/speak/cast(list/targets,mob/user = usr) - for(var/mob/living/carbon/human/H in targets) - H.mind.miming=!H.mind.miming - if(H.mind.miming) - to_chat(H, "You make a vow of silence.") - SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "vow") - else - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vow", /datum/mood_event/broken_vow) - to_chat(H, "You break your vow of silence.") - -// These spells can only be gotten from the "Guide for Advanced Mimery series" for Mime Traitors. - -/obj/effect/proc_holder/spell/targeted/forcewall/mime - name = "Invisible Blockade" - desc = "Form an invisible three tile wide blockade." - school = "mime" - panel = "Mime" - wall_type = /obj/effect/forcefield/mime/advanced - invocation_type = "emote" - invocation_emote_self = "You form a blockade in front of yourself." - charge_max = 600 - sound = null - clothes_req = 0 - range = -1 - include_user = 1 - - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/targeted/forcewall/mime/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first.") - return - invocation = "[usr.real_name] looks as if a blockade is in front of [usr.p_them()]." - else - invocation_type ="none" - ..() - -/obj/effect/proc_holder/spell/aimed/finger_guns - name = "Finger Guns" - desc = "Shoot a mimed bullet from your fingers that stuns and does some damage." - school = "mime" - panel = "Mime" - charge_max = 300 - clothes_req = 0 - invocation_type = "emote" - invocation_emote_self = "You fire your finger gun!" - range = 20 - projectile_type = /obj/item/projectile/bullet/mime - projectile_amount = 3 - sound = null - active_msg = "You draw your fingers!" - deactive_msg = "You put your fingers at ease. Another time." - active = FALSE - - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - base_icon_state = "mime" - - -/obj/effect/proc_holder/spell/aimed/finger_guns/Click() - var/mob/living/carbon/human/owner = usr - if(owner.incapacitated()) - to_chat(owner, "You can't properly point your fingers while incapacitated.") - return - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first.") - return - invocation = "[usr.real_name] fires [usr.p_their()] finger gun!" - else - invocation_type ="none" - ..() - - -/obj/item/book/granter/spell/mimery_blockade - spell = /obj/effect/proc_holder/spell/targeted/forcewall/mime - spellname = "Invisible Blockade" - name = "Guide to Advanced Mimery Vol 1" - desc = "The pages don't make any sound when turned." - icon_state ="bookmime" - remarks = list("...") - -/obj/item/book/granter/spell/mimery_blockade/attack_self(mob/user) - ..() - if(!locate(/obj/effect/proc_holder/spell/targeted/mime/speak) in user.mind.spell_list) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak) - -/obj/item/book/granter/spell/mimery_guns - spell = /obj/effect/proc_holder/spell/aimed/finger_guns - spellname = "Finger Guns" - name = "Guide to Advanced Mimery Vol 2" - desc = "There aren't any words written..." - icon_state ="bookmime" - remarks = list("...") - -/obj/item/book/granter/spell/mimery_guns/attack_self(mob/user) - ..() - if(!locate(/obj/effect/proc_holder/spell/targeted/mime/speak) in user.mind.spell_list) +/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall + name = "Invisible Wall" + desc = "The mime's performance transmutates into physical reality." + school = "mime" + panel = "Mime" + summon_type = list(/obj/effect/forcefield/mime) + invocation_type = "emote" + invocation_emote_self = "You form a wall in front of yourself." + summon_lifespan = 300 + charge_max = 300 + clothes_req = 0 + range = 0 + cast_sound = null + human_req = 1 + + action_icon_state = "mime" + action_background_icon_state = "bg_mime" + +/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall/Click() + if(usr && usr.mind) + if(!usr.mind.miming) + to_chat(usr, "You must dedicate yourself to silence first.") + return + invocation = "[usr.real_name] looks as if a wall is in front of [usr.p_them()]." + else + invocation_type ="none" + ..() + + +/obj/effect/proc_holder/spell/targeted/mime/speak + name = "Speech" + desc = "Make or break a vow of silence." + school = "mime" + panel = "Mime" + clothes_req = 0 + human_req = 1 + charge_max = 3000 + range = -1 + include_user = 1 + + action_icon_state = "mime" + action_background_icon_state = "bg_mime" + +/obj/effect/proc_holder/spell/targeted/mime/speak/Click() + if(!usr) + return + if(!ishuman(usr)) + return + var/mob/living/carbon/human/H = usr + if(H.mind.miming) + still_recharging_msg = "You can't break your vow of silence that fast!" + else + still_recharging_msg = "You'll have to wait before you can give your vow of silence again!" + ..() + +/obj/effect/proc_holder/spell/targeted/mime/speak/cast(list/targets,mob/user = usr) + for(var/mob/living/carbon/human/H in targets) + H.mind.miming=!H.mind.miming + if(H.mind.miming) + to_chat(H, "You make a vow of silence.") + SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "vow") + else + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vow", /datum/mood_event/broken_vow) + to_chat(H, "You break your vow of silence.") + +// These spells can only be gotten from the "Guide for Advanced Mimery series" for Mime Traitors. + +/obj/effect/proc_holder/spell/targeted/forcewall/mime + name = "Invisible Blockade" + desc = "Form an invisible three tile wide blockade." + school = "mime" + panel = "Mime" + wall_type = /obj/effect/forcefield/mime/advanced + invocation_type = "emote" + invocation_emote_self = "You form a blockade in front of yourself." + charge_max = 600 + sound = null + clothes_req = 0 + range = -1 + include_user = 1 + + action_icon_state = "mime" + action_background_icon_state = "bg_mime" + +/obj/effect/proc_holder/spell/targeted/forcewall/mime/Click() + if(usr && usr.mind) + if(!usr.mind.miming) + to_chat(usr, "You must dedicate yourself to silence first.") + return + invocation = "[usr.real_name] looks as if a blockade is in front of [usr.p_them()]." + else + invocation_type ="none" + ..() + +/obj/effect/proc_holder/spell/aimed/finger_guns + name = "Finger Guns" + desc = "Shoot a mimed bullet from your fingers that stuns and does some damage." + school = "mime" + panel = "Mime" + charge_max = 300 + clothes_req = 0 + invocation_type = "emote" + invocation_emote_self = "You fire your finger gun!" + range = 20 + projectile_type = /obj/item/projectile/bullet/mime + projectile_amount = 3 + sound = null + active_msg = "You draw your fingers!" + deactive_msg = "You put your fingers at ease. Another time." + active = FALSE + + action_icon_state = "mime" + action_background_icon_state = "bg_mime" + base_icon_state = "mime" + + +/obj/effect/proc_holder/spell/aimed/finger_guns/Click() + var/mob/living/carbon/human/owner = usr + if(owner.incapacitated()) + to_chat(owner, "You can't properly point your fingers while incapacitated.") + return + if(usr && usr.mind) + if(!usr.mind.miming) + to_chat(usr, "You must dedicate yourself to silence first.") + return + invocation = "[usr.real_name] fires [usr.p_their()] finger gun!" + else + invocation_type ="none" + ..() + + +/obj/item/book/granter/spell/mimery_blockade + spell = /obj/effect/proc_holder/spell/targeted/forcewall/mime + spellname = "Invisible Blockade" + name = "Guide to Advanced Mimery Vol 1" + desc = "The pages don't make any sound when turned." + icon_state ="bookmime" + remarks = list("...") + +/obj/item/book/granter/spell/mimery_blockade/attack_self(mob/user) + ..() + if(!locate(/obj/effect/proc_holder/spell/targeted/mime/speak) in user.mind.spell_list) + user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak) + +/obj/item/book/granter/spell/mimery_guns + spell = /obj/effect/proc_holder/spell/aimed/finger_guns + spellname = "Finger Guns" + name = "Guide to Advanced Mimery Vol 2" + desc = "There aren't any words written..." + icon_state ="bookmime" + remarks = list("...") + +/obj/item/book/granter/spell/mimery_guns/attack_self(mob/user) + ..() + if(!locate(/obj/effect/proc_holder/spell/targeted/mime/speak) in user.mind.spell_list) user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak) \ No newline at end of file diff --git a/code/modules/spells/spell_types/mind_transfer.dm b/code/modules/spells/spell_types/mind_transfer.dm index d78b32c643..982bc3dd06 100644 --- a/code/modules/spells/spell_types/mind_transfer.dm +++ b/code/modules/spells/spell_types/mind_transfer.dm @@ -1,88 +1,88 @@ -/obj/effect/proc_holder/spell/targeted/mind_transfer - name = "Mind Transfer" - desc = "This spell allows the user to switch bodies with a target." - - school = "transmutation" - charge_max = 600 - clothes_req = 0 - invocation = "GIN'YU CAPAN" - invocation_type = "whisper" - range = 1 - cooldown_min = 200 //100 deciseconds reduction per rank - var/unconscious_amount_caster = 400 //how much the caster is stunned for after the spell - var/unconscious_amount_victim = 400 //how much the victim is stunned for after the spell - - action_icon_state = "mindswap" - -/* -Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do. -Make sure spells that are removed from spell_list are actually removed and deleted when mind transferring. -Also, you never added distance checking after target is selected. I've went ahead and did that. -*/ -/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/living/user = usr, distanceoverride, silent = FALSE) - if(!targets.len) - if(!silent) - to_chat(user, "No mind found!") - return - - if(targets.len > 1) - if(!silent) - to_chat(user, "Too many minds! You're not a hive damnit!") - return - - var/mob/living/target = targets[1] - - var/t_He = target.p_they(TRUE) - var/t_is = target.p_are() - - if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it. - if(!silent) - to_chat(user, "[t_He] [t_is] too far away!") - return - - if(ismegafauna(target)) - if(!silent) - to_chat(user, "This creature is too powerful to control!") - return - - if(target.stat == DEAD) - if(!silent) - to_chat(user, "You don't particularly want to be dead!") - return - - if(!target.key || !target.mind) - if(!silent) - to_chat(user, "[t_He] appear[target.p_s()] to be catatonic! Not even magic can affect [target.p_their()] vacant mind.") - return - - if(user.suiciding) - if(!silent) - to_chat(user, "You're killing yourself! You can't concentrate enough to do this!") - return - - var/datum/mind/TM = target.mind - if((target.anti_magic_check(TRUE, FALSE) || TM.has_antag_datum(/datum/antagonist/wizard) || TM.has_antag_datum(/datum/antagonist/cult) || TM.has_antag_datum(/datum/antagonist/clockcult) || TM.has_antag_datum(/datum/antagonist/changeling) || TM.has_antag_datum(/datum/antagonist/rev)) || cmptext(copytext(target.key,1,2),"@")) - if(!silent) - to_chat(user, "[target.p_their(TRUE)] mind is resisting your spell!") - return - - var/mob/living/victim = target//The target of the spell whos body will be transferred to. - var/mob/living/caster = user//The wizard/whomever doing the body transferring. - - //MIND TRANSFER BEGIN - var/mob/dead/observer/ghost = victim.ghostize(FALSE, TRUE) - caster.mind.transfer_to(victim) - - ghost.mind.transfer_to(caster) - if(ghost.key) - ghost.transfer_ckey(caster) //have to transfer the key since the mind was not active - qdel(ghost) - - //MIND TRANSFER END - - //Here we knock both mobs out for a time. - caster.Unconscious(unconscious_amount_caster) - victim.Unconscious(unconscious_amount_victim) - SEND_SOUND(caster, sound('sound/magic/mandswap.ogg')) - SEND_SOUND(victim, sound('sound/magic/mandswap.ogg'))// only the caster and victim hear the sounds, that way no one knows for sure if the swap happened - return TRUE +/obj/effect/proc_holder/spell/targeted/mind_transfer + name = "Mind Transfer" + desc = "This spell allows the user to switch bodies with a target." + + school = "transmutation" + charge_max = 600 + clothes_req = 0 + invocation = "GIN'YU CAPAN" + invocation_type = "whisper" + range = 1 + cooldown_min = 200 //100 deciseconds reduction per rank + var/unconscious_amount_caster = 400 //how much the caster is stunned for after the spell + var/unconscious_amount_victim = 400 //how much the victim is stunned for after the spell + + action_icon_state = "mindswap" + +/* +Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do. +Make sure spells that are removed from spell_list are actually removed and deleted when mind transferring. +Also, you never added distance checking after target is selected. I've went ahead and did that. +*/ +/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/living/user = usr, distanceoverride, silent = FALSE) + if(!targets.len) + if(!silent) + to_chat(user, "No mind found!") + return + + if(targets.len > 1) + if(!silent) + to_chat(user, "Too many minds! You're not a hive damnit!") + return + + var/mob/living/target = targets[1] + + var/t_He = target.p_they(TRUE) + var/t_is = target.p_are() + + if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it. + if(!silent) + to_chat(user, "[t_He] [t_is] too far away!") + return + + if(ismegafauna(target)) + if(!silent) + to_chat(user, "This creature is too powerful to control!") + return + + if(target.stat == DEAD) + if(!silent) + to_chat(user, "You don't particularly want to be dead!") + return + + if(!target.key || !target.mind) + if(!silent) + to_chat(user, "[t_He] appear[target.p_s()] to be catatonic! Not even magic can affect [target.p_their()] vacant mind.") + return + + if(user.suiciding) + if(!silent) + to_chat(user, "You're killing yourself! You can't concentrate enough to do this!") + return + + var/datum/mind/TM = target.mind + if((target.anti_magic_check(TRUE, FALSE) || TM.has_antag_datum(/datum/antagonist/wizard) || TM.has_antag_datum(/datum/antagonist/cult) || TM.has_antag_datum(/datum/antagonist/clockcult) || TM.has_antag_datum(/datum/antagonist/changeling) || TM.has_antag_datum(/datum/antagonist/rev)) || cmptext(copytext(target.key,1,2),"@")) + if(!silent) + to_chat(user, "[target.p_their(TRUE)] mind is resisting your spell!") + return + + var/mob/living/victim = target//The target of the spell whos body will be transferred to. + var/mob/living/caster = user//The wizard/whomever doing the body transferring. + + //MIND TRANSFER BEGIN + var/mob/dead/observer/ghost = victim.ghostize(FALSE, TRUE) + caster.mind.transfer_to(victim) + + ghost.mind.transfer_to(caster) + if(ghost.key) + ghost.transfer_ckey(caster) //have to transfer the key since the mind was not active + qdel(ghost) + + //MIND TRANSFER END + + //Here we knock both mobs out for a time. + caster.Unconscious(unconscious_amount_caster) + victim.Unconscious(unconscious_amount_victim) + SEND_SOUND(caster, sound('sound/magic/mandswap.ogg')) + SEND_SOUND(victim, sound('sound/magic/mandswap.ogg'))// only the caster and victim hear the sounds, that way no one knows for sure if the swap happened + return TRUE diff --git a/code/modules/spells/spell_types/projectile.dm b/code/modules/spells/spell_types/projectile.dm index 4bbd9ac4a6..be305520a2 100644 --- a/code/modules/spells/spell_types/projectile.dm +++ b/code/modules/spells/spell_types/projectile.dm @@ -1,96 +1,96 @@ - -//NEEDS MAJOR CODE CLEANUP. - -/obj/effect/proc_holder/spell/targeted/projectile - name = "Projectile" - desc = "This spell summons projectiles which try to hit the targets." - - var/proj_icon = 'icons/obj/projectiles.dmi' - var/proj_icon_state = "spell" - var/proj_name = "a spell projectile" - - var/proj_trail = 0 //if it leaves a trail - var/proj_trail_lifespan = 0 //deciseconds - var/proj_trail_icon = 'icons/obj/wizard.dmi' - var/proj_trail_icon_state = "trail" - - - var/proj_type = "/obj/effect/proc_holder/spell/targeted" //IMPORTANT use only subtypes of this - - var/proj_lingering = 0 //if it lingers or disappears upon hitting an obstacle - var/proj_homing = 1 //if it follows the target - var/proj_insubstantial = 0 //if it can pass through dense objects or not - var/proj_trigger_range = 0 //the range from target at which the projectile triggers cast(target) - - var/proj_lifespan = 15 //in deciseconds * proj_step_delay - var/proj_step_delay = 1 //lower = faster - -/obj/effect/proc_holder/spell/targeted/projectile/cast(list/targets, mob/user = usr) - playMagSound() - for(var/mob/living/target in targets) - launch(target, user) - -/obj/effect/proc_holder/spell/targeted/projectile/proc/launch(mob/living/target, mob/user) - set waitfor = FALSE - var/obj/effect/proc_holder/spell/targeted/projectile - if(istext(proj_type)) - var/projectile_type = text2path(proj_type) - projectile = new projectile_type(user) - if(istype(proj_type, /obj/effect/proc_holder/spell)) - projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user) - var/obj/effect/proc_holder/spell/targeted/trigger/T = projectile - T.linked_spells += proj_type - projectile.icon = proj_icon - projectile.icon_state = proj_icon_state - projectile.setDir(get_dir(target,projectile)) - projectile.name = proj_name - - var/current_loc = user.loc - - projectile.forceMove(current_loc) - - for(var/i = 0,i < proj_lifespan,i++) - if(!projectile) - break - - if(proj_homing) - if(proj_insubstantial) - projectile.setDir(get_dir(projectile,target)) - projectile.forceMove(get_step_to(projectile,target)) - else - step_to(projectile,target) - else - if(proj_insubstantial) - projectile.forceMove(get_step(projectile,dir)) - else - step(projectile,dir) - - if(!projectile) // step and step_to sleeps so we'll have to check again. - break - - if(!target || (!proj_lingering && projectile.loc == current_loc)) //if it didn't move since last time - qdel(projectile) - break - - if(proj_trail && projectile) - spawntrail(projectile) - - if(projectile.loc in range(target.loc,proj_trigger_range)) - projectile.perform(list(target),user=user) - break - - current_loc = projectile.loc - - sleep(proj_step_delay) - - if(projectile) - qdel(projectile) - -/obj/effect/proc_holder/spell/targeted/projectile/proc/spawntrail(obj/effect/proc_holder/spell/targeted/projectile) - set waitfor = FALSE - if(projectile) - var/obj/effect/overlay/trail = new /obj/effect/overlay(projectile.loc) - trail.icon = proj_trail_icon - trail.icon_state = proj_trail_icon_state - trail.density = FALSE - QDEL_IN(trail, proj_trail_lifespan) + +//NEEDS MAJOR CODE CLEANUP. + +/obj/effect/proc_holder/spell/targeted/projectile + name = "Projectile" + desc = "This spell summons projectiles which try to hit the targets." + + var/proj_icon = 'icons/obj/projectiles.dmi' + var/proj_icon_state = "spell" + var/proj_name = "a spell projectile" + + var/proj_trail = 0 //if it leaves a trail + var/proj_trail_lifespan = 0 //deciseconds + var/proj_trail_icon = 'icons/obj/wizard.dmi' + var/proj_trail_icon_state = "trail" + + + var/proj_type = "/obj/effect/proc_holder/spell/targeted" //IMPORTANT use only subtypes of this + + var/proj_lingering = 0 //if it lingers or disappears upon hitting an obstacle + var/proj_homing = 1 //if it follows the target + var/proj_insubstantial = 0 //if it can pass through dense objects or not + var/proj_trigger_range = 0 //the range from target at which the projectile triggers cast(target) + + var/proj_lifespan = 15 //in deciseconds * proj_step_delay + var/proj_step_delay = 1 //lower = faster + +/obj/effect/proc_holder/spell/targeted/projectile/cast(list/targets, mob/user = usr) + playMagSound() + for(var/mob/living/target in targets) + launch(target, user) + +/obj/effect/proc_holder/spell/targeted/projectile/proc/launch(mob/living/target, mob/user) + set waitfor = FALSE + var/obj/effect/proc_holder/spell/targeted/projectile + if(istext(proj_type)) + var/projectile_type = text2path(proj_type) + projectile = new projectile_type(user) + if(istype(proj_type, /obj/effect/proc_holder/spell)) + projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user) + var/obj/effect/proc_holder/spell/targeted/trigger/T = projectile + T.linked_spells += proj_type + projectile.icon = proj_icon + projectile.icon_state = proj_icon_state + projectile.setDir(get_dir(target,projectile)) + projectile.name = proj_name + + var/current_loc = user.loc + + projectile.forceMove(current_loc) + + for(var/i = 0,i < proj_lifespan,i++) + if(!projectile) + break + + if(proj_homing) + if(proj_insubstantial) + projectile.setDir(get_dir(projectile,target)) + projectile.forceMove(get_step_to(projectile,target)) + else + step_to(projectile,target) + else + if(proj_insubstantial) + projectile.forceMove(get_step(projectile,dir)) + else + step(projectile,dir) + + if(!projectile) // step and step_to sleeps so we'll have to check again. + break + + if(!target || (!proj_lingering && projectile.loc == current_loc)) //if it didn't move since last time + qdel(projectile) + break + + if(proj_trail && projectile) + spawntrail(projectile) + + if(projectile.loc in range(target.loc,proj_trigger_range)) + projectile.perform(list(target),user=user) + break + + current_loc = projectile.loc + + sleep(proj_step_delay) + + if(projectile) + qdel(projectile) + +/obj/effect/proc_holder/spell/targeted/projectile/proc/spawntrail(obj/effect/proc_holder/spell/targeted/projectile) + set waitfor = FALSE + if(projectile) + var/obj/effect/overlay/trail = new /obj/effect/overlay(projectile.loc) + trail.icon = proj_trail_icon + trail.icon_state = proj_trail_icon_state + trail.density = FALSE + QDEL_IN(trail, proj_trail_lifespan) diff --git a/code/modules/spells/spell_types/rightandwrong.dm b/code/modules/spells/spell_types/rightandwrong.dm index 13686e2069..e5e856c630 100644 --- a/code/modules/spells/spell_types/rightandwrong.dm +++ b/code/modules/spells/spell_types/rightandwrong.dm @@ -1,176 +1,176 @@ -//In this file: Summon Magic/Summon Guns/Summon Events - -// 1 in 50 chance of getting something really special. -#define SPECIALIST_MAGIC_PROB 2 - -GLOBAL_LIST_INIT(summoned_guns, list( - /obj/item/gun/energy/e_gun/advtaser, - /obj/item/gun/energy/e_gun, - /obj/item/gun/energy/laser, - /obj/item/gun/ballistic/revolver, - /obj/item/gun/ballistic/revolver/detective, - /obj/item/gun/ballistic/automatic/pistol/deagle/camo, - /obj/item/gun/ballistic/automatic/gyropistol, - /obj/item/gun/energy/pulse, - /obj/item/gun/ballistic/automatic/pistol/suppressed, - /obj/item/gun/ballistic/revolver/doublebarrel, - /obj/item/gun/ballistic/shotgun, - /obj/item/gun/ballistic/shotgun/automatic/combat, - /obj/item/gun/ballistic/automatic/ar, - /obj/item/gun/ballistic/revolver/mateba, - /obj/item/gun/ballistic/shotgun/boltaction, - /obj/item/gun/ballistic/automatic/speargun, - /obj/item/gun/ballistic/automatic/mini_uzi, - /obj/item/gun/energy/lasercannon, - /obj/item/gun/energy/kinetic_accelerator/crossbow/large, - /obj/item/gun/energy/e_gun/nuclear, - /obj/item/gun/ballistic/automatic/proto, - /obj/item/gun/ballistic/automatic/c20r, - /obj/item/gun/ballistic/automatic/l6_saw, - /obj/item/gun/ballistic/automatic/m90, - /obj/item/gun/energy/alien, - /obj/item/gun/energy/e_gun/dragnet, - /obj/item/gun/energy/e_gun/turret, - /obj/item/gun/energy/pulse/carbine, - /obj/item/gun/energy/decloner, - /obj/item/gun/energy/mindflayer, - /obj/item/gun/energy/kinetic_accelerator, - /obj/item/gun/energy/plasmacutter/adv, - /obj/item/gun/energy/wormhole_projector, - /obj/item/gun/ballistic/automatic/wt550, - /obj/item/gun/ballistic/automatic/shotgun/bulldog, - /obj/item/gun/ballistic/revolver/grenadelauncher, - /obj/item/gun/ballistic/revolver/golden, - /obj/item/gun/ballistic/automatic/sniper_rifle, - /obj/item/gun/ballistic/rocketlauncher, - /obj/item/gun/medbeam, - /obj/item/gun/energy/laser/scatter, - /obj/item/gun/energy/gravity_gun)) - -GLOBAL_LIST_INIT(summoned_magic, list( - /obj/item/book/granter/spell/fireball, - /obj/item/book/granter/spell/smoke, - /obj/item/book/granter/spell/blind, - /obj/item/book/granter/spell/mindswap, - /obj/item/book/granter/spell/forcewall, - /obj/item/book/granter/spell/knock, - /obj/item/book/granter/spell/barnyard, - /obj/item/book/granter/spell/charge, - /obj/item/book/granter/spell/summonitem, - /obj/item/gun/magic/wand, - /obj/item/gun/magic/wand/death, - /obj/item/gun/magic/wand/resurrection, - /obj/item/gun/magic/wand/polymorph, - /obj/item/gun/magic/wand/teleport, - /obj/item/gun/magic/wand/door, - /obj/item/gun/magic/wand/fireball, - /obj/item/gun/magic/staff/healing, - /obj/item/gun/magic/staff/door, - /obj/item/scrying, - /obj/item/voodoo, - /obj/item/warpwhistle, - /obj/item/clothing/suit/space/hardsuit/shielded/wizard, - /obj/item/immortality_talisman, - /obj/item/melee/ghost_sword)) - -GLOBAL_LIST_INIT(summoned_special_magic, list( - /obj/item/gun/magic/staff/change, - /obj/item/gun/magic/staff/animate, - /obj/item/storage/belt/wands/full, - /obj/item/antag_spawner/contract, - /obj/item/gun/magic/staff/chaos, - /obj/item/necromantic_stone, - /obj/item/blood_contract)) - -// If true, it's the probability of triggering "survivor" antag. -GLOBAL_VAR_INIT(summon_guns_triggered, FALSE) -GLOBAL_VAR_INIT(summon_magic_triggered, FALSE) - -/proc/give_guns(mob/living/carbon/human/H) - if(H.stat == DEAD || !(H.client)) - return - if(H.mind) - if(iswizard(H) || H.mind.has_antag_datum(/datum/antagonist/survivalist/guns)) - return - - if(prob(GLOB.summon_guns_triggered) && !(H.mind.has_antag_datum(/datum/antagonist))) - SSticker.mode.traitors += H.mind - - H.mind.add_antag_datum(/datum/antagonist/survivalist/guns) - H.log_message("was made into a survivalist, and trusts no one!", LOG_ATTACK, color="red") - - var/gun_type = pick(GLOB.summoned_guns) - var/obj/item/gun/G = new gun_type(get_turf(H)) - G.unlock() - playsound(get_turf(H),'sound/magic/summon_guns.ogg', 50, 1) - - var/in_hand = H.put_in_hands(G) // not always successful - - to_chat(H, "\A [G] appears [in_hand ? "in your hand" : "at your feet"]!") - -/proc/give_magic(mob/living/carbon/human/H) - if(H.stat == DEAD || !(H.client)) - return - if(H.mind) - if(iswizard(H) || H.mind.has_antag_datum(/datum/antagonist/survivalist/magic)) - return - - if(prob(GLOB.summon_magic_triggered) && !(H.mind.has_antag_datum(/datum/antagonist))) - H.mind.add_antag_datum(/datum/antagonist/survivalist/magic) - H.log_message("was made into a survivalist, and trusts no one!
                ", LOG_ATTACK, color="red") - - var/magic_type = pick(GLOB.summoned_magic) - var/lucky = FALSE - if(prob(SPECIALIST_MAGIC_PROB)) - magic_type = pick(GLOB.summoned_special_magic) - lucky = TRUE - - var/obj/item/M = new magic_type(get_turf(H)) - playsound(get_turf(H),'sound/magic/summon_magic.ogg', 50, 1) - - var/in_hand = H.put_in_hands(M) - - to_chat(H, "\A [M] appears [in_hand ? "in your hand" : "at your feet"]!") - if(lucky) - to_chat(H, "You feel incredibly lucky.") - - -/proc/rightandwrong(summon_type, mob/user, survivor_probability) - if(user) //in this case either someone holding a spellbook or a badmin - to_chat(user, "You summoned [summon_type]!") - message_admins("[key_name_admin(user, TRUE)] summoned [summon_type]!") - log_game("[key_name(user)] summoned [summon_type]!") - - if(summon_type == SUMMON_MAGIC) - GLOB.summon_magic_triggered = survivor_probability - else if(summon_type == SUMMON_GUNS) - GLOB.summon_guns_triggered = survivor_probability - else - CRASH("Bad summon_type given: [summon_type]") - - for(var/mob/living/carbon/human/H in GLOB.player_list) - var/turf/T = get_turf(H) - if(T && is_away_level(T.z)) - continue - if(summon_type == SUMMON_MAGIC) - give_magic(H) - else - give_guns(H) - -/proc/summonevents() - if(!SSevents.wizardmode) - SSevents.frequency_lower = 600 //1 minute lower bound - SSevents.frequency_upper = 3000 //5 minutes upper bound - SSevents.toggleWizardmode() - SSevents.reschedule() - - else //Speed it up - SSevents.frequency_upper -= 600 //The upper bound falls a minute each time, making the AVERAGE time between events lessen - if(SSevents.frequency_upper < SSevents.frequency_lower) //Sanity - SSevents.frequency_upper = SSevents.frequency_lower - - SSevents.reschedule() - message_admins("Summon Events intensifies, events will now occur every [SSevents.frequency_lower / 600] to [SSevents.frequency_upper / 600] minutes.") - log_game("Summon Events was increased!") - -#undef SPECIALIST_MAGIC_PROB +//In this file: Summon Magic/Summon Guns/Summon Events + +// 1 in 50 chance of getting something really special. +#define SPECIALIST_MAGIC_PROB 2 + +GLOBAL_LIST_INIT(summoned_guns, list( + /obj/item/gun/energy/e_gun/advtaser, + /obj/item/gun/energy/e_gun, + /obj/item/gun/energy/laser, + /obj/item/gun/ballistic/revolver, + /obj/item/gun/ballistic/revolver/detective, + /obj/item/gun/ballistic/automatic/pistol/deagle/camo, + /obj/item/gun/ballistic/automatic/gyropistol, + /obj/item/gun/energy/pulse, + /obj/item/gun/ballistic/automatic/pistol/suppressed, + /obj/item/gun/ballistic/revolver/doublebarrel, + /obj/item/gun/ballistic/shotgun, + /obj/item/gun/ballistic/shotgun/automatic/combat, + /obj/item/gun/ballistic/automatic/ar, + /obj/item/gun/ballistic/revolver/mateba, + /obj/item/gun/ballistic/shotgun/boltaction, + /obj/item/gun/ballistic/automatic/speargun, + /obj/item/gun/ballistic/automatic/mini_uzi, + /obj/item/gun/energy/lasercannon, + /obj/item/gun/energy/kinetic_accelerator/crossbow/large, + /obj/item/gun/energy/e_gun/nuclear, + /obj/item/gun/ballistic/automatic/proto, + /obj/item/gun/ballistic/automatic/c20r, + /obj/item/gun/ballistic/automatic/l6_saw, + /obj/item/gun/ballistic/automatic/m90, + /obj/item/gun/energy/alien, + /obj/item/gun/energy/e_gun/dragnet, + /obj/item/gun/energy/e_gun/turret, + /obj/item/gun/energy/pulse/carbine, + /obj/item/gun/energy/decloner, + /obj/item/gun/energy/mindflayer, + /obj/item/gun/energy/kinetic_accelerator, + /obj/item/gun/energy/plasmacutter/adv, + /obj/item/gun/energy/wormhole_projector, + /obj/item/gun/ballistic/automatic/wt550, + /obj/item/gun/ballistic/automatic/shotgun/bulldog, + /obj/item/gun/ballistic/revolver/grenadelauncher, + /obj/item/gun/ballistic/revolver/golden, + /obj/item/gun/ballistic/automatic/sniper_rifle, + /obj/item/gun/ballistic/rocketlauncher, + /obj/item/gun/medbeam, + /obj/item/gun/energy/laser/scatter, + /obj/item/gun/energy/gravity_gun)) + +GLOBAL_LIST_INIT(summoned_magic, list( + /obj/item/book/granter/spell/fireball, + /obj/item/book/granter/spell/smoke, + /obj/item/book/granter/spell/blind, + /obj/item/book/granter/spell/mindswap, + /obj/item/book/granter/spell/forcewall, + /obj/item/book/granter/spell/knock, + /obj/item/book/granter/spell/barnyard, + /obj/item/book/granter/spell/charge, + /obj/item/book/granter/spell/summonitem, + /obj/item/gun/magic/wand, + /obj/item/gun/magic/wand/death, + /obj/item/gun/magic/wand/resurrection, + /obj/item/gun/magic/wand/polymorph, + /obj/item/gun/magic/wand/teleport, + /obj/item/gun/magic/wand/door, + /obj/item/gun/magic/wand/fireball, + /obj/item/gun/magic/staff/healing, + /obj/item/gun/magic/staff/door, + /obj/item/scrying, + /obj/item/voodoo, + /obj/item/warpwhistle, + /obj/item/clothing/suit/space/hardsuit/shielded/wizard, + /obj/item/immortality_talisman, + /obj/item/melee/ghost_sword)) + +GLOBAL_LIST_INIT(summoned_special_magic, list( + /obj/item/gun/magic/staff/change, + /obj/item/gun/magic/staff/animate, + /obj/item/storage/belt/wands/full, + /obj/item/antag_spawner/contract, + /obj/item/gun/magic/staff/chaos, + /obj/item/necromantic_stone, + /obj/item/blood_contract)) + +// If true, it's the probability of triggering "survivor" antag. +GLOBAL_VAR_INIT(summon_guns_triggered, FALSE) +GLOBAL_VAR_INIT(summon_magic_triggered, FALSE) + +/proc/give_guns(mob/living/carbon/human/H) + if(H.stat == DEAD || !(H.client)) + return + if(H.mind) + if(iswizard(H) || H.mind.has_antag_datum(/datum/antagonist/survivalist/guns)) + return + + if(prob(GLOB.summon_guns_triggered) && !(H.mind.has_antag_datum(/datum/antagonist))) + SSticker.mode.traitors += H.mind + + H.mind.add_antag_datum(/datum/antagonist/survivalist/guns) + H.log_message("was made into a survivalist, and trusts no one!", LOG_ATTACK, color="red") + + var/gun_type = pick(GLOB.summoned_guns) + var/obj/item/gun/G = new gun_type(get_turf(H)) + G.unlock() + playsound(get_turf(H),'sound/magic/summon_guns.ogg', 50, 1) + + var/in_hand = H.put_in_hands(G) // not always successful + + to_chat(H, "\A [G] appears [in_hand ? "in your hand" : "at your feet"]!") + +/proc/give_magic(mob/living/carbon/human/H) + if(H.stat == DEAD || !(H.client)) + return + if(H.mind) + if(iswizard(H) || H.mind.has_antag_datum(/datum/antagonist/survivalist/magic)) + return + + if(prob(GLOB.summon_magic_triggered) && !(H.mind.has_antag_datum(/datum/antagonist))) + H.mind.add_antag_datum(/datum/antagonist/survivalist/magic) + H.log_message("was made into a survivalist, and trusts no one!
                ", LOG_ATTACK, color="red") + + var/magic_type = pick(GLOB.summoned_magic) + var/lucky = FALSE + if(prob(SPECIALIST_MAGIC_PROB)) + magic_type = pick(GLOB.summoned_special_magic) + lucky = TRUE + + var/obj/item/M = new magic_type(get_turf(H)) + playsound(get_turf(H),'sound/magic/summon_magic.ogg', 50, 1) + + var/in_hand = H.put_in_hands(M) + + to_chat(H, "\A [M] appears [in_hand ? "in your hand" : "at your feet"]!") + if(lucky) + to_chat(H, "You feel incredibly lucky.") + + +/proc/rightandwrong(summon_type, mob/user, survivor_probability) + if(user) //in this case either someone holding a spellbook or a badmin + to_chat(user, "You summoned [summon_type]!") + message_admins("[key_name_admin(user, TRUE)] summoned [summon_type]!") + log_game("[key_name(user)] summoned [summon_type]!") + + if(summon_type == SUMMON_MAGIC) + GLOB.summon_magic_triggered = survivor_probability + else if(summon_type == SUMMON_GUNS) + GLOB.summon_guns_triggered = survivor_probability + else + CRASH("Bad summon_type given: [summon_type]") + + for(var/mob/living/carbon/human/H in GLOB.player_list) + var/turf/T = get_turf(H) + if(T && is_away_level(T.z)) + continue + if(summon_type == SUMMON_MAGIC) + give_magic(H) + else + give_guns(H) + +/proc/summonevents() + if(!SSevents.wizardmode) + SSevents.frequency_lower = 600 //1 minute lower bound + SSevents.frequency_upper = 3000 //5 minutes upper bound + SSevents.toggleWizardmode() + SSevents.reschedule() + + else //Speed it up + SSevents.frequency_upper -= 600 //The upper bound falls a minute each time, making the AVERAGE time between events lessen + if(SSevents.frequency_upper < SSevents.frequency_lower) //Sanity + SSevents.frequency_upper = SSevents.frequency_lower + + SSevents.reschedule() + message_admins("Summon Events intensifies, events will now occur every [SSevents.frequency_lower / 600] to [SSevents.frequency_upper / 600] minutes.") + log_game("Summon Events was increased!") + +#undef SPECIALIST_MAGIC_PROB diff --git a/code/modules/spells/spell_types/trigger.dm b/code/modules/spells/spell_types/trigger.dm index 7dfda25730..39cff63d98 100644 --- a/code/modules/spells/spell_types/trigger.dm +++ b/code/modules/spells/spell_types/trigger.dm @@ -1,30 +1,30 @@ -/obj/effect/proc_holder/spell/targeted/trigger - name = "Trigger" - desc = "This spell triggers another spell or a few." - - var/list/linked_spells = list() //those are just referenced by the trigger spell and are unaffected by it directly - var/list/starting_spells = list() //those are added on New() to contents from default spells and are deleted when the trigger spell is deleted to prevent memory leaks - -/obj/effect/proc_holder/spell/targeted/trigger/Initialize() - . = ..() - - for(var/spell in starting_spells) - var/spell_to_add = text2path(spell) - new spell_to_add(src) //should result in adding to contents, needs testing - -/obj/effect/proc_holder/spell/targeted/trigger/Destroy() - for(var/spell in contents) - qdel(spell) - linked_spells = null - starting_spells = null - return ..() - -/obj/effect/proc_holder/spell/targeted/trigger/cast(list/targets,mob/user = usr) - playMagSound() - for(var/mob/living/target in targets) - for(var/obj/effect/proc_holder/spell/spell in contents) - spell.perform(list(target),0) - for(var/obj/effect/proc_holder/spell/spell in linked_spells) - spell.perform(list(target),0) - +/obj/effect/proc_holder/spell/targeted/trigger + name = "Trigger" + desc = "This spell triggers another spell or a few." + + var/list/linked_spells = list() //those are just referenced by the trigger spell and are unaffected by it directly + var/list/starting_spells = list() //those are added on New() to contents from default spells and are deleted when the trigger spell is deleted to prevent memory leaks + +/obj/effect/proc_holder/spell/targeted/trigger/Initialize() + . = ..() + + for(var/spell in starting_spells) + var/spell_to_add = text2path(spell) + new spell_to_add(src) //should result in adding to contents, needs testing + +/obj/effect/proc_holder/spell/targeted/trigger/Destroy() + for(var/spell in contents) + qdel(spell) + linked_spells = null + starting_spells = null + return ..() + +/obj/effect/proc_holder/spell/targeted/trigger/cast(list/targets,mob/user = usr) + playMagSound() + for(var/mob/living/target in targets) + for(var/obj/effect/proc_holder/spell/spell in contents) + spell.perform(list(target),0) + for(var/obj/effect/proc_holder/spell/spell in linked_spells) + spell.perform(list(target),0) + return \ No newline at end of file diff --git a/code/modules/spells/spell_types/wizard.dm b/code/modules/spells/spell_types/wizard.dm index c4d2c34a71..262f656967 100644 --- a/code/modules/spells/spell_types/wizard.dm +++ b/code/modules/spells/spell_types/wizard.dm @@ -1,378 +1,378 @@ -/obj/effect/proc_holder/spell/targeted/projectile/magic_missile - name = "Magic Missile" - desc = "This spell fires several, slow moving, magic projectiles at nearby targets." - - school = "evocation" - charge_max = 200 - clothes_req = 1 - invocation = "FORTI GY AMA" - invocation_type = "shout" - range = 7 - cooldown_min = 60 //35 deciseconds reduction per rank - - max_targets = 0 - - proj_icon_state = "magicm" - proj_name = "a magic missile" - proj_lingering = 1 - proj_type = "/obj/effect/proc_holder/spell/targeted/inflict_handler/magic_missile" - - proj_lifespan = 20 - proj_step_delay = 5 - - proj_trail = 1 - proj_trail_lifespan = 5 - proj_trail_icon_state = "magicmd" - - action_icon_state = "magicm" - sound = 'sound/magic/magic_missile.ogg' - -/obj/effect/proc_holder/spell/targeted/inflict_handler/magic_missile - amt_knockdown = 120 - amt_hardstun = 5 - sound = 'sound/magic/mm_hit.ogg' - -/obj/effect/proc_holder/spell/targeted/genetic/mutate - name = "Mutate" - desc = "This spell causes you to turn into a hulk and gain laser vision for a short while." - - school = "transmutation" - charge_max = 400 - clothes_req = 1 - invocation = "BIRUZ BENNAR" - invocation_type = "shout" - range = -1 - include_user = 1 - - mutations = list(LASEREYES, HULK) - duration = 300 - cooldown_min = 300 //25 deciseconds reduction per rank - - action_icon_state = "mutate" - sound = 'sound/magic/mutate.ogg' - - -/obj/effect/proc_holder/spell/targeted/smoke - name = "Smoke" - desc = "This spell spawns a cloud of choking smoke at your location and does not require wizard garb." - - school = "conjuration" - charge_max = 120 - clothes_req = 0 - invocation = "none" - invocation_type = "none" - range = -1 - include_user = 1 - cooldown_min = 20 //25 deciseconds reduction per rank - - smoke_spread = 2 - smoke_amt = 4 - - action_icon_state = "smoke" - - -/obj/effect/proc_holder/spell/targeted/smoke/lesser //Chaplain smoke book - name = "Smoke" - desc = "This spell spawns a small cloud of choking smoke at your location." - - school = "conjuration" - charge_max = 360 - clothes_req = 0 - invocation = "none" - invocation_type = "none" - range = -1 - include_user = 1 - - smoke_spread = 1 - smoke_amt = 2 - - action_icon_state = "smoke" - -/obj/effect/proc_holder/spell/targeted/emplosion/disable_tech - name = "Disable Tech" - desc = "This spell disables all weapons, cameras and most other technology in range." - charge_max = 400 - clothes_req = 1 - invocation = "NEC CANTIO" - invocation_type = "shout" - range = -1 - include_user = 1 - cooldown_min = 200 //50 deciseconds reduction per rank - - emp_heavy = 6 - emp_light = 10 - sound = 'sound/magic/disable_tech.ogg' - -/obj/effect/proc_holder/spell/targeted/turf_teleport/blink - name = "Blink" - desc = "This spell randomly teleports you a short distance." - - school = "abjuration" - charge_max = 20 - clothes_req = 1 - invocation = "none" - invocation_type = "none" - range = -1 - include_user = 1 - cooldown_min = 5 //4 deciseconds reduction per rank - - - smoke_spread = 1 - smoke_amt = 0 - - inner_tele_radius = 0 - outer_tele_radius = 6 - - action_icon_state = "blink" - sound1 = 'sound/magic/blink.ogg' - sound2 = 'sound/magic/blink.ogg' - -/obj/effect/proc_holder/spell/targeted/turf_teleport/blink/cult - name = "quickstep" - - charge_max = 100 - clothes_req = 0 - cult_req = 1 - -/obj/effect/proc_holder/spell/targeted/area_teleport/teleport - name = "Teleport" - desc = "This spell teleports you to a type of area of your selection." - - school = "abjuration" - charge_max = 600 - clothes_req = 1 - invocation = "SCYAR NILA" - invocation_type = "shout" - range = -1 - include_user = 1 - cooldown_min = 200 //100 deciseconds reduction per rank - - smoke_spread = 1 - smoke_amt = 2 - sound1 = 'sound/magic/teleport_diss.ogg' - sound2 = 'sound/magic/teleport_app.ogg' - -/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop - name = "Stop Time" - desc = "This spell stops time for everyone except for you, allowing you to move freely while your enemies and even projectiles are frozen." - charge_max = 500 - clothes_req = 1 - invocation = "TOKI WO TOMARE" - invocation_type = "shout" - range = 0 - cooldown_min = 100 - summon_amt = 1 - action_icon_state = "time" - - summon_type = list(/obj/effect/timestop/wizard) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/carp - name = "Summon Carp" - desc = "This spell conjures a simple carp." - - school = "conjuration" - charge_max = 1200 - clothes_req = 1 - invocation = "NOUK FHUNMM SACP RISSKA" - invocation_type = "shout" - range = 1 - - summon_type = list(/mob/living/simple_animal/hostile/carp) - cast_sound = 'sound/magic/summon_karp.ogg' - - -/obj/effect/proc_holder/spell/aoe_turf/conjure/construct - name = "Artificer" - desc = "This spell conjures a construct which may be controlled by Shades." - - school = "conjuration" - charge_max = 600 - clothes_req = 0 - invocation = "none" - invocation_type = "none" - range = 0 - - summon_type = list(/obj/structure/constructshell) - - action_icon_state = "artificer" - cast_sound = 'sound/magic/summonitems_generic.ogg' - - -/obj/effect/proc_holder/spell/aoe_turf/conjure/creature - name = "Summon Creature Swarm" - desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth." - - school = "conjuration" - charge_max = 1200 - clothes_req = 0 - invocation = "IA IA" - invocation_type = "shout" - summon_amt = 10 - range = 3 - - summon_type = list(/mob/living/simple_animal/hostile/netherworld) - cast_sound = 'sound/magic/summonitems_generic.ogg' - -/obj/effect/proc_holder/spell/targeted/trigger/blind - name = "Blind" - desc = "This spell temporarily blinds a single person and does not require wizard garb." - - school = "transmutation" - charge_max = 300 - clothes_req = 0 - invocation = "STI KALY" - invocation_type = "whisper" - message = "Your eyes cry out in pain!" - cooldown_min = 50 //12 deciseconds reduction per rank - - starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind","/obj/effect/proc_holder/spell/targeted/genetic/blind") - - action_icon_state = "blind" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/creature/cult - name = "Summon Creatures (DANGEROUS)" - cult_req = 1 - charge_max = 5000 - summon_amt = 2 - - - -/obj/effect/proc_holder/spell/targeted/inflict_handler/blind - amt_eye_blind = 10 - amt_eye_blurry = 20 - sound = 'sound/magic/blind.ogg' - -/obj/effect/proc_holder/spell/targeted/genetic/blind - mutations = list(BLINDMUT) - duration = 300 - sound = 'sound/magic/blind.ogg' -/obj/effect/proc_holder/spell/aoe_turf/repulse - name = "Repulse" - desc = "This spell throws everything around the user away." - charge_max = 400 - clothes_req = 1 - invocation = "GITTAH WEIGH" - invocation_type = "shout" - range = 5 - cooldown_min = 150 - selection_type = "view" - sound = 'sound/magic/repulse.ogg' - var/maxthrow = 5 - var/sparkle_path = /obj/effect/temp_visual/gravpush - var/anti_magic_check = TRUE - - action_icon_state = "repulse" - -/obj/effect/proc_holder/spell/aoe_turf/repulse/cast(list/targets,mob/user = usr, stun_amt = 50) - var/list/thrownatoms = list() - var/atom/throwtarget - var/distfromcaster - playMagSound() - for(var/turf/T in targets) //Done this way so things don't get thrown all around hilariously. - for(var/atom/movable/AM in T) - thrownatoms += AM - - for(var/am in thrownatoms) - var/atom/movable/AM = am - if(AM == user || AM.anchored) - continue - - if(ismob(AM)) - var/mob/M = AM - if(M.anti_magic_check(anti_magic_check, FALSE)) - continue - - throwtarget = get_edge_target_turf(user, get_dir(user, get_step_away(AM, user))) - distfromcaster = get_dist(user, AM) - if(distfromcaster == 0) - if(isliving(AM)) - var/mob/living/M = AM - M.Knockdown(100, override_hardstun = 20) - M.adjustBruteLoss(5) - to_chat(M, "You're slammed into the floor by [user]!") - else - new sparkle_path(get_turf(AM), get_dir(user, AM)) //created sparkles will disappear on their own - if(isliving(AM)) - var/mob/living/M = AM - M.Knockdown(stun_amt, override_hardstun = stun_amt * 0.2) - to_chat(M, "You're thrown back by [user]!") - AM.throw_at(throwtarget, ((CLAMP((maxthrow - (CLAMP(distfromcaster - 2, 0, distfromcaster))), 3, maxthrow))), 1,user)//So stuff gets tossed around at the same time. - -/obj/effect/proc_holder/spell/aoe_turf/repulse/xeno //i fixed conflicts only to find out that this is in the WIZARD file instead of the xeno file?! - name = "Tail Sweep" - desc = "Throw back attackers with a sweep of your tail." - sound = 'sound/magic/tail_swing.ogg' - charge_max = 150 - clothes_req = 0 - range = 2 - cooldown_min = 150 - invocation_type = "none" - sparkle_path = /obj/effect/temp_visual/dir_setting/tailsweep - action_icon = 'icons/mob/actions/actions_xeno.dmi' - action_icon_state = "tailsweep" - action_background_icon_state = "bg_alien" - anti_magic_check = FALSE - -/obj/effect/proc_holder/spell/aoe_turf/repulse/xeno/cast(list/targets,mob/user = usr) - if(iscarbon(user)) - var/mob/living/carbon/C = user - playsound(C.loc, 'sound/voice/hiss5.ogg', 80, 1, 1) - C.spin(6,1) - ..(targets, user, 60) - -/obj/effect/proc_holder/spell/targeted/sacred_flame - name = "Sacred Flame" - desc = "Makes everyone around you more flammable, and lights yourself on fire." - charge_max = 60 - clothes_req = 0 - invocation = "FI'RAN DADISKO" - invocation_type = "shout" - max_targets = 0 - range = 6 - include_user = 1 - selection_type = "view" - action_icon_state = "sacredflame" - sound = 'sound/magic/fireball.ogg' - -/obj/effect/proc_holder/spell/targeted/sacred_flame/cast(list/targets, mob/user = usr) - for(var/mob/living/L in targets) - if(L.anti_magic_check(TRUE, TRUE)) - continue - L.adjust_fire_stacks(20) - if(isliving(user)) - var/mob/living/U = user - if(!U.anti_magic_check(TRUE, TRUE)) - U.IgniteMob() - -/obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket - name = "Thrown Lightning" - desc = "Forged from eldrich energies, a packet of pure power, known as a spell packet will appear in your hand, that when thrown will stun the target." - clothes_req = 1 - item_type = /obj/item/spellpacket/lightningbolt - charge_max = 10 - -/obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket/cast(list/targets, mob/user = usr) - ..() - for(var/mob/living/carbon/C in targets) - C.throw_mode_on() - -/obj/item/spellpacket/lightningbolt - name = "\improper Lightning bolt Spell Packet" - desc = "Some birdseed wrapped in cloth that somehow crackles with electricity." - icon = 'icons/obj/toy.dmi' - icon_state = "snappop" - w_class = WEIGHT_CLASS_TINY - -/obj/item/spellpacket/lightningbolt/throw_impact(atom/hit_atom) - if(!..()) - if(isliving(hit_atom)) - var/mob/living/M = hit_atom - if(!M.anti_magic_check()) - M.electrocute_act(80, src, illusion = 1) - qdel(src) - -/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback) - . = ..() - if(ishuman(thrower)) - var/mob/living/carbon/human/H = thrower - H.say("LIGHTNINGBOLT!!", forced = "spell") +/obj/effect/proc_holder/spell/targeted/projectile/magic_missile + name = "Magic Missile" + desc = "This spell fires several, slow moving, magic projectiles at nearby targets." + + school = "evocation" + charge_max = 200 + clothes_req = 1 + invocation = "FORTI GY AMA" + invocation_type = "shout" + range = 7 + cooldown_min = 60 //35 deciseconds reduction per rank + + max_targets = 0 + + proj_icon_state = "magicm" + proj_name = "a magic missile" + proj_lingering = 1 + proj_type = "/obj/effect/proc_holder/spell/targeted/inflict_handler/magic_missile" + + proj_lifespan = 20 + proj_step_delay = 5 + + proj_trail = 1 + proj_trail_lifespan = 5 + proj_trail_icon_state = "magicmd" + + action_icon_state = "magicm" + sound = 'sound/magic/magic_missile.ogg' + +/obj/effect/proc_holder/spell/targeted/inflict_handler/magic_missile + amt_knockdown = 120 + amt_hardstun = 5 + sound = 'sound/magic/mm_hit.ogg' + +/obj/effect/proc_holder/spell/targeted/genetic/mutate + name = "Mutate" + desc = "This spell causes you to turn into a hulk and gain laser vision for a short while." + + school = "transmutation" + charge_max = 400 + clothes_req = 1 + invocation = "BIRUZ BENNAR" + invocation_type = "shout" + range = -1 + include_user = 1 + + mutations = list(LASEREYES, HULK) + duration = 300 + cooldown_min = 300 //25 deciseconds reduction per rank + + action_icon_state = "mutate" + sound = 'sound/magic/mutate.ogg' + + +/obj/effect/proc_holder/spell/targeted/smoke + name = "Smoke" + desc = "This spell spawns a cloud of choking smoke at your location and does not require wizard garb." + + school = "conjuration" + charge_max = 120 + clothes_req = 0 + invocation = "none" + invocation_type = "none" + range = -1 + include_user = 1 + cooldown_min = 20 //25 deciseconds reduction per rank + + smoke_spread = 2 + smoke_amt = 4 + + action_icon_state = "smoke" + + +/obj/effect/proc_holder/spell/targeted/smoke/lesser //Chaplain smoke book + name = "Smoke" + desc = "This spell spawns a small cloud of choking smoke at your location." + + school = "conjuration" + charge_max = 360 + clothes_req = 0 + invocation = "none" + invocation_type = "none" + range = -1 + include_user = 1 + + smoke_spread = 1 + smoke_amt = 2 + + action_icon_state = "smoke" + +/obj/effect/proc_holder/spell/targeted/emplosion/disable_tech + name = "Disable Tech" + desc = "This spell disables all weapons, cameras and most other technology in range." + charge_max = 400 + clothes_req = 1 + invocation = "NEC CANTIO" + invocation_type = "shout" + range = -1 + include_user = 1 + cooldown_min = 200 //50 deciseconds reduction per rank + + emp_heavy = 6 + emp_light = 10 + sound = 'sound/magic/disable_tech.ogg' + +/obj/effect/proc_holder/spell/targeted/turf_teleport/blink + name = "Blink" + desc = "This spell randomly teleports you a short distance." + + school = "abjuration" + charge_max = 20 + clothes_req = 1 + invocation = "none" + invocation_type = "none" + range = -1 + include_user = 1 + cooldown_min = 5 //4 deciseconds reduction per rank + + + smoke_spread = 1 + smoke_amt = 0 + + inner_tele_radius = 0 + outer_tele_radius = 6 + + action_icon_state = "blink" + sound1 = 'sound/magic/blink.ogg' + sound2 = 'sound/magic/blink.ogg' + +/obj/effect/proc_holder/spell/targeted/turf_teleport/blink/cult + name = "quickstep" + + charge_max = 100 + clothes_req = 0 + cult_req = 1 + +/obj/effect/proc_holder/spell/targeted/area_teleport/teleport + name = "Teleport" + desc = "This spell teleports you to a type of area of your selection." + + school = "abjuration" + charge_max = 600 + clothes_req = 1 + invocation = "SCYAR NILA" + invocation_type = "shout" + range = -1 + include_user = 1 + cooldown_min = 200 //100 deciseconds reduction per rank + + smoke_spread = 1 + smoke_amt = 2 + sound1 = 'sound/magic/teleport_diss.ogg' + sound2 = 'sound/magic/teleport_app.ogg' + +/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop + name = "Stop Time" + desc = "This spell stops time for everyone except for you, allowing you to move freely while your enemies and even projectiles are frozen." + charge_max = 500 + clothes_req = 1 + invocation = "TOKI WO TOMARE" + invocation_type = "shout" + range = 0 + cooldown_min = 100 + summon_amt = 1 + action_icon_state = "time" + + summon_type = list(/obj/effect/timestop/wizard) + +/obj/effect/proc_holder/spell/aoe_turf/conjure/carp + name = "Summon Carp" + desc = "This spell conjures a simple carp." + + school = "conjuration" + charge_max = 1200 + clothes_req = 1 + invocation = "NOUK FHUNMM SACP RISSKA" + invocation_type = "shout" + range = 1 + + summon_type = list(/mob/living/simple_animal/hostile/carp) + cast_sound = 'sound/magic/summon_karp.ogg' + + +/obj/effect/proc_holder/spell/aoe_turf/conjure/construct + name = "Artificer" + desc = "This spell conjures a construct which may be controlled by Shades." + + school = "conjuration" + charge_max = 600 + clothes_req = 0 + invocation = "none" + invocation_type = "none" + range = 0 + + summon_type = list(/obj/structure/constructshell) + + action_icon_state = "artificer" + cast_sound = 'sound/magic/summonitems_generic.ogg' + + +/obj/effect/proc_holder/spell/aoe_turf/conjure/creature + name = "Summon Creature Swarm" + desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth." + + school = "conjuration" + charge_max = 1200 + clothes_req = 0 + invocation = "IA IA" + invocation_type = "shout" + summon_amt = 10 + range = 3 + + summon_type = list(/mob/living/simple_animal/hostile/netherworld) + cast_sound = 'sound/magic/summonitems_generic.ogg' + +/obj/effect/proc_holder/spell/targeted/trigger/blind + name = "Blind" + desc = "This spell temporarily blinds a single person and does not require wizard garb." + + school = "transmutation" + charge_max = 300 + clothes_req = 0 + invocation = "STI KALY" + invocation_type = "whisper" + message = "Your eyes cry out in pain!" + cooldown_min = 50 //12 deciseconds reduction per rank + + starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind","/obj/effect/proc_holder/spell/targeted/genetic/blind") + + action_icon_state = "blind" + +/obj/effect/proc_holder/spell/aoe_turf/conjure/creature/cult + name = "Summon Creatures (DANGEROUS)" + cult_req = 1 + charge_max = 5000 + summon_amt = 2 + + + +/obj/effect/proc_holder/spell/targeted/inflict_handler/blind + amt_eye_blind = 10 + amt_eye_blurry = 20 + sound = 'sound/magic/blind.ogg' + +/obj/effect/proc_holder/spell/targeted/genetic/blind + mutations = list(BLINDMUT) + duration = 300 + sound = 'sound/magic/blind.ogg' +/obj/effect/proc_holder/spell/aoe_turf/repulse + name = "Repulse" + desc = "This spell throws everything around the user away." + charge_max = 400 + clothes_req = 1 + invocation = "GITTAH WEIGH" + invocation_type = "shout" + range = 5 + cooldown_min = 150 + selection_type = "view" + sound = 'sound/magic/repulse.ogg' + var/maxthrow = 5 + var/sparkle_path = /obj/effect/temp_visual/gravpush + var/anti_magic_check = TRUE + + action_icon_state = "repulse" + +/obj/effect/proc_holder/spell/aoe_turf/repulse/cast(list/targets,mob/user = usr, stun_amt = 50) + var/list/thrownatoms = list() + var/atom/throwtarget + var/distfromcaster + playMagSound() + for(var/turf/T in targets) //Done this way so things don't get thrown all around hilariously. + for(var/atom/movable/AM in T) + thrownatoms += AM + + for(var/am in thrownatoms) + var/atom/movable/AM = am + if(AM == user || AM.anchored) + continue + + if(ismob(AM)) + var/mob/M = AM + if(M.anti_magic_check(anti_magic_check, FALSE)) + continue + + throwtarget = get_edge_target_turf(user, get_dir(user, get_step_away(AM, user))) + distfromcaster = get_dist(user, AM) + if(distfromcaster == 0) + if(isliving(AM)) + var/mob/living/M = AM + M.Knockdown(100, override_hardstun = 20) + M.adjustBruteLoss(5) + to_chat(M, "You're slammed into the floor by [user]!") + else + new sparkle_path(get_turf(AM), get_dir(user, AM)) //created sparkles will disappear on their own + if(isliving(AM)) + var/mob/living/M = AM + M.Knockdown(stun_amt, override_hardstun = stun_amt * 0.2) + to_chat(M, "You're thrown back by [user]!") + AM.throw_at(throwtarget, ((CLAMP((maxthrow - (CLAMP(distfromcaster - 2, 0, distfromcaster))), 3, maxthrow))), 1,user)//So stuff gets tossed around at the same time. + +/obj/effect/proc_holder/spell/aoe_turf/repulse/xeno //i fixed conflicts only to find out that this is in the WIZARD file instead of the xeno file?! + name = "Tail Sweep" + desc = "Throw back attackers with a sweep of your tail." + sound = 'sound/magic/tail_swing.ogg' + charge_max = 150 + clothes_req = 0 + range = 2 + cooldown_min = 150 + invocation_type = "none" + sparkle_path = /obj/effect/temp_visual/dir_setting/tailsweep + action_icon = 'icons/mob/actions/actions_xeno.dmi' + action_icon_state = "tailsweep" + action_background_icon_state = "bg_alien" + anti_magic_check = FALSE + +/obj/effect/proc_holder/spell/aoe_turf/repulse/xeno/cast(list/targets,mob/user = usr) + if(iscarbon(user)) + var/mob/living/carbon/C = user + playsound(C.loc, 'sound/voice/hiss5.ogg', 80, 1, 1) + C.spin(6,1) + ..(targets, user, 60) + +/obj/effect/proc_holder/spell/targeted/sacred_flame + name = "Sacred Flame" + desc = "Makes everyone around you more flammable, and lights yourself on fire." + charge_max = 60 + clothes_req = 0 + invocation = "FI'RAN DADISKO" + invocation_type = "shout" + max_targets = 0 + range = 6 + include_user = 1 + selection_type = "view" + action_icon_state = "sacredflame" + sound = 'sound/magic/fireball.ogg' + +/obj/effect/proc_holder/spell/targeted/sacred_flame/cast(list/targets, mob/user = usr) + for(var/mob/living/L in targets) + if(L.anti_magic_check(TRUE, TRUE)) + continue + L.adjust_fire_stacks(20) + if(isliving(user)) + var/mob/living/U = user + if(!U.anti_magic_check(TRUE, TRUE)) + U.IgniteMob() + +/obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket + name = "Thrown Lightning" + desc = "Forged from eldrich energies, a packet of pure power, known as a spell packet will appear in your hand, that when thrown will stun the target." + clothes_req = 1 + item_type = /obj/item/spellpacket/lightningbolt + charge_max = 10 + +/obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket/cast(list/targets, mob/user = usr) + ..() + for(var/mob/living/carbon/C in targets) + C.throw_mode_on() + +/obj/item/spellpacket/lightningbolt + name = "\improper Lightning bolt Spell Packet" + desc = "Some birdseed wrapped in cloth that somehow crackles with electricity." + icon = 'icons/obj/toy.dmi' + icon_state = "snappop" + w_class = WEIGHT_CLASS_TINY + +/obj/item/spellpacket/lightningbolt/throw_impact(atom/hit_atom) + if(!..()) + if(isliving(hit_atom)) + var/mob/living/M = hit_atom + if(!M.anti_magic_check()) + M.electrocute_act(80, src, illusion = 1) + qdel(src) + +/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback) + . = ..() + if(ishuman(thrower)) + var/mob/living/carbon/human/H = thrower + H.say("LIGHTNINGBOLT!!", forced = "spell") diff --git a/code/modules/surgery/core_removal.dm b/code/modules/surgery/core_removal.dm index 3f36a8bb8c..07b762bd2f 100644 --- a/code/modules/surgery/core_removal.dm +++ b/code/modules/surgery/core_removal.dm @@ -1,41 +1,41 @@ -/datum/surgery/core_removal - name = "Core removal" - steps = list(/datum/surgery_step/incise, /datum/surgery_step/extract_core) - target_mobtypes = list(/mob/living/simple_animal/slime) - possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD) - lying_required = FALSE - ignore_clothes = TRUE - -/datum/surgery/core_removal/can_start(mob/user, mob/living/target) - if(target.stat == DEAD) - return 1 - return 0 -//extract brain -/datum/surgery_step/extract_core - name = "extract core" - implements = list(TOOL_HEMOSTAT = 100, TOOL_CROWBAR = 100) - time = 16 - -/datum/surgery_step/extract_core/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You begin to extract a core from [target]...", - "[user] begins to extract a core from [target].", - "[user] begins to extract a core from [target].") - -/datum/surgery_step/extract_core/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/mob/living/simple_animal/slime/slime = target - if(slime.cores > 0) - slime.cores-- - display_results(user, target, "You successfully extract a core from [target]. [slime.cores] core\s remaining.", - "[user] successfully extracts a core from [target]!", - "[user] successfully extracts a core from [target]!") - - new slime.coretype(slime.loc) - - if(slime.cores <= 0) - slime.icon_state = "[slime.colour] baby slime dead-nocore" - return 1 - else - return 0 - else - to_chat(user, "There aren't any cores left in [target]!") - return 1 +/datum/surgery/core_removal + name = "Core removal" + steps = list(/datum/surgery_step/incise, /datum/surgery_step/extract_core) + target_mobtypes = list(/mob/living/simple_animal/slime) + possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD) + lying_required = FALSE + ignore_clothes = TRUE + +/datum/surgery/core_removal/can_start(mob/user, mob/living/target) + if(target.stat == DEAD) + return 1 + return 0 +//extract brain +/datum/surgery_step/extract_core + name = "extract core" + implements = list(TOOL_HEMOSTAT = 100, TOOL_CROWBAR = 100) + time = 16 + +/datum/surgery_step/extract_core/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You begin to extract a core from [target]...", + "[user] begins to extract a core from [target].", + "[user] begins to extract a core from [target].") + +/datum/surgery_step/extract_core/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + var/mob/living/simple_animal/slime/slime = target + if(slime.cores > 0) + slime.cores-- + display_results(user, target, "You successfully extract a core from [target]. [slime.cores] core\s remaining.", + "[user] successfully extracts a core from [target]!", + "[user] successfully extracts a core from [target]!") + + new slime.coretype(slime.loc) + + if(slime.cores <= 0) + slime.icon_state = "[slime.colour] baby slime dead-nocore" + return 1 + else + return 0 + else + to_chat(user, "There aren't any cores left in [target]!") + return 1 diff --git a/code/modules/surgery/eye_surgery.dm b/code/modules/surgery/eye_surgery.dm index 28e813ccac..c104afd25a 100644 --- a/code/modules/surgery/eye_surgery.dm +++ b/code/modules/surgery/eye_surgery.dm @@ -1,46 +1,46 @@ -/datum/surgery/eye_surgery - name = "Eye surgery" - steps = list(/datum/surgery_step/incise, /datum/surgery_step/retract_skin, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/fix_eyes, /datum/surgery_step/close) - target_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - possible_locs = list(BODY_ZONE_PRECISE_EYES) - requires_bodypart_type = 0 -//fix eyes -/datum/surgery_step/fix_eyes - name = "fix eyes" - implements = list(TOOL_HEMOSTAT = 100, TOOL_SCREWDRIVER = 45, /obj/item/pen = 25) - time = 64 -/datum/surgery/eye_surgery/can_start(mob/user, mob/living/carbon/target) - var/obj/item/organ/eyes/E = target.getorganslot(ORGAN_SLOT_EYES) - if(!E) - to_chat(user, "It's hard to do surgery on someone's eyes when [target.p_they()] [target.p_do()]n't have any.") - return FALSE - return TRUE - -/datum/surgery_step/fix_eyes/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You begin to fix [target]'s eyes...", - "[user] begins to fix [target]'s eyes.", - "[user] begins to perform surgery on [target]'s eyes.") - -/datum/surgery_step/fix_eyes/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - var/obj/item/organ/eyes/E = target.getorganslot(ORGAN_SLOT_EYES) - display_results(user, target, "You succeed in fixing [target]'s eyes.", - "[user] successfully fixes [target]'s eyes!", - "[user] completes the surgery on [target]'s eyes.") - target.cure_blind(list(EYE_DAMAGE)) - target.set_blindness(0) - target.cure_nearsighted(list(EYE_DAMAGE)) - target.blur_eyes(35) //this will fix itself slowly. - E.setOrganDamage(0) - return TRUE - -/datum/surgery_step/fix_eyes/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(target.getorgan(/obj/item/organ/brain)) - display_results(user, target, "You accidentally stab [target] right in the brain!", - "[user] accidentally stabs [target] right in the brain!", - "[user] accidentally stabs [target] right in the brain!") - target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 70) - else - display_results(user, target, "You accidentally stab [target] right in the brain! Or would have, if [target] had a brain.", - "[user] accidentally stabs [target] right in the brain! Or would have, if [target] had a brain.", - "[user] accidentally stabs [target] right in the brain!") - return FALSE +/datum/surgery/eye_surgery + name = "Eye surgery" + steps = list(/datum/surgery_step/incise, /datum/surgery_step/retract_skin, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/fix_eyes, /datum/surgery_step/close) + target_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + possible_locs = list(BODY_ZONE_PRECISE_EYES) + requires_bodypart_type = 0 +//fix eyes +/datum/surgery_step/fix_eyes + name = "fix eyes" + implements = list(TOOL_HEMOSTAT = 100, TOOL_SCREWDRIVER = 45, /obj/item/pen = 25) + time = 64 +/datum/surgery/eye_surgery/can_start(mob/user, mob/living/carbon/target) + var/obj/item/organ/eyes/E = target.getorganslot(ORGAN_SLOT_EYES) + if(!E) + to_chat(user, "It's hard to do surgery on someone's eyes when [target.p_they()] [target.p_do()]n't have any.") + return FALSE + return TRUE + +/datum/surgery_step/fix_eyes/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You begin to fix [target]'s eyes...", + "[user] begins to fix [target]'s eyes.", + "[user] begins to perform surgery on [target]'s eyes.") + +/datum/surgery_step/fix_eyes/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + var/obj/item/organ/eyes/E = target.getorganslot(ORGAN_SLOT_EYES) + display_results(user, target, "You succeed in fixing [target]'s eyes.", + "[user] successfully fixes [target]'s eyes!", + "[user] completes the surgery on [target]'s eyes.") + target.cure_blind(list(EYE_DAMAGE)) + target.set_blindness(0) + target.cure_nearsighted(list(EYE_DAMAGE)) + target.blur_eyes(35) //this will fix itself slowly. + E.setOrganDamage(0) + return TRUE + +/datum/surgery_step/fix_eyes/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + if(target.getorgan(/obj/item/organ/brain)) + display_results(user, target, "You accidentally stab [target] right in the brain!", + "[user] accidentally stabs [target] right in the brain!", + "[user] accidentally stabs [target] right in the brain!") + target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 70) + else + display_results(user, target, "You accidentally stab [target] right in the brain! Or would have, if [target] had a brain.", + "[user] accidentally stabs [target] right in the brain! Or would have, if [target] had a brain.", + "[user] accidentally stabs [target] right in the brain!") + return FALSE diff --git a/code/modules/surgery/helpers.dm b/code/modules/surgery/helpers.dm index f52bf97147..cdae1aea48 100644 --- a/code/modules/surgery/helpers.dm +++ b/code/modules/surgery/helpers.dm @@ -1,175 +1,175 @@ -/proc/attempt_initiate_surgery(obj/item/I, mob/living/M, mob/user) - if(!istype(M)) - return - - var/mob/living/carbon/C - var/obj/item/bodypart/affecting - var/selected_zone = user.zone_selected - - if(iscarbon(M)) - C = M - affecting = C.get_bodypart(check_zone(selected_zone)) - - var/datum/surgery/current_surgery - - for(var/datum/surgery/S in M.surgeries) - if(S.location == selected_zone) - current_surgery = S - - if(!current_surgery) - var/list/all_surgeries = GLOB.surgeries_list.Copy() - var/list/available_surgeries = list() - - for(var/datum/surgery/S in all_surgeries) - if(!S.possible_locs.Find(selected_zone)) - continue - if(affecting) - if(!S.requires_bodypart) - continue - if(S.requires_bodypart_type && affecting.status != S.requires_bodypart_type) - continue - if(S.requires_real_bodypart && affecting.is_pseudopart) - continue - else if(C && S.requires_bodypart) //mob with no limb in surgery zone when we need a limb - continue - if(S.lying_required && !(M.lying)) - continue - if(!S.can_start(user, M)) - continue - for(var/path in S.target_mobtypes) - if(istype(M, path)) - available_surgeries[S.name] = S - break - - if(!available_surgeries.len) - return - - var/P = input("Begin which procedure?", "Surgery", null, null) as null|anything in available_surgeries - if(P && user && user.Adjacent(M) && (I in user)) - var/datum/surgery/S = available_surgeries[P] - - for(var/datum/surgery/other in M.surgeries) - if(other.location == S.location) - return //during the input() another surgery was started at the same location. - - //we check that the surgery is still doable after the input() wait. - if(C) - affecting = C.get_bodypart(check_zone(selected_zone)) - if(affecting) - if(!S.requires_bodypart) - return - if(S.requires_bodypart_type && affecting.status != S.requires_bodypart_type) - return - else if(C && S.requires_bodypart) - return - if(S.lying_required && !(M.lying)) - return - if(!S.can_start(user, M)) - return - - if(S.ignore_clothes || get_location_accessible(M, selected_zone)) - var/datum/surgery/procedure = new S.type(M, selected_zone, affecting) - user.visible_message("[user] drapes [I] over [M]'s [parse_zone(selected_zone)] to prepare for surgery.", \ - "You drape [I] over [M]'s [parse_zone(selected_zone)] to prepare for \an [procedure.name].") - - log_combat(user, M, "operated on", null, "(OPERATION TYPE: [procedure.name]) (TARGET AREA: [selected_zone])") - else - to_chat(user, "You need to expose [M]'s [parse_zone(selected_zone)] first!") - - else if(!current_surgery.step_in_progress) - attempt_cancel_surgery(current_surgery, I, M, user) - - return 1 - -/proc/attempt_cancel_surgery(datum/surgery/S, obj/item/I, mob/living/M, mob/user) - var/selected_zone = user.zone_selected - if(S.status == 1) - M.surgeries -= S - user.visible_message("[user] removes [I] from [M]'s [parse_zone(selected_zone)].", \ - "You remove [I] from [M]'s [parse_zone(selected_zone)].") - qdel(S) - else if(S.can_cancel) - var/close_tool_type = /obj/item/cautery - var/obj/item/close_tool = user.get_inactive_held_item() - var/is_robotic = S.requires_bodypart_type == BODYPART_ROBOTIC - if(is_robotic) - close_tool_type = /obj/item/screwdriver - if(istype(close_tool, close_tool_type) || iscyborg(user)) - M.surgeries -= S - user.visible_message("[user] closes [M]'s [parse_zone(selected_zone)] with [close_tool] and removes [I].", \ - "You close [M]'s [parse_zone(selected_zone)] with [close_tool] and remove [I].") - qdel(S) - else - to_chat(user, "You need to hold a [is_robotic ? "screwdriver" : "cautery"] in your inactive hand to stop [M]'s surgery!") - -/proc/get_location_modifier(mob/M) - var/turf/T = get_turf(M) - if(locate(/obj/structure/table/optable, T)) - return 1 - else if(locate(/obj/structure/table, T)) - return 0.8 - else if(locate(/obj/structure/bed, T)) - return 0.7 - else - return 0.5 - - -/proc/get_location_accessible(mob/M, location) - var/covered_locations = 0 //based on body_parts_covered - var/face_covered = 0 //based on flags_inv - var/eyesmouth_covered = 0 //based on flags_cover - if(iscarbon(M)) - var/mob/living/carbon/C = M - for(var/obj/item/clothing/I in list(C.back, C.wear_mask, C.head)) - covered_locations |= I.body_parts_covered - face_covered |= I.flags_inv - eyesmouth_covered |= I.flags_cover - if(ishuman(C)) - var/mob/living/carbon/human/H = C - for(var/obj/item/I in list(H.wear_suit, H.w_uniform, H.shoes, H.belt, H.gloves, H.glasses, H.ears)) - covered_locations |= I.body_parts_covered - face_covered |= I.flags_inv - eyesmouth_covered |= I.flags_cover - - switch(location) - if(BODY_ZONE_HEAD) - if(covered_locations & HEAD) - return 0 - if(BODY_ZONE_PRECISE_EYES) - if(covered_locations & HEAD || face_covered & HIDEEYES || eyesmouth_covered & GLASSESCOVERSEYES) - return 0 - if(BODY_ZONE_PRECISE_MOUTH) - if(covered_locations & HEAD || face_covered & HIDEFACE || eyesmouth_covered & MASKCOVERSMOUTH || eyesmouth_covered & HEADCOVERSMOUTH) - return 0 - if(BODY_ZONE_CHEST) - if(covered_locations & CHEST) - return 0 - if(BODY_ZONE_PRECISE_GROIN) - if(covered_locations & GROIN) - return 0 - if(BODY_ZONE_L_ARM) - if(covered_locations & ARM_LEFT) - return 0 - if(BODY_ZONE_R_ARM) - if(covered_locations & ARM_RIGHT) - return 0 - if(BODY_ZONE_L_LEG) - if(covered_locations & LEG_LEFT) - return 0 - if(BODY_ZONE_R_LEG) - if(covered_locations & LEG_RIGHT) - return 0 - if(BODY_ZONE_PRECISE_L_HAND) - if(covered_locations & HAND_LEFT) - return 0 - if(BODY_ZONE_PRECISE_R_HAND) - if(covered_locations & HAND_RIGHT) - return 0 - if(BODY_ZONE_PRECISE_L_FOOT) - if(covered_locations & FOOT_LEFT) - return 0 - if(BODY_ZONE_PRECISE_R_FOOT) - if(covered_locations & FOOT_RIGHT) - return 0 - - return 1 +/proc/attempt_initiate_surgery(obj/item/I, mob/living/M, mob/user) + if(!istype(M)) + return + + var/mob/living/carbon/C + var/obj/item/bodypart/affecting + var/selected_zone = user.zone_selected + + if(iscarbon(M)) + C = M + affecting = C.get_bodypart(check_zone(selected_zone)) + + var/datum/surgery/current_surgery + + for(var/datum/surgery/S in M.surgeries) + if(S.location == selected_zone) + current_surgery = S + + if(!current_surgery) + var/list/all_surgeries = GLOB.surgeries_list.Copy() + var/list/available_surgeries = list() + + for(var/datum/surgery/S in all_surgeries) + if(!S.possible_locs.Find(selected_zone)) + continue + if(affecting) + if(!S.requires_bodypart) + continue + if(S.requires_bodypart_type && affecting.status != S.requires_bodypart_type) + continue + if(S.requires_real_bodypart && affecting.is_pseudopart) + continue + else if(C && S.requires_bodypart) //mob with no limb in surgery zone when we need a limb + continue + if(S.lying_required && !(M.lying)) + continue + if(!S.can_start(user, M)) + continue + for(var/path in S.target_mobtypes) + if(istype(M, path)) + available_surgeries[S.name] = S + break + + if(!available_surgeries.len) + return + + var/P = input("Begin which procedure?", "Surgery", null, null) as null|anything in available_surgeries + if(P && user && user.Adjacent(M) && (I in user)) + var/datum/surgery/S = available_surgeries[P] + + for(var/datum/surgery/other in M.surgeries) + if(other.location == S.location) + return //during the input() another surgery was started at the same location. + + //we check that the surgery is still doable after the input() wait. + if(C) + affecting = C.get_bodypart(check_zone(selected_zone)) + if(affecting) + if(!S.requires_bodypart) + return + if(S.requires_bodypart_type && affecting.status != S.requires_bodypart_type) + return + else if(C && S.requires_bodypart) + return + if(S.lying_required && !(M.lying)) + return + if(!S.can_start(user, M)) + return + + if(S.ignore_clothes || get_location_accessible(M, selected_zone)) + var/datum/surgery/procedure = new S.type(M, selected_zone, affecting) + user.visible_message("[user] drapes [I] over [M]'s [parse_zone(selected_zone)] to prepare for surgery.", \ + "You drape [I] over [M]'s [parse_zone(selected_zone)] to prepare for \an [procedure.name].") + + log_combat(user, M, "operated on", null, "(OPERATION TYPE: [procedure.name]) (TARGET AREA: [selected_zone])") + else + to_chat(user, "You need to expose [M]'s [parse_zone(selected_zone)] first!") + + else if(!current_surgery.step_in_progress) + attempt_cancel_surgery(current_surgery, I, M, user) + + return 1 + +/proc/attempt_cancel_surgery(datum/surgery/S, obj/item/I, mob/living/M, mob/user) + var/selected_zone = user.zone_selected + if(S.status == 1) + M.surgeries -= S + user.visible_message("[user] removes [I] from [M]'s [parse_zone(selected_zone)].", \ + "You remove [I] from [M]'s [parse_zone(selected_zone)].") + qdel(S) + else if(S.can_cancel) + var/close_tool_type = /obj/item/cautery + var/obj/item/close_tool = user.get_inactive_held_item() + var/is_robotic = S.requires_bodypart_type == BODYPART_ROBOTIC + if(is_robotic) + close_tool_type = /obj/item/screwdriver + if(istype(close_tool, close_tool_type) || iscyborg(user)) + M.surgeries -= S + user.visible_message("[user] closes [M]'s [parse_zone(selected_zone)] with [close_tool] and removes [I].", \ + "You close [M]'s [parse_zone(selected_zone)] with [close_tool] and remove [I].") + qdel(S) + else + to_chat(user, "You need to hold a [is_robotic ? "screwdriver" : "cautery"] in your inactive hand to stop [M]'s surgery!") + +/proc/get_location_modifier(mob/M) + var/turf/T = get_turf(M) + if(locate(/obj/structure/table/optable, T)) + return 1 + else if(locate(/obj/structure/table, T)) + return 0.8 + else if(locate(/obj/structure/bed, T)) + return 0.7 + else + return 0.5 + + +/proc/get_location_accessible(mob/M, location) + var/covered_locations = 0 //based on body_parts_covered + var/face_covered = 0 //based on flags_inv + var/eyesmouth_covered = 0 //based on flags_cover + if(iscarbon(M)) + var/mob/living/carbon/C = M + for(var/obj/item/clothing/I in list(C.back, C.wear_mask, C.head)) + covered_locations |= I.body_parts_covered + face_covered |= I.flags_inv + eyesmouth_covered |= I.flags_cover + if(ishuman(C)) + var/mob/living/carbon/human/H = C + for(var/obj/item/I in list(H.wear_suit, H.w_uniform, H.shoes, H.belt, H.gloves, H.glasses, H.ears)) + covered_locations |= I.body_parts_covered + face_covered |= I.flags_inv + eyesmouth_covered |= I.flags_cover + + switch(location) + if(BODY_ZONE_HEAD) + if(covered_locations & HEAD) + return 0 + if(BODY_ZONE_PRECISE_EYES) + if(covered_locations & HEAD || face_covered & HIDEEYES || eyesmouth_covered & GLASSESCOVERSEYES) + return 0 + if(BODY_ZONE_PRECISE_MOUTH) + if(covered_locations & HEAD || face_covered & HIDEFACE || eyesmouth_covered & MASKCOVERSMOUTH || eyesmouth_covered & HEADCOVERSMOUTH) + return 0 + if(BODY_ZONE_CHEST) + if(covered_locations & CHEST) + return 0 + if(BODY_ZONE_PRECISE_GROIN) + if(covered_locations & GROIN) + return 0 + if(BODY_ZONE_L_ARM) + if(covered_locations & ARM_LEFT) + return 0 + if(BODY_ZONE_R_ARM) + if(covered_locations & ARM_RIGHT) + return 0 + if(BODY_ZONE_L_LEG) + if(covered_locations & LEG_LEFT) + return 0 + if(BODY_ZONE_R_LEG) + if(covered_locations & LEG_RIGHT) + return 0 + if(BODY_ZONE_PRECISE_L_HAND) + if(covered_locations & HAND_LEFT) + return 0 + if(BODY_ZONE_PRECISE_R_HAND) + if(covered_locations & HAND_RIGHT) + return 0 + if(BODY_ZONE_PRECISE_L_FOOT) + if(covered_locations & FOOT_LEFT) + return 0 + if(BODY_ZONE_PRECISE_R_FOOT) + if(covered_locations & FOOT_RIGHT) + return 0 + + return 1 diff --git a/code/modules/surgery/implant_removal.dm b/code/modules/surgery/implant_removal.dm index 3eb05c91c7..7010638004 100644 --- a/code/modules/surgery/implant_removal.dm +++ b/code/modules/surgery/implant_removal.dm @@ -1,60 +1,60 @@ -/datum/surgery/implant_removal - name = "implant removal" - steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/extract_implant, /datum/surgery_step/close) - target_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) - possible_locs = list(BODY_ZONE_CHEST) -//extract implant -/datum/surgery_step/extract_implant - name = "extract implant" - implements = list(TOOL_HEMOSTAT = 100, TOOL_CROWBAR = 65) - time = 64 - var/obj/item/implant/I = null -/datum/surgery_step/extract_implant/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - for(var/obj/item/O in target.implants) - I = O - break - if(I) - display_results(user, target, "You begin to extract [I] from [target]'s [target_zone]...", - "[user] begins to extract [I] from [target]'s [target_zone].", - "[user] begins to extract something from [target]'s [target_zone].") - else - display_results(user, target, "You look for an implant in [target]'s [target_zone]...", - "[user] looks for an implant in [target]'s [target_zone].", - "[user] looks for something in [target]'s [target_zone].") - -/datum/surgery_step/extract_implant/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(I) - display_results(user, target, "You successfully remove [I] from [target]'s [target_zone].", - "[user] successfully removes [I] from [target]'s [target_zone]!", - "[user] successfully removes something from [target]'s [target_zone]!") - I.removed(target) - - var/obj/item/implantcase/case - for(var/obj/item/implantcase/ic in user.held_items) - case = ic - break - if(!case) - case = locate(/obj/item/implantcase) in get_turf(target) - if(case && !case.imp) - case.imp = I - I.forceMove(case) - case.update_icon() - display_results(user, target, "You place [I] into [case].", - "[user] places [I] into [case]!", - "[user] places it into [case]!") - else - qdel(I) - - else - to_chat(user, "You can't find anything in [target]'s [target_zone]!") - return 1 -/datum/surgery/implant_removal/mechanic - name = "implant removal" - requires_bodypart_type = BODYPART_ROBOTIC - steps = list( - /datum/surgery_step/mechanic_open, - /datum/surgery_step/open_hatch, - /datum/surgery_step/mechanic_unwrench, - /datum/surgery_step/extract_implant, - /datum/surgery_step/mechanic_wrench, - /datum/surgery_step/mechanic_close) +/datum/surgery/implant_removal + name = "implant removal" + steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/extract_implant, /datum/surgery_step/close) + target_mobtypes = list(/mob/living/carbon/human, /mob/living/carbon/monkey) + possible_locs = list(BODY_ZONE_CHEST) +//extract implant +/datum/surgery_step/extract_implant + name = "extract implant" + implements = list(TOOL_HEMOSTAT = 100, TOOL_CROWBAR = 65) + time = 64 + var/obj/item/implant/I = null +/datum/surgery_step/extract_implant/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + for(var/obj/item/O in target.implants) + I = O + break + if(I) + display_results(user, target, "You begin to extract [I] from [target]'s [target_zone]...", + "[user] begins to extract [I] from [target]'s [target_zone].", + "[user] begins to extract something from [target]'s [target_zone].") + else + display_results(user, target, "You look for an implant in [target]'s [target_zone]...", + "[user] looks for an implant in [target]'s [target_zone].", + "[user] looks for something in [target]'s [target_zone].") + +/datum/surgery_step/extract_implant/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + if(I) + display_results(user, target, "You successfully remove [I] from [target]'s [target_zone].", + "[user] successfully removes [I] from [target]'s [target_zone]!", + "[user] successfully removes something from [target]'s [target_zone]!") + I.removed(target) + + var/obj/item/implantcase/case + for(var/obj/item/implantcase/ic in user.held_items) + case = ic + break + if(!case) + case = locate(/obj/item/implantcase) in get_turf(target) + if(case && !case.imp) + case.imp = I + I.forceMove(case) + case.update_icon() + display_results(user, target, "You place [I] into [case].", + "[user] places [I] into [case]!", + "[user] places it into [case]!") + else + qdel(I) + + else + to_chat(user, "You can't find anything in [target]'s [target_zone]!") + return 1 +/datum/surgery/implant_removal/mechanic + name = "implant removal" + requires_bodypart_type = BODYPART_ROBOTIC + steps = list( + /datum/surgery_step/mechanic_open, + /datum/surgery_step/open_hatch, + /datum/surgery_step/mechanic_unwrench, + /datum/surgery_step/extract_implant, + /datum/surgery_step/mechanic_wrench, + /datum/surgery_step/mechanic_close) diff --git a/code/modules/surgery/limb_augmentation.dm b/code/modules/surgery/limb_augmentation.dm index 684996a0f5..92059f04d4 100644 --- a/code/modules/surgery/limb_augmentation.dm +++ b/code/modules/surgery/limb_augmentation.dm @@ -1,50 +1,50 @@ -/////AUGMENTATION SURGERIES////// -//SURGERY STEPS - -/datum/surgery_step/replace_limb - name = "replace limb" - implements = list(/obj/item/bodypart = 100, /obj/item/organ_storage = 100) - time = 32 - var/obj/item/bodypart/L = null // L because "limb" -/datum/surgery_step/replace_limb/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(istype(tool, /obj/item/organ_storage) && istype(tool.contents[1], /obj/item/bodypart)) - tool = tool.contents[1] - var/obj/item/bodypart/aug = tool - if(aug.status != BODYPART_ROBOTIC) - to_chat(user, "That's not an augment, silly!") - return -1 - if(aug.body_zone != target_zone) - to_chat(user, "[tool] isn't the right type for [parse_zone(target_zone)].") - return -1 - L = surgery.operated_bodypart - if(L) - display_results(user, target, "You begin to augment [target]'s [parse_zone(user.zone_selected)]...", - "[user] begins to augment [target]'s [parse_zone(user.zone_selected)] with [aug].", - "[user] begins to augment [target]'s [parse_zone(user.zone_selected)].") - else - user.visible_message("[user] looks for [target]'s [parse_zone(user.zone_selected)].", "You look for [target]'s [parse_zone(user.zone_selected)]...") - -//ACTUAL SURGERIES -/datum/surgery/augmentation - name = "Augmentation" - steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/replace_limb) - target_mobtypes = list(/mob/living/carbon/human) - possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD) - requires_real_bodypart = TRUE -//SURGERY STEP SUCCESSES -/datum/surgery_step/replace_limb/success(mob/user, mob/living/carbon/target, target_zone, obj/item/bodypart/tool, datum/surgery/surgery) - if(L) - if(istype(tool, /obj/item/organ_storage)) - tool.icon_state = initial(tool.icon_state) - tool.desc = initial(tool.desc) - tool.cut_overlays() - tool = tool.contents[1] - if(istype(tool) && user.temporarilyRemoveItemFromInventory(tool)) - tool.replace_limb(target, TRUE) - display_results(user, target, "You successfully augment [target]'s [parse_zone(target_zone)].", - "[user] successfully augments [target]'s [parse_zone(target_zone)] with [tool]!", - "[user] successfully augments [target]'s [parse_zone(target_zone)]!") - log_combat(user, target, "augmented", addition="by giving him new [parse_zone(target_zone)] INTENT: [uppertext(user.a_intent)]") - else - to_chat(user, "[target] has no organic [parse_zone(target_zone)] there!") - return TRUE +/////AUGMENTATION SURGERIES////// +//SURGERY STEPS + +/datum/surgery_step/replace_limb + name = "replace limb" + implements = list(/obj/item/bodypart = 100, /obj/item/organ_storage = 100) + time = 32 + var/obj/item/bodypart/L = null // L because "limb" +/datum/surgery_step/replace_limb/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + if(istype(tool, /obj/item/organ_storage) && istype(tool.contents[1], /obj/item/bodypart)) + tool = tool.contents[1] + var/obj/item/bodypart/aug = tool + if(aug.status != BODYPART_ROBOTIC) + to_chat(user, "That's not an augment, silly!") + return -1 + if(aug.body_zone != target_zone) + to_chat(user, "[tool] isn't the right type for [parse_zone(target_zone)].") + return -1 + L = surgery.operated_bodypart + if(L) + display_results(user, target, "You begin to augment [target]'s [parse_zone(user.zone_selected)]...", + "[user] begins to augment [target]'s [parse_zone(user.zone_selected)] with [aug].", + "[user] begins to augment [target]'s [parse_zone(user.zone_selected)].") + else + user.visible_message("[user] looks for [target]'s [parse_zone(user.zone_selected)].", "You look for [target]'s [parse_zone(user.zone_selected)]...") + +//ACTUAL SURGERIES +/datum/surgery/augmentation + name = "Augmentation" + steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/replace_limb) + target_mobtypes = list(/mob/living/carbon/human) + possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD) + requires_real_bodypart = TRUE +//SURGERY STEP SUCCESSES +/datum/surgery_step/replace_limb/success(mob/user, mob/living/carbon/target, target_zone, obj/item/bodypart/tool, datum/surgery/surgery) + if(L) + if(istype(tool, /obj/item/organ_storage)) + tool.icon_state = initial(tool.icon_state) + tool.desc = initial(tool.desc) + tool.cut_overlays() + tool = tool.contents[1] + if(istype(tool) && user.temporarilyRemoveItemFromInventory(tool)) + tool.replace_limb(target, TRUE) + display_results(user, target, "You successfully augment [target]'s [parse_zone(target_zone)].", + "[user] successfully augments [target]'s [parse_zone(target_zone)] with [tool]!", + "[user] successfully augments [target]'s [parse_zone(target_zone)]!") + log_combat(user, target, "augmented", addition="by giving him new [parse_zone(target_zone)] INTENT: [uppertext(user.a_intent)]") + else + to_chat(user, "[target] has no organic [parse_zone(target_zone)] there!") + return TRUE diff --git a/code/modules/surgery/lipoplasty.dm b/code/modules/surgery/lipoplasty.dm index b28b439f9f..b774883528 100644 --- a/code/modules/surgery/lipoplasty.dm +++ b/code/modules/surgery/lipoplasty.dm @@ -1,56 +1,56 @@ -/datum/surgery/lipoplasty - name = "Lipoplasty" - steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/cut_fat, /datum/surgery_step/remove_fat, /datum/surgery_step/close) - possible_locs = list(BODY_ZONE_CHEST) -/datum/surgery/lipoplasty/can_start(mob/user, mob/living/carbon/target) - if(HAS_TRAIT(target, TRAIT_FAT)) - return 1 - return 0 -//cut fat -/datum/surgery_step/cut_fat - name = "cut excess fat" - implements = list(TOOL_SAW = 100, /obj/item/hatchet = 35, /obj/item/kitchen/knife/butcher = 25) //why we need a saw to cut adipose tissue is beyond me, shit's soft as fuck - time = 64 - -/datum/surgery_step/cut_fat/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You begin to cut away [target]'s excess fat...", - "[user] begins to cut away [target]'s excess fat.", - "[user] begins to cut [target]'s [target_zone] with [tool].") - -/datum/surgery_step/cut_fat/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You cut [target]'s excess fat loose.", - "[user] cuts [target]'s excess fat loose!", - "[user] finishes the cut on [target]'s [target_zone].") - return 1 - -//remove fat -/datum/surgery_step/remove_fat - name = "remove loose fat" - implements = list(TOOL_RETRACTOR = 100, TOOL_SCREWDRIVER = 45, TOOL_WIRECUTTER = 35) - time = 32 - -/datum/surgery_step/remove_fat/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You begin to extract [target]'s loose fat...", - "[user] begins to extract [target]'s loose fat!", - "[user] begins to extract something from [target]'s [target_zone].") - -/datum/surgery_step/remove_fat/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You extract [target]'s fat.", - "[user] extracts [target]'s fat!", - "[user] extracts [target]'s fat!") - target.overeatduration = 0 //patient is unfatted - var/removednutriment = target.nutrition - target.nutrition = NUTRITION_LEVEL_WELL_FED - removednutriment -= 450 //whatever was removed goes into the meat - var/mob/living/carbon/human/H = target - var/typeofmeat = /obj/item/reagent_containers/food/snacks/meat/slab/human - if(H.dna && H.dna.species) - typeofmeat = H.dna.species.meat - var/obj/item/reagent_containers/food/snacks/meat/slab/human/newmeat = new typeofmeat - newmeat.name = "fatty meat" - newmeat.desc = "Extremely fatty tissue taken from a patient." - newmeat.subjectname = H.real_name - newmeat.subjectjob = H.job - newmeat.reagents.add_reagent (/datum/reagent/consumable/nutriment, (removednutriment / 15)) //To balance with nutriment_factor of nutriment - newmeat.forceMove(target.loc) - return 1 +/datum/surgery/lipoplasty + name = "Lipoplasty" + steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/cut_fat, /datum/surgery_step/remove_fat, /datum/surgery_step/close) + possible_locs = list(BODY_ZONE_CHEST) +/datum/surgery/lipoplasty/can_start(mob/user, mob/living/carbon/target) + if(HAS_TRAIT(target, TRAIT_FAT)) + return 1 + return 0 +//cut fat +/datum/surgery_step/cut_fat + name = "cut excess fat" + implements = list(TOOL_SAW = 100, /obj/item/hatchet = 35, /obj/item/kitchen/knife/butcher = 25) //why we need a saw to cut adipose tissue is beyond me, shit's soft as fuck + time = 64 + +/datum/surgery_step/cut_fat/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You begin to cut away [target]'s excess fat...", + "[user] begins to cut away [target]'s excess fat.", + "[user] begins to cut [target]'s [target_zone] with [tool].") + +/datum/surgery_step/cut_fat/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You cut [target]'s excess fat loose.", + "[user] cuts [target]'s excess fat loose!", + "[user] finishes the cut on [target]'s [target_zone].") + return 1 + +//remove fat +/datum/surgery_step/remove_fat + name = "remove loose fat" + implements = list(TOOL_RETRACTOR = 100, TOOL_SCREWDRIVER = 45, TOOL_WIRECUTTER = 35) + time = 32 + +/datum/surgery_step/remove_fat/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You begin to extract [target]'s loose fat...", + "[user] begins to extract [target]'s loose fat!", + "[user] begins to extract something from [target]'s [target_zone].") + +/datum/surgery_step/remove_fat/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You extract [target]'s fat.", + "[user] extracts [target]'s fat!", + "[user] extracts [target]'s fat!") + target.overeatduration = 0 //patient is unfatted + var/removednutriment = target.nutrition + target.nutrition = NUTRITION_LEVEL_WELL_FED + removednutriment -= 450 //whatever was removed goes into the meat + var/mob/living/carbon/human/H = target + var/typeofmeat = /obj/item/reagent_containers/food/snacks/meat/slab/human + if(H.dna && H.dna.species) + typeofmeat = H.dna.species.meat + var/obj/item/reagent_containers/food/snacks/meat/slab/human/newmeat = new typeofmeat + newmeat.name = "fatty meat" + newmeat.desc = "Extremely fatty tissue taken from a patient." + newmeat.subjectname = H.real_name + newmeat.subjectjob = H.job + newmeat.reagents.add_reagent (/datum/reagent/consumable/nutriment, (removednutriment / 15)) //To balance with nutriment_factor of nutriment + newmeat.forceMove(target.loc) + return 1 diff --git a/code/modules/surgery/organs/helpers.dm b/code/modules/surgery/organs/helpers.dm index 06ec88557f..964349b769 100644 --- a/code/modules/surgery/organs/helpers.dm +++ b/code/modules/surgery/organs/helpers.dm @@ -1,29 +1,29 @@ -/mob/proc/getorgan(typepath) - return - -/mob/proc/getorganszone(zone) - return - -/mob/proc/getorganslot(slot) - return - -/mob/living/carbon/getorgan(typepath) - return (locate(typepath) in internal_organs) - -/mob/living/carbon/getorganszone(zone, subzones = 0) - var/list/returnorg = list() - if(subzones) - // Include subzones - groin for chest, eyes and mouth for head - if(zone == BODY_ZONE_HEAD) - returnorg = getorganszone(BODY_ZONE_PRECISE_EYES) + getorganszone(BODY_ZONE_PRECISE_MOUTH) - if(zone == BODY_ZONE_CHEST) - returnorg = getorganszone(BODY_ZONE_PRECISE_GROIN) - - for(var/X in internal_organs) - var/obj/item/organ/O = X - if(zone == O.zone) - returnorg += O - return returnorg - -/mob/living/carbon/getorganslot(slot) - return internal_organs_slot[slot] +/mob/proc/getorgan(typepath) + return + +/mob/proc/getorganszone(zone) + return + +/mob/proc/getorganslot(slot) + return + +/mob/living/carbon/getorgan(typepath) + return (locate(typepath) in internal_organs) + +/mob/living/carbon/getorganszone(zone, subzones = 0) + var/list/returnorg = list() + if(subzones) + // Include subzones - groin for chest, eyes and mouth for head + if(zone == BODY_ZONE_HEAD) + returnorg = getorganszone(BODY_ZONE_PRECISE_EYES) + getorganszone(BODY_ZONE_PRECISE_MOUTH) + if(zone == BODY_ZONE_CHEST) + returnorg = getorganszone(BODY_ZONE_PRECISE_GROIN) + + for(var/X in internal_organs) + var/obj/item/organ/O = X + if(zone == O.zone) + returnorg += O + return returnorg + +/mob/living/carbon/getorganslot(slot) + return internal_organs_slot[slot] diff --git a/code/modules/surgery/organs/liver.dm b/code/modules/surgery/organs/liver.dm index 59185ebaaf..bc59ee397c 100755 --- a/code/modules/surgery/organs/liver.dm +++ b/code/modules/surgery/organs/liver.dm @@ -1,121 +1,121 @@ -#define LIVER_DEFAULT_HEALTH 100 //amount of damage required for liver failure -#define LIVER_DEFAULT_TOX_TOLERANCE 3 //amount of toxins the liver can filter out -#define LIVER_DEFAULT_TOX_LETHALITY 0.01 //lower values lower how harmful toxins are to the liver -#define LIVER_SWELLING_MOVE_MODIFY "pharma" - -/obj/item/organ/liver - name = "liver" - icon_state = "liver" - w_class = WEIGHT_CLASS_NORMAL - zone = BODY_ZONE_CHEST - slot = ORGAN_SLOT_LIVER - desc = "Pairing suggestion: chianti and fava beans." - - maxHealth = STANDARD_ORGAN_THRESHOLD - healing_factor = STANDARD_ORGAN_HEALING - decay_factor = STANDARD_ORGAN_DECAY - - var/alcohol_tolerance = ALCOHOL_RATE//affects how much damage the liver takes from alcohol - var/failing //is this liver failing? - var/toxTolerance = LIVER_DEFAULT_TOX_TOLERANCE//maximum amount of toxins the liver can just shrug off - var/toxLethality = LIVER_DEFAULT_TOX_LETHALITY//affects how much damage toxins do to the liver - var/filterToxins = TRUE //whether to filter toxins - var/swelling = 0 - var/cachedmoveCalc = 1 - -/obj/item/organ/liver/on_life() - var/mob/living/carbon/C = owner - - if(istype(C)) - if(!(organ_flags & ORGAN_FAILING))//can't process reagents with a failing liver - //slowly heal liver damage - damage = max(0, damage - 0.1) - - if(filterToxins && !HAS_TRAIT(owner, TRAIT_TOXINLOVER)) - //handle liver toxin filtration - for(var/datum/reagent/toxin/T in C.reagents.reagent_list) - var/thisamount = C.reagents.get_reagent_amount(T.type) - if (thisamount && thisamount <= toxTolerance) - C.reagents.remove_reagent(T.type, 1) - else - damage += (thisamount*toxLethality) - - //metabolize reagents - C.reagents.metabolize(C, can_overdose=TRUE) - - if(damage > 10 && prob(damage/3))//the higher the damage the higher the probability - to_chat(C, "You feel a dull pain in your abdomen.") - - if(damage > maxHealth)//cap liver damage - damage = maxHealth - - if(swelling >= 10) - pharmacokinesis() - -/obj/item/organ/liver/prepare_eat() - var/obj/S = ..() - S.reagents.add_reagent(/datum/reagent/iron, 5) - return S - -//Just in case -/obj/item/organ/liver/Remove(mob/living/carbon/M, special = 0) - ..() - M.remove_movespeed_modifier(LIVER_SWELLING_MOVE_MODIFY) - M.ResetBloodVol() //At the moment, this shouldn't allow application twice. You either have this OR a thirsty ferret. - sizeMoveMod(1, M) - -//Applies some of the effects to the patient. -/obj/item/organ/liver/proc/pharmacokinesis() - var/moveCalc = 1+((round(swelling) - 9)/3) - if(moveCalc == cachedmoveCalc)//reduce calculations - return - if(prob(5)) - to_chat(owner, "You feel a stange ache in your side, almost like a stitch. This pain is affecting your movements and making you feel lightheaded.") - var/mob/living/carbon/human/H = owner - H.add_movespeed_modifier(LIVER_SWELLING_MOVE_MODIFY, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc) - H.AdjustBloodVol(moveCalc/3) - sizeMoveMod(moveCalc, H) - -/obj/item/organ/liver/proc/sizeMoveMod(var/value, mob/living/carbon/human/H) - if(cachedmoveCalc == value) - return - H.next_move_modifier /= cachedmoveCalc - H.next_move_modifier *= value - cachedmoveCalc = value - -/obj/item/organ/liver/fly - name = "insectoid liver" - icon_state = "liver-x" //xenomorph liver? It's just a black liver so it fits. - desc = "A mutant liver designed to handle the unique diet of a flyperson." - alcohol_tolerance = 0.007 //flies eat vomit, so a lower alcohol tolerance is perfect! - -/obj/item/organ/liver/plasmaman - name = "reagent processing crystal" - icon_state = "liver-p" - desc = "A large crystal that is somehow capable of metabolizing chemicals, these are found in plasmamen." - -/obj/item/organ/liver/cybernetic - name = "cybernetic liver" - icon_state = "liver-c" - desc = "An electronic device designed to mimic the functions of a human liver. It has no benefits over an organic liver, but is easy to produce." - organ_flags = ORGAN_SYNTHETIC - maxHealth = 1.1 * STANDARD_ORGAN_THRESHOLD - -/obj/item/organ/liver/cybernetic/upgraded - name = "upgraded cybernetic liver" - icon_state = "liver-c-u" - desc = "An upgraded version of the cybernetic liver, designed to improve upon organic livers. It is resistant to alcohol poisoning and is very robust at filtering toxins." - alcohol_tolerance = 0.001 - maxHealth = 2 * STANDARD_ORGAN_THRESHOLD - toxTolerance = 15 //can shrug off up to 15u of toxins - toxLethality = 0.008 //20% less damage than a normal liver - -/obj/item/organ/liver/cybernetic/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - switch(severity) - if(1) - damage+=100 - if(2) - damage+=50 +#define LIVER_DEFAULT_HEALTH 100 //amount of damage required for liver failure +#define LIVER_DEFAULT_TOX_TOLERANCE 3 //amount of toxins the liver can filter out +#define LIVER_DEFAULT_TOX_LETHALITY 0.01 //lower values lower how harmful toxins are to the liver +#define LIVER_SWELLING_MOVE_MODIFY "pharma" + +/obj/item/organ/liver + name = "liver" + icon_state = "liver" + w_class = WEIGHT_CLASS_NORMAL + zone = BODY_ZONE_CHEST + slot = ORGAN_SLOT_LIVER + desc = "Pairing suggestion: chianti and fava beans." + + maxHealth = STANDARD_ORGAN_THRESHOLD + healing_factor = STANDARD_ORGAN_HEALING + decay_factor = STANDARD_ORGAN_DECAY + + var/alcohol_tolerance = ALCOHOL_RATE//affects how much damage the liver takes from alcohol + var/failing //is this liver failing? + var/toxTolerance = LIVER_DEFAULT_TOX_TOLERANCE//maximum amount of toxins the liver can just shrug off + var/toxLethality = LIVER_DEFAULT_TOX_LETHALITY//affects how much damage toxins do to the liver + var/filterToxins = TRUE //whether to filter toxins + var/swelling = 0 + var/cachedmoveCalc = 1 + +/obj/item/organ/liver/on_life() + var/mob/living/carbon/C = owner + + if(istype(C)) + if(!(organ_flags & ORGAN_FAILING))//can't process reagents with a failing liver + //slowly heal liver damage + damage = max(0, damage - 0.1) + + if(filterToxins && !HAS_TRAIT(owner, TRAIT_TOXINLOVER)) + //handle liver toxin filtration + for(var/datum/reagent/toxin/T in C.reagents.reagent_list) + var/thisamount = C.reagents.get_reagent_amount(T.type) + if (thisamount && thisamount <= toxTolerance) + C.reagents.remove_reagent(T.type, 1) + else + damage += (thisamount*toxLethality) + + //metabolize reagents + C.reagents.metabolize(C, can_overdose=TRUE) + + if(damage > 10 && prob(damage/3))//the higher the damage the higher the probability + to_chat(C, "You feel a dull pain in your abdomen.") + + if(damage > maxHealth)//cap liver damage + damage = maxHealth + + if(swelling >= 10) + pharmacokinesis() + +/obj/item/organ/liver/prepare_eat() + var/obj/S = ..() + S.reagents.add_reagent(/datum/reagent/iron, 5) + return S + +//Just in case +/obj/item/organ/liver/Remove(mob/living/carbon/M, special = 0) + ..() + M.remove_movespeed_modifier(LIVER_SWELLING_MOVE_MODIFY) + M.ResetBloodVol() //At the moment, this shouldn't allow application twice. You either have this OR a thirsty ferret. + sizeMoveMod(1, M) + +//Applies some of the effects to the patient. +/obj/item/organ/liver/proc/pharmacokinesis() + var/moveCalc = 1+((round(swelling) - 9)/3) + if(moveCalc == cachedmoveCalc)//reduce calculations + return + if(prob(5)) + to_chat(owner, "You feel a stange ache in your side, almost like a stitch. This pain is affecting your movements and making you feel lightheaded.") + var/mob/living/carbon/human/H = owner + H.add_movespeed_modifier(LIVER_SWELLING_MOVE_MODIFY, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc) + H.AdjustBloodVol(moveCalc/3) + sizeMoveMod(moveCalc, H) + +/obj/item/organ/liver/proc/sizeMoveMod(var/value, mob/living/carbon/human/H) + if(cachedmoveCalc == value) + return + H.next_move_modifier /= cachedmoveCalc + H.next_move_modifier *= value + cachedmoveCalc = value + +/obj/item/organ/liver/fly + name = "insectoid liver" + icon_state = "liver-x" //xenomorph liver? It's just a black liver so it fits. + desc = "A mutant liver designed to handle the unique diet of a flyperson." + alcohol_tolerance = 0.007 //flies eat vomit, so a lower alcohol tolerance is perfect! + +/obj/item/organ/liver/plasmaman + name = "reagent processing crystal" + icon_state = "liver-p" + desc = "A large crystal that is somehow capable of metabolizing chemicals, these are found in plasmamen." + +/obj/item/organ/liver/cybernetic + name = "cybernetic liver" + icon_state = "liver-c" + desc = "An electronic device designed to mimic the functions of a human liver. It has no benefits over an organic liver, but is easy to produce." + organ_flags = ORGAN_SYNTHETIC + maxHealth = 1.1 * STANDARD_ORGAN_THRESHOLD + +/obj/item/organ/liver/cybernetic/upgraded + name = "upgraded cybernetic liver" + icon_state = "liver-c-u" + desc = "An upgraded version of the cybernetic liver, designed to improve upon organic livers. It is resistant to alcohol poisoning and is very robust at filtering toxins." + alcohol_tolerance = 0.001 + maxHealth = 2 * STANDARD_ORGAN_THRESHOLD + toxTolerance = 15 //can shrug off up to 15u of toxins + toxLethality = 0.008 //20% less damage than a normal liver + +/obj/item/organ/liver/cybernetic/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + switch(severity) + if(1) + damage+=100 + if(2) + damage+=50 diff --git a/code/modules/surgery/organs/stomach.dm b/code/modules/surgery/organs/stomach.dm index a1624efd61..93210fe3ff 100755 --- a/code/modules/surgery/organs/stomach.dm +++ b/code/modules/surgery/organs/stomach.dm @@ -1,98 +1,98 @@ -/obj/item/organ/stomach - name = "stomach" - icon_state = "stomach" - w_class = WEIGHT_CLASS_NORMAL - zone = BODY_ZONE_CHEST - slot = ORGAN_SLOT_STOMACH - attack_verb = list("gored", "squished", "slapped", "digested") - desc = "Onaka ga suite imasu." - var/disgust_metabolism = 1 - - healing_factor = STANDARD_ORGAN_HEALING - decay_factor = STANDARD_ORGAN_DECAY - - low_threshold_passed = "Your stomach flashes with pain before subsiding. Food doesn't seem like a good idea right now." - high_threshold_passed = "Your stomach flares up with constant pain- you can hardly stomach the idea of food right now!" - high_threshold_cleared = "The pain in your stomach dies down for now, but food still seems unappealing." - low_threshold_cleared = "The last bouts of pain in your stomach have died out." - -/obj/item/organ/stomach/on_life() - ..() - var/datum/reagent/consumable/nutriment/Nutri - if(ishuman(owner)) - var/mob/living/carbon/human/H = owner - if(!(organ_flags & ORGAN_FAILING)) - H.dna.species.handle_digestion(H) - handle_disgust(H) - Nutri = locate(/datum/reagent/consumable/nutriment) in H.reagents.reagent_list - - if(Nutri) - if(prob((damage/40) * Nutri.volume * Nutri.volume)) - H.vomit(damage) - to_chat(H, "Your stomach reels in pain as you're incapable of holding down all that food!") - - else if(Nutri && damage > high_threshold) - if(prob((damage/10) * Nutri.volume * Nutri.volume)) - H.vomit(damage) - to_chat(H, "Your stomach reels in pain as you're incapable of holding down all that food!") - - - else if(iscarbon(owner)) - var/mob/living/carbon/C = owner - Nutri = locate(/datum/reagent/consumable/nutriment) in C.reagents.reagent_list - - if(damage < low_threshold) - return - - -/obj/item/organ/stomach/proc/handle_disgust(mob/living/carbon/human/H) - if(H.disgust) - var/pukeprob = 5 + 0.05 * H.disgust - if(H.disgust >= DISGUST_LEVEL_GROSS) - if(prob(10)) - H.stuttering += 1 - H.confused += 2 - if(prob(10) && !H.stat) - to_chat(H, "You feel kind of iffy...") - H.jitteriness = max(H.jitteriness - 3, 0) - if(H.disgust >= DISGUST_LEVEL_VERYGROSS) - if(prob(pukeprob)) //iT hAndLeS mOrE ThaN PukInG - H.confused += 2.5 - H.stuttering += 1 - H.vomit(10, 0, 1, 0, 1, 0) - H.Dizzy(5) - if(H.disgust >= DISGUST_LEVEL_DISGUSTED) - if(prob(25)) - H.blur_eyes(3) //We need to add more shit down here - - H.adjust_disgust(-0.5 * disgust_metabolism) - switch(H.disgust) - if(0 to DISGUST_LEVEL_GROSS) - H.clear_alert("disgust") - SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "disgust") - if(DISGUST_LEVEL_GROSS to DISGUST_LEVEL_VERYGROSS) - H.throw_alert("disgust", /obj/screen/alert/gross) - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "disgust", /datum/mood_event/gross) - if(DISGUST_LEVEL_VERYGROSS to DISGUST_LEVEL_DISGUSTED) - H.throw_alert("disgust", /obj/screen/alert/verygross) - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "disgust", /datum/mood_event/verygross) - if(DISGUST_LEVEL_DISGUSTED to INFINITY) - H.throw_alert("disgust", /obj/screen/alert/disgusted) - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "disgust", /datum/mood_event/disgusted) - -/obj/item/organ/stomach/Remove(mob/living/carbon/M, special = 0) - var/mob/living/carbon/human/H = owner - if(istype(H)) - H.clear_alert("disgust") - SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "disgust") - ..() - -/obj/item/organ/stomach/fly - name = "insectoid stomach" - icon_state = "stomach-x" //xenomorph liver? It's just a black liver so it fits. - desc = "A mutant stomach designed to handle the unique diet of a flyperson." - -/obj/item/organ/stomach/plasmaman - name = "digestive crystal" - icon_state = "stomach-p" - desc = "A strange crystal that is responsible for metabolizing the unseen energy force that feeds plasmamen." +/obj/item/organ/stomach + name = "stomach" + icon_state = "stomach" + w_class = WEIGHT_CLASS_NORMAL + zone = BODY_ZONE_CHEST + slot = ORGAN_SLOT_STOMACH + attack_verb = list("gored", "squished", "slapped", "digested") + desc = "Onaka ga suite imasu." + var/disgust_metabolism = 1 + + healing_factor = STANDARD_ORGAN_HEALING + decay_factor = STANDARD_ORGAN_DECAY + + low_threshold_passed = "Your stomach flashes with pain before subsiding. Food doesn't seem like a good idea right now." + high_threshold_passed = "Your stomach flares up with constant pain- you can hardly stomach the idea of food right now!" + high_threshold_cleared = "The pain in your stomach dies down for now, but food still seems unappealing." + low_threshold_cleared = "The last bouts of pain in your stomach have died out." + +/obj/item/organ/stomach/on_life() + ..() + var/datum/reagent/consumable/nutriment/Nutri + if(ishuman(owner)) + var/mob/living/carbon/human/H = owner + if(!(organ_flags & ORGAN_FAILING)) + H.dna.species.handle_digestion(H) + handle_disgust(H) + Nutri = locate(/datum/reagent/consumable/nutriment) in H.reagents.reagent_list + + if(Nutri) + if(prob((damage/40) * Nutri.volume * Nutri.volume)) + H.vomit(damage) + to_chat(H, "Your stomach reels in pain as you're incapable of holding down all that food!") + + else if(Nutri && damage > high_threshold) + if(prob((damage/10) * Nutri.volume * Nutri.volume)) + H.vomit(damage) + to_chat(H, "Your stomach reels in pain as you're incapable of holding down all that food!") + + + else if(iscarbon(owner)) + var/mob/living/carbon/C = owner + Nutri = locate(/datum/reagent/consumable/nutriment) in C.reagents.reagent_list + + if(damage < low_threshold) + return + + +/obj/item/organ/stomach/proc/handle_disgust(mob/living/carbon/human/H) + if(H.disgust) + var/pukeprob = 5 + 0.05 * H.disgust + if(H.disgust >= DISGUST_LEVEL_GROSS) + if(prob(10)) + H.stuttering += 1 + H.confused += 2 + if(prob(10) && !H.stat) + to_chat(H, "You feel kind of iffy...") + H.jitteriness = max(H.jitteriness - 3, 0) + if(H.disgust >= DISGUST_LEVEL_VERYGROSS) + if(prob(pukeprob)) //iT hAndLeS mOrE ThaN PukInG + H.confused += 2.5 + H.stuttering += 1 + H.vomit(10, 0, 1, 0, 1, 0) + H.Dizzy(5) + if(H.disgust >= DISGUST_LEVEL_DISGUSTED) + if(prob(25)) + H.blur_eyes(3) //We need to add more shit down here + + H.adjust_disgust(-0.5 * disgust_metabolism) + switch(H.disgust) + if(0 to DISGUST_LEVEL_GROSS) + H.clear_alert("disgust") + SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "disgust") + if(DISGUST_LEVEL_GROSS to DISGUST_LEVEL_VERYGROSS) + H.throw_alert("disgust", /obj/screen/alert/gross) + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "disgust", /datum/mood_event/gross) + if(DISGUST_LEVEL_VERYGROSS to DISGUST_LEVEL_DISGUSTED) + H.throw_alert("disgust", /obj/screen/alert/verygross) + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "disgust", /datum/mood_event/verygross) + if(DISGUST_LEVEL_DISGUSTED to INFINITY) + H.throw_alert("disgust", /obj/screen/alert/disgusted) + SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "disgust", /datum/mood_event/disgusted) + +/obj/item/organ/stomach/Remove(mob/living/carbon/M, special = 0) + var/mob/living/carbon/human/H = owner + if(istype(H)) + H.clear_alert("disgust") + SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "disgust") + ..() + +/obj/item/organ/stomach/fly + name = "insectoid stomach" + icon_state = "stomach-x" //xenomorph liver? It's just a black liver so it fits. + desc = "A mutant stomach designed to handle the unique diet of a flyperson." + +/obj/item/organ/stomach/plasmaman + name = "digestive crystal" + icon_state = "stomach-p" + desc = "A strange crystal that is responsible for metabolizing the unseen energy force that feeds plasmamen." diff --git a/code/modules/surgery/plastic_surgery.dm b/code/modules/surgery/plastic_surgery.dm index fe22ffaede..ce9de95b4c 100644 --- a/code/modules/surgery/plastic_surgery.dm +++ b/code/modules/surgery/plastic_surgery.dm @@ -1,51 +1,51 @@ -/datum/surgery/plastic_surgery - name = "Plastic surgery" - steps = list(/datum/surgery_step/incise, /datum/surgery_step/retract_skin, /datum/surgery_step/reshape_face, /datum/surgery_step/close) - possible_locs = list(BODY_ZONE_HEAD) -//reshape_face -/datum/surgery_step/reshape_face - name = "reshape face" - implements = list(TOOL_SCALPEL = 100, /obj/item/kitchen/knife = 50, TOOL_WIRECUTTER = 35) - time = 64 - -/datum/surgery_step/reshape_face/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You begin to alter [target]'s appearance...", - "[user] begins to alter [target]'s appearance.", - "[user] begins to make an incision in [target]'s face.") - -/datum/surgery_step/reshape_face/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - if(HAS_TRAIT_FROM(target, TRAIT_DISFIGURED, TRAIT_GENERIC)) - REMOVE_TRAIT(target, TRAIT_DISFIGURED, TRAIT_GENERIC) - display_results(user, target, "You successfully restore [target]'s appearance.", - "[user] successfully restores [target]'s appearance!", - "[user] finishes the operation on [target]'s face.") - else - var/list/names = list() - if(!isabductor(user)) - for(var/i in 1 to 10) - names += target.dna.species.random_name(target.gender, TRUE) - else - for(var/_i in 1 to 9) - names += "Subject [target.gender == MALE ? "i" : "o"]-[pick("a", "b", "c", "d", "e")]-[rand(10000, 99999)]" - names += target.dna.species.random_name(target.gender, TRUE) //give one normal name in case they want to do regular plastic surgery - var/chosen_name = input(user, "Choose a new name to assign.", "Plastic Surgery") as null|anything in names - if(!chosen_name) - return - var/oldname = target.real_name - target.real_name = chosen_name - var/newname = target.real_name //something about how the code handles names required that I use this instead of target.real_name - display_results(user, target, "You alter [oldname]'s appearance completely, [target.p_they()] is now [newname].", - "[user] alters [oldname]'s appearance completely, [target.p_they()] is now [newname]!", - "[user] finishes the operation on [target]'s face.") - if(ishuman(target)) - var/mob/living/carbon/human/H = target - H.sec_hud_set_ID() - return 1 - return TRUE - -/datum/surgery_step/reshape_face/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You screw up, leaving [target]'s appearance disfigured!", - "[user] screws up, disfiguring [target]'s appearance!", - "[user] finishes the operation on [target]'s face.") - ADD_TRAIT(target, TRAIT_DISFIGURED, TRAIT_GENERIC) - return FALSE +/datum/surgery/plastic_surgery + name = "Plastic surgery" + steps = list(/datum/surgery_step/incise, /datum/surgery_step/retract_skin, /datum/surgery_step/reshape_face, /datum/surgery_step/close) + possible_locs = list(BODY_ZONE_HEAD) +//reshape_face +/datum/surgery_step/reshape_face + name = "reshape face" + implements = list(TOOL_SCALPEL = 100, /obj/item/kitchen/knife = 50, TOOL_WIRECUTTER = 35) + time = 64 + +/datum/surgery_step/reshape_face/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You begin to alter [target]'s appearance...", + "[user] begins to alter [target]'s appearance.", + "[user] begins to make an incision in [target]'s face.") + +/datum/surgery_step/reshape_face/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + if(HAS_TRAIT_FROM(target, TRAIT_DISFIGURED, TRAIT_GENERIC)) + REMOVE_TRAIT(target, TRAIT_DISFIGURED, TRAIT_GENERIC) + display_results(user, target, "You successfully restore [target]'s appearance.", + "[user] successfully restores [target]'s appearance!", + "[user] finishes the operation on [target]'s face.") + else + var/list/names = list() + if(!isabductor(user)) + for(var/i in 1 to 10) + names += target.dna.species.random_name(target.gender, TRUE) + else + for(var/_i in 1 to 9) + names += "Subject [target.gender == MALE ? "i" : "o"]-[pick("a", "b", "c", "d", "e")]-[rand(10000, 99999)]" + names += target.dna.species.random_name(target.gender, TRUE) //give one normal name in case they want to do regular plastic surgery + var/chosen_name = input(user, "Choose a new name to assign.", "Plastic Surgery") as null|anything in names + if(!chosen_name) + return + var/oldname = target.real_name + target.real_name = chosen_name + var/newname = target.real_name //something about how the code handles names required that I use this instead of target.real_name + display_results(user, target, "You alter [oldname]'s appearance completely, [target.p_they()] is now [newname].", + "[user] alters [oldname]'s appearance completely, [target.p_they()] is now [newname]!", + "[user] finishes the operation on [target]'s face.") + if(ishuman(target)) + var/mob/living/carbon/human/H = target + H.sec_hud_set_ID() + return 1 + return TRUE + +/datum/surgery_step/reshape_face/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You screw up, leaving [target]'s appearance disfigured!", + "[user] screws up, disfiguring [target]'s appearance!", + "[user] finishes the operation on [target]'s face.") + ADD_TRAIT(target, TRAIT_DISFIGURED, TRAIT_GENERIC) + return FALSE diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index 71f813a5b5..1857f8db48 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -1,130 +1,130 @@ -/datum/surgery_step - var/name - var/list/implements = list() //format is path = probability of success. alternatively - var/implement_type = null //the current type of implement used. This has to be stored, as the actual typepath of the tool may not match the list type. - var/accept_hand = FALSE //does the surgery step require an open hand? If true, ignores implements. Compatible with accept_any_item. - var/accept_any_item = FALSE //does the surgery step accept any item? If true, ignores implements. Compatible with require_hand. - var/time = 10 //how long does the step take? - var/repeatable = FALSE //can this step be repeated? Make shure it isn't last step, or it used in surgery with `can_cancel = 1`. Or surgion will be stuck in the loop - var/list/chems_needed = list() //list of chems needed to complete the step. Even on success, the step will have no effect if there aren't the chems required in the mob. - var/require_all_chems = TRUE //any on the list or all on the list? - var/silicons_obey_prob = FALSE - -/datum/surgery_step/proc/try_op(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) - var/success = FALSE - if(accept_hand) - if(!tool) - success = TRUE - if(iscyborg(user)) - success = TRUE - if(accept_any_item) - if(tool && tool_check(user, tool)) - success = TRUE - else if(tool) - for(var/key in implements) - var/match = FALSE - if(ispath(key) && istype(tool, key)) - match = TRUE - else if(tool.tool_behaviour == key) - match = TRUE - if(match) - implement_type = key - if(tool_check(user, tool)) - success = TRUE - break - if(success) - if(target_zone == surgery.location) - if(get_location_accessible(target, target_zone) || surgery.ignore_clothes) - return initiate(user, target, target_zone, tool, surgery, try_to_fail) - else - to_chat(user, "You need to expose [target]'s [parse_zone(target_zone)] to perform surgery on it!") - return TRUE //returns TRUE so we don't stab the guy in the dick or wherever. - if(repeatable) - var/datum/surgery_step/next_step = surgery.get_surgery_next_step() - if(next_step) - surgery.status++ - if(next_step.try_op(user, target, user.zone_selected, user.get_active_held_item(), surgery)) - return TRUE - else - surgery.status-- - return FALSE - -/datum/surgery_step/proc/initiate(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) - surgery.step_in_progress = TRUE - var/speed_mod = 1 - var/advance = FALSE - if(preop(user, target, target_zone, tool, surgery) == -1) - surgery.step_in_progress = FALSE - return FALSE - if(tool) - speed_mod = tool.toolspeed - if(do_after(user, time * speed_mod, target = target)) - var/prob_chance = 100 - if(implement_type) //this means it isn't a require hand or any item step. - prob_chance = implements[implement_type] - prob_chance *= surgery.get_propability_multiplier() - - if((prob(prob_chance) || (iscyborg(user) && !silicons_obey_prob)) && chem_check(target) && !try_to_fail) - if(success(user, target, target_zone, tool, surgery)) - advance = TRUE - else - if(failure(user, target, target_zone, tool, surgery)) - advance = TRUE - if(advance && !repeatable) - surgery.status++ - if(surgery.status > surgery.steps.len) - surgery.complete() - surgery.step_in_progress = FALSE - return advance - -/datum/surgery_step/proc/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You begin to perform surgery on [target]...", - "[user] begins to perform surgery on [target].", - "[user] begins to perform surgery on [target].") - -/datum/surgery_step/proc/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You succeed.", - "[user] succeeds!", - "[user] finishes.") - return TRUE - -/datum/surgery_step/proc/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) - display_results(user, target, "You screw up!", - "[user] screws up!", - "[user] finishes.", TRUE) //By default the patient will notice if the wrong thing has been cut - return FALSE - -/datum/surgery_step/proc/tool_check(mob/user, obj/item/tool) - return TRUE - -/datum/surgery_step/proc/chem_check(mob/living/target) - if(!LAZYLEN(chems_needed)) - return TRUE - if(require_all_chems) - . = TRUE - for(var/R in chems_needed) - if(!target.reagents.has_reagent(R)) - return FALSE - else - . = FALSE - for(var/R in chems_needed) - if(target.reagents.has_reagent(R)) - return TRUE -/datum/surgery_step/proc/get_chem_list() - if(!LAZYLEN(chems_needed)) - return - var/list/chems = list() - for(var/R in chems_needed) - var/datum/reagent/temp = GLOB.chemical_reagents_list[R] - if(temp) - var/chemname = temp.name - chems += chemname - return english_list(chems, and_text = require_all_chems ? " and " : " or ") - -//Replaces visible_message during operations so only people looking over the surgeon can tell what they're doing, allowing for shenanigans. -/datum/surgery_step/proc/display_results(mob/user, mob/living/carbon/target, self_message, detailed_message, vague_message, target_detailed = FALSE) - var/list/detailed_mobs = get_hearers_in_view(1, user) //Only the surgeon and people looking over his shoulder can see the operation clearly - if(!target_detailed) - detailed_mobs -= target //The patient can't see well what's going on, unless it's something like getting cut - user.visible_message(detailed_message, self_message, vision_distance = 1, ignored_mobs = target_detailed ? null : target) - user.visible_message(vague_message, "", ignored_mobs = detailed_mobs) +/datum/surgery_step + var/name + var/list/implements = list() //format is path = probability of success. alternatively + var/implement_type = null //the current type of implement used. This has to be stored, as the actual typepath of the tool may not match the list type. + var/accept_hand = FALSE //does the surgery step require an open hand? If true, ignores implements. Compatible with accept_any_item. + var/accept_any_item = FALSE //does the surgery step accept any item? If true, ignores implements. Compatible with require_hand. + var/time = 10 //how long does the step take? + var/repeatable = FALSE //can this step be repeated? Make shure it isn't last step, or it used in surgery with `can_cancel = 1`. Or surgion will be stuck in the loop + var/list/chems_needed = list() //list of chems needed to complete the step. Even on success, the step will have no effect if there aren't the chems required in the mob. + var/require_all_chems = TRUE //any on the list or all on the list? + var/silicons_obey_prob = FALSE + +/datum/surgery_step/proc/try_op(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) + var/success = FALSE + if(accept_hand) + if(!tool) + success = TRUE + if(iscyborg(user)) + success = TRUE + if(accept_any_item) + if(tool && tool_check(user, tool)) + success = TRUE + else if(tool) + for(var/key in implements) + var/match = FALSE + if(ispath(key) && istype(tool, key)) + match = TRUE + else if(tool.tool_behaviour == key) + match = TRUE + if(match) + implement_type = key + if(tool_check(user, tool)) + success = TRUE + break + if(success) + if(target_zone == surgery.location) + if(get_location_accessible(target, target_zone) || surgery.ignore_clothes) + return initiate(user, target, target_zone, tool, surgery, try_to_fail) + else + to_chat(user, "You need to expose [target]'s [parse_zone(target_zone)] to perform surgery on it!") + return TRUE //returns TRUE so we don't stab the guy in the dick or wherever. + if(repeatable) + var/datum/surgery_step/next_step = surgery.get_surgery_next_step() + if(next_step) + surgery.status++ + if(next_step.try_op(user, target, user.zone_selected, user.get_active_held_item(), surgery)) + return TRUE + else + surgery.status-- + return FALSE + +/datum/surgery_step/proc/initiate(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE) + surgery.step_in_progress = TRUE + var/speed_mod = 1 + var/advance = FALSE + if(preop(user, target, target_zone, tool, surgery) == -1) + surgery.step_in_progress = FALSE + return FALSE + if(tool) + speed_mod = tool.toolspeed + if(do_after(user, time * speed_mod, target = target)) + var/prob_chance = 100 + if(implement_type) //this means it isn't a require hand or any item step. + prob_chance = implements[implement_type] + prob_chance *= surgery.get_propability_multiplier() + + if((prob(prob_chance) || (iscyborg(user) && !silicons_obey_prob)) && chem_check(target) && !try_to_fail) + if(success(user, target, target_zone, tool, surgery)) + advance = TRUE + else + if(failure(user, target, target_zone, tool, surgery)) + advance = TRUE + if(advance && !repeatable) + surgery.status++ + if(surgery.status > surgery.steps.len) + surgery.complete() + surgery.step_in_progress = FALSE + return advance + +/datum/surgery_step/proc/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You begin to perform surgery on [target]...", + "[user] begins to perform surgery on [target].", + "[user] begins to perform surgery on [target].") + +/datum/surgery_step/proc/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You succeed.", + "[user] succeeds!", + "[user] finishes.") + return TRUE + +/datum/surgery_step/proc/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, "You screw up!", + "[user] screws up!", + "[user] finishes.", TRUE) //By default the patient will notice if the wrong thing has been cut + return FALSE + +/datum/surgery_step/proc/tool_check(mob/user, obj/item/tool) + return TRUE + +/datum/surgery_step/proc/chem_check(mob/living/target) + if(!LAZYLEN(chems_needed)) + return TRUE + if(require_all_chems) + . = TRUE + for(var/R in chems_needed) + if(!target.reagents.has_reagent(R)) + return FALSE + else + . = FALSE + for(var/R in chems_needed) + if(target.reagents.has_reagent(R)) + return TRUE +/datum/surgery_step/proc/get_chem_list() + if(!LAZYLEN(chems_needed)) + return + var/list/chems = list() + for(var/R in chems_needed) + var/datum/reagent/temp = GLOB.chemical_reagents_list[R] + if(temp) + var/chemname = temp.name + chems += chemname + return english_list(chems, and_text = require_all_chems ? " and " : " or ") + +//Replaces visible_message during operations so only people looking over the surgeon can tell what they're doing, allowing for shenanigans. +/datum/surgery_step/proc/display_results(mob/user, mob/living/carbon/target, self_message, detailed_message, vague_message, target_detailed = FALSE) + var/list/detailed_mobs = get_hearers_in_view(1, user) //Only the surgeon and people looking over his shoulder can see the operation clearly + if(!target_detailed) + detailed_mobs -= target //The patient can't see well what's going on, unless it's something like getting cut + user.visible_message(detailed_message, self_message, vision_distance = 1, ignored_mobs = target_detailed ? null : target) + user.visible_message(vague_message, "", ignored_mobs = detailed_mobs) diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm index f4adfd265a..c8fe7597da 100644 --- a/code/modules/surgery/tools.dm +++ b/code/modules/surgery/tools.dm @@ -1,360 +1,360 @@ -/obj/item/retractor - name = "retractor" - desc = "Retracts stuff." - icon = 'icons/obj/surgery.dmi' - icon_state = "retractor" - materials = list(MAT_METAL=6000, MAT_GLASS=3000) - item_flags = SURGICAL_TOOL - flags_1 = CONDUCT_1 - w_class = WEIGHT_CLASS_TINY - tool_behaviour = TOOL_RETRACTOR - toolspeed = 1 - -/obj/item/retractor/advanced - name = "mechanical pinches" - desc = "An agglomerate of rods and gears." - icon = 'icons/obj/surgery.dmi' - icon_state = "retractor_a" - toolspeed = 0.7 - -/obj/item/retractor/advanced/attack_self(mob/user) - playsound(get_turf(user), 'sound/items/change_drill.ogg', 50, TRUE) - if(tool_behaviour == TOOL_RETRACTOR) - tool_behaviour = TOOL_HEMOSTAT - to_chat(user, "You configure the gears of [src], they are now in hemostat mode.") - icon_state = "hemostat_a" - else - tool_behaviour = TOOL_RETRACTOR - to_chat(user, "You configure the gears of [src], they are now in retractor mode.") - icon_state = "retractor_a" - -/obj/item/retractor/advanced/examine(mob/living/user) - . = ..() - . += "You focus the lenses of [src], it is now in mending mode.") - icon_state = "cautery_a" - else - tool_behaviour = TOOL_DRILL - to_chat(user, "You dilate the lenses of [src], it is now in drilling mode.") - icon_state = "surgicaldrill_a" - -/obj/item/surgicaldrill/advanced/examine(mob/living/user) - . = ..() - . += "" - -/obj/item/surgicaldrill/augment - name = "surgical drill" - desc = "Effectively a small power drill contained within your arm, edges dulled to prevent tissue damage. May or may not pierce the heavens." - icon = 'icons/obj/surgery.dmi' - icon_state = "drill" - hitsound = 'sound/weapons/circsawhit.ogg' - materials = list(MAT_METAL=10000, MAT_GLASS=6000) - flags_1 = CONDUCT_1 - force = 10 - w_class = WEIGHT_CLASS_SMALL - toolspeed = 0.5 - attack_verb = list("drilled") - - -/obj/item/scalpel - name = "scalpel" - desc = "Cut, cut, and once more cut." - icon = 'icons/obj/surgery.dmi' - icon_state = "scalpel" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - flags_1 = CONDUCT_1 - force = 10 - w_class = WEIGHT_CLASS_TINY - throwforce = 5 - throw_speed = 3 - throw_range = 5 - materials = list(MAT_METAL=4000, MAT_GLASS=1000) - item_flags = SURGICAL_TOOL - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP_ACCURATE - tool_behaviour = TOOL_SCALPEL - toolspeed = 1 - -/obj/item/scalpel/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 80 * toolspeed, 100, 0) - -/obj/item/scalpel/advanced - name = "laser scalpel" - desc = "An advanced scalpel which uses laser technology to cut." - icon = 'icons/obj/surgery.dmi' - icon_state = "scalpel_a" - hitsound = 'sound/weapons/blade1.ogg' - force = 16 - toolspeed = 0.7 - light_color = LIGHT_COLOR_GREEN - sharpness = IS_SHARP_ACCURATE - -/obj/item/scalpel/advanced/Initialize() - . = ..() - set_light(1) - -/obj/item/scalpel/advanced/attack_self(mob/user) - playsound(get_turf(user), 'sound/machines/click.ogg', 50, TRUE) - if(tool_behaviour == TOOL_SCALPEL) - tool_behaviour = TOOL_SAW - to_chat(user, "You increase the power of [src], now it can cut bones.") - set_light(2) - force += 1 //we don't want to ruin sharpened stuff - icon_state = "saw_a" - else - tool_behaviour = TOOL_SCALPEL - to_chat(user, "You lower the power of [src], it can no longer cut bones.") - set_light(1) - force -= 1 - icon_state = "scalpel_a" - -/obj/item/scalpel/advanced/examine(mob/living/user) - . = ..() - . += "" - -/obj/item/scalpel/augment - name = "scalpel" - desc = "Ultra-sharp blade attached directly to your bone for extra-accuracy." - icon = 'icons/obj/surgery.dmi' - icon_state = "scalpel" - flags_1 = CONDUCT_1 - force = 10 - w_class = WEIGHT_CLASS_TINY - throwforce = 5 - throw_speed = 3 - throw_range = 5 - materials = list(MAT_METAL=4000, MAT_GLASS=1000) - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - toolspeed = 0.5 - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP_ACCURATE - -/obj/item/scalpel/suicide_act(mob/user) - user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat", "stomach")] with [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return (BRUTELOSS) - - -/obj/item/circular_saw - name = "circular saw" - desc = "For heavy duty cutting." - icon = 'icons/obj/surgery.dmi' - icon_state = "saw" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - hitsound = 'sound/weapons/circsawhit.ogg' - throwhitsound = 'sound/weapons/pierce.ogg' - item_flags = SURGICAL_TOOL - flags_1 = CONDUCT_1 - force = 15 - w_class = WEIGHT_CLASS_NORMAL - throwforce = 9 - throw_speed = 2 - throw_range = 5 - materials = list(MAT_METAL=10000, MAT_GLASS=6000) - attack_verb = list("attacked", "slashed", "sawed", "cut") - sharpness = IS_SHARP - tool_behaviour = TOOL_SAW - toolspeed = 1 - -/obj/item/circular_saw/Initialize() - . = ..() - AddComponent(/datum/component/butchering, 40 * toolspeed, 100, 5, 'sound/weapons/circsawhit.ogg') //saws are very accurate and fast at butchering - - -/obj/item/circular_saw/augment - name = "circular saw" - desc = "A small but very fast spinning saw. Edges dulled to prevent accidental cutting inside of the surgeon." - icon = 'icons/obj/surgery.dmi' - icon_state = "saw" - hitsound = 'sound/weapons/circsawhit.ogg' - throwhitsound = 'sound/weapons/pierce.ogg' - flags_1 = CONDUCT_1 - force = 10 - w_class = WEIGHT_CLASS_SMALL - throwforce = 9 - throw_speed = 2 - throw_range = 5 - materials = list(MAT_METAL=10000, MAT_GLASS=6000) - toolspeed = 0.5 - attack_verb = list("attacked", "slashed", "sawed", "cut") - sharpness = IS_SHARP - -/obj/item/surgical_drapes - name = "surgical drapes" - desc = "Nanotrasen brand surgical drapes provide optimal safety and infection control." - icon = 'icons/obj/surgery.dmi' - icon_state = "surgical_drapes" - w_class = WEIGHT_CLASS_TINY - attack_verb = list("slapped") - -/obj/item/surgical_drapes/attack(mob/living/M, mob/user) - if(!attempt_initiate_surgery(src, M, user)) - ..() - -/obj/item/organ_storage //allows medical cyborgs to manipulate organs without hands - name = "organ storage bag" - desc = "A container for holding body parts." - icon = 'icons/obj/storage.dmi' - icon_state = "evidenceobj" - item_flags = SURGICAL_TOOL - -/obj/item/organ_storage/afterattack(obj/item/I, mob/user, proximity) - . = ..() - if(!proximity) - return - if(contents.len) - to_chat(user, "[src] already has something inside it.") - return - if(!isorgan(I) && !isbodypart(I)) - to_chat(user, "[src] can only hold body parts!") - return - - user.visible_message("[user] puts [I] into [src].", "You put [I] inside [src].") - icon_state = "evidence" - var/xx = I.pixel_x - var/yy = I.pixel_y - I.pixel_x = 0 - I.pixel_y = 0 - var/image/img = image("icon"=I, "layer"=FLOAT_LAYER) - img.plane = FLOAT_PLANE - I.pixel_x = xx - I.pixel_y = yy - add_overlay(img) - add_overlay("evidence") - desc = "An organ storage container holding [I]." - I.forceMove(src) - w_class = I.w_class - -/obj/item/organ_storage/attack_self(mob/user) - if(contents.len) - var/obj/item/I = contents[1] - user.visible_message("[user] dumps [I] from [src].", "You dump [I] from [src].") - cut_overlays() - I.forceMove(get_turf(src)) - icon_state = "evidenceobj" - desc = "A container for holding body parts." - else - to_chat(user, "[src] is empty.") - return - -/obj/item/surgical_processor //allows medical cyborgs to scan and initiate advanced surgeries - name = "\improper Surgical Processor" - desc = "A device for scanning and initiating surgeries from a disk or operating computer." - icon = 'icons/obj/device.dmi' - icon_state = "spectrometer" - item_flags = NOBLUDGEON - var/list/advanced_surgeries = list() - -/obj/item/surgical_processor/afterattack(obj/item/O, mob/user, proximity) - . = ..() - if(!proximity) - return - if(istype(O, /obj/item/disk/surgery)) - to_chat(user, "You load the surgery protocol from [O] into [src].") - var/obj/item/disk/surgery/D = O - if(do_after(user, 10, target = O)) - advanced_surgeries |= D.surgeries - return TRUE - if(istype(O, /obj/machinery/computer/operating)) - to_chat(user, "You copy surgery protocols from [O] into [src].") - var/obj/machinery/computer/operating/OC = O - if(do_after(user, 10, target = O)) - advanced_surgeries |= OC.advanced_surgeries - return TRUE - return +/obj/item/retractor + name = "retractor" + desc = "Retracts stuff." + icon = 'icons/obj/surgery.dmi' + icon_state = "retractor" + materials = list(MAT_METAL=6000, MAT_GLASS=3000) + item_flags = SURGICAL_TOOL + flags_1 = CONDUCT_1 + w_class = WEIGHT_CLASS_TINY + tool_behaviour = TOOL_RETRACTOR + toolspeed = 1 + +/obj/item/retractor/advanced + name = "mechanical pinches" + desc = "An agglomerate of rods and gears." + icon = 'icons/obj/surgery.dmi' + icon_state = "retractor_a" + toolspeed = 0.7 + +/obj/item/retractor/advanced/attack_self(mob/user) + playsound(get_turf(user), 'sound/items/change_drill.ogg', 50, TRUE) + if(tool_behaviour == TOOL_RETRACTOR) + tool_behaviour = TOOL_HEMOSTAT + to_chat(user, "You configure the gears of [src], they are now in hemostat mode.") + icon_state = "hemostat_a" + else + tool_behaviour = TOOL_RETRACTOR + to_chat(user, "You configure the gears of [src], they are now in retractor mode.") + icon_state = "retractor_a" + +/obj/item/retractor/advanced/examine(mob/living/user) + . = ..() + . += "You focus the lenses of [src], it is now in mending mode.") + icon_state = "cautery_a" + else + tool_behaviour = TOOL_DRILL + to_chat(user, "You dilate the lenses of [src], it is now in drilling mode.") + icon_state = "surgicaldrill_a" + +/obj/item/surgicaldrill/advanced/examine(mob/living/user) + . = ..() + . += "" + +/obj/item/surgicaldrill/augment + name = "surgical drill" + desc = "Effectively a small power drill contained within your arm, edges dulled to prevent tissue damage. May or may not pierce the heavens." + icon = 'icons/obj/surgery.dmi' + icon_state = "drill" + hitsound = 'sound/weapons/circsawhit.ogg' + materials = list(MAT_METAL=10000, MAT_GLASS=6000) + flags_1 = CONDUCT_1 + force = 10 + w_class = WEIGHT_CLASS_SMALL + toolspeed = 0.5 + attack_verb = list("drilled") + + +/obj/item/scalpel + name = "scalpel" + desc = "Cut, cut, and once more cut." + icon = 'icons/obj/surgery.dmi' + icon_state = "scalpel" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + flags_1 = CONDUCT_1 + force = 10 + w_class = WEIGHT_CLASS_TINY + throwforce = 5 + throw_speed = 3 + throw_range = 5 + materials = list(MAT_METAL=4000, MAT_GLASS=1000) + item_flags = SURGICAL_TOOL + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP_ACCURATE + tool_behaviour = TOOL_SCALPEL + toolspeed = 1 + +/obj/item/scalpel/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 80 * toolspeed, 100, 0) + +/obj/item/scalpel/advanced + name = "laser scalpel" + desc = "An advanced scalpel which uses laser technology to cut." + icon = 'icons/obj/surgery.dmi' + icon_state = "scalpel_a" + hitsound = 'sound/weapons/blade1.ogg' + force = 16 + toolspeed = 0.7 + light_color = LIGHT_COLOR_GREEN + sharpness = IS_SHARP_ACCURATE + +/obj/item/scalpel/advanced/Initialize() + . = ..() + set_light(1) + +/obj/item/scalpel/advanced/attack_self(mob/user) + playsound(get_turf(user), 'sound/machines/click.ogg', 50, TRUE) + if(tool_behaviour == TOOL_SCALPEL) + tool_behaviour = TOOL_SAW + to_chat(user, "You increase the power of [src], now it can cut bones.") + set_light(2) + force += 1 //we don't want to ruin sharpened stuff + icon_state = "saw_a" + else + tool_behaviour = TOOL_SCALPEL + to_chat(user, "You lower the power of [src], it can no longer cut bones.") + set_light(1) + force -= 1 + icon_state = "scalpel_a" + +/obj/item/scalpel/advanced/examine(mob/living/user) + . = ..() + . += "" + +/obj/item/scalpel/augment + name = "scalpel" + desc = "Ultra-sharp blade attached directly to your bone for extra-accuracy." + icon = 'icons/obj/surgery.dmi' + icon_state = "scalpel" + flags_1 = CONDUCT_1 + force = 10 + w_class = WEIGHT_CLASS_TINY + throwforce = 5 + throw_speed = 3 + throw_range = 5 + materials = list(MAT_METAL=4000, MAT_GLASS=1000) + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + toolspeed = 0.5 + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP_ACCURATE + +/obj/item/scalpel/suicide_act(mob/user) + user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat", "stomach")] with [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return (BRUTELOSS) + + +/obj/item/circular_saw + name = "circular saw" + desc = "For heavy duty cutting." + icon = 'icons/obj/surgery.dmi' + icon_state = "saw" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + hitsound = 'sound/weapons/circsawhit.ogg' + throwhitsound = 'sound/weapons/pierce.ogg' + item_flags = SURGICAL_TOOL + flags_1 = CONDUCT_1 + force = 15 + w_class = WEIGHT_CLASS_NORMAL + throwforce = 9 + throw_speed = 2 + throw_range = 5 + materials = list(MAT_METAL=10000, MAT_GLASS=6000) + attack_verb = list("attacked", "slashed", "sawed", "cut") + sharpness = IS_SHARP + tool_behaviour = TOOL_SAW + toolspeed = 1 + +/obj/item/circular_saw/Initialize() + . = ..() + AddComponent(/datum/component/butchering, 40 * toolspeed, 100, 5, 'sound/weapons/circsawhit.ogg') //saws are very accurate and fast at butchering + + +/obj/item/circular_saw/augment + name = "circular saw" + desc = "A small but very fast spinning saw. Edges dulled to prevent accidental cutting inside of the surgeon." + icon = 'icons/obj/surgery.dmi' + icon_state = "saw" + hitsound = 'sound/weapons/circsawhit.ogg' + throwhitsound = 'sound/weapons/pierce.ogg' + flags_1 = CONDUCT_1 + force = 10 + w_class = WEIGHT_CLASS_SMALL + throwforce = 9 + throw_speed = 2 + throw_range = 5 + materials = list(MAT_METAL=10000, MAT_GLASS=6000) + toolspeed = 0.5 + attack_verb = list("attacked", "slashed", "sawed", "cut") + sharpness = IS_SHARP + +/obj/item/surgical_drapes + name = "surgical drapes" + desc = "Nanotrasen brand surgical drapes provide optimal safety and infection control." + icon = 'icons/obj/surgery.dmi' + icon_state = "surgical_drapes" + w_class = WEIGHT_CLASS_TINY + attack_verb = list("slapped") + +/obj/item/surgical_drapes/attack(mob/living/M, mob/user) + if(!attempt_initiate_surgery(src, M, user)) + ..() + +/obj/item/organ_storage //allows medical cyborgs to manipulate organs without hands + name = "organ storage bag" + desc = "A container for holding body parts." + icon = 'icons/obj/storage.dmi' + icon_state = "evidenceobj" + item_flags = SURGICAL_TOOL + +/obj/item/organ_storage/afterattack(obj/item/I, mob/user, proximity) + . = ..() + if(!proximity) + return + if(contents.len) + to_chat(user, "[src] already has something inside it.") + return + if(!isorgan(I) && !isbodypart(I)) + to_chat(user, "[src] can only hold body parts!") + return + + user.visible_message("[user] puts [I] into [src].", "You put [I] inside [src].") + icon_state = "evidence" + var/xx = I.pixel_x + var/yy = I.pixel_y + I.pixel_x = 0 + I.pixel_y = 0 + var/image/img = image("icon"=I, "layer"=FLOAT_LAYER) + img.plane = FLOAT_PLANE + I.pixel_x = xx + I.pixel_y = yy + add_overlay(img) + add_overlay("evidence") + desc = "An organ storage container holding [I]." + I.forceMove(src) + w_class = I.w_class + +/obj/item/organ_storage/attack_self(mob/user) + if(contents.len) + var/obj/item/I = contents[1] + user.visible_message("[user] dumps [I] from [src].", "You dump [I] from [src].") + cut_overlays() + I.forceMove(get_turf(src)) + icon_state = "evidenceobj" + desc = "A container for holding body parts." + else + to_chat(user, "[src] is empty.") + return + +/obj/item/surgical_processor //allows medical cyborgs to scan and initiate advanced surgeries + name = "\improper Surgical Processor" + desc = "A device for scanning and initiating surgeries from a disk or operating computer." + icon = 'icons/obj/device.dmi' + icon_state = "spectrometer" + item_flags = NOBLUDGEON + var/list/advanced_surgeries = list() + +/obj/item/surgical_processor/afterattack(obj/item/O, mob/user, proximity) + . = ..() + if(!proximity) + return + if(istype(O, /obj/item/disk/surgery)) + to_chat(user, "You load the surgery protocol from [O] into [src].") + var/obj/item/disk/surgery/D = O + if(do_after(user, 10, target = O)) + advanced_surgeries |= D.surgeries + return TRUE + if(istype(O, /obj/machinery/computer/operating)) + to_chat(user, "You copy surgery protocols from [O] into [src].") + var/obj/machinery/computer/operating/OC = O + if(do_after(user, 10, target = O)) + advanced_surgeries |= OC.advanced_surgeries + return TRUE + return diff --git a/code/modules/tgs/core/_definitions.dm b/code/modules/tgs/core/_definitions.dm index d5e1a0075b..ebf6d17c2a 100644 --- a/code/modules/tgs/core/_definitions.dm +++ b/code/modules/tgs/core/_definitions.dm @@ -1,2 +1,2 @@ -#define TGS_UNIMPLEMENTED "___unimplemented" -#define TGS_VERSION_PARAMETER "server_service_version" +#define TGS_UNIMPLEMENTED "___unimplemented" +#define TGS_VERSION_PARAMETER "server_service_version" diff --git a/code/modules/tgs/core/core.dm b/code/modules/tgs/core/core.dm index 79c42ed37b..61f1e71d2e 100644 --- a/code/modules/tgs/core/core.dm +++ b/code/modules/tgs/core/core.dm @@ -1,144 +1,144 @@ -/world/TgsNew(datum/tgs_event_handler/event_handler) - var/tgs_version = world.params[TGS_VERSION_PARAMETER] - if(!tgs_version) - return - - var/path = SelectTgsApi(tgs_version) - if(!path) - TGS_ERROR_LOG("Found unsupported API version: [tgs_version]. If this is a valid version please report this, backporting is done on demand.") - - TGS_INFO_LOG("Activating API for version [tgs_version]") - var/datum/tgs_api/new_api = new path - - var/result = new_api.OnWorldNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default) - if(result && result != TGS_UNIMPLEMENTED) - TGS_WRITE_GLOBAL(tgs, new_api) - else - TGS_ERROR_LOG("Failed to activate API!") - -/world/proc/SelectTgsApi(tgs_version) - //remove the old 3.0 header - tgs_version = replacetext(tgs_version, "/tg/station 13 Server v", "") - - var/list/version_bits = splittext(tgs_version, ".") - - var/super = text2num(version_bits[1]) - var/major = text2num(version_bits[2]) - var/minor = text2num(version_bits[3]) - var/patch = text2num(version_bits[4]) - - switch(super) - if(3) - switch(major) - if(2) - return /datum/tgs_api/v3210 - - if(super != null && major != null && minor != null && patch != null && tgs_version > TgsMaximumAPIVersion()) - TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.") - return /datum/tgs_api/latest - -/world/TgsMaximumAPIVersion() - return "4.0.0.0" - -/world/TgsMinimumAPIVersion() - return "3.2.0.0" - -/world/TgsInitializationComplete() - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - api.OnInitializationComplete() - -/world/proc/TgsTopic(T) - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - var/result = api.OnTopic(T) - if(result != TGS_UNIMPLEMENTED) - return result - -/world/TgsRevision() - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - var/result = api.Revision() - if(result != TGS_UNIMPLEMENTED) - return result - -/world/TgsReboot() - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - api.OnReboot() - -/world/TgsAvailable() - return TGS_READ_GLOBAL(tgs) != null - -/world/TgsVersion() - return world.params[TGS_VERSION_PARAMETER] - -/world/TgsInstanceName() - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - var/result = api.InstanceName() - if(result != TGS_UNIMPLEMENTED) - return result - -/world/TgsTestMerges() - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - var/result = api.TestMerges() - if(result != TGS_UNIMPLEMENTED) - return result - return list() - -/world/TgsEndProcess() - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - api.EndProcess() - -/world/TgsChatChannelInfo() - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - var/result = api.ChatChannelInfo() - if(result != TGS_UNIMPLEMENTED) - return result - return list() - -/world/TgsChatBroadcast(message, list/channels) - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - api.ChatBroadcast(message, channels) - -/world/TgsTargetedChatBroadcast(message, admin_only) - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - api.ChatTargetedBroadcast(message, admin_only) - -/world/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) - var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) - if(api) - api.ChatPrivateMessage(message, user) - -/* -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ +/world/TgsNew(datum/tgs_event_handler/event_handler) + var/tgs_version = world.params[TGS_VERSION_PARAMETER] + if(!tgs_version) + return + + var/path = SelectTgsApi(tgs_version) + if(!path) + TGS_ERROR_LOG("Found unsupported API version: [tgs_version]. If this is a valid version please report this, backporting is done on demand.") + + TGS_INFO_LOG("Activating API for version [tgs_version]") + var/datum/tgs_api/new_api = new path + + var/result = new_api.OnWorldNew(event_handler ? event_handler : new /datum/tgs_event_handler/tgs_default) + if(result && result != TGS_UNIMPLEMENTED) + TGS_WRITE_GLOBAL(tgs, new_api) + else + TGS_ERROR_LOG("Failed to activate API!") + +/world/proc/SelectTgsApi(tgs_version) + //remove the old 3.0 header + tgs_version = replacetext(tgs_version, "/tg/station 13 Server v", "") + + var/list/version_bits = splittext(tgs_version, ".") + + var/super = text2num(version_bits[1]) + var/major = text2num(version_bits[2]) + var/minor = text2num(version_bits[3]) + var/patch = text2num(version_bits[4]) + + switch(super) + if(3) + switch(major) + if(2) + return /datum/tgs_api/v3210 + + if(super != null && major != null && minor != null && patch != null && tgs_version > TgsMaximumAPIVersion()) + TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.") + return /datum/tgs_api/latest + +/world/TgsMaximumAPIVersion() + return "4.0.0.0" + +/world/TgsMinimumAPIVersion() + return "3.2.0.0" + +/world/TgsInitializationComplete() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.OnInitializationComplete() + +/world/proc/TgsTopic(T) + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + var/result = api.OnTopic(T) + if(result != TGS_UNIMPLEMENTED) + return result + +/world/TgsRevision() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + var/result = api.Revision() + if(result != TGS_UNIMPLEMENTED) + return result + +/world/TgsReboot() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.OnReboot() + +/world/TgsAvailable() + return TGS_READ_GLOBAL(tgs) != null + +/world/TgsVersion() + return world.params[TGS_VERSION_PARAMETER] + +/world/TgsInstanceName() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + var/result = api.InstanceName() + if(result != TGS_UNIMPLEMENTED) + return result + +/world/TgsTestMerges() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + var/result = api.TestMerges() + if(result != TGS_UNIMPLEMENTED) + return result + return list() + +/world/TgsEndProcess() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.EndProcess() + +/world/TgsChatChannelInfo() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + var/result = api.ChatChannelInfo() + if(result != TGS_UNIMPLEMENTED) + return result + return list() + +/world/TgsChatBroadcast(message, list/channels) + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.ChatBroadcast(message, channels) + +/world/TgsTargetedChatBroadcast(message, admin_only) + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.ChatTargetedBroadcast(message, admin_only) + +/world/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + api.ChatPrivateMessage(message, user) + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/code/modules/tgs/core/datum.dm b/code/modules/tgs/core/datum.dm index 5da73bfdc5..6af39a75df 100644 --- a/code/modules/tgs/core/datum.dm +++ b/code/modules/tgs/core/datum.dm @@ -1,74 +1,74 @@ -TGS_DEFINE_AND_SET_GLOBAL(tgs, null) - -/datum/tgs_api - -/datum/tgs_api/latest - parent_type = /datum/tgs_api/v3210 - -TGS_PROTECT_DATUM(/datum/tgs_api) - -/datum/tgs_api/proc/ApiVersion() - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/OnWorldNew(datum/tgs_event_handler/event_handler) - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/OnInitializationComplete() - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/OnTopic(T) - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/OnReboot() - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/InstanceName() - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/TestMerges() - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/EndProcess() - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/Revision() - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/ChatChannelInfo() - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/ChatBroadcast(message, list/channels) - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only) - return TGS_UNIMPLEMENTED - -/datum/tgs_api/proc/ChatPrivateMessage(message, admin_only) - return TGS_UNIMPLEMENTED - -/* -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ +TGS_DEFINE_AND_SET_GLOBAL(tgs, null) + +/datum/tgs_api + +/datum/tgs_api/latest + parent_type = /datum/tgs_api/v3210 + +TGS_PROTECT_DATUM(/datum/tgs_api) + +/datum/tgs_api/proc/ApiVersion() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/OnWorldNew(datum/tgs_event_handler/event_handler) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/OnInitializationComplete() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/OnTopic(T) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/OnReboot() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/InstanceName() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/TestMerges() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/EndProcess() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/Revision() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/ChatChannelInfo() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/ChatBroadcast(message, list/channels) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/ChatTargetedBroadcast(message, admin_only) + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/ChatPrivateMessage(message, admin_only) + return TGS_UNIMPLEMENTED + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/code/modules/tgs/core/default_event_handler.dm b/code/modules/tgs/core/default_event_handler.dm index c0ea8eec46..716715bb26 100644 --- a/code/modules/tgs/core/default_event_handler.dm +++ b/code/modules/tgs/core/default_event_handler.dm @@ -1,30 +1,30 @@ -/datum/tgs_event_handler/tgs_default/HandleEvent(event_code) - //TODO - return - -/* -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ +/datum/tgs_event_handler/tgs_default/HandleEvent(event_code) + //TODO + return + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/code/modules/tgs/includes.dm b/code/modules/tgs/includes.dm index 586cbbe427..7ca906c840 100644 --- a/code/modules/tgs/includes.dm +++ b/code/modules/tgs/includes.dm @@ -1,6 +1,6 @@ -#include "core\_definitions.dm" -#include "core\core.dm" -#include "core\datum.dm" -#include "core\default_event_handler.dm" -#include "v3210\api.dm" -#include "v3210\commands.dm" +#include "core\_definitions.dm" +#include "core\core.dm" +#include "core\datum.dm" +#include "core\default_event_handler.dm" +#include "v3210\api.dm" +#include "v3210\commands.dm" diff --git a/code/modules/tgs/v3210/api.dm b/code/modules/tgs/v3210/api.dm index f9b32471de..2a95f7861e 100644 --- a/code/modules/tgs/v3210/api.dm +++ b/code/modules/tgs/v3210/api.dm @@ -1,248 +1,248 @@ -#define REBOOT_MODE_NORMAL 0 -#define REBOOT_MODE_HARD 1 -#define REBOOT_MODE_SHUTDOWN 2 - -#define SERVICE_WORLD_PARAM "server_service" -#define SERVICE_INSTANCE_PARAM "server_instance" -#define SERVICE_PR_TEST_JSON "prtestjob.json" -#define SERVICE_INTERFACE_DLL "TGDreamDaemonBridge.dll" -#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint" - -#define SERVICE_CMD_HARD_REBOOT "hard_reboot" -#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown" -#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce" -#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands" -#define SERVICE_CMD_API_COMPATIBLE "api_compat" -#define SERVICE_CMD_PLAYER_COUNT "client_count" - -#define SERVICE_CMD_PARAM_KEY "serviceCommsKey" -#define SERVICE_CMD_PARAM_COMMAND "command" -#define SERVICE_CMD_PARAM_SENDER "sender" -#define SERVICE_CMD_PARAM_CUSTOM "custom" - -#define SERVICE_REQUEST_KILL_PROCESS "killme" -#define SERVICE_REQUEST_IRC_BROADCAST "irc" -#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc" -#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot" -#define SERVICE_REQUEST_API_VERSION "api_ver" - -#define SERVICE_RETURN_SUCCESS "SUCCESS" - -/datum/tgs_api/v3210 - var/reboot_mode = REBOOT_MODE_NORMAL - var/comms_key - var/instance_name - var/originmastercommit - var/commit - var/list/cached_custom_tgs_chat_commands - var/warned_revison = FALSE - var/warned_custom_commands = FALSE - -/datum/tgs_api/v3210/ApiVersion() - return "3.2.1.0" - -/datum/tgs_api/v3210/proc/trim_left(text) - for (var/i = 1 to length(text)) - if (text2ascii(text, i) > 32) - return copytext(text, i) - return "" - -/datum/tgs_api/v3210/proc/trim_right(text) - for (var/i = length(text), i > 0, i--) - if (text2ascii(text, i) > 32) - return copytext(text, 1, i + 1) - return "" - -/datum/tgs_api/v3210/proc/file2list(filename) - return splittext(trim_left(trim_right(file2text(filename))), "\n") - -/datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler) //don't use event handling in this version - . = FALSE - comms_key = world.params[SERVICE_WORLD_PARAM] - instance_name = world.params[SERVICE_INSTANCE_PARAM] - if(!instance_name) - instance_name = "TG Station Server" //maybe just upgraded - - var/list/logs = file2list(".git/logs/HEAD") - if(logs.len) - logs = splittext(logs[logs.len - 1], " ") - commit = logs[2] - logs = file2list(".git/logs/refs/remotes/origin/master") - if(logs.len) - originmastercommit = splittext(logs[logs.len - 1], " ")[2] - - if(world.system_type != MS_WINDOWS) - TGS_ERROR_LOG("This API version is only supported on Windows. Not running on Windows. Aborting initialization!") - return - ListServiceCustomCommands(TRUE) - ExportService("[SERVICE_REQUEST_API_VERSION] [ApiVersion()]", TRUE) - return TRUE - -//nothing to do for v3 -/datum/tgs_api/v3210/OnInitializationComplete() - return - -/datum/tgs_api/v3210/InstanceName() - return world.params[SERVICE_INSTANCE_PARAM] - -/datum/tgs_api/v3210/proc/ExportService(command, skip_compat_check = FALSE) - . = FALSE - if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL)) - TGS_ERROR_LOG("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.") - return - call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval - return TRUE - -/datum/tgs_api/v3210/OnTopic(T) - var/list/params = params2list(T) - var/their_sCK = params[SERVICE_CMD_PARAM_KEY] - if(!their_sCK) - return FALSE //continue world/Topic - - if(their_sCK != comms_key) - return "Invalid comms key!"; - - var/command = params[SERVICE_CMD_PARAM_COMMAND] - if(!command) - return "No command!" - - switch(command) - if(SERVICE_CMD_API_COMPATIBLE) - return SERVICE_RETURN_SUCCESS - if(SERVICE_CMD_HARD_REBOOT) - if(reboot_mode != REBOOT_MODE_HARD) - reboot_mode = REBOOT_MODE_HARD - TGS_INFO_LOG("Hard reboot requested by service") - TGS_NOTIFY_ADMINS("The world will hard reboot at the end of the game. Requested by TGS.") - if(SERVICE_CMD_GRACEFUL_SHUTDOWN) - if(reboot_mode != REBOOT_MODE_SHUTDOWN) - reboot_mode = REBOOT_MODE_SHUTDOWN - TGS_INFO_LOG("Shutdown requested by service") - TGS_NOTIFY_ADMINS("The world will shutdown at the end of the game. Requested by TGS.") - if(SERVICE_CMD_WORLD_ANNOUNCE) - var/msg = params["message"] - if(!istext(msg) || !msg) - return "No message set!" - TGS_WORLD_ANNOUNCE(msg) - return SERVICE_RETURN_SUCCESS - if(SERVICE_CMD_PLAYER_COUNT) - return "[TGS_CLIENT_COUNT]" - if(SERVICE_CMD_LIST_CUSTOM) - return json_encode(ListServiceCustomCommands(FALSE)) - else - var/custom_command_result = HandleServiceCustomCommand(lowertext(command), params[SERVICE_CMD_PARAM_SENDER], params[SERVICE_CMD_PARAM_CUSTOM]) - if(custom_command_result) - return istext(custom_command_result) ? custom_command_result : SERVICE_RETURN_SUCCESS - return "Unknown command: [command]" - -/datum/tgs_api/v3210/OnReboot() - switch(reboot_mode) - if(REBOOT_MODE_HARD) - TGS_WORLD_ANNOUNCE("Hard reboot triggered, you will automatically reconnect...") - EndProcess() - if(REBOOT_MODE_SHUTDOWN) - TGS_WORLD_ANNOUNCE("The server is shutting down...") - EndProcess() - else - ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know - -/datum/tgs_api/v3210/TestMerges() - //do the best we can here as the datum can't be completed using the v3 api - . = list() - if(!fexists(SERVICE_PR_TEST_JSON)) - return - var/list/json = json_decode(file2text(SERVICE_PR_TEST_JSON)) - if(!json) - return - for(var/I in json) - var/datum/tgs_revision_information/test_merge/tm = new - tm.number = text2num(I) - var/list/entry = json[I] - tm.pull_request_commit = entry["commit"] - tm.author = entry["author"] - tm.title = entry["title"] - . += tm - -/datum/tgs_api/v3210/Revision() - if(!warned_revison) - TGS_ERROR_LOG("Use of TgsRevision on [ApiVersion()] origin_commit only points to master!") - warned_revison = TRUE - var/datum/tgs_revision_information/ri = new - ri.commit = commit - ri.origin_commit = originmastercommit - -/datum/tgs_api/v3210/EndProcess() - sleep(world.tick_lag) //flush the buffers - ExportService(SERVICE_REQUEST_KILL_PROCESS) - -/datum/tgs_api/v3210/ChatChannelInfo() - return list() - -/datum/tgs_api/v3210/ChatBroadcast(message, list/channels) - if(channels) - return TGS_UNIMPLEMENTED - ChatTargetedBroadcast(message, TRUE) - ChatTargetedBroadcast(message, FALSE) - -/datum/tgs_api/v3210/ChatTargetedBroadcast(message, admin_only) - ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message]") - -/datum/tgs_api/v3210/ChatPrivateMessage(message, admin_only) - return TGS_UNIMPLEMENTED - -#undef REBOOT_MODE_NORMAL -#undef REBOOT_MODE_HARD -#undef REBOOT_MODE_SHUTDOWN - -#undef SERVICE_WORLD_PARAM -#undef SERVICE_INSTANCE_PARAM -#undef SERVICE_PR_TEST_JSON -#undef SERVICE_INTERFACE_DLL -#undef SERVICE_INTERFACE_FUNCTION - -#undef SERVICE_CMD_HARD_REBOOT -#undef SERVICE_CMD_GRACEFUL_SHUTDOWN -#undef SERVICE_CMD_WORLD_ANNOUNCE -#undef SERVICE_CMD_LIST_CUSTOM -#undef SERVICE_CMD_API_COMPATIBLE -#undef SERVICE_CMD_PLAYER_COUNT - -#undef SERVICE_CMD_PARAM_KEY -#undef SERVICE_CMD_PARAM_COMMAND -#undef SERVICE_CMD_PARAM_SENDER -#undef SERVICE_CMD_PARAM_CUSTOM - -#undef SERVICE_REQUEST_KILL_PROCESS -#undef SERVICE_REQUEST_IRC_BROADCAST -#undef SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE -#undef SERVICE_REQUEST_WORLD_REBOOT -#undef SERVICE_REQUEST_API_VERSION - -#undef SERVICE_RETURN_SUCCESS - -/* -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ +#define REBOOT_MODE_NORMAL 0 +#define REBOOT_MODE_HARD 1 +#define REBOOT_MODE_SHUTDOWN 2 + +#define SERVICE_WORLD_PARAM "server_service" +#define SERVICE_INSTANCE_PARAM "server_instance" +#define SERVICE_PR_TEST_JSON "prtestjob.json" +#define SERVICE_INTERFACE_DLL "TGDreamDaemonBridge.dll" +#define SERVICE_INTERFACE_FUNCTION "DDEntryPoint" + +#define SERVICE_CMD_HARD_REBOOT "hard_reboot" +#define SERVICE_CMD_GRACEFUL_SHUTDOWN "graceful_shutdown" +#define SERVICE_CMD_WORLD_ANNOUNCE "world_announce" +#define SERVICE_CMD_LIST_CUSTOM "list_custom_commands" +#define SERVICE_CMD_API_COMPATIBLE "api_compat" +#define SERVICE_CMD_PLAYER_COUNT "client_count" + +#define SERVICE_CMD_PARAM_KEY "serviceCommsKey" +#define SERVICE_CMD_PARAM_COMMAND "command" +#define SERVICE_CMD_PARAM_SENDER "sender" +#define SERVICE_CMD_PARAM_CUSTOM "custom" + +#define SERVICE_REQUEST_KILL_PROCESS "killme" +#define SERVICE_REQUEST_IRC_BROADCAST "irc" +#define SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE "send2irc" +#define SERVICE_REQUEST_WORLD_REBOOT "worldreboot" +#define SERVICE_REQUEST_API_VERSION "api_ver" + +#define SERVICE_RETURN_SUCCESS "SUCCESS" + +/datum/tgs_api/v3210 + var/reboot_mode = REBOOT_MODE_NORMAL + var/comms_key + var/instance_name + var/originmastercommit + var/commit + var/list/cached_custom_tgs_chat_commands + var/warned_revison = FALSE + var/warned_custom_commands = FALSE + +/datum/tgs_api/v3210/ApiVersion() + return "3.2.1.0" + +/datum/tgs_api/v3210/proc/trim_left(text) + for (var/i = 1 to length(text)) + if (text2ascii(text, i) > 32) + return copytext(text, i) + return "" + +/datum/tgs_api/v3210/proc/trim_right(text) + for (var/i = length(text), i > 0, i--) + if (text2ascii(text, i) > 32) + return copytext(text, 1, i + 1) + return "" + +/datum/tgs_api/v3210/proc/file2list(filename) + return splittext(trim_left(trim_right(file2text(filename))), "\n") + +/datum/tgs_api/v3210/OnWorldNew(datum/tgs_event_handler/event_handler) //don't use event handling in this version + . = FALSE + comms_key = world.params[SERVICE_WORLD_PARAM] + instance_name = world.params[SERVICE_INSTANCE_PARAM] + if(!instance_name) + instance_name = "TG Station Server" //maybe just upgraded + + var/list/logs = file2list(".git/logs/HEAD") + if(logs.len) + logs = splittext(logs[logs.len - 1], " ") + commit = logs[2] + logs = file2list(".git/logs/refs/remotes/origin/master") + if(logs.len) + originmastercommit = splittext(logs[logs.len - 1], " ")[2] + + if(world.system_type != MS_WINDOWS) + TGS_ERROR_LOG("This API version is only supported on Windows. Not running on Windows. Aborting initialization!") + return + ListServiceCustomCommands(TRUE) + ExportService("[SERVICE_REQUEST_API_VERSION] [ApiVersion()]", TRUE) + return TRUE + +//nothing to do for v3 +/datum/tgs_api/v3210/OnInitializationComplete() + return + +/datum/tgs_api/v3210/InstanceName() + return world.params[SERVICE_INSTANCE_PARAM] + +/datum/tgs_api/v3210/proc/ExportService(command, skip_compat_check = FALSE) + . = FALSE + if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL)) + TGS_ERROR_LOG("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.") + return + call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval + return TRUE + +/datum/tgs_api/v3210/OnTopic(T) + var/list/params = params2list(T) + var/their_sCK = params[SERVICE_CMD_PARAM_KEY] + if(!their_sCK) + return FALSE //continue world/Topic + + if(their_sCK != comms_key) + return "Invalid comms key!"; + + var/command = params[SERVICE_CMD_PARAM_COMMAND] + if(!command) + return "No command!" + + switch(command) + if(SERVICE_CMD_API_COMPATIBLE) + return SERVICE_RETURN_SUCCESS + if(SERVICE_CMD_HARD_REBOOT) + if(reboot_mode != REBOOT_MODE_HARD) + reboot_mode = REBOOT_MODE_HARD + TGS_INFO_LOG("Hard reboot requested by service") + TGS_NOTIFY_ADMINS("The world will hard reboot at the end of the game. Requested by TGS.") + if(SERVICE_CMD_GRACEFUL_SHUTDOWN) + if(reboot_mode != REBOOT_MODE_SHUTDOWN) + reboot_mode = REBOOT_MODE_SHUTDOWN + TGS_INFO_LOG("Shutdown requested by service") + TGS_NOTIFY_ADMINS("The world will shutdown at the end of the game. Requested by TGS.") + if(SERVICE_CMD_WORLD_ANNOUNCE) + var/msg = params["message"] + if(!istext(msg) || !msg) + return "No message set!" + TGS_WORLD_ANNOUNCE(msg) + return SERVICE_RETURN_SUCCESS + if(SERVICE_CMD_PLAYER_COUNT) + return "[TGS_CLIENT_COUNT]" + if(SERVICE_CMD_LIST_CUSTOM) + return json_encode(ListServiceCustomCommands(FALSE)) + else + var/custom_command_result = HandleServiceCustomCommand(lowertext(command), params[SERVICE_CMD_PARAM_SENDER], params[SERVICE_CMD_PARAM_CUSTOM]) + if(custom_command_result) + return istext(custom_command_result) ? custom_command_result : SERVICE_RETURN_SUCCESS + return "Unknown command: [command]" + +/datum/tgs_api/v3210/OnReboot() + switch(reboot_mode) + if(REBOOT_MODE_HARD) + TGS_WORLD_ANNOUNCE("Hard reboot triggered, you will automatically reconnect...") + EndProcess() + if(REBOOT_MODE_SHUTDOWN) + TGS_WORLD_ANNOUNCE("The server is shutting down...") + EndProcess() + else + ExportService(SERVICE_REQUEST_WORLD_REBOOT) //just let em know + +/datum/tgs_api/v3210/TestMerges() + //do the best we can here as the datum can't be completed using the v3 api + . = list() + if(!fexists(SERVICE_PR_TEST_JSON)) + return + var/list/json = json_decode(file2text(SERVICE_PR_TEST_JSON)) + if(!json) + return + for(var/I in json) + var/datum/tgs_revision_information/test_merge/tm = new + tm.number = text2num(I) + var/list/entry = json[I] + tm.pull_request_commit = entry["commit"] + tm.author = entry["author"] + tm.title = entry["title"] + . += tm + +/datum/tgs_api/v3210/Revision() + if(!warned_revison) + TGS_ERROR_LOG("Use of TgsRevision on [ApiVersion()] origin_commit only points to master!") + warned_revison = TRUE + var/datum/tgs_revision_information/ri = new + ri.commit = commit + ri.origin_commit = originmastercommit + +/datum/tgs_api/v3210/EndProcess() + sleep(world.tick_lag) //flush the buffers + ExportService(SERVICE_REQUEST_KILL_PROCESS) + +/datum/tgs_api/v3210/ChatChannelInfo() + return list() + +/datum/tgs_api/v3210/ChatBroadcast(message, list/channels) + if(channels) + return TGS_UNIMPLEMENTED + ChatTargetedBroadcast(message, TRUE) + ChatTargetedBroadcast(message, FALSE) + +/datum/tgs_api/v3210/ChatTargetedBroadcast(message, admin_only) + ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message]") + +/datum/tgs_api/v3210/ChatPrivateMessage(message, admin_only) + return TGS_UNIMPLEMENTED + +#undef REBOOT_MODE_NORMAL +#undef REBOOT_MODE_HARD +#undef REBOOT_MODE_SHUTDOWN + +#undef SERVICE_WORLD_PARAM +#undef SERVICE_INSTANCE_PARAM +#undef SERVICE_PR_TEST_JSON +#undef SERVICE_INTERFACE_DLL +#undef SERVICE_INTERFACE_FUNCTION + +#undef SERVICE_CMD_HARD_REBOOT +#undef SERVICE_CMD_GRACEFUL_SHUTDOWN +#undef SERVICE_CMD_WORLD_ANNOUNCE +#undef SERVICE_CMD_LIST_CUSTOM +#undef SERVICE_CMD_API_COMPATIBLE +#undef SERVICE_CMD_PLAYER_COUNT + +#undef SERVICE_CMD_PARAM_KEY +#undef SERVICE_CMD_PARAM_COMMAND +#undef SERVICE_CMD_PARAM_SENDER +#undef SERVICE_CMD_PARAM_CUSTOM + +#undef SERVICE_REQUEST_KILL_PROCESS +#undef SERVICE_REQUEST_IRC_BROADCAST +#undef SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE +#undef SERVICE_REQUEST_WORLD_REBOOT +#undef SERVICE_REQUEST_API_VERSION + +#undef SERVICE_RETURN_SUCCESS + +/* +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/code/modules/tgs/v3210/commands.dm b/code/modules/tgs/v3210/commands.dm index e674fd4e78..e5bc6f4895 100644 --- a/code/modules/tgs/v3210/commands.dm +++ b/code/modules/tgs/v3210/commands.dm @@ -1,78 +1,78 @@ -#define SERVICE_JSON_PARAM_HELPTEXT "help_text" -#define SERVICE_JSON_PARAM_ADMINONLY "admin_only" -#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters" - -/datum/tgs_api/v3210/proc/ListServiceCustomCommands(warnings_only) - if(!warnings_only) - . = list() - var/list/command_name_types = list() - var/list/warned_command_names = warnings_only ? list() : null - var/warned_about_the_dangers_of_robutussin = !warnings_only - for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) - if(!warned_about_the_dangers_of_robutussin) - TGS_ERROR_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!") - warned_about_the_dangers_of_robutussin = TRUE - var/datum/tgs_chat_command/stc = I - var/command_name = initial(stc.name) - if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) - if(warnings_only && !warned_command_names[command_name]) - TGS_ERROR_LOG("Custom command [command_name] can't be used as it is empty or contains illegal characters!") - warned_command_names[command_name] = TRUE - continue - - if(command_name_types[command_name]) - if(warnings_only) - TGS_ERROR_LOG("Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!") - continue - command_name_types[stc] = command_name - - if(!warnings_only) - .[command_name] = list(SERVICE_JSON_PARAM_HELPTEXT = initial(stc.help_text), SERVICE_JSON_PARAM_ADMINONLY = initial(stc.admin_only), SERVICE_JSON_PARAM_REQUIREDPARAMETERS = 0) - -/datum/tgs_api/v3210/proc/HandleServiceCustomCommand(command, sender, params) - if(!cached_custom_tgs_chat_commands) - cached_custom_tgs_chat_commands = list() - for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) - var/datum/tgs_chat_command/stc = I - cached_custom_tgs_chat_commands[lowertext(initial(stc.name))] = stc - - var/command_type = cached_custom_tgs_chat_commands[command] - if(!command_type) - return FALSE - var/datum/tgs_chat_command/stc = new command_type - var/datum/tgs_chat_user/user = new - user.friendly_name = sender - user.mention = sender - return stc.Run(user, params) || TRUE - -/* - -#undef SERVICE_JSON_PARAM_HELPTEXT -#undef SERVICE_JSON_PARAM_ADMINONLY -#undef SERVICE_JSON_PARAM_REQUIREDPARAMETERS - -The MIT License - -Copyright (c) 2017 Jordan Brown - -Permission is hereby granted, free of charge, -to any person obtaining a copy of this software and -associated documentation files (the "Software"), to -deal in the Software without restriction, including -without limitation the rights to use, copy, modify, -merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom -the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ +#define SERVICE_JSON_PARAM_HELPTEXT "help_text" +#define SERVICE_JSON_PARAM_ADMINONLY "admin_only" +#define SERVICE_JSON_PARAM_REQUIREDPARAMETERS "required_parameters" + +/datum/tgs_api/v3210/proc/ListServiceCustomCommands(warnings_only) + if(!warnings_only) + . = list() + var/list/command_name_types = list() + var/list/warned_command_names = warnings_only ? list() : null + var/warned_about_the_dangers_of_robutussin = !warnings_only + for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) + if(!warned_about_the_dangers_of_robutussin) + TGS_ERROR_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!") + warned_about_the_dangers_of_robutussin = TRUE + var/datum/tgs_chat_command/stc = I + var/command_name = initial(stc.name) + if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) + if(warnings_only && !warned_command_names[command_name]) + TGS_ERROR_LOG("Custom command [command_name] can't be used as it is empty or contains illegal characters!") + warned_command_names[command_name] = TRUE + continue + + if(command_name_types[command_name]) + if(warnings_only) + TGS_ERROR_LOG("Custom commands [command_name_types[command_name]] and [stc] have the same name, only [command_name_types[command_name]] will be available!") + continue + command_name_types[stc] = command_name + + if(!warnings_only) + .[command_name] = list(SERVICE_JSON_PARAM_HELPTEXT = initial(stc.help_text), SERVICE_JSON_PARAM_ADMINONLY = initial(stc.admin_only), SERVICE_JSON_PARAM_REQUIREDPARAMETERS = 0) + +/datum/tgs_api/v3210/proc/HandleServiceCustomCommand(command, sender, params) + if(!cached_custom_tgs_chat_commands) + cached_custom_tgs_chat_commands = list() + for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) + var/datum/tgs_chat_command/stc = I + cached_custom_tgs_chat_commands[lowertext(initial(stc.name))] = stc + + var/command_type = cached_custom_tgs_chat_commands[command] + if(!command_type) + return FALSE + var/datum/tgs_chat_command/stc = new command_type + var/datum/tgs_chat_user/user = new + user.friendly_name = sender + user.mention = sender + return stc.Run(user, params) || TRUE + +/* + +#undef SERVICE_JSON_PARAM_HELPTEXT +#undef SERVICE_JSON_PARAM_ADMINONLY +#undef SERVICE_JSON_PARAM_REQUIREDPARAMETERS + +The MIT License + +Copyright (c) 2017 Jordan Brown + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), to +deal in the Software without restriction, including +without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom +the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 9b94295414..77ee3697ba 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -1,387 +1,387 @@ - /** - * tgui - * - * /tg/station user interface library - **/ - - /** - * tgui datum (represents a UI). - **/ -/datum/tgui - var/mob/user // The mob who opened/is using the UI. - var/datum/src_object // The object which owns the UI. - var/title // The title of te UI. - var/ui_key // The ui_key of the UI. This allows multiple UIs for one src_object. - var/window_id // The window_id for browse() and onclose(). - var/width = 0 // The window width. - var/height = 0 // The window height - var/window_options = list( // Extra options to winset(). - "focus" = FALSE, - "titlebar" = TRUE, - "can_resize" = TRUE, - "can_minimize" = TRUE, - "can_maximize" = FALSE, - "can_close" = TRUE, - "auto_format" = FALSE - ) - var/style = "nanotrasen" // The style to be used for this UI. - var/interface // The interface (template) to be used for this UI. - var/autoupdate = TRUE // Update the UI every MC tick. - var/initialized = FALSE // If the UI has been initialized yet. - var/list/initial_data // The data (and datastructure) used to initialize the UI. - var/status = UI_INTERACTIVE // The status/visibility of the UI. - var/datum/ui_state/state = null // Topic state used to determine status/interactability. - var/datum/tgui/master_ui // The parent UI. - var/list/datum/tgui/children = list() // Children of this UI. - var/titlebar = TRUE - var/custom_browser_id = FALSE - var/ui_screen = "home" - - /** - * public - * - * Create a new UI. - * - * required user mob The mob who opened/is using the UI. - * required src_object datum The object or datum which owns the UI. - * required ui_key string The ui_key of the UI. - * required interface string The interface used to render the UI. - * optional title string The title of the UI. - * optional width int The window width. - * optional height int The window height. - * optional master_ui datum/tgui The parent UI. - * optional state datum/ui_state The state used to determine status. - * - * return datum/tgui The requested UI. - **/ -/datum/tgui/New(mob/user, datum/src_object, ui_key, interface, title, width = 0, height = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state, browser_id = null) - src.user = user - src.src_object = src_object - src.ui_key = ui_key - src.window_id = browser_id ? browser_id : "[REF(src_object)]-[ui_key]" - src.custom_browser_id = browser_id ? TRUE : FALSE - - set_interface(interface) - - if(title) - src.title = sanitize(title) - if(width) - src.width = width - if(height) - src.height = height - - src.master_ui = master_ui - if(master_ui) - master_ui.children += src - src.state = state - - var/datum/asset/assets = get_asset_datum(/datum/asset/simple/tgui) - assets.send(user) - - /** - * public - * - * Open this UI (and initialize it with data). - **/ -/datum/tgui/proc/open() - if(!user.client) - return // Bail if there is no client. - - update_status(push = 0) // Update the window status. - if(status < UI_UPDATE) - return // Bail if we're not supposed to open. - - if(!initial_data) - set_initial_data(src_object.ui_data(user)) // Get the UI data. - - var/window_size = "" - if(width && height) // If we have a width and height, use them. - window_size = "size=[width]x[height];" - - var/debugable = check_rights_for(user.client, R_DEBUG) - user << browse(get_html(debugable), "window=[window_id];[window_size][list2params(window_options)]") // Open the window. - if (!custom_browser_id) - winset(user, window_id, "on-close=\"uiclose [REF(src)]\"") // Instruct the client to signal UI when the window is closed. - SStgui.on_open(src) - - /** - * public - * - * Reinitialize the UI. - * (Possibly with a new interface and/or data). - * - * optional template string The name of the new interface. - * optional data list The new initial data. - **/ -/datum/tgui/proc/reinitialize(interface, list/data) - if(interface) - set_interface(interface) // Set a new interface. - if(data) - set_initial_data(data) // Replace the initial_data. - open() - - /** - * public - * - * Close the UI, and all its children. - **/ -/datum/tgui/proc/close() - user << browse(null, "window=[window_id]") // Close the window. - src_object.ui_close() - SStgui.on_close(src) - for(var/datum/tgui/child in children) // Loop through and close all children. - child.close() - children.Cut() - state = null - master_ui = null - qdel(src) - - /** - * public - * - * Sets the browse() window options for this UI. - * - * required window_options list The window options to set. - **/ -/datum/tgui/proc/set_window_options(list/window_options) - src.window_options = window_options - - /** - * public - * - * Set the style for this UI. - * - * required style string The new UI style. - **/ -/datum/tgui/proc/set_style(style) - src.style = lowertext(style) - - /** - * public - * - * Set the interface (template) for this UI. - * - * required interface string The new UI interface. - **/ -/datum/tgui/proc/set_interface(interface) - src.interface = lowertext(interface) - - /** - * public - * - * Enable/disable auto-updating of the UI. - * - * required state bool Enable/disable auto-updating. - **/ -/datum/tgui/proc/set_autoupdate(state = 1) - autoupdate = state - - /** - * private - * - * Set the data to initialize the UI with. - * The datastructure cannot be changed by subsequent updates. - * - * optional data list The data/datastructure to initialize the UI with. - **/ -/datum/tgui/proc/set_initial_data(list/data) - initial_data = data - - /** - * private - * - * Generate HTML for this UI. - * - * optional bool inline If the JSON should be inlined into the HTML (for debugging). - * - * return string UI HTML output. - **/ -/datum/tgui/proc/get_html(var/inline) - var/html - html = SStgui.basehtml - - //Allow the src object to override the html if needed - html = src_object.ui_base_html(html) - //Strip out any remaining custom tags that are used in ui_base_html - html = replacetext(html, "", "") - - // Poplate HTML with JSON if we're supposed to inline. - if(inline) - html = replacetextEx(html, "{}", get_json(initial_data)) - - - //Setup for tgui stuff, including styles - html = replacetextEx(html, "\[ref]", "[REF(src)]") - html = replacetextEx(html, "\[style]", style) - return html - - /** - * private - * - * Get the config data/datastructure to initialize the UI with. - * - * return list The config data. - **/ -/datum/tgui/proc/get_config_data() - var/list/config_data = list( - "title" = title, - "status" = status, - "screen" = ui_screen, - "style" = style, - "interface" = interface, - "fancy" = user.client.prefs.tgui_fancy, - "locked" = user.client.prefs.tgui_lock && !custom_browser_id, - "window" = window_id, - "ref" = "[REF(src)]", - "user" = list( - "name" = user.name, - "ref" = "[REF(user)]" - ), - "srcObject" = list( - "name" = "[src_object]", - "ref" = "[REF(src_object)]" - ), - "titlebar" = titlebar - ) - return config_data - - /** - * private - * - * Package the data to send to the UI, as JSON. - * This includes the UI data and config_data. - * - * return string The packaged JSON. - **/ -/datum/tgui/proc/get_json(list/data) - var/list/json_data = list() - - json_data["config"] = get_config_data() - if(!isnull(data)) - json_data["data"] = data - - // Generate the JSON. - var/json = json_encode(json_data) - // Strip #255/improper. - json = replacetext(json, "\proper", "") - json = replacetext(json, "\improper", "") - return json - - /** - * private - * - * Handle clicks from the UI. - * Call the src_object's ui_act() if status is UI_INTERACTIVE. - * If the src_object's ui_act() returns 1, update all UIs attacked to it. - **/ -/datum/tgui/Topic(href, href_list) - if(user != usr) - return // Something is not right here. - - var/action = href_list["action"] - var/params = href_list; params -= "action" - - switch(action) - if("tgui:initialize") - user << output(url_encode(get_json(initial_data)), "[custom_browser_id ? window_id : "[window_id].browser"]:initialize") - initialized = TRUE - if("tgui:view") - if(params["screen"]) - ui_screen = params["screen"] - SStgui.update_uis(src_object) - if("tgui:link") - user << link(params["url"]) - if("tgui:fancy") - user.client.prefs.tgui_fancy = TRUE - if("tgui:nofrills") - user.client.prefs.tgui_fancy = FALSE - else - update_status(push = 0) // Update the window state. - if(src_object.ui_act(action, params, src, state)) // Call ui_act() on the src_object. - SStgui.update_uis(src_object) // Update if the object requested it. - - /** - * private - * - * Update the UI. - * Only updates the data if update is true, otherwise only updates the status. - * - * optional force bool If the UI should be forced to update. - **/ -/datum/tgui/process(force = 0) - var/datum/host = src_object.ui_host(user) - if(!src_object || !host || !user) // If the object or user died (or something else), abort. - close() - return - - if(status && (force || autoupdate)) - update() // Update the UI if the status and update settings allow it. - else - update_status(push = 1) // Otherwise only update status. - - /** - * private - * - * Push data to an already open UI. - * - * required data list The data to send. - * optional force bool If the update should be sent regardless of state. - **/ -/datum/tgui/proc/push_data(data, force = 0) - update_status(push = 0) // Update the window state. - if(!initialized) - return // Cannot update UI if it is not set up yet. - if(status <= UI_DISABLED && !force) - return // Cannot update UI, we have no visibility. - - // Send the new JSON to the update() Javascript function. - user << output(url_encode(get_json(data)), "[custom_browser_id ? window_id : "[window_id].browser"]:update") - - /** - * private - * - * Updates the UI by interacting with the src_object again, which will hopefully - * call try_ui_update on it. - * - * optional force_open bool If force_open should be passed to ui_interact. - **/ -/datum/tgui/proc/update(force_open = FALSE) - src_object.ui_interact(user, ui_key, src, force_open, master_ui, state) - - /** - * private - * - * Update the status/visibility of the UI for its user. - * - * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED). - **/ -/datum/tgui/proc/update_status(push = 0) - var/status = src_object.ui_status(user, state) - if(master_ui) - status = min(status, master_ui.status) - - set_status(status, push) - if(status == UI_CLOSE) - close() - - /** - * private - * - * Set the status/visibility of the UI. - * - * required status int The status to set (UI_CLOSE/UI_DISABLED/UI_UPDATE/UI_INTERACTIVE). - * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED). - **/ -/datum/tgui/proc/set_status(status, push = 0) - if(src.status != status) // Only update if status has changed. - if(src.status == UI_DISABLED) - src.status = status - if(push) - update() - else - src.status = status - if(status == UI_DISABLED || push) // Update if the UI just because disabled, or a push is requested. - push_data(null, force = 1) - -/datum/tgui/proc/set_titlebar(value) - titlebar = value + /** + * tgui + * + * /tg/station user interface library + **/ + + /** + * tgui datum (represents a UI). + **/ +/datum/tgui + var/mob/user // The mob who opened/is using the UI. + var/datum/src_object // The object which owns the UI. + var/title // The title of te UI. + var/ui_key // The ui_key of the UI. This allows multiple UIs for one src_object. + var/window_id // The window_id for browse() and onclose(). + var/width = 0 // The window width. + var/height = 0 // The window height + var/window_options = list( // Extra options to winset(). + "focus" = FALSE, + "titlebar" = TRUE, + "can_resize" = TRUE, + "can_minimize" = TRUE, + "can_maximize" = FALSE, + "can_close" = TRUE, + "auto_format" = FALSE + ) + var/style = "nanotrasen" // The style to be used for this UI. + var/interface // The interface (template) to be used for this UI. + var/autoupdate = TRUE // Update the UI every MC tick. + var/initialized = FALSE // If the UI has been initialized yet. + var/list/initial_data // The data (and datastructure) used to initialize the UI. + var/status = UI_INTERACTIVE // The status/visibility of the UI. + var/datum/ui_state/state = null // Topic state used to determine status/interactability. + var/datum/tgui/master_ui // The parent UI. + var/list/datum/tgui/children = list() // Children of this UI. + var/titlebar = TRUE + var/custom_browser_id = FALSE + var/ui_screen = "home" + + /** + * public + * + * Create a new UI. + * + * required user mob The mob who opened/is using the UI. + * required src_object datum The object or datum which owns the UI. + * required ui_key string The ui_key of the UI. + * required interface string The interface used to render the UI. + * optional title string The title of the UI. + * optional width int The window width. + * optional height int The window height. + * optional master_ui datum/tgui The parent UI. + * optional state datum/ui_state The state used to determine status. + * + * return datum/tgui The requested UI. + **/ +/datum/tgui/New(mob/user, datum/src_object, ui_key, interface, title, width = 0, height = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state, browser_id = null) + src.user = user + src.src_object = src_object + src.ui_key = ui_key + src.window_id = browser_id ? browser_id : "[REF(src_object)]-[ui_key]" + src.custom_browser_id = browser_id ? TRUE : FALSE + + set_interface(interface) + + if(title) + src.title = sanitize(title) + if(width) + src.width = width + if(height) + src.height = height + + src.master_ui = master_ui + if(master_ui) + master_ui.children += src + src.state = state + + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/tgui) + assets.send(user) + + /** + * public + * + * Open this UI (and initialize it with data). + **/ +/datum/tgui/proc/open() + if(!user.client) + return // Bail if there is no client. + + update_status(push = 0) // Update the window status. + if(status < UI_UPDATE) + return // Bail if we're not supposed to open. + + if(!initial_data) + set_initial_data(src_object.ui_data(user)) // Get the UI data. + + var/window_size = "" + if(width && height) // If we have a width and height, use them. + window_size = "size=[width]x[height];" + + var/debugable = check_rights_for(user.client, R_DEBUG) + user << browse(get_html(debugable), "window=[window_id];[window_size][list2params(window_options)]") // Open the window. + if (!custom_browser_id) + winset(user, window_id, "on-close=\"uiclose [REF(src)]\"") // Instruct the client to signal UI when the window is closed. + SStgui.on_open(src) + + /** + * public + * + * Reinitialize the UI. + * (Possibly with a new interface and/or data). + * + * optional template string The name of the new interface. + * optional data list The new initial data. + **/ +/datum/tgui/proc/reinitialize(interface, list/data) + if(interface) + set_interface(interface) // Set a new interface. + if(data) + set_initial_data(data) // Replace the initial_data. + open() + + /** + * public + * + * Close the UI, and all its children. + **/ +/datum/tgui/proc/close() + user << browse(null, "window=[window_id]") // Close the window. + src_object.ui_close() + SStgui.on_close(src) + for(var/datum/tgui/child in children) // Loop through and close all children. + child.close() + children.Cut() + state = null + master_ui = null + qdel(src) + + /** + * public + * + * Sets the browse() window options for this UI. + * + * required window_options list The window options to set. + **/ +/datum/tgui/proc/set_window_options(list/window_options) + src.window_options = window_options + + /** + * public + * + * Set the style for this UI. + * + * required style string The new UI style. + **/ +/datum/tgui/proc/set_style(style) + src.style = lowertext(style) + + /** + * public + * + * Set the interface (template) for this UI. + * + * required interface string The new UI interface. + **/ +/datum/tgui/proc/set_interface(interface) + src.interface = lowertext(interface) + + /** + * public + * + * Enable/disable auto-updating of the UI. + * + * required state bool Enable/disable auto-updating. + **/ +/datum/tgui/proc/set_autoupdate(state = 1) + autoupdate = state + + /** + * private + * + * Set the data to initialize the UI with. + * The datastructure cannot be changed by subsequent updates. + * + * optional data list The data/datastructure to initialize the UI with. + **/ +/datum/tgui/proc/set_initial_data(list/data) + initial_data = data + + /** + * private + * + * Generate HTML for this UI. + * + * optional bool inline If the JSON should be inlined into the HTML (for debugging). + * + * return string UI HTML output. + **/ +/datum/tgui/proc/get_html(var/inline) + var/html + html = SStgui.basehtml + + //Allow the src object to override the html if needed + html = src_object.ui_base_html(html) + //Strip out any remaining custom tags that are used in ui_base_html + html = replacetext(html, "", "") + + // Poplate HTML with JSON if we're supposed to inline. + if(inline) + html = replacetextEx(html, "{}", get_json(initial_data)) + + + //Setup for tgui stuff, including styles + html = replacetextEx(html, "\[ref]", "[REF(src)]") + html = replacetextEx(html, "\[style]", style) + return html + + /** + * private + * + * Get the config data/datastructure to initialize the UI with. + * + * return list The config data. + **/ +/datum/tgui/proc/get_config_data() + var/list/config_data = list( + "title" = title, + "status" = status, + "screen" = ui_screen, + "style" = style, + "interface" = interface, + "fancy" = user.client.prefs.tgui_fancy, + "locked" = user.client.prefs.tgui_lock && !custom_browser_id, + "window" = window_id, + "ref" = "[REF(src)]", + "user" = list( + "name" = user.name, + "ref" = "[REF(user)]" + ), + "srcObject" = list( + "name" = "[src_object]", + "ref" = "[REF(src_object)]" + ), + "titlebar" = titlebar + ) + return config_data + + /** + * private + * + * Package the data to send to the UI, as JSON. + * This includes the UI data and config_data. + * + * return string The packaged JSON. + **/ +/datum/tgui/proc/get_json(list/data) + var/list/json_data = list() + + json_data["config"] = get_config_data() + if(!isnull(data)) + json_data["data"] = data + + // Generate the JSON. + var/json = json_encode(json_data) + // Strip #255/improper. + json = replacetext(json, "\proper", "") + json = replacetext(json, "\improper", "") + return json + + /** + * private + * + * Handle clicks from the UI. + * Call the src_object's ui_act() if status is UI_INTERACTIVE. + * If the src_object's ui_act() returns 1, update all UIs attacked to it. + **/ +/datum/tgui/Topic(href, href_list) + if(user != usr) + return // Something is not right here. + + var/action = href_list["action"] + var/params = href_list; params -= "action" + + switch(action) + if("tgui:initialize") + user << output(url_encode(get_json(initial_data)), "[custom_browser_id ? window_id : "[window_id].browser"]:initialize") + initialized = TRUE + if("tgui:view") + if(params["screen"]) + ui_screen = params["screen"] + SStgui.update_uis(src_object) + if("tgui:link") + user << link(params["url"]) + if("tgui:fancy") + user.client.prefs.tgui_fancy = TRUE + if("tgui:nofrills") + user.client.prefs.tgui_fancy = FALSE + else + update_status(push = 0) // Update the window state. + if(src_object.ui_act(action, params, src, state)) // Call ui_act() on the src_object. + SStgui.update_uis(src_object) // Update if the object requested it. + + /** + * private + * + * Update the UI. + * Only updates the data if update is true, otherwise only updates the status. + * + * optional force bool If the UI should be forced to update. + **/ +/datum/tgui/process(force = 0) + var/datum/host = src_object.ui_host(user) + if(!src_object || !host || !user) // If the object or user died (or something else), abort. + close() + return + + if(status && (force || autoupdate)) + update() // Update the UI if the status and update settings allow it. + else + update_status(push = 1) // Otherwise only update status. + + /** + * private + * + * Push data to an already open UI. + * + * required data list The data to send. + * optional force bool If the update should be sent regardless of state. + **/ +/datum/tgui/proc/push_data(data, force = 0) + update_status(push = 0) // Update the window state. + if(!initialized) + return // Cannot update UI if it is not set up yet. + if(status <= UI_DISABLED && !force) + return // Cannot update UI, we have no visibility. + + // Send the new JSON to the update() Javascript function. + user << output(url_encode(get_json(data)), "[custom_browser_id ? window_id : "[window_id].browser"]:update") + + /** + * private + * + * Updates the UI by interacting with the src_object again, which will hopefully + * call try_ui_update on it. + * + * optional force_open bool If force_open should be passed to ui_interact. + **/ +/datum/tgui/proc/update(force_open = FALSE) + src_object.ui_interact(user, ui_key, src, force_open, master_ui, state) + + /** + * private + * + * Update the status/visibility of the UI for its user. + * + * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED). + **/ +/datum/tgui/proc/update_status(push = 0) + var/status = src_object.ui_status(user, state) + if(master_ui) + status = min(status, master_ui.status) + + set_status(status, push) + if(status == UI_CLOSE) + close() + + /** + * private + * + * Set the status/visibility of the UI. + * + * required status int The status to set (UI_CLOSE/UI_DISABLED/UI_UPDATE/UI_INTERACTIVE). + * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED). + **/ +/datum/tgui/proc/set_status(status, push = 0) + if(src.status != status) // Only update if status has changed. + if(src.status == UI_DISABLED) + src.status = status + if(push) + update() + else + src.status = status + if(status == UI_DISABLED || push) // Update if the UI just because disabled, or a push is requested. + push_data(null, force = 1) + +/datum/tgui/proc/set_titlebar(value) + titlebar = value diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 642052026b..c23662de5a 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -1,12 +1,12 @@ -//include unit test files in this module in this ifdef -//Keep this sorted alphabetically - -#ifdef UNIT_TESTS -#include "component_tests.dm" -#include "reagent_id_typos.dm" -#include "reagent_recipe_collisions.dm" -#include "spawn_humans.dm" -#include "subsystem_init.dm" -#include "timer_sanity.dm" -#include "unit_test.dm" -#endif +//include unit test files in this module in this ifdef +//Keep this sorted alphabetically + +#ifdef UNIT_TESTS +#include "component_tests.dm" +#include "reagent_id_typos.dm" +#include "reagent_recipe_collisions.dm" +#include "spawn_humans.dm" +#include "subsystem_init.dm" +#include "timer_sanity.dm" +#include "unit_test.dm" +#endif diff --git a/code/modules/uplink/uplink_devices.dm b/code/modules/uplink/uplink_devices.dm index 5c47a2d342..2bcfb40c45 100644 --- a/code/modules/uplink/uplink_devices.dm +++ b/code/modules/uplink/uplink_devices.dm @@ -1,59 +1,59 @@ -// A collection of pre-set uplinks, for admin spawns. - -// Radio-like uplink; not an actual radio because this uplink is most commonly -// used for nuke ops, for whom opening the radio GUI and the uplink GUI -// simultaneously is an annoying distraction. -/obj/item/uplink - name = "station bounced radio" - icon = 'icons/obj/radio.dmi' - icon_state = "radio" - item_state = "walkietalkie" - desc = "A basic handheld radio that communicates with local telecommunication networks." - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - dog_fashion = /datum/dog_fashion/back - - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_BELT - throw_speed = 3 - throw_range = 7 - w_class = WEIGHT_CLASS_SMALL - -/obj/item/uplink/Initialize(mapload, owner, tc_amount = 20) - . = ..() - AddComponent(/datum/component/uplink, owner, FALSE, TRUE, null, tc_amount) - -/obj/item/uplink/nuclear/Initialize() - . = ..() - var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) - hidden_uplink.set_gamemode(/datum/game_mode/nuclear) - -/obj/item/uplink/nuclear_restricted/Initialize() - . = ..() - var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) - hidden_uplink.allow_restricted = FALSE - hidden_uplink.set_gamemode(/datum/game_mode/nuclear) - -/obj/item/uplink/clownop/Initialize() - . = ..() - var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) - hidden_uplink.set_gamemode(/datum/game_mode/nuclear/clown_ops) - -/obj/item/uplink/old - name = "dusty radio" - desc = "A dusty looking radio." - -/obj/item/uplink/old/Initialize(mapload, owner, tc_amount = 10) - . = ..() - var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) - hidden_uplink.name = "dusty radio" - -// Multitool uplink -/obj/item/multitool/uplink/Initialize(mapload, owner, tc_amount = 20) - . = ..() - AddComponent(/datum/component/uplink, owner, FALSE, TRUE, null, tc_amount) - -// Pen uplink -/obj/item/pen/uplink/Initialize(mapload, owner, tc_amount = 20) - . = ..() - AddComponent(/datum/component/uplink, owner, TRUE, FALSE, null, tc_amount) +// A collection of pre-set uplinks, for admin spawns. + +// Radio-like uplink; not an actual radio because this uplink is most commonly +// used for nuke ops, for whom opening the radio GUI and the uplink GUI +// simultaneously is an annoying distraction. +/obj/item/uplink + name = "station bounced radio" + icon = 'icons/obj/radio.dmi' + icon_state = "radio" + item_state = "walkietalkie" + desc = "A basic handheld radio that communicates with local telecommunication networks." + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + dog_fashion = /datum/dog_fashion/back + + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_BELT + throw_speed = 3 + throw_range = 7 + w_class = WEIGHT_CLASS_SMALL + +/obj/item/uplink/Initialize(mapload, owner, tc_amount = 20) + . = ..() + AddComponent(/datum/component/uplink, owner, FALSE, TRUE, null, tc_amount) + +/obj/item/uplink/nuclear/Initialize() + . = ..() + var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) + hidden_uplink.set_gamemode(/datum/game_mode/nuclear) + +/obj/item/uplink/nuclear_restricted/Initialize() + . = ..() + var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) + hidden_uplink.allow_restricted = FALSE + hidden_uplink.set_gamemode(/datum/game_mode/nuclear) + +/obj/item/uplink/clownop/Initialize() + . = ..() + var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) + hidden_uplink.set_gamemode(/datum/game_mode/nuclear/clown_ops) + +/obj/item/uplink/old + name = "dusty radio" + desc = "A dusty looking radio." + +/obj/item/uplink/old/Initialize(mapload, owner, tc_amount = 10) + . = ..() + var/datum/component/uplink/hidden_uplink = GetComponent(/datum/component/uplink) + hidden_uplink.name = "dusty radio" + +// Multitool uplink +/obj/item/multitool/uplink/Initialize(mapload, owner, tc_amount = 20) + . = ..() + AddComponent(/datum/component/uplink, owner, FALSE, TRUE, null, tc_amount) + +// Pen uplink +/obj/item/pen/uplink/Initialize(mapload, owner, tc_amount = 20) + . = ..() + AddComponent(/datum/component/uplink, owner, TRUE, FALSE, null, tc_amount) diff --git a/code/modules/vehicles/lavaboat.dm b/code/modules/vehicles/lavaboat.dm index ce33338ddc..32f53a15aa 100644 --- a/code/modules/vehicles/lavaboat.dm +++ b/code/modules/vehicles/lavaboat.dm @@ -1,69 +1,69 @@ - -//Boat - -/obj/vehicle/ridden/lavaboat - name = "lava boat" - desc = "A boat used for traversing lava." - icon_state = "goliath_boat" - icon = 'icons/obj/lavaland/dragonboat.dmi' - resistance_flags = LAVA_PROOF | FIRE_PROOF - can_buckle = TRUE - -/obj/vehicle/ridden/lavaboat/Initialize() - . = ..() - var/datum/component/riding/D = LoadComponent(/datum/component/riding) - D.keytype = /obj/item/oar - D.allowed_turf_typecache = typecacheof(/turf/open/lava) - -/obj/item/oar - name = "oar" - icon = 'icons/obj/vehicles.dmi' - icon_state = "oar" - item_state = "oar" - lefthand_file = 'icons/mob/inhands/misc/lavaland_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/lavaland_righthand.dmi' - desc = "Not to be confused with the kind Research hassles you for." - force = 12 - w_class = WEIGHT_CLASS_NORMAL - resistance_flags = LAVA_PROOF | FIRE_PROOF - -/datum/crafting_recipe/oar - name = "Goliath Bone Oar" - result = /obj/item/oar - reqs = list(/obj/item/stack/sheet/bone = 2) - time = 15 - category = CAT_PRIMAL - -/datum/crafting_recipe/boat - name = "Goliath Hide Boat" - result = /obj/vehicle/ridden/lavaboat - reqs = list(/obj/item/stack/sheet/animalhide/goliath_hide = 3) - time = 50 - category = CAT_PRIMAL - -//Dragon Boat - - -/obj/item/ship_in_a_bottle - name = "ship in a bottle" - desc = "A tiny ship inside a bottle." - icon = 'icons/obj/lavaland/artefacts.dmi' - icon_state = "ship_bottle" - -/obj/item/ship_in_a_bottle/attack_self(mob/user) - to_chat(user, "You're not sure how they get the ships in these things, but you're pretty sure you know how to get it out.") - playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, 1) - new /obj/vehicle/ridden/lavaboat/dragon(get_turf(src)) - qdel(src) - -/obj/vehicle/ridden/lavaboat/dragon - name = "mysterious boat" - desc = "This boat moves where you will it, without the need for an oar." - icon_state = "dragon_boat" - -/obj/vehicle/ridden/lavaboat/dragon/Initialize() - . = ..() - var/datum/component/riding/D = LoadComponent(/datum/component/riding) - D.vehicle_move_delay = 1 - D.set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(1, 2), TEXT_SOUTH = list(1, 2), TEXT_EAST = list(1, 2), TEXT_WEST = list( 1, 2))) - D.keytype = null + +//Boat + +/obj/vehicle/ridden/lavaboat + name = "lava boat" + desc = "A boat used for traversing lava." + icon_state = "goliath_boat" + icon = 'icons/obj/lavaland/dragonboat.dmi' + resistance_flags = LAVA_PROOF | FIRE_PROOF + can_buckle = TRUE + +/obj/vehicle/ridden/lavaboat/Initialize() + . = ..() + var/datum/component/riding/D = LoadComponent(/datum/component/riding) + D.keytype = /obj/item/oar + D.allowed_turf_typecache = typecacheof(/turf/open/lava) + +/obj/item/oar + name = "oar" + icon = 'icons/obj/vehicles.dmi' + icon_state = "oar" + item_state = "oar" + lefthand_file = 'icons/mob/inhands/misc/lavaland_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/lavaland_righthand.dmi' + desc = "Not to be confused with the kind Research hassles you for." + force = 12 + w_class = WEIGHT_CLASS_NORMAL + resistance_flags = LAVA_PROOF | FIRE_PROOF + +/datum/crafting_recipe/oar + name = "Goliath Bone Oar" + result = /obj/item/oar + reqs = list(/obj/item/stack/sheet/bone = 2) + time = 15 + category = CAT_PRIMAL + +/datum/crafting_recipe/boat + name = "Goliath Hide Boat" + result = /obj/vehicle/ridden/lavaboat + reqs = list(/obj/item/stack/sheet/animalhide/goliath_hide = 3) + time = 50 + category = CAT_PRIMAL + +//Dragon Boat + + +/obj/item/ship_in_a_bottle + name = "ship in a bottle" + desc = "A tiny ship inside a bottle." + icon = 'icons/obj/lavaland/artefacts.dmi' + icon_state = "ship_bottle" + +/obj/item/ship_in_a_bottle/attack_self(mob/user) + to_chat(user, "You're not sure how they get the ships in these things, but you're pretty sure you know how to get it out.") + playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, 1) + new /obj/vehicle/ridden/lavaboat/dragon(get_turf(src)) + qdel(src) + +/obj/vehicle/ridden/lavaboat/dragon + name = "mysterious boat" + desc = "This boat moves where you will it, without the need for an oar." + icon_state = "dragon_boat" + +/obj/vehicle/ridden/lavaboat/dragon/Initialize() + . = ..() + var/datum/component/riding/D = LoadComponent(/datum/component/riding) + D.vehicle_move_delay = 1 + D.set_riding_offsets(RIDING_OFFSET_ALL, list(TEXT_NORTH = list(1, 2), TEXT_SOUTH = list(1, 2), TEXT_EAST = list(1, 2), TEXT_WEST = list( 1, 2))) + D.keytype = null diff --git a/code/modules/vehicles/ridden.dm b/code/modules/vehicles/ridden.dm index 13a139ef2e..4f3970dd37 100644 --- a/code/modules/vehicles/ridden.dm +++ b/code/modules/vehicles/ridden.dm @@ -1,79 +1,79 @@ -/obj/vehicle/ridden - name = "ridden vehicle" - can_buckle = TRUE - max_buckled_mobs = 1 - buckle_lying = FALSE - default_driver_move = FALSE - var/legs_required = 1 - var/arms_required = 0 //why not? - -/obj/vehicle/ridden/Initialize() - . = ..() - LoadComponent(/datum/component/riding) - -/obj/vehicle/ridden/examine(mob/user) - . = ..() - if(key_type) - if(!inserted_key) - . += "Put a key inside it by clicking it with the key." - else - . += "Alt-click [src] to remove the key." - -/obj/vehicle/ridden/generate_action_type(actiontype) - var/datum/action/vehicle/ridden/A = ..() - . = A - if(istype(A)) - A.vehicle_ridden_target = src - -/obj/vehicle/ridden/post_unbuckle_mob(mob/living/M) - remove_occupant(M) - return ..() - -/obj/vehicle/ridden/post_buckle_mob(mob/living/M) - add_occupant(M) - if(M.get_num_legs() < legs_required) - to_chat(M, "You don't have enough legs to operate the pedals!") - unbuckle_mob(M) - return ..() - -/obj/vehicle/ridden/attackby(obj/item/I, mob/user, params) - if(key_type && !is_key(inserted_key) && is_key(I)) - if(user.transferItemToLoc(I, src)) - to_chat(user, "You insert \the [I] into \the [src].") - if(inserted_key) //just in case there's an invalid key - inserted_key.forceMove(drop_location()) - inserted_key = I - else - to_chat(user, "[I] seems to be stuck to your hand!") - return - return ..() - -/obj/vehicle/ridden/AltClick(mob/user) - . = ..() - if(inserted_key && user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - if(!is_occupant(user)) - to_chat(user, "You must be riding the [src] to remove [src]'s key!") - return - to_chat(user, "You remove \the [inserted_key] from \the [src].") - inserted_key.forceMove(drop_location()) - user.put_in_hands(inserted_key) - inserted_key = null - return TRUE - -/obj/vehicle/ridden/driver_move(mob/user, direction) - if(key_type && !is_key(inserted_key)) - to_chat(user, "[src] has no key inserted!") - return FALSE - var/datum/component/riding/R = GetComponent(/datum/component/riding) - R.handle_ride(user, direction) - return ..() - -/obj/vehicle/ridden/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE) - if(!in_range(user, src) || !in_range(M, src)) - return FALSE - . = ..(M, user, FALSE) - -/obj/vehicle/ridden/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE) - if(!force && occupant_amount() >= max_occupants) - return FALSE - return ..() +/obj/vehicle/ridden + name = "ridden vehicle" + can_buckle = TRUE + max_buckled_mobs = 1 + buckle_lying = FALSE + default_driver_move = FALSE + var/legs_required = 1 + var/arms_required = 0 //why not? + +/obj/vehicle/ridden/Initialize() + . = ..() + LoadComponent(/datum/component/riding) + +/obj/vehicle/ridden/examine(mob/user) + . = ..() + if(key_type) + if(!inserted_key) + . += "Put a key inside it by clicking it with the key." + else + . += "Alt-click [src] to remove the key." + +/obj/vehicle/ridden/generate_action_type(actiontype) + var/datum/action/vehicle/ridden/A = ..() + . = A + if(istype(A)) + A.vehicle_ridden_target = src + +/obj/vehicle/ridden/post_unbuckle_mob(mob/living/M) + remove_occupant(M) + return ..() + +/obj/vehicle/ridden/post_buckle_mob(mob/living/M) + add_occupant(M) + if(M.get_num_legs() < legs_required) + to_chat(M, "You don't have enough legs to operate the pedals!") + unbuckle_mob(M) + return ..() + +/obj/vehicle/ridden/attackby(obj/item/I, mob/user, params) + if(key_type && !is_key(inserted_key) && is_key(I)) + if(user.transferItemToLoc(I, src)) + to_chat(user, "You insert \the [I] into \the [src].") + if(inserted_key) //just in case there's an invalid key + inserted_key.forceMove(drop_location()) + inserted_key = I + else + to_chat(user, "[I] seems to be stuck to your hand!") + return + return ..() + +/obj/vehicle/ridden/AltClick(mob/user) + . = ..() + if(inserted_key && user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + if(!is_occupant(user)) + to_chat(user, "You must be riding the [src] to remove [src]'s key!") + return + to_chat(user, "You remove \the [inserted_key] from \the [src].") + inserted_key.forceMove(drop_location()) + user.put_in_hands(inserted_key) + inserted_key = null + return TRUE + +/obj/vehicle/ridden/driver_move(mob/user, direction) + if(key_type && !is_key(inserted_key)) + to_chat(user, "[src] has no key inserted!") + return FALSE + var/datum/component/riding/R = GetComponent(/datum/component/riding) + R.handle_ride(user, direction) + return ..() + +/obj/vehicle/ridden/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE) + if(!in_range(user, src) || !in_range(M, src)) + return FALSE + . = ..(M, user, FALSE) + +/obj/vehicle/ridden/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE) + if(!force && occupant_amount() >= max_occupants) + return FALSE + return ..() diff --git a/code/modules/vehicles/vehicle_actions.dm b/code/modules/vehicles/vehicle_actions.dm index 4ff6770e85..c780ce6fd3 100644 --- a/code/modules/vehicles/vehicle_actions.dm +++ b/code/modules/vehicles/vehicle_actions.dm @@ -1,166 +1,166 @@ -//VEHICLE DEFAULT HANDLING -/obj/vehicle/proc/generate_actions() - return - -/obj/vehicle/proc/generate_action_type(actiontype) - var/datum/action/vehicle/A = new actiontype - if(!istype(A)) - return - A.vehicle_target = src - return A - -/obj/vehicle/proc/initialize_passenger_action_type(actiontype) - autogrant_actions_passenger += actiontype - for(var/i in occupants) - grant_passenger_actions(i) //refresh - -/obj/vehicle/proc/initialize_controller_action_type(actiontype, control_flag) - LAZYINITLIST(autogrant_actions_controller["[control_flag]"]) - autogrant_actions_controller["[control_flag]"] += actiontype - for(var/i in occupants) - grant_controller_actions(i) //refresh - -/obj/vehicle/proc/grant_action_type_to_mob(actiontype, mob/m) - if(isnull(occupants[m]) || !actiontype) - return FALSE - LAZYINITLIST(occupant_actions[m]) - if(occupant_actions[m][actiontype]) - return TRUE - var/datum/action/action = generate_action_type(actiontype) - action.Grant(m) - occupant_actions[m][action.type] = action - return TRUE - -/obj/vehicle/proc/remove_action_type_from_mob(actiontype, mob/m) - if(isnull(occupants[m]) || !actiontype) - return FALSE - LAZYINITLIST(occupant_actions[m]) - if(occupant_actions[m][actiontype]) - var/datum/action/action = occupant_actions[m][actiontype] - action.Remove(m) - occupant_actions[m] -= actiontype - return TRUE - -/obj/vehicle/proc/grant_passenger_actions(mob/M) - for(var/v in autogrant_actions_passenger) - grant_action_type_to_mob(v, M) - -/obj/vehicle/proc/remove_passenger_actions(mob/M) - for(var/v in autogrant_actions_passenger) - remove_action_type_from_mob(v, M) - -/obj/vehicle/proc/grant_controller_actions(mob/M) - if(!istype(M) || isnull(occupants[M])) - return FALSE - for(var/i in GLOB.bitflags) - if(occupants[M] & i) - grant_controller_actions_by_flag(M, i) - return TRUE - -/obj/vehicle/proc/remove_controller_actions(mob/M) - if(!istype(M) || isnull(occupants[M])) - return FALSE - for(var/i in GLOB.bitflags) - remove_controller_actions_by_flag(M, i) - return TRUE - -/obj/vehicle/proc/grant_controller_actions_by_flag(mob/M, flag) - if(!istype(M)) - return FALSE - for(var/v in autogrant_actions_controller["[flag]"]) - grant_action_type_to_mob(v, M) - return TRUE - -/obj/vehicle/proc/remove_controller_actions_by_flag(mob/M, flag) - if(!istype(M)) - return FALSE - for(var/v in autogrant_actions_controller["[flag]"]) - remove_action_type_from_mob(v, M) - return TRUE - -/obj/vehicle/proc/cleanup_actions_for_mob(mob/M) - if(!istype(M)) - return FALSE - for(var/path in occupant_actions[M]) - stack_trace("Leftover action type [path] in vehicle type [type] for mob type [M.type] - THIS SHOULD NOT BE HAPPENING!") - var/datum/action/action = occupant_actions[M][path] - action.Remove(M) - occupant_actions[M] -= path - occupant_actions -= M - return TRUE - -//ACTION DATUMS - -/datum/action/vehicle - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS - icon_icon = 'icons/mob/actions/actions_vehicle.dmi' - button_icon_state = "vehicle_eject" - var/obj/vehicle/vehicle_target - -/datum/action/vehicle/sealed - var/obj/vehicle/sealed/vehicle_entered_target - -/datum/action/vehicle/sealed/climb_out - name = "Climb Out" - desc = "Climb out of your vehicle!" - button_icon_state = "car_eject" - -/datum/action/vehicle/sealed/climb_out/Trigger() - if(..() && istype(vehicle_entered_target)) - vehicle_entered_target.mob_try_exit(owner, owner) - -/datum/action/vehicle/ridden - var/obj/vehicle/ridden/vehicle_ridden_target - -/datum/action/vehicle/sealed/remove_key - name = "Remove key" - desc = "Take your key out of the vehicle's ignition" - button_icon_state = "car_removekey" - -/datum/action/vehicle/sealed/remove_key/Trigger() - vehicle_entered_target.remove_key(owner) - -//CLOWN CAR ACTION DATUMS -/datum/action/vehicle/sealed/horn - name = "Honk Horn" - desc = "Honk your classy horn." - button_icon_state = "car_horn" - var/hornsound = 'sound/items/carhorn.ogg' - var/last_honk_time - -/datum/action/vehicle/sealed/horn/Trigger() - if(world.time - last_honk_time > 20) - vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks") - to_chat(owner, "You press the vehicle's horn.") - playsound(vehicle_entered_target, hornsound, 75) - last_honk_time = world.time - -/datum/action/vehicle/sealed/horn/clowncar/Trigger() - if(world.time - last_honk_time > 20) - vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks") - to_chat(owner, "You press the vehicle's horn.") - last_honk_time = world.time - if(vehicle_target.inserted_key) - vehicle_target.inserted_key.attack_self(owner) //The key plays a sound - else - playsound(vehicle_entered_target, hornsound, 75) - -/datum/action/vehicle/sealed/DumpKidnappedMobs - name = "Dump kidnapped mobs" - desc = "Dump all objects and people in your car on the floor." - button_icon_state = "car_dump" - -/datum/action/vehicle/sealed/DumpKidnappedMobs/Trigger() - vehicle_entered_target.visible_message("[vehicle_entered_target] starts dumping the people inside of it.") - vehicle_entered_target.DumpSpecificMobs(VEHICLE_CONTROL_KIDNAPPED) - - -/datum/action/vehicle/sealed/RollTheDice - name = "Press a colorful button" - desc = "Press one of those colorful buttons on your display panel!" - button_icon_state = "car_rtd" - -/datum/action/vehicle/sealed/RollTheDice/Trigger() - if(istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar)) - var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target - C.RollTheDice(owner) +//VEHICLE DEFAULT HANDLING +/obj/vehicle/proc/generate_actions() + return + +/obj/vehicle/proc/generate_action_type(actiontype) + var/datum/action/vehicle/A = new actiontype + if(!istype(A)) + return + A.vehicle_target = src + return A + +/obj/vehicle/proc/initialize_passenger_action_type(actiontype) + autogrant_actions_passenger += actiontype + for(var/i in occupants) + grant_passenger_actions(i) //refresh + +/obj/vehicle/proc/initialize_controller_action_type(actiontype, control_flag) + LAZYINITLIST(autogrant_actions_controller["[control_flag]"]) + autogrant_actions_controller["[control_flag]"] += actiontype + for(var/i in occupants) + grant_controller_actions(i) //refresh + +/obj/vehicle/proc/grant_action_type_to_mob(actiontype, mob/m) + if(isnull(occupants[m]) || !actiontype) + return FALSE + LAZYINITLIST(occupant_actions[m]) + if(occupant_actions[m][actiontype]) + return TRUE + var/datum/action/action = generate_action_type(actiontype) + action.Grant(m) + occupant_actions[m][action.type] = action + return TRUE + +/obj/vehicle/proc/remove_action_type_from_mob(actiontype, mob/m) + if(isnull(occupants[m]) || !actiontype) + return FALSE + LAZYINITLIST(occupant_actions[m]) + if(occupant_actions[m][actiontype]) + var/datum/action/action = occupant_actions[m][actiontype] + action.Remove(m) + occupant_actions[m] -= actiontype + return TRUE + +/obj/vehicle/proc/grant_passenger_actions(mob/M) + for(var/v in autogrant_actions_passenger) + grant_action_type_to_mob(v, M) + +/obj/vehicle/proc/remove_passenger_actions(mob/M) + for(var/v in autogrant_actions_passenger) + remove_action_type_from_mob(v, M) + +/obj/vehicle/proc/grant_controller_actions(mob/M) + if(!istype(M) || isnull(occupants[M])) + return FALSE + for(var/i in GLOB.bitflags) + if(occupants[M] & i) + grant_controller_actions_by_flag(M, i) + return TRUE + +/obj/vehicle/proc/remove_controller_actions(mob/M) + if(!istype(M) || isnull(occupants[M])) + return FALSE + for(var/i in GLOB.bitflags) + remove_controller_actions_by_flag(M, i) + return TRUE + +/obj/vehicle/proc/grant_controller_actions_by_flag(mob/M, flag) + if(!istype(M)) + return FALSE + for(var/v in autogrant_actions_controller["[flag]"]) + grant_action_type_to_mob(v, M) + return TRUE + +/obj/vehicle/proc/remove_controller_actions_by_flag(mob/M, flag) + if(!istype(M)) + return FALSE + for(var/v in autogrant_actions_controller["[flag]"]) + remove_action_type_from_mob(v, M) + return TRUE + +/obj/vehicle/proc/cleanup_actions_for_mob(mob/M) + if(!istype(M)) + return FALSE + for(var/path in occupant_actions[M]) + stack_trace("Leftover action type [path] in vehicle type [type] for mob type [M.type] - THIS SHOULD NOT BE HAPPENING!") + var/datum/action/action = occupant_actions[M][path] + action.Remove(M) + occupant_actions[M] -= path + occupant_actions -= M + return TRUE + +//ACTION DATUMS + +/datum/action/vehicle + check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS + icon_icon = 'icons/mob/actions/actions_vehicle.dmi' + button_icon_state = "vehicle_eject" + var/obj/vehicle/vehicle_target + +/datum/action/vehicle/sealed + var/obj/vehicle/sealed/vehicle_entered_target + +/datum/action/vehicle/sealed/climb_out + name = "Climb Out" + desc = "Climb out of your vehicle!" + button_icon_state = "car_eject" + +/datum/action/vehicle/sealed/climb_out/Trigger() + if(..() && istype(vehicle_entered_target)) + vehicle_entered_target.mob_try_exit(owner, owner) + +/datum/action/vehicle/ridden + var/obj/vehicle/ridden/vehicle_ridden_target + +/datum/action/vehicle/sealed/remove_key + name = "Remove key" + desc = "Take your key out of the vehicle's ignition" + button_icon_state = "car_removekey" + +/datum/action/vehicle/sealed/remove_key/Trigger() + vehicle_entered_target.remove_key(owner) + +//CLOWN CAR ACTION DATUMS +/datum/action/vehicle/sealed/horn + name = "Honk Horn" + desc = "Honk your classy horn." + button_icon_state = "car_horn" + var/hornsound = 'sound/items/carhorn.ogg' + var/last_honk_time + +/datum/action/vehicle/sealed/horn/Trigger() + if(world.time - last_honk_time > 20) + vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks") + to_chat(owner, "You press the vehicle's horn.") + playsound(vehicle_entered_target, hornsound, 75) + last_honk_time = world.time + +/datum/action/vehicle/sealed/horn/clowncar/Trigger() + if(world.time - last_honk_time > 20) + vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks") + to_chat(owner, "You press the vehicle's horn.") + last_honk_time = world.time + if(vehicle_target.inserted_key) + vehicle_target.inserted_key.attack_self(owner) //The key plays a sound + else + playsound(vehicle_entered_target, hornsound, 75) + +/datum/action/vehicle/sealed/DumpKidnappedMobs + name = "Dump kidnapped mobs" + desc = "Dump all objects and people in your car on the floor." + button_icon_state = "car_dump" + +/datum/action/vehicle/sealed/DumpKidnappedMobs/Trigger() + vehicle_entered_target.visible_message("[vehicle_entered_target] starts dumping the people inside of it.") + vehicle_entered_target.DumpSpecificMobs(VEHICLE_CONTROL_KIDNAPPED) + + +/datum/action/vehicle/sealed/RollTheDice + name = "Press a colorful button" + desc = "Press one of those colorful buttons on your display panel!" + button_icon_state = "car_rtd" + +/datum/action/vehicle/sealed/RollTheDice/Trigger() + if(istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar)) + var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target + C.RollTheDice(owner) diff --git a/interface/interface.dm b/interface/interface.dm index 215765c88d..ecd13e1f03 100644 --- a/interface/interface.dm +++ b/interface/interface.dm @@ -1,227 +1,227 @@ -//Please use mob or src (not usr) in these procs. This way they can be called in the same fashion as procs. -/client/verb/wiki(query as text) - set name = "wiki" - set desc = "Type what you want to know about. This will open the wiki in your web browser. Type nothing to go to the main page." - set hidden = 1 - var/wikiurl = CONFIG_GET(string/wikiurl) - var/wikiurltg = CONFIG_GET(string/wikiurltg) - if(wikiurl) - if(query) - var/output = wikiurl + "?search=" + query - src << link(output) - output = wikiurltg + "/index.php?title=Special%3ASearch&profile=default&search=" + query - src << link(output) - else if (query != null) - src << link(wikiurltg) - src << link(wikiurl) - else - to_chat(src, "The wiki URL is not set in the server configuration.") - return - -/client/verb/forum() - set name = "forum" - set desc = "Visit the forum." - set hidden = 1 - var/forumurl = CONFIG_GET(string/forumurl) - if(forumurl) - if(alert("This will open the forum in your browser. Are you sure?",,"Yes","No")!="Yes") - return - src << link(forumurl) - else - to_chat(src, "The forum URL is not set in the server configuration.") - return - -/client/verb/rules() - set name = "rules" - set desc = "Show Server Rules." - set hidden = 1 - var/rulesurl = CONFIG_GET(string/rulesurl) - if(rulesurl) - if(alert("This will open the rules in your browser. Are you sure?",,"Yes","No")!="Yes") - return - src << link(rulesurl) - else - to_chat(src, "The rules URL is not set in the server configuration.") - return - -/client/verb/github() - set name = "github" - set desc = "Visit Github" - set hidden = 1 - var/githuburl = CONFIG_GET(string/githuburl) - if(githuburl) - if(alert("This will open the Github repository in your browser. Are you sure?",,"Yes","No")!="Yes") - return - src << link(githuburl) - else - to_chat(src, "The Github URL is not set in the server configuration.") - return - -/client/verb/reportissue() - set name = "report-issue" - set desc = "Report an issue" - set hidden = 1 - var/githuburl = CONFIG_GET(string/githuburl) - if(githuburl) - var/message = "This will open the Github issue reporter in your browser. Are you sure?" - if(GLOB.revdata.testmerge.len) - message += "
                The following experimental changes are active and are probably the cause of any new or sudden issues you may experience. If possible, please try to find a specific thread for your issue instead of posting to the general issue tracker:
                " - message += GLOB.revdata.GetTestMergeInfo(FALSE) - if(tgalert(src, message, "Report Issue","Yes","No")!="Yes") - return - var/static/issue_template = file2text(".github/ISSUE_TEMPLATE.md") - var/servername = CONFIG_GET(string/servername) - var/url_params = "Reporting client version: [byond_version]\n\n[issue_template]" - if(GLOB.round_id || servername) - url_params = "Issue reported from [GLOB.round_id ? " Round ID: [GLOB.round_id][servername ? " ([servername])" : ""]" : servername]\n\n[url_params]" - DIRECT_OUTPUT(src, link("[githuburl]/issues/new?body=[url_encode(url_params)]")) - else - to_chat(src, "The Github URL is not set in the server configuration.") - return - -/client/verb/hotkeys_help() - set name = "hotkeys-help" - set category = "OOC" - - var/adminhotkeys = {" -Admin: -\tF3 = asay -\tF5 = Aghost (admin-ghost) -\tF6 = player-panel -\tF7 = Buildmode -\tF8 = Invisimin -\tCtrl+F8 = Stealthmin -"} - - mob.hotkey_help() - - if(holder) - to_chat(src, adminhotkeys) - -/client/verb/changelog() - set name = "Changelog" - set category = "OOC" - var/datum/asset/changelog = get_asset_datum(/datum/asset/simple/changelog) - changelog.send(src) - src << browse('html/changelog.html', "window=changes;size=675x650") - if(prefs.lastchangelog != GLOB.changelog_hash) - prefs.lastchangelog = GLOB.changelog_hash - prefs.save_preferences() - winset(src, "infowindow.changelog", "font-style=;") - - -/mob/proc/hotkey_help() - var/hotkey_mode = {" -Hotkey-Mode: (hotkey-mode must be on) -\tTAB = toggle hotkey-mode -\ta = left -\ts = down -\td = right -\tw = up -\tq = drop -\te = equip -\tr = throw -\tm = me -\tt = say -\to = OOC -\tb = resist -\th = stop pulling -\tx = swap-hand -\tz = activate held object (or y) -\tShift+e = Put held item into belt or take out most recent item added to belt. -\tShift+b = Put held item into backpack or take out most recent item added to backpack. -\tf = cycle-intents-left -\tg = cycle-intents-right -\t1 = help-intent -\t2 = disarm-intent -\t3 = grab-intent -\t4 = harm-intent -\tNumpad = Body target selection (Press 8 repeatedly for Head->Eyes->Mouth) -\tAlt(HOLD) = Alter movement intent -"} - - var/other = {" -Any-Mode: (hotkey doesn't need to be on) -\tCtrl+a = left -\tCtrl+s = down -\tCtrl+d = right -\tCtrl+w = up -\tCtrl+q = drop -\tCtrl+e = equip -\tCtrl+r = throw -\tCtrl+b = resist -\tCtrl+h = stop pulling -\tCtrl+o = OOC -\tCtrl+x = swap-hand -\tCtrl+z = activate held object (or Ctrl+y) -\tCtrl+f = cycle-intents-left -\tCtrl+g = cycle-intents-right -\tCtrl+1 = help-intent -\tCtrl+2 = disarm-intent -\tCtrl+3 = grab-intent -\tCtrl+4 = harm-intent -\tCtrl+'+/-' OR -\tShift+Mousewheel = Ghost zoom in/out -\tDEL = stop pulling -\tINS = cycle-intents-right -\tHOME = drop -\tPGUP = swap-hand -\tPGDN = activate held object -\tEND = throw -\tCtrl+Numpad = Body target selection (Press 8 repeatedly for Head->Eyes->Mouth) -"} - - to_chat(src, hotkey_mode) - to_chat(src, other) - -/mob/living/silicon/robot/hotkey_help() - //h = talk-wheel has a nonsense tag in it because \th is an escape sequence in BYOND. - var/hotkey_mode = {" -Hotkey-Mode: (hotkey-mode must be on) -\tTAB = toggle hotkey-mode -\ta = left -\ts = down -\td = right -\tw = up -\tq = unequip active module -\th = stop pulling -\tm = me -\tt = say -\to = OOC -\tx = cycle active modules -\tb = resist -\tz = activate held object (or y) -\tf = cycle-intents-left -\tg = cycle-intents-right -\t1 = activate module 1 -\t2 = activate module 2 -\t3 = activate module 3 -\t4 = toggle intents -"} - - var/other = {" -Any-Mode: (hotkey doesn't need to be on) -\tCtrl+a = left -\tCtrl+s = down -\tCtrl+d = right -\tCtrl+w = up -\tCtrl+q = unequip active module -\tCtrl+x = cycle active modules -\tCtrl+b = resist -\tCtrl+h = stop pulling -\tCtrl+o = OOC -\tCtrl+z = activate held object (or Ctrl+y) -\tCtrl+f = cycle-intents-left -\tCtrl+g = cycle-intents-right -\tCtrl+1 = activate module 1 -\tCtrl+2 = activate module 2 -\tCtrl+3 = activate module 3 -\tCtrl+4 = toggle intents -\tDEL = stop pulling -\tINS = toggle intents -\tPGUP = cycle active modules -\tPGDN = activate held object -"} - - to_chat(src, hotkey_mode) - to_chat(src, other) +//Please use mob or src (not usr) in these procs. This way they can be called in the same fashion as procs. +/client/verb/wiki(query as text) + set name = "wiki" + set desc = "Type what you want to know about. This will open the wiki in your web browser. Type nothing to go to the main page." + set hidden = 1 + var/wikiurl = CONFIG_GET(string/wikiurl) + var/wikiurltg = CONFIG_GET(string/wikiurltg) + if(wikiurl) + if(query) + var/output = wikiurl + "?search=" + query + src << link(output) + output = wikiurltg + "/index.php?title=Special%3ASearch&profile=default&search=" + query + src << link(output) + else if (query != null) + src << link(wikiurltg) + src << link(wikiurl) + else + to_chat(src, "The wiki URL is not set in the server configuration.") + return + +/client/verb/forum() + set name = "forum" + set desc = "Visit the forum." + set hidden = 1 + var/forumurl = CONFIG_GET(string/forumurl) + if(forumurl) + if(alert("This will open the forum in your browser. Are you sure?",,"Yes","No")!="Yes") + return + src << link(forumurl) + else + to_chat(src, "The forum URL is not set in the server configuration.") + return + +/client/verb/rules() + set name = "rules" + set desc = "Show Server Rules." + set hidden = 1 + var/rulesurl = CONFIG_GET(string/rulesurl) + if(rulesurl) + if(alert("This will open the rules in your browser. Are you sure?",,"Yes","No")!="Yes") + return + src << link(rulesurl) + else + to_chat(src, "The rules URL is not set in the server configuration.") + return + +/client/verb/github() + set name = "github" + set desc = "Visit Github" + set hidden = 1 + var/githuburl = CONFIG_GET(string/githuburl) + if(githuburl) + if(alert("This will open the Github repository in your browser. Are you sure?",,"Yes","No")!="Yes") + return + src << link(githuburl) + else + to_chat(src, "The Github URL is not set in the server configuration.") + return + +/client/verb/reportissue() + set name = "report-issue" + set desc = "Report an issue" + set hidden = 1 + var/githuburl = CONFIG_GET(string/githuburl) + if(githuburl) + var/message = "This will open the Github issue reporter in your browser. Are you sure?" + if(GLOB.revdata.testmerge.len) + message += "
                The following experimental changes are active and are probably the cause of any new or sudden issues you may experience. If possible, please try to find a specific thread for your issue instead of posting to the general issue tracker:
                " + message += GLOB.revdata.GetTestMergeInfo(FALSE) + if(tgalert(src, message, "Report Issue","Yes","No")!="Yes") + return + var/static/issue_template = file2text(".github/ISSUE_TEMPLATE.md") + var/servername = CONFIG_GET(string/servername) + var/url_params = "Reporting client version: [byond_version]\n\n[issue_template]" + if(GLOB.round_id || servername) + url_params = "Issue reported from [GLOB.round_id ? " Round ID: [GLOB.round_id][servername ? " ([servername])" : ""]" : servername]\n\n[url_params]" + DIRECT_OUTPUT(src, link("[githuburl]/issues/new?body=[url_encode(url_params)]")) + else + to_chat(src, "The Github URL is not set in the server configuration.") + return + +/client/verb/hotkeys_help() + set name = "hotkeys-help" + set category = "OOC" + + var/adminhotkeys = {" +Admin: +\tF3 = asay +\tF5 = Aghost (admin-ghost) +\tF6 = player-panel +\tF7 = Buildmode +\tF8 = Invisimin +\tCtrl+F8 = Stealthmin +"} + + mob.hotkey_help() + + if(holder) + to_chat(src, adminhotkeys) + +/client/verb/changelog() + set name = "Changelog" + set category = "OOC" + var/datum/asset/changelog = get_asset_datum(/datum/asset/simple/changelog) + changelog.send(src) + src << browse('html/changelog.html', "window=changes;size=675x650") + if(prefs.lastchangelog != GLOB.changelog_hash) + prefs.lastchangelog = GLOB.changelog_hash + prefs.save_preferences() + winset(src, "infowindow.changelog", "font-style=;") + + +/mob/proc/hotkey_help() + var/hotkey_mode = {" +Hotkey-Mode: (hotkey-mode must be on) +\tTAB = toggle hotkey-mode +\ta = left +\ts = down +\td = right +\tw = up +\tq = drop +\te = equip +\tr = throw +\tm = me +\tt = say +\to = OOC +\tb = resist +\th = stop pulling +\tx = swap-hand +\tz = activate held object (or y) +\tShift+e = Put held item into belt or take out most recent item added to belt. +\tShift+b = Put held item into backpack or take out most recent item added to backpack. +\tf = cycle-intents-left +\tg = cycle-intents-right +\t1 = help-intent +\t2 = disarm-intent +\t3 = grab-intent +\t4 = harm-intent +\tNumpad = Body target selection (Press 8 repeatedly for Head->Eyes->Mouth) +\tAlt(HOLD) = Alter movement intent +"} + + var/other = {" +Any-Mode: (hotkey doesn't need to be on) +\tCtrl+a = left +\tCtrl+s = down +\tCtrl+d = right +\tCtrl+w = up +\tCtrl+q = drop +\tCtrl+e = equip +\tCtrl+r = throw +\tCtrl+b = resist +\tCtrl+h = stop pulling +\tCtrl+o = OOC +\tCtrl+x = swap-hand +\tCtrl+z = activate held object (or Ctrl+y) +\tCtrl+f = cycle-intents-left +\tCtrl+g = cycle-intents-right +\tCtrl+1 = help-intent +\tCtrl+2 = disarm-intent +\tCtrl+3 = grab-intent +\tCtrl+4 = harm-intent +\tCtrl+'+/-' OR +\tShift+Mousewheel = Ghost zoom in/out +\tDEL = stop pulling +\tINS = cycle-intents-right +\tHOME = drop +\tPGUP = swap-hand +\tPGDN = activate held object +\tEND = throw +\tCtrl+Numpad = Body target selection (Press 8 repeatedly for Head->Eyes->Mouth) +"} + + to_chat(src, hotkey_mode) + to_chat(src, other) + +/mob/living/silicon/robot/hotkey_help() + //h = talk-wheel has a nonsense tag in it because \th is an escape sequence in BYOND. + var/hotkey_mode = {" +Hotkey-Mode: (hotkey-mode must be on) +\tTAB = toggle hotkey-mode +\ta = left +\ts = down +\td = right +\tw = up +\tq = unequip active module +\th = stop pulling +\tm = me +\tt = say +\to = OOC +\tx = cycle active modules +\tb = resist +\tz = activate held object (or y) +\tf = cycle-intents-left +\tg = cycle-intents-right +\t1 = activate module 1 +\t2 = activate module 2 +\t3 = activate module 3 +\t4 = toggle intents +"} + + var/other = {" +Any-Mode: (hotkey doesn't need to be on) +\tCtrl+a = left +\tCtrl+s = down +\tCtrl+d = right +\tCtrl+w = up +\tCtrl+q = unequip active module +\tCtrl+x = cycle active modules +\tCtrl+b = resist +\tCtrl+h = stop pulling +\tCtrl+o = OOC +\tCtrl+z = activate held object (or Ctrl+y) +\tCtrl+f = cycle-intents-left +\tCtrl+g = cycle-intents-right +\tCtrl+1 = activate module 1 +\tCtrl+2 = activate module 2 +\tCtrl+3 = activate module 3 +\tCtrl+4 = toggle intents +\tDEL = stop pulling +\tINS = toggle intents +\tPGUP = cycle active modules +\tPGDN = activate held object +"} + + to_chat(src, hotkey_mode) + to_chat(src, other) diff --git a/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm b/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm index 98de5eed14..f2743d853b 100644 --- a/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm +++ b/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm @@ -1,449 +1,449 @@ -/* -DOG BORG EQUIPMENT HERE -SLEEPER CODE IS IN game/objects/items/devices/dogborg_sleeper.dm ! -*/ - -/obj/item/dogborg/jaws - name = "Dogborg jaws" - desc = "The jaws of the debug errors oh god." - icon = 'icons/mob/dogborg.dmi' - flags_1 = CONDUCT_1 - force = 1 - throwforce = 0 - w_class = 3 - hitsound = 'sound/weapons/bite.ogg' - sharpness = IS_SHARP - var/stamtostunconversion = 0.1 //Total stamloss gets multiplied by this value for the help intent hard stun. Resting adds an additional 2x multiplier on top. Keep this low or so help me god. - var/stuncooldown = 4 SECONDS //How long it takes before you're able to attempt to stun a target again - var/nextstuntime - -/obj/item/dogborg/jaws/examine(mob/user) - . = ..() - if(!CONFIG_GET(flag/weaken_secborg)) - . += "Use help intent to attempt to non-lethally incapacitate the target by latching on with your maw. This is more effective against exhausted and resting targets." - -/obj/item/dogborg/jaws/big - name = "combat jaws" - desc = "The jaws of the law. Very sharp." - icon_state = "jaws" - force = 15 //Chomp chomp. Crew harm. - attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") - stamtostunconversion = 0.2 // 100*0.2*2=40. Stun's just long enough to slap on cuffs with click delay if the target is near hard stamcrit. - stuncooldown = 6 SECONDS - - -/obj/item/dogborg/jaws/small - name = "puppy jaws" - desc = "Rubberized teeth designed to protect accidental harm. Sharp enough for specialized tasks however." - icon_state = "smalljaws" - force = 6 - attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") - var/status = 0 - -/obj/item/dogborg/jaws/attack(atom/A, mob/living/silicon/robot/user) - if(!istype(user)) - return - if(!CONFIG_GET(flag/weaken_secborg) && user.a_intent != INTENT_HARM && istype(A, /mob/living)) - if(A == user.pulling) - to_chat(user, "You already have [A] in your jaws.") - return - if(nextstuntime >= world.time) - to_chat(user, "Your jaw servos are still recharging.") - return - nextstuntime = world.time + stuncooldown - var/mob/living/M = A - var/cachedstam = M.getStaminaLoss() - var/totalstuntime = cachedstam * stamtostunconversion * (M.lying ? 2 : 1) - if(!M.resting) - M.Knockdown(cachedstam*2) //BORK BORK. GET DOWN. - M.Stun(totalstuntime) - user.do_attack_animation(A, ATTACK_EFFECT_BITE) - user.start_pulling(M, TRUE) //Yip yip. Come with. - user.changeNext_move(CLICK_CD_MELEE) - M.visible_message("[user] clamps [user.p_their()] [src] onto [M] and latches on!", "[user] clamps [user.p_their()] [src] onto you and latches on!") - if(totalstuntime >= 4 SECONDS) - playsound(usr, 'sound/effects/k9_jaw_strong.ogg', 75, FALSE, 2) //Wuff wuff. Big stun. - else - playsound(usr, 'sound/effects/k9_jaw_weak.ogg', 50, TRUE, -1) //Arf arf. Pls buff. - else - . = ..() - user.do_attack_animation(A, ATTACK_EFFECT_BITE) - -/obj/item/dogborg/jaws/small/attack_self(mob/user) - var/mob/living/silicon/robot/R = user - if(R.cell && R.cell.charge > 100) - if(R.emagged && status == 0) - name = "combat jaws" - icon_state = "jaws" - desc = "The jaws of the law." - force = 12 - attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") - stamtostunconversion = 0.15 - stuncooldown = 5 SECONDS - status = 1 - to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") - else - name = "puppy jaws" - icon_state = "smalljaws" - desc = "The jaws of a small dog." - force = initial(force) - attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") - stamtostunconversion = initial(stamtostunconversion) - stuncooldown = initial(stuncooldown) - status = 0 - if(R.emagged) - to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") - update_icon() - -//Boop - -/obj/item/analyzer/nose - name = "boop module" - icon = 'icons/mob/dogborg.dmi' - icon_state = "nose" - desc = "The BOOP module" - flags_1 = CONDUCT_1 - force = 0 - throwforce = 0 - attack_verb = list("nuzzles", "pushes", "boops") - w_class = 1 - -/obj/item/analyzer/nose/attack_self(mob/user) - user.visible_message("[user] sniffs around the air.", "You sniff the air for gas traces.") - - var/turf/location = user.loc - if(!istype(location)) - return - - var/datum/gas_mixture/environment = location.return_air() - - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - to_chat(user, "Results:") - if(abs(pressure - ONE_ATMOSPHERE) < 10) - to_chat(user, "Pressure: [round(pressure,0.1)] kPa") - else - to_chat(user, "Pressure: [round(pressure,0.1)] kPa") - if(total_moles) - var/list/env_gases = environment.gases - - var/o2_concentration = env_gases[/datum/gas/oxygen]/total_moles - var/n2_concentration = env_gases[/datum/gas/nitrogen]/total_moles - var/co2_concentration = env_gases[/datum/gas/carbon_dioxide]/total_moles - var/plasma_concentration = env_gases[/datum/gas/plasma]/total_moles - GAS_GARBAGE_COLLECT(environment.gases) - - if(abs(n2_concentration - N2STANDARD) < 20) - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") - else - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") - - if(abs(o2_concentration - O2STANDARD) < 2) - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") - else - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") - - if(co2_concentration > 0.01) - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") - else - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") - - if(plasma_concentration > 0.005) - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") - else - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") - - - for(var/id in env_gases) - if(id in GLOB.hardcoded_gases) - continue - var/gas_concentration = env_gases[id]/total_moles - to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] %") - to_chat(user, "Temperature: [round(environment.temperature-T0C)] °C") - -/obj/item/analyzer/nose/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity) - return - do_attack_animation(target, null, src) - user.visible_message("[user] [pick(attack_verb)] \the [target.name] with their nose!") - -//Delivery -/obj/item/storage/bag/borgdelivery - name = "fetching storage" - desc = "Fetch the thing!" - icon = 'icons/mob/dogborg.dmi' - icon_state = "dbag" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/bag/borgdelivery/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.max_combined_w_class = 5 - STR.max_items = 1 - STR.cant_hold = typecacheof(list(/obj/item/disk/nuclear, /obj/item/radio/intercom)) - -//Tongue stuff -/obj/item/soap/tongue - name = "synthetic tongue" - desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." - icon = 'icons/mob/dogborg.dmi' - icon_state = "synthtongue" - hitsound = 'sound/effects/attackblob.ogg' - cleanspeed = 80 - var/status = 0 - -/obj/item/soap/tongue/scrubpup - cleanspeed = 25 //slightly faster than a mop. - -/obj/item/soap/tongue/New() - ..() - item_flags |= NOBLUDGEON //No more attack messages - -/obj/item/soap/tongue/attack_self(mob/user) - var/mob/living/silicon/robot/R = user - if(R.cell && R.cell.charge > 100) - if(R.emagged && status == 0) - status = !status - name = "energized tongue" - desc = "Your tongue is energized for dangerously maximum efficency." - icon_state = "syndietongue" - to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") - cleanspeed = 10 //(nerf'd)tator soap stat - else - status = 0 - name = "synthetic tongue" - desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." - icon_state = "synthtongue" - cleanspeed = initial(cleanspeed) - if(R.emagged) - to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") - update_icon() - -/obj/item/soap/tongue/afterattack(atom/target, mob/user, proximity) - var/mob/living/silicon/robot/R = user - if(!proximity || !check_allowed_items(target)) - return - if(R.client && (target in R.client.screen)) - to_chat(R, "You need to take that [target.name] off before cleaning it!") - else if(is_cleanable(target)) - R.visible_message("[R] begins to lick off \the [target.name].", "You begin to lick off \the [target.name]...") - if(do_after(R, src.cleanspeed, target = target)) - if(!in_range(src, target)) //Proximity is probably old news by now, do a new check. - return //If they moved away, you can't eat them. - to_chat(R, "You finish licking off \the [target.name].") - qdel(target) - R.cell.give(50) - else if(isobj(target)) //hoo boy. danger zone man - if(istype(target,/obj/item/trash)) - R.visible_message("[R] nibbles away at \the [target.name].", "You begin to nibble away at \the [target.name]...") - if(!do_after(R, src.cleanspeed, target = target)) - return //If they moved away, you can't eat them. - to_chat(R, "You finish off \the [target.name].") - qdel(target) - R.cell.give(250) - return - if(istype(target,/obj/item/stock_parts/cell)) - R.visible_message("[R] begins cramming \the [target.name] down its throat.", "You begin cramming \the [target.name] down your throat...") - if(!do_after(R, 50, target = target)) - return //If they moved away, you can't eat them. - to_chat(R, "You finish off \the [target.name].") - var/obj/item/stock_parts/cell/C = target - R.cell.charge = R.cell.charge + (C.charge / 3) //Instant full cell upgrades op idgaf - qdel(target) - return - var/obj/item/I = target //HAHA FUCK IT, NOT LIKE WE ALREADY HAVE A SHITTON OF WAYS TO REMOVE SHIT - if(!I.anchored && R.emagged) - R.visible_message("[R] begins chewing up \the [target.name]. Looks like it's trying to loophole around its diet restriction!", "You begin chewing up \the [target.name]...") - if(!do_after(R, 100, target = I)) //Nerf dat time yo - return //If they moved away, you can't eat them. - visible_message("[R] chews up \the [target.name] and cleans off the debris!") - to_chat(R, "You finish off \the [target.name].") - qdel(I) - R.cell.give(500) - return - R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") - else if(ishuman(target)) - var/mob/living/L = target - if(status == 0 && check_zone(R.zone_selected) == "head") - R.visible_message("\the [R] affectionally licks \the [L]'s face!", "You affectionally lick \the [L]'s face!") - playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) - if(istype(L) && L.fire_stacks > 0) - L.adjust_fire_stacks(-10) - return - else if(status == 0) - R.visible_message("\the [R] affectionally licks \the [L]!", "You affectionally lick \the [L]!") - playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) - if(istype(L) && L.fire_stacks > 0) - L.adjust_fire_stacks(-10) - return - else - if(R.cell.charge <= 800) - to_chat(R, "Insufficent Power!") - return - L.Stun(4) // normal stunbaton is force 7 gimme a break good sir! - L.Knockdown(80) - L.apply_effect(EFFECT_STUTTER, 4) - L.visible_message("[R] has shocked [L] with its tongue!", \ - "[R] has shocked you with its tongue!") - playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) - R.cell.use(666) - log_combat(R, L, "tongue stunned") - - else if(istype(target, /obj/structure/window)) - R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") - if(do_after(user, src.cleanspeed, target = target)) - to_chat(user, "You clean \the [target.name].") - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - target.set_opacity(initial(target.opacity)) - else - R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") - if(do_after(user, src.cleanspeed, target = target)) - to_chat(user, "You clean \the [target.name].") - var/obj/effect/decal/cleanable/C = locate() in target - qdel(C) - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) - target.wash_cream() - return - -//Nerfed tongue for flavour reasons (haha geddit?). Used for aux skins for regular borgs -/obj/item/soap/tongue/flavour - desc = "For giving affectionate kisses." - -/obj/item/soap/tongue/flavour/attack_self(mob/user) - return - -/obj/item/soap/tongue/flavour/afterattack(atom/target, mob/user, proximity) - if(!proximity) - return - var/mob/living/silicon/robot/R = user - if(ishuman(target)) - var/mob/living/L = target - if(status == 0 && check_zone(R.zone_selected) == "head") - R.visible_message("\the [R] affectionally licks \the [L]'s face!", "You affectionally lick \the [L]'s face!") - playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) - return - else if(status == 0) - R.visible_message("\the [R] affectionally licks \the [L]!", "You affectionally lick \the [L]!") - playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) - return - -//Same as above but for noses -/obj/item/analyzer/nose/flavour/AltClick(mob/user) - return - -/obj/item/analyzer/nose/flavour/attack_self(mob/user) - return - -/obj/item/analyzer/nose/flavour/afterattack(atom/target, mob/user, proximity) - if(!proximity) - return - do_attack_animation(target, null, src) - user.visible_message("[user] [pick(attack_verb)] \the [target.name] with their nose!") - - -//Dogfood - -/obj/item/trash/rkibble - name = "robo kibble" - desc = "A novelty bowl of assorted mech fabricator byproducts. Mockingly feed this to the sec-dog to help it recharge." - icon = 'icons/mob/dogborg.dmi' - icon_state= "kibble" - -// Pounce stuff for K-9 - -/obj/item/dogborg/pounce - name = "pounce" - icon = 'icons/mob/dogborg.dmi' - icon_state = "pounce" - desc = "Leap at your target to momentarily stun them." - force = 0 - throwforce = 0 - -/obj/item/dogborg/pounce/New() - ..() - item_flags |= NOBLUDGEON - -/mob/living/silicon/robot - var/leaping = 0 - var/pounce_cooldown = 0 - var/pounce_cooldown_time = 30 //Time in deciseconds between pounces - var/pounce_spoolup = 5 //Time in deciseconds for the pounce to happen after clicking - var/pounce_stamloss_cap = 120 //How much staminaloss pounces alone are capable of bringing a spaceman to - var/pounce_stamloss = 80 //Base staminaloss value of the pounce - var/leap_at - var/disabler - var/laser - var/sleeper_g - var/sleeper_r - var/sleeper_nv - -#define MAX_K9_LEAP_DIST 4 //because something's definitely borked the pounce functioning from a distance. - -/obj/item/dogborg/pounce/afterattack(atom/A, mob/user) - var/mob/living/silicon/robot/R = user - if(R && (world.time >= R.pounce_cooldown)) - R.pounce_cooldown = world.time + R.pounce_cooldown_time - to_chat(R, "Your targeting systems lock on to [A]...") - playsound(R, 'sound/effects/servostep.ogg', 100, TRUE) - addtimer(CALLBACK(R, /mob/living/silicon/robot.proc/leap_at, A), R.pounce_spoolup) - else if(R && (world.time < R.pounce_cooldown)) - to_chat(R, "Your leg actuators are still recharging!") - -/mob/living/silicon/robot/proc/leap_at(atom/A) - if(leaping || stat || buckled || lying) - return - - if(!has_gravity(src) || !has_gravity(A)) - to_chat(src,"It is unsafe to leap without gravity!") - //It's also extremely buggy visually, so it's balance+bugfix - return - - if(cell.charge <= 750) - to_chat(src,"Insufficent reserves for jump actuators!") - return - - else - leaping = 1 - weather_immunities += "lava" - pixel_y = 10 - update_icons() - throw_at(A, MAX_K9_LEAP_DIST, 1, spin=0, diagonals_first = 1) - cell.use(750) //Less than a stunbaton since stunbatons hit everytime. - playsound(src, 'sound/effects/stealthoff.ogg', 25, TRUE, -1) - weather_immunities -= "lava" - -/mob/living/silicon/robot/throw_impact(atom/A) - - if(!leaping) - return ..() - - if(A) - if(isliving(A)) - var/mob/living/L = A - if(!L.check_shields(0, "the [name]", src, attack_type = LEAP_ATTACK)) - L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") - L.Knockdown(iscarbon(L) ? 60 : 45, override_stamdmg = CLAMP(pounce_stamloss, 0, pounce_stamloss_cap-L.getStaminaLoss())) // Temporary. If someone could rework how dogborg pounces work to accomodate for combat changes, that'd be nice. - playsound(src, 'sound/weapons/Egloves.ogg', 50, 1) - sleep(2)//Runtime prevention (infinite bump() calls on hulks) - step_towards(src,L) - log_combat(src, L, "borg pounced") - else - Knockdown(15, 1, 1) - - pounce_cooldown = !pounce_cooldown - spawn(pounce_cooldown_time) //3s by default - pounce_cooldown = !pounce_cooldown - else if(A.density && !A.CanPass(src)) - visible_message("[src] smashes into [A]!", "You smash into [A]!") - playsound(src, 'sound/items/trayhit1.ogg', 50, 1) - Knockdown(15, 1, 1) - - if(leaping) - leaping = 0 - pixel_y = initial(pixel_y) - update_icons() - update_canmove() +/* +DOG BORG EQUIPMENT HERE +SLEEPER CODE IS IN game/objects/items/devices/dogborg_sleeper.dm ! +*/ + +/obj/item/dogborg/jaws + name = "Dogborg jaws" + desc = "The jaws of the debug errors oh god." + icon = 'icons/mob/dogborg.dmi' + flags_1 = CONDUCT_1 + force = 1 + throwforce = 0 + w_class = 3 + hitsound = 'sound/weapons/bite.ogg' + sharpness = IS_SHARP + var/stamtostunconversion = 0.1 //Total stamloss gets multiplied by this value for the help intent hard stun. Resting adds an additional 2x multiplier on top. Keep this low or so help me god. + var/stuncooldown = 4 SECONDS //How long it takes before you're able to attempt to stun a target again + var/nextstuntime + +/obj/item/dogborg/jaws/examine(mob/user) + . = ..() + if(!CONFIG_GET(flag/weaken_secborg)) + . += "Use help intent to attempt to non-lethally incapacitate the target by latching on with your maw. This is more effective against exhausted and resting targets." + +/obj/item/dogborg/jaws/big + name = "combat jaws" + desc = "The jaws of the law. Very sharp." + icon_state = "jaws" + force = 15 //Chomp chomp. Crew harm. + attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") + stamtostunconversion = 0.2 // 100*0.2*2=40. Stun's just long enough to slap on cuffs with click delay if the target is near hard stamcrit. + stuncooldown = 6 SECONDS + + +/obj/item/dogborg/jaws/small + name = "puppy jaws" + desc = "Rubberized teeth designed to protect accidental harm. Sharp enough for specialized tasks however." + icon_state = "smalljaws" + force = 6 + attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") + var/status = 0 + +/obj/item/dogborg/jaws/attack(atom/A, mob/living/silicon/robot/user) + if(!istype(user)) + return + if(!CONFIG_GET(flag/weaken_secborg) && user.a_intent != INTENT_HARM && istype(A, /mob/living)) + if(A == user.pulling) + to_chat(user, "You already have [A] in your jaws.") + return + if(nextstuntime >= world.time) + to_chat(user, "Your jaw servos are still recharging.") + return + nextstuntime = world.time + stuncooldown + var/mob/living/M = A + var/cachedstam = M.getStaminaLoss() + var/totalstuntime = cachedstam * stamtostunconversion * (M.lying ? 2 : 1) + if(!M.resting) + M.Knockdown(cachedstam*2) //BORK BORK. GET DOWN. + M.Stun(totalstuntime) + user.do_attack_animation(A, ATTACK_EFFECT_BITE) + user.start_pulling(M, TRUE) //Yip yip. Come with. + user.changeNext_move(CLICK_CD_MELEE) + M.visible_message("[user] clamps [user.p_their()] [src] onto [M] and latches on!", "[user] clamps [user.p_their()] [src] onto you and latches on!") + if(totalstuntime >= 4 SECONDS) + playsound(usr, 'sound/effects/k9_jaw_strong.ogg', 75, FALSE, 2) //Wuff wuff. Big stun. + else + playsound(usr, 'sound/effects/k9_jaw_weak.ogg', 50, TRUE, -1) //Arf arf. Pls buff. + else + . = ..() + user.do_attack_animation(A, ATTACK_EFFECT_BITE) + +/obj/item/dogborg/jaws/small/attack_self(mob/user) + var/mob/living/silicon/robot/R = user + if(R.cell && R.cell.charge > 100) + if(R.emagged && status == 0) + name = "combat jaws" + icon_state = "jaws" + desc = "The jaws of the law." + force = 12 + attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") + stamtostunconversion = 0.15 + stuncooldown = 5 SECONDS + status = 1 + to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") + else + name = "puppy jaws" + icon_state = "smalljaws" + desc = "The jaws of a small dog." + force = initial(force) + attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") + stamtostunconversion = initial(stamtostunconversion) + stuncooldown = initial(stuncooldown) + status = 0 + if(R.emagged) + to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") + update_icon() + +//Boop + +/obj/item/analyzer/nose + name = "boop module" + icon = 'icons/mob/dogborg.dmi' + icon_state = "nose" + desc = "The BOOP module" + flags_1 = CONDUCT_1 + force = 0 + throwforce = 0 + attack_verb = list("nuzzles", "pushes", "boops") + w_class = 1 + +/obj/item/analyzer/nose/attack_self(mob/user) + user.visible_message("[user] sniffs around the air.", "You sniff the air for gas traces.") + + var/turf/location = user.loc + if(!istype(location)) + return + + var/datum/gas_mixture/environment = location.return_air() + + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + to_chat(user, "Results:") + if(abs(pressure - ONE_ATMOSPHERE) < 10) + to_chat(user, "Pressure: [round(pressure,0.1)] kPa") + else + to_chat(user, "Pressure: [round(pressure,0.1)] kPa") + if(total_moles) + var/list/env_gases = environment.gases + + var/o2_concentration = env_gases[/datum/gas/oxygen]/total_moles + var/n2_concentration = env_gases[/datum/gas/nitrogen]/total_moles + var/co2_concentration = env_gases[/datum/gas/carbon_dioxide]/total_moles + var/plasma_concentration = env_gases[/datum/gas/plasma]/total_moles + GAS_GARBAGE_COLLECT(environment.gases) + + if(abs(n2_concentration - N2STANDARD) < 20) + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") + else + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") + + if(abs(o2_concentration - O2STANDARD) < 2) + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") + else + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") + + if(co2_concentration > 0.01) + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") + else + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") + + if(plasma_concentration > 0.005) + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") + else + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") + + + for(var/id in env_gases) + if(id in GLOB.hardcoded_gases) + continue + var/gas_concentration = env_gases[id]/total_moles + to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] %") + to_chat(user, "Temperature: [round(environment.temperature-T0C)] °C") + +/obj/item/analyzer/nose/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity) + return + do_attack_animation(target, null, src) + user.visible_message("[user] [pick(attack_verb)] \the [target.name] with their nose!") + +//Delivery +/obj/item/storage/bag/borgdelivery + name = "fetching storage" + desc = "Fetch the thing!" + icon = 'icons/mob/dogborg.dmi' + icon_state = "dbag" + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/bag/borgdelivery/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.max_combined_w_class = 5 + STR.max_items = 1 + STR.cant_hold = typecacheof(list(/obj/item/disk/nuclear, /obj/item/radio/intercom)) + +//Tongue stuff +/obj/item/soap/tongue + name = "synthetic tongue" + desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." + icon = 'icons/mob/dogborg.dmi' + icon_state = "synthtongue" + hitsound = 'sound/effects/attackblob.ogg' + cleanspeed = 80 + var/status = 0 + +/obj/item/soap/tongue/scrubpup + cleanspeed = 25 //slightly faster than a mop. + +/obj/item/soap/tongue/New() + ..() + item_flags |= NOBLUDGEON //No more attack messages + +/obj/item/soap/tongue/attack_self(mob/user) + var/mob/living/silicon/robot/R = user + if(R.cell && R.cell.charge > 100) + if(R.emagged && status == 0) + status = !status + name = "energized tongue" + desc = "Your tongue is energized for dangerously maximum efficency." + icon_state = "syndietongue" + to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") + cleanspeed = 10 //(nerf'd)tator soap stat + else + status = 0 + name = "synthetic tongue" + desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." + icon_state = "synthtongue" + cleanspeed = initial(cleanspeed) + if(R.emagged) + to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") + update_icon() + +/obj/item/soap/tongue/afterattack(atom/target, mob/user, proximity) + var/mob/living/silicon/robot/R = user + if(!proximity || !check_allowed_items(target)) + return + if(R.client && (target in R.client.screen)) + to_chat(R, "You need to take that [target.name] off before cleaning it!") + else if(is_cleanable(target)) + R.visible_message("[R] begins to lick off \the [target.name].", "You begin to lick off \the [target.name]...") + if(do_after(R, src.cleanspeed, target = target)) + if(!in_range(src, target)) //Proximity is probably old news by now, do a new check. + return //If they moved away, you can't eat them. + to_chat(R, "You finish licking off \the [target.name].") + qdel(target) + R.cell.give(50) + else if(isobj(target)) //hoo boy. danger zone man + if(istype(target,/obj/item/trash)) + R.visible_message("[R] nibbles away at \the [target.name].", "You begin to nibble away at \the [target.name]...") + if(!do_after(R, src.cleanspeed, target = target)) + return //If they moved away, you can't eat them. + to_chat(R, "You finish off \the [target.name].") + qdel(target) + R.cell.give(250) + return + if(istype(target,/obj/item/stock_parts/cell)) + R.visible_message("[R] begins cramming \the [target.name] down its throat.", "You begin cramming \the [target.name] down your throat...") + if(!do_after(R, 50, target = target)) + return //If they moved away, you can't eat them. + to_chat(R, "You finish off \the [target.name].") + var/obj/item/stock_parts/cell/C = target + R.cell.charge = R.cell.charge + (C.charge / 3) //Instant full cell upgrades op idgaf + qdel(target) + return + var/obj/item/I = target //HAHA FUCK IT, NOT LIKE WE ALREADY HAVE A SHITTON OF WAYS TO REMOVE SHIT + if(!I.anchored && R.emagged) + R.visible_message("[R] begins chewing up \the [target.name]. Looks like it's trying to loophole around its diet restriction!", "You begin chewing up \the [target.name]...") + if(!do_after(R, 100, target = I)) //Nerf dat time yo + return //If they moved away, you can't eat them. + visible_message("[R] chews up \the [target.name] and cleans off the debris!") + to_chat(R, "You finish off \the [target.name].") + qdel(I) + R.cell.give(500) + return + R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") + else if(ishuman(target)) + var/mob/living/L = target + if(status == 0 && check_zone(R.zone_selected) == "head") + R.visible_message("\the [R] affectionally licks \the [L]'s face!", "You affectionally lick \the [L]'s face!") + playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) + if(istype(L) && L.fire_stacks > 0) + L.adjust_fire_stacks(-10) + return + else if(status == 0) + R.visible_message("\the [R] affectionally licks \the [L]!", "You affectionally lick \the [L]!") + playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) + if(istype(L) && L.fire_stacks > 0) + L.adjust_fire_stacks(-10) + return + else + if(R.cell.charge <= 800) + to_chat(R, "Insufficent Power!") + return + L.Stun(4) // normal stunbaton is force 7 gimme a break good sir! + L.Knockdown(80) + L.apply_effect(EFFECT_STUTTER, 4) + L.visible_message("[R] has shocked [L] with its tongue!", \ + "[R] has shocked you with its tongue!") + playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) + R.cell.use(666) + log_combat(R, L, "tongue stunned") + + else if(istype(target, /obj/structure/window)) + R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") + if(do_after(user, src.cleanspeed, target = target)) + to_chat(user, "You clean \the [target.name].") + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + target.set_opacity(initial(target.opacity)) + else + R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") + if(do_after(user, src.cleanspeed, target = target)) + to_chat(user, "You clean \the [target.name].") + var/obj/effect/decal/cleanable/C = locate() in target + qdel(C) + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) + target.wash_cream() + return + +//Nerfed tongue for flavour reasons (haha geddit?). Used for aux skins for regular borgs +/obj/item/soap/tongue/flavour + desc = "For giving affectionate kisses." + +/obj/item/soap/tongue/flavour/attack_self(mob/user) + return + +/obj/item/soap/tongue/flavour/afterattack(atom/target, mob/user, proximity) + if(!proximity) + return + var/mob/living/silicon/robot/R = user + if(ishuman(target)) + var/mob/living/L = target + if(status == 0 && check_zone(R.zone_selected) == "head") + R.visible_message("\the [R] affectionally licks \the [L]'s face!", "You affectionally lick \the [L]'s face!") + playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) + return + else if(status == 0) + R.visible_message("\the [R] affectionally licks \the [L]!", "You affectionally lick \the [L]!") + playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) + return + +//Same as above but for noses +/obj/item/analyzer/nose/flavour/AltClick(mob/user) + return + +/obj/item/analyzer/nose/flavour/attack_self(mob/user) + return + +/obj/item/analyzer/nose/flavour/afterattack(atom/target, mob/user, proximity) + if(!proximity) + return + do_attack_animation(target, null, src) + user.visible_message("[user] [pick(attack_verb)] \the [target.name] with their nose!") + + +//Dogfood + +/obj/item/trash/rkibble + name = "robo kibble" + desc = "A novelty bowl of assorted mech fabricator byproducts. Mockingly feed this to the sec-dog to help it recharge." + icon = 'icons/mob/dogborg.dmi' + icon_state= "kibble" + +// Pounce stuff for K-9 + +/obj/item/dogborg/pounce + name = "pounce" + icon = 'icons/mob/dogborg.dmi' + icon_state = "pounce" + desc = "Leap at your target to momentarily stun them." + force = 0 + throwforce = 0 + +/obj/item/dogborg/pounce/New() + ..() + item_flags |= NOBLUDGEON + +/mob/living/silicon/robot + var/leaping = 0 + var/pounce_cooldown = 0 + var/pounce_cooldown_time = 30 //Time in deciseconds between pounces + var/pounce_spoolup = 5 //Time in deciseconds for the pounce to happen after clicking + var/pounce_stamloss_cap = 120 //How much staminaloss pounces alone are capable of bringing a spaceman to + var/pounce_stamloss = 80 //Base staminaloss value of the pounce + var/leap_at + var/disabler + var/laser + var/sleeper_g + var/sleeper_r + var/sleeper_nv + +#define MAX_K9_LEAP_DIST 4 //because something's definitely borked the pounce functioning from a distance. + +/obj/item/dogborg/pounce/afterattack(atom/A, mob/user) + var/mob/living/silicon/robot/R = user + if(R && (world.time >= R.pounce_cooldown)) + R.pounce_cooldown = world.time + R.pounce_cooldown_time + to_chat(R, "Your targeting systems lock on to [A]...") + playsound(R, 'sound/effects/servostep.ogg', 100, TRUE) + addtimer(CALLBACK(R, /mob/living/silicon/robot.proc/leap_at, A), R.pounce_spoolup) + else if(R && (world.time < R.pounce_cooldown)) + to_chat(R, "Your leg actuators are still recharging!") + +/mob/living/silicon/robot/proc/leap_at(atom/A) + if(leaping || stat || buckled || lying) + return + + if(!has_gravity(src) || !has_gravity(A)) + to_chat(src,"It is unsafe to leap without gravity!") + //It's also extremely buggy visually, so it's balance+bugfix + return + + if(cell.charge <= 750) + to_chat(src,"Insufficent reserves for jump actuators!") + return + + else + leaping = 1 + weather_immunities += "lava" + pixel_y = 10 + update_icons() + throw_at(A, MAX_K9_LEAP_DIST, 1, spin=0, diagonals_first = 1) + cell.use(750) //Less than a stunbaton since stunbatons hit everytime. + playsound(src, 'sound/effects/stealthoff.ogg', 25, TRUE, -1) + weather_immunities -= "lava" + +/mob/living/silicon/robot/throw_impact(atom/A) + + if(!leaping) + return ..() + + if(A) + if(isliving(A)) + var/mob/living/L = A + if(!L.check_shields(0, "the [name]", src, attack_type = LEAP_ATTACK)) + L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") + L.Knockdown(iscarbon(L) ? 60 : 45, override_stamdmg = CLAMP(pounce_stamloss, 0, pounce_stamloss_cap-L.getStaminaLoss())) // Temporary. If someone could rework how dogborg pounces work to accomodate for combat changes, that'd be nice. + playsound(src, 'sound/weapons/Egloves.ogg', 50, 1) + sleep(2)//Runtime prevention (infinite bump() calls on hulks) + step_towards(src,L) + log_combat(src, L, "borg pounced") + else + Knockdown(15, 1, 1) + + pounce_cooldown = !pounce_cooldown + spawn(pounce_cooldown_time) //3s by default + pounce_cooldown = !pounce_cooldown + else if(A.density && !A.CanPass(src)) + visible_message("[src] smashes into [A]!", "You smash into [A]!") + playsound(src, 'sound/items/trayhit1.ogg', 50, 1) + Knockdown(15, 1, 1) + + if(leaping) + leaping = 0 + pixel_y = initial(pixel_y) + update_icons() + update_canmove() diff --git a/tools/Redirector/Configurations.dm b/tools/Redirector/Configurations.dm index 6b89ace90d..bc0d56361c 100644 --- a/tools/Redirector/Configurations.dm +++ b/tools/Redirector/Configurations.dm @@ -1,53 +1,53 @@ -/* - Written by contributor Doohl for the /tg/station Open Source project, hosted on Google Code. - (2012) - */ - -var/list/config_stream = list() -var/list/servers = list() -var/list/servernames = list() -var/list/adminfiles = list() -var/list/adminkeys = list() - -proc/gen_configs() - - config_stream = dd_file2list("config.txt") - - var/server_gen = 0 // if the stream is looking for servers - var/admin_gen = 0 // if the stream is looking for admins - for(var/line in config_stream) - - if(line == "\[SERVERS\]") - server_gen = 1 - if(admin_gen) - admin_gen = 0 - - else if(line == "\[ADMINS\]") - admin_gen = 1 - if(server_gen) - server_gen = 0 - - else - if(findtext(line, ".") && !findtext(line, "##")) - if(server_gen) - var/filterline = dd_replacetext(line, " ", "") - var/serverlink = copytext(filterline, findtext( filterline, ")") + 1) - servers.Add(serverlink) - servernames.Add( copytext(line, findtext(line, "("), findtext(line, ")") + 1)) - - else if(admin_gen) - adminfiles.Add(line) - world << line - - - // Generate the list of admins now - - for(var/file in adminfiles) - var/admin_config_stream = dd_file2list(file) - - for(var/line in admin_config_stream) - - var/akey = copytext(line, 1, findtext(line, " ")) - adminkeys.Add(akey) - - +/* + Written by contributor Doohl for the /tg/station Open Source project, hosted on Google Code. + (2012) + */ + +var/list/config_stream = list() +var/list/servers = list() +var/list/servernames = list() +var/list/adminfiles = list() +var/list/adminkeys = list() + +proc/gen_configs() + + config_stream = dd_file2list("config.txt") + + var/server_gen = 0 // if the stream is looking for servers + var/admin_gen = 0 // if the stream is looking for admins + for(var/line in config_stream) + + if(line == "\[SERVERS\]") + server_gen = 1 + if(admin_gen) + admin_gen = 0 + + else if(line == "\[ADMINS\]") + admin_gen = 1 + if(server_gen) + server_gen = 0 + + else + if(findtext(line, ".") && !findtext(line, "##")) + if(server_gen) + var/filterline = dd_replacetext(line, " ", "") + var/serverlink = copytext(filterline, findtext( filterline, ")") + 1) + servers.Add(serverlink) + servernames.Add( copytext(line, findtext(line, "("), findtext(line, ")") + 1)) + + else if(admin_gen) + adminfiles.Add(line) + world << line + + + // Generate the list of admins now + + for(var/file in adminfiles) + var/admin_config_stream = dd_file2list(file) + + for(var/line in admin_config_stream) + + var/akey = copytext(line, 1, findtext(line, " ")) + adminkeys.Add(akey) + + diff --git a/tools/Redirector/textprocs.dm b/tools/Redirector/textprocs.dm index aedafe39da..e23eab2442 100644 --- a/tools/Redirector/textprocs.dm +++ b/tools/Redirector/textprocs.dm @@ -1,153 +1,153 @@ -/* - Written by contributor Doohl for the /tg/station Open Source project, hosted on Google Code. - (2012) - - NOTE: The below functions are part of BYOND user Deadron's "TextHandling" library. - [ http://www.byond.com/developer/Deadron/TextHandling ] - */ - - -proc - /////////////////// - // Reading files // - /////////////////// - dd_file2list(file_path, separator = "\n") - var/file - if (isfile(file_path)) - file = file_path - else - file = file(file_path) - return dd_text2list(file2text(file), separator) - - - //////////////////// - // Replacing text // - //////////////////// - dd_replacetext(text, search_string, replacement_string) - // A nice way to do this is to split the text into an array based on the search_string, - // then put it back together into text using replacement_string as the new separator. - var/list/textList = dd_text2list(text, search_string) - return dd_list2text(textList, replacement_string) - - - dd_replaceText(text, search_string, replacement_string) - var/list/textList = dd_text2List(text, search_string) - return dd_list2text(textList, replacement_string) - - - ///////////////////// - // Prefix checking // - ///////////////////// - dd_hasprefix(text, prefix) - var/start = 1 - var/end = length(prefix) + 1 - return findtext(text, prefix, start, end) - - dd_hasPrefix(text, prefix) - var/start = 1 - var/end = length(prefix) + 1 - return findtextEx(text, prefix, start, end) - - - ///////////////////// - // Suffix checking // - ///////////////////// - dd_hassuffix(text, suffix) - var/start = length(text) - length(suffix) - if (start) - return findtext(text, suffix, start) - - dd_hasSuffix(text, suffix) - var/start = length(text) - length(suffix) - if (start) - return findtextEx(text, suffix, start) - - ///////////////////////////// - // Turning text into lists // - ///////////////////////////// - dd_text2list(text, separator) - var/textlength = length(text) - var/separatorlength = length(separator) - var/list/textList = new /list() - var/searchPosition = 1 - var/findPosition = 1 - var/buggyText - while (1) // Loop forever. - findPosition = findtext(text, separator, searchPosition, 0) - buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element. - textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext(). - - searchPosition = findPosition + separatorlength // Skip over separator. - if (findPosition == 0) // Didn't find anything at end of string so stop here. - return textList - else - if (searchPosition > textlength) // Found separator at very end of string. - textList += "" // So add empty element. - return textList - - dd_text2List(text, separator) - var/textlength = length(text) - var/separatorlength = length(separator) - var/list/textList = new /list() - var/searchPosition = 1 - var/findPosition = 1 - var/buggyText - while (1) // Loop forever. - findPosition = findtextEx(text, separator, searchPosition, 0) - buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element. - textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext(). - - searchPosition = findPosition + separatorlength // Skip over separator. - if (findPosition == 0) // Didn't find anything at end of string so stop here. - return textList - else - if (searchPosition > textlength) // Found separator at very end of string. - textList += "" // So add empty element. - return textList - - dd_list2text(list/the_list, separator) - var/total = the_list.len - if (total == 0) // Nothing to work with. - return - - var/newText = "[the_list[1]]" // Treats any object/number as text also. - var/count - for (count = 2, count <= total, count++) - if (separator) - newText += separator - newText += "[the_list[count]]" - return newText - - dd_centertext(message, length) - var/new_message = message - var/size = length(message) - if (size == length) - return new_message - if (size > length) - return copytext(new_message, 1, length + 1) - - // Need to pad text to center it. - var/delta = length - size - if (delta == 1) - // Add one space after it. - return new_message + " " - - // Is this an odd number? If so, add extra space to front. - if (delta % 2) - new_message = " " + new_message - delta-- - - // Divide delta in 2, add those spaces to both ends. - delta = delta / 2 - var/spaces = "" - for (var/count = 1, count <= delta, count++) - spaces += " " - return spaces + new_message + spaces - - dd_limittext(message, length) - // Truncates text to limit if necessary. - var/size = length(message) - if (size <= length) - return message - else +/* + Written by contributor Doohl for the /tg/station Open Source project, hosted on Google Code. + (2012) + + NOTE: The below functions are part of BYOND user Deadron's "TextHandling" library. + [ http://www.byond.com/developer/Deadron/TextHandling ] + */ + + +proc + /////////////////// + // Reading files // + /////////////////// + dd_file2list(file_path, separator = "\n") + var/file + if (isfile(file_path)) + file = file_path + else + file = file(file_path) + return dd_text2list(file2text(file), separator) + + + //////////////////// + // Replacing text // + //////////////////// + dd_replacetext(text, search_string, replacement_string) + // A nice way to do this is to split the text into an array based on the search_string, + // then put it back together into text using replacement_string as the new separator. + var/list/textList = dd_text2list(text, search_string) + return dd_list2text(textList, replacement_string) + + + dd_replaceText(text, search_string, replacement_string) + var/list/textList = dd_text2List(text, search_string) + return dd_list2text(textList, replacement_string) + + + ///////////////////// + // Prefix checking // + ///////////////////// + dd_hasprefix(text, prefix) + var/start = 1 + var/end = length(prefix) + 1 + return findtext(text, prefix, start, end) + + dd_hasPrefix(text, prefix) + var/start = 1 + var/end = length(prefix) + 1 + return findtextEx(text, prefix, start, end) + + + ///////////////////// + // Suffix checking // + ///////////////////// + dd_hassuffix(text, suffix) + var/start = length(text) - length(suffix) + if (start) + return findtext(text, suffix, start) + + dd_hasSuffix(text, suffix) + var/start = length(text) - length(suffix) + if (start) + return findtextEx(text, suffix, start) + + ///////////////////////////// + // Turning text into lists // + ///////////////////////////// + dd_text2list(text, separator) + var/textlength = length(text) + var/separatorlength = length(separator) + var/list/textList = new /list() + var/searchPosition = 1 + var/findPosition = 1 + var/buggyText + while (1) // Loop forever. + findPosition = findtext(text, separator, searchPosition, 0) + buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element. + textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext(). + + searchPosition = findPosition + separatorlength // Skip over separator. + if (findPosition == 0) // Didn't find anything at end of string so stop here. + return textList + else + if (searchPosition > textlength) // Found separator at very end of string. + textList += "" // So add empty element. + return textList + + dd_text2List(text, separator) + var/textlength = length(text) + var/separatorlength = length(separator) + var/list/textList = new /list() + var/searchPosition = 1 + var/findPosition = 1 + var/buggyText + while (1) // Loop forever. + findPosition = findtextEx(text, separator, searchPosition, 0) + buggyText = copytext(text, searchPosition, findPosition) // Everything from searchPosition to findPosition goes into a list element. + textList += "[buggyText]" // Working around weird problem where "text" != "text" after this copytext(). + + searchPosition = findPosition + separatorlength // Skip over separator. + if (findPosition == 0) // Didn't find anything at end of string so stop here. + return textList + else + if (searchPosition > textlength) // Found separator at very end of string. + textList += "" // So add empty element. + return textList + + dd_list2text(list/the_list, separator) + var/total = the_list.len + if (total == 0) // Nothing to work with. + return + + var/newText = "[the_list[1]]" // Treats any object/number as text also. + var/count + for (count = 2, count <= total, count++) + if (separator) + newText += separator + newText += "[the_list[count]]" + return newText + + dd_centertext(message, length) + var/new_message = message + var/size = length(message) + if (size == length) + return new_message + if (size > length) + return copytext(new_message, 1, length + 1) + + // Need to pad text to center it. + var/delta = length - size + if (delta == 1) + // Add one space after it. + return new_message + " " + + // Is this an odd number? If so, add extra space to front. + if (delta % 2) + new_message = " " + new_message + delta-- + + // Divide delta in 2, add those spaces to both ends. + delta = delta / 2 + var/spaces = "" + for (var/count = 1, count <= delta, count++) + spaces += " " + return spaces + new_message + spaces + + dd_limittext(message, length) + // Truncates text to limit if necessary. + var/size = length(message) + if (size <= length) + return message + else return copytext(message, 1, length + 1) \ No newline at end of file